From f50d713d566a99f27b55bdb816c8173552ecc04a Mon Sep 17 00:00:00 2001 From: SyMind Date: Tue, 15 Jun 2021 07:09:01 +0800 Subject: [PATCH 1/6] docs: add Chinese translation for README.md --- README-zh_CN.md | 82 ++++++++++++++ part-one-zh_CN.md | 282 ++++++++++++++++++++++++++++++++++++++++++++++ part-two-zh_CN.md | 172 ++++++++++++++++++++++++++++ 3 files changed, 536 insertions(+) create mode 100644 README-zh_CN.md create mode 100644 part-one-zh_CN.md create mode 100644 part-two-zh_CN.md diff --git a/README-zh_CN.md b/README-zh_CN.md new file mode 100644 index 0000000..6daed56 --- /dev/null +++ b/README-zh_CN.md @@ -0,0 +1,82 @@ +# Building a custom React renderer + +[![Build Status](https://travis-ci.org/nitin42/Making-a-custom-React-renderer.svg?branch=master)](https://travis-ci.org/nitin42/Making-a-custom-React-renderer) + +> 让我们创建一个自定义的 React 渲染器 😎 + +

+ +

+ +## Introduction + +这是一个关于如何创建自定义 React 渲染器并将组件渲染到你需要的宿主环境的教程。教程分为三部分 —— + +* **第一部分** - 创建一个 React 调度器(使用 [`react-reconciler`](https://github.com/facebook/react/tree/master/packages/react-reconciler) 包)。 + +* **第二部分** - Creating a public interface to the reconciler i.e "Renderer". + +* **第三部分** - Creating a render method to flush everything to the host environment we need. + +## Brief + +### [第一部分](./part-one.md) + +在第一部分,我们将使用 [`react-reconciler`](https://github.com/facebook/react/tree/master/packages/react-reconciler) 创建一个 React 调度器。我们将使用 Fiber 实现渲染器,因为它拥有优秀的用于创建自定义渲染器的 API。 + +### [第二部分](./part-two.md) + +In part two, we will create a public interface to the reconciler i.e a renderer. We will create a custom method for `createElement` and will also architect the component API for our example. + +### [第三部分](./part-three.md) + +In part three, we will create a render method which will render our input component. + +## 我们将创建什么? + +我们将创建一个自定义渲染器,将 React 组件渲染到 word 文档中。我已经做了一个。完整的源代码和文档在[这里](https://github.com/nitin42/redocx)。 + +我们将使用 [officegen](https://github.com/Ziv-Barber/officegen)。我将在这里解释一些基本概念。 + +Officegen 可以为 Microsoft Office 2007 及更高版本生成 Open Office XML 文件。它生成一个输出流而不是文件。它独立于任何输出工具。 + +**创建一个文档对象** + +```js +let doc = officegen('docx', { __someOptions__ }); +``` + +**生成输出流** + +```js +let output = fs.createWriteStream (__filePath__); + +doc.generate(output); +``` + +**事件** + +`finalize` - It is fired after a stream has been generated successfully. + +`error` - Fired when there are any errors + +## 运行这个项目 + +``` +git clone https://github.com/nitin42/Making-a-custom-React-renderer +cd Making-a-custom-React-renderer +yarn install +yarn example +``` + +After you run `yarn example`, a docx file will be generated in the [demo](./demo) folder. + +## Contributing + +Suggestions to improve the tutorial are welcome 😃. + +**If you've completed the tutorial successfully, you can either watch/star this repo or follow me on [twitter](https://twitter.com/NTulswani) for more updates.** + + + Sponsor + diff --git a/part-one-zh_CN.md b/part-one-zh_CN.md new file mode 100644 index 0000000..9c08876 --- /dev/null +++ b/part-one-zh_CN.md @@ -0,0 +1,282 @@ +# 第一部分 + +这是教程的第一部分。这是教程中最重要的部分,你将在其中了解 Fiber 及其结构与字段的详细说明,为渲染器创建与宿主环境相关的配置并将渲染器注入开发者工具中。 + +在这个部分,我们将使用 [`react-reconciler`](https://github.com/facebook/react/tree/master/packages/react-reconciler) 包创建一个 React 调度器。我们将使用 Fiber 实现渲染器。初期,React 使用 **栈渲染器**,它是在传统的 JavaScript 栈上实现。另一方面,Fiber 受代数效应和函数式思维的影响。它可以被认为是一个 JavaScript 对象,其中包含组件、输入和输出的信息。 + +在我们继续之前,我建议你阅读 [这个](https://github.com/acdlite/react-fiber-architecture) 文档 [Andrew Clark](https://twitter.com/ acdlite?lang=en)。这会让你容易些。 + +让我们开始! + +我们将首先安装这些依赖。 + +```bash +npm install react-reconciler fbjs +``` + +让我们从 `react-reconciler` 导入 `Reconciler`,然后导入其它模块。 + +```js +import Reconciler from 'react-reconciler'; +import emptyObject from 'fbjs/lib/emptyObject'; + +import { createElement } from './utils/createElement'; +``` + +注意我们还导入了 `createElement` 函数。别担心,我们稍后会实现它。 + +我们将传入 **host config** 对象到 `Reconciler` 创建一个实例。在这个对象中,我们将定义一些可以被认为是渲染器生命周期的方法(更新、添加子组件、删除子组件、提交)。 React 将管理所有非宿主组件。 + +```js +const WordRenderer = Reconciler({ + appendInitialChild(parentInstance, child) { + if (parentInstance.appendChild) { + parentInstance.appendChild(child); + } else { + parentInstance.document = child; + } + }, + + createInstance(type, props, internalInstanceHandle) { + return createElement(type, props, internalInstanceHandle); + }, + + createTextInstance(text, rootContainerInstance, internalInstanceHandle) { + return text; + }, + + finalizeInitialChildren(wordElement, type, props) { + return false; + }, + + getPublicInstance(inst) { + return inst; + }, + + prepareForCommit() { + // noop + }, + + prepareUpdate(wordElement, type, oldProps, newProps) { + return true; + }, + + resetAfterCommit() { + // noop + }, + + resetTextContent(wordElement) { + // noop + }, + + getRootHostContext(rootInstance) { + // 你可以使用此 'rootInstance' 从根传递数据。 + }, + + getChildHostContext() { + return emptyObject; + }, + + shouldSetTextContent(type, props) { + return false; + }, + + now: () => {} + + supportsMutation: false +}); +``` + +让我们分析一下我们的 host config - + +**`createInstance`** + +这个方法根据 `type`、`props` 和 `internalInstanceHandle` 创建一个组件实例。 + +例子 - 假设我们渲染如下内容, + +```js +Hello World +``` + +`createInstance` 将返回元素的 `type` (' TEXT ')、`props` ( { children: 'Hello World' } )和根元素的实例(`WordDocument`)信息。 + +**Fiber** + +Fiber 是组件需要完成或已经完成的工作。最多,一个组件实例有两个 Fiber,已完成的 Fiber 和进行中的 Fiber。 + +`internalInstanceHandle` 包含的信息有 `tag`、`type`、`key`、`stateNode` 和工作完成后退回的 Fiber。这个对象(Fiber)未来包含的信息有 - + +* **`tag`** - Fiber 的类型。 +* **`key`** - 子元素的唯一标识。 +* **`type`** - 该 Fiber 关联的方法、类、模块。 +* **`stateNode`** - 该 Fiber 关联的本地状态。 + +* **`return`** - 当前 Fiber 执行完毕后将要退回的 Fiber(父 Fiber)。 +* **`child`** - `child`、`sibling` 和 `index` 表达 **单向列表数据结构**。 +* **`sibling`** +* **`index`** +* **`ref`** - ref 最终用于关联这个节点。 + +* **`pendingProps`** - 该属性用于标签重载时。 +* **`memoizedProps`** - 该属性用于创建输出。 +* **`updateQueue`** - 用于状态更新和回调的队列。 +* **`memoizedState`** - 该状态用于创建输出。 + +* **`internalContextTag`** - 位运算数据结构。Fiber 使用位运算数据结构来保存有关 Fiber 及其子树的信息序列,该子树存储在相邻的计算机内存位置。该属性中的位用于确定属性的状态。称为标志的位域集合表示操作的结果或某些中间状态。React Fiber 使用 AsyncUpdates 标志来指示子树是否是异步的。 +* **`effectTag`** - 副作用标记。 +* **`nextEffect`** - 在单向链表中快速到下一个具有副作用的 Fiber。 +* **`firstEffect`** - 子树中有副作用的第一个(firstEffect)和最后一个(lastEffect)Fiber。重用此 Fiber 中已经完成的工作。 + +* **`expirationTime`** - 该属性表示工作在未来应该已经完成的时间点。 +* **`alternate`** - Fiber 的历史版本,包含随时可用的 Fiber,而非使用时分配。在计算机图形学中,这个概念被抽象为 **双缓冲** 模式。它使用更多内存,但我们可以清理它。 + +* `pendingWorkPriority` +* `progressedPriority` +* `progressedChild` +* `progressedFirstDeletion` +* `progressedLastDeletion` + +**`appendInitialChild`** + +它用于添加子组件。如果子组件包含在父组件中(例如:`Document`),那么我们将添加所有子组件到父组件中,否则我们将在父节点上创建一个名为 `document` 的属性并将所有子节点添加给它。 + +例子 - + +```js +const data = document.render(); // 返回输出 +``` + +**`prepareUpdate`** + +它计算实例的差异。即使 Fiber 暂停或中止对树的一部分渲染,也可以重用这项工作。 + +**`commitUpdate`** + +提交更新或将计算的差异应用到宿主环境的节点 (WordDocument)。 + +**`commitMount`** + +渲染器挂载宿主组件,但可能会在表单自动获得焦点之后进行一些工作。仅当没有当前或备用(alternate) Fiber 时才挂载宿主组件。 + +**`hostContext`** + +宿主上下文是一个内部对象,我们的渲染器可以根据树中的位置使用它。在 DOM 中,这个对象需要被正确调用,例如根据当前上下文在 html 或 MathMl 中创建元素。 + +**`getPublicInstance`** + +这用来关联标识,这意味着它始终返回用作其参数的相同值。它是为 TestRenderers 添加的。 + +**`resetTextContent`** + +在进行任何插入(将宿主节点插入到父节点)之前重置父节点的文本内容。这类似于 OpenGl 中的双缓冲技术,在向其写入新像素并执行光栅化之前先清除缓冲区。 + +**`commitTextUpdate`** + +与 `commitUpdate` 类似,但它为文本节点提交更新内容。 + +**`removeChild and removeChildFromContainer`** + +当我们在一个被移除的宿主组件中时,现在可以从树中移除节点。如果返回的 Fiber 是容器,那么我们使用 `removeChildFromContainer` 从容器中删除节点,否则我们使用 `removeChild`。 + +**`insertBefore`** + +它是一个 `commitPlacement` 钩子,在所有节点递归插入父节点时调用。这被抽象为 `getHostSibling` 的函数,该函数会持续搜索树,直到找到同级宿主节点(React 可能会在下一个版本中改变这种方法,因为它不是一种高效的方法,因为它会导致指数级的搜索复杂性)。 + +**`appendChildToContainer`** + +如果 Fiber 的类型是 `HostRoot` 或 `HostPortal`,则将子节点添加到该容器中。 + +**`appendChild`** + +添加子节点到父节点中。 + +**`shouldSetTextContent`** + +如果它返回 false,则重置文本内容。 + +**`getHostContext`** + +它用于标记当前的宿主上下文(根节点实例),用于发送更的内容,从而更新正在进行的 Fiber 队列(可能表示存在变化)。 + +**`createTextInstance`** + +创建文本节点的实例。 + +**`supportsMutation`** + +`True` 用于 **可变渲染器**,其中宿主目标具有像 DOM 中的 `appendChild` 这样的可变 API。 + +### Note + +* 你 **不应该** 依赖 Fiber 的数据结构。将其字段视为私有。 +* 将 'internalInstanceHandle' 对象视为一个黑盒。 +* 使用宿主上下文方法从根节点获取数据。 + +> 在与 [Dan Abramov](https://twitter.com/dan_abramov) 讨论宿主配置方法和 Fiber 属性后,将上述要点添加到教程中。 + +## 将第三方渲染器注入开发工具中 + +你还可以将渲染器注入 react-devtools 以调试环境的宿主组件。早期,注入第三方渲染器是不可能的,但现在使用返回的 `reconciler` 实例,可以将渲染器注入 react-devtools。 + +**使用** + +安装 react-devtools 的独立应用程序。 + +```bash +yarn add --dev react-devtools +``` + +Run + +```bash +yarn react-devtools +``` + +或者使用 npm, + +```bash +npm install --save-dev react-devtools +``` + +然后运行它 + +```bash +npx react-devtools +``` + +```js +const Reconciler = require('react-reconciler'); + +let hostConfig = { + // 根据上面的解释在这里添加方法 +}; + +const CustomRenderer = Reconciler(hostConfig); + +module.exports = CustomRenderer; +``` + +然后在你的 `render` 方法中, + +```js +const CustomRenderer = require('./reconciler') + +function render(element, target, callback) { + ... // 在这里,使用 CustomRenderer.updateContainer() 进行顶层更新,有关更多详细信息,请参阅 Part-IV。 + CustomRenderer.injectIntoDevTools({ + bundleType: 1, // 0 for PROD, 1 for DEV + version: '0.1.0', // version for your renderer + rendererPackageName: 'custom-renderer', // package name + findHostInstanceByFiber: CustomRenderer.findHostInstance // host instance (root) + })) +} +``` + +我们完成了教程的第一部分。我知道仅通过阅读代码很难理解其中的某些概念。开始的时候感觉很混沌,但是继续尝试,最终它会变得清晰。刚开始学习 Fiber 架构的时候,我什么都不懂。我感到极为沮丧,但我在上述代码的每一部分都使用了 `console.log()` 并试图理解它的实现,然后出现了“芜湖 芜湖”的时刻,它最终帮助我构建了 [redocx](https://github.com/nitin42/redocx)。它是有点难以理解,但你终将会明白。 + +如果您仍然有任何疑问,我在 Twitter 上的 [@NTulswani](https://twitter.com/NTulswani)。 + +[更多渲染器的实际示例](https://github.com/facebook/react/tree/master/packages/react-reconciler#practical-examples) + +[继续第二部分]](./part-two.md) diff --git a/part-two-zh_CN.md b/part-two-zh_CN.md new file mode 100644 index 0000000..d74c675 --- /dev/null +++ b/part-two-zh_CN.md @@ -0,0 +1,172 @@ +# Part-II + +在上一节中,我们构建了一个 React 协调器,并了解了它如何管理渲染器的生命周期。 + +In part two, we'll create a public interface to the reconciler. We will design our component API and will also build a custom version +of `createElement` method. + +## 组件 + +对于我们的例子,我们将只实现一个 `Text` 组件。`Text` 组件用于向文档添加文本。 + +> Text 组件并不是创建特殊的文本节点。与 DOM API 相比,它具有不同的语义。 + +我们将首先为我们的组件创建一个根容器(还记得协调器中的 `rootContainerInstance` 吗?)它负责用 [officegen](https://github.com/Ziv-Barber/officegen) 创建一个文档实例。 + +**`WordDocument.js`** + +```js +import officegen from 'officegen' + +// 用于创建文档实例 +class WordDocument { + constructor() { + this.doc = officegen('docx') + } +} + +export default WordDocument +``` + +现在,让我们创建我们的 `Text` 组件。 + +**`Text.js`** + +```js +class Text { + constructor(root, props) { + this.root = root; + this.props = props; + + this.adder = this.root.doc.createP(); + } + + appendChild(child) { + // 用于附加子节点的 API + // 注意:这在不同的宿主环境中会有所不同。例如:在浏览器中,你可以使用 document.appendChild(child) + if (typeof child === 'string') { + // 添加字符串并渲染文本节点 + this.adder.addText(child); + } + } +} + +export default Text; + +``` + +让我们看看这里发生了什么! + +**`constructor()`** + +在 `constructor` 中,我们初始化 `root` 实例和 `props`。我们还对之前在 `WordDocument.js` 中创建的 `doc` 实例创建了一个引用。使用此引用来通过向其添加文本节点来创建段落。 + +例子 - + +``` +this.adder.addText(__someText__) +``` + +**`appendChild`** + +This method appends the child nodes using the platform specific function for `docx` i.e `appendChild`. Remember we used this in our reconciler's `appendInitialChild` method to check whether the +parent instance has a method called `appendChild` or not !? + +```js +appendInitialChild(parentInstance, child) { + if (parentInstance.appendChild) { + parentInstance.appendChild(child); + } else { + parentInstance.document = child; + } +} +``` + +Along with `appendChild` method, you can also add `removeChild` method to remove child nodes. Since our host target does not provide a mutative API for removing child nodes, we are not using this method. + +> For this tutorial, the scope is kept limited for `Text` component. In a more practical example, you might want to validate the nesting of components too. + +#### Note + +- Do not track the children inside an array in your class component API. Instead, directly append them using specific host API, as React provides all the valuable information about the child (which was removed or added) + +This is correct + +```js +class MyComponent { + constructor(rootInstance, props) { + this.props = props + this.root = rootInstance + } + + appendChild(child) { + some_platform_api.add(child) + // In browser, you may use something like: document.appendChild(child) + } +} +``` + +这是错误的 + +```js +class MyComponent { + children = [] + + constructor(rootInstance, props) { + this.props = props + this.root = rootInstance + } + + appendChild(child) { + this.children.push(child) + } + + renderChildren() { + for (let i = 0; i < this.children.length; i++) { + // do something with this.children[i] + } + } + + render() { + this.renderChildren() + } +} +``` + +- If you're rendering target does not provide a mutate method like `appendChild` and instead only lets you replace the whole "scene" at once, you might want to use the "persistent" renderer mode instead. Here's an [example host config for persistent renderer](https://github.com/facebook/react/blob/master/packages/react-native-renderer/src/ReactFabricHostConfig.js). + +## createElement + +This is similar to the `React.createElement()` for DOM as a target. + +**`createElement.js`** + +```js +import { Text, WordDocument } from '../components/index' + +/** + * Creates an element for a document + * @param {string} type Element type + * @param {Object} props Component props + * @param {Object} root Root instance + */ +function createElement(type, props, root) { + const COMPONENTS = { + ROOT: () => new WordDocument(), + TEXT: () => new Text(root, props), + default: undefined + } + + return COMPONENTS[type]() || COMPONENTS.default +} + +export { createElement } +``` + +I think you can easily understand what's happening inside the `createElement` method. It takes an element, props, and the root instance. + +Depending upon the type of element, we return an instance based on it else we return `undefined`. + +We're done with the part two of our tutorial. We created the API for our two components (`Document` and `Text`) and a `createElement` method to create an element. In the next part, we will create a render method to flush everything to the host environment. + +[Continue to Part-III](./part-three.md) From c0c447a2013be779d178567ce3a6ea48c16185fd Mon Sep 17 00:00:00 2001 From: SyMind Date: Wed, 17 Nov 2021 19:06:48 +0800 Subject: [PATCH 2/6] docs: add Chinese translation for part-two --- part-one-zh_CN.md | 2 +- part-two-zh_CN.md | 47 ++++++++++++++++++++++------------------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/part-one-zh_CN.md b/part-one-zh_CN.md index 9c08876..5fc1e83 100644 --- a/part-one-zh_CN.md +++ b/part-one-zh_CN.md @@ -275,7 +275,7 @@ function render(element, target, callback) { 我们完成了教程的第一部分。我知道仅通过阅读代码很难理解其中的某些概念。开始的时候感觉很混沌,但是继续尝试,最终它会变得清晰。刚开始学习 Fiber 架构的时候,我什么都不懂。我感到极为沮丧,但我在上述代码的每一部分都使用了 `console.log()` 并试图理解它的实现,然后出现了“芜湖 芜湖”的时刻,它最终帮助我构建了 [redocx](https://github.com/nitin42/redocx)。它是有点难以理解,但你终将会明白。 -如果您仍然有任何疑问,我在 Twitter 上的 [@NTulswani](https://twitter.com/NTulswani)。 +如果你仍然有任何疑问,我在 Twitter 上的 [@NTulswani](https://twitter.com/NTulswani)。 [更多渲染器的实际示例](https://github.com/facebook/react/tree/master/packages/react-reconciler#practical-examples) diff --git a/part-two-zh_CN.md b/part-two-zh_CN.md index d74c675..176feff 100644 --- a/part-two-zh_CN.md +++ b/part-two-zh_CN.md @@ -1,9 +1,8 @@ -# Part-II +# 第二部分 在上一节中,我们构建了一个 React 协调器,并了解了它如何管理渲染器的生命周期。 -In part two, we'll create a public interface to the reconciler. We will design our component API and will also build a custom version -of `createElement` method. +在第二部分,我们我们将为调度器创建一个公共接口。我们将设计我们组件的 API,然后将创建一个自定义版本的 `createElement` 方法。 ## 组件 @@ -42,7 +41,7 @@ class Text { } appendChild(child) { - // 用于附加子节点的 API + // 用于添加子节点的 API // 注意:这在不同的宿主环境中会有所不同。例如:在浏览器中,你可以使用 document.appendChild(child) if (typeof child === 'string') { // 添加字符串并渲染文本节点 @@ -63,14 +62,13 @@ export default Text; 例子 - -``` +```js this.adder.addText(__someText__) ``` **`appendChild`** -This method appends the child nodes using the platform specific function for `docx` i.e `appendChild`. Remember we used this in our reconciler's `appendInitialChild` method to check whether the -parent instance has a method called `appendChild` or not !? +此方法使用 `docx` 的特定平台方法(即 `appendChild`)添加子节点。请记住,我们在调度器的 `appendInitialChild` 方法中检查父实例是否存在 `appendChild` 方法!? ```js appendInitialChild(parentInstance, child) { @@ -82,15 +80,15 @@ appendInitialChild(parentInstance, child) { } ``` -Along with `appendChild` method, you can also add `removeChild` method to remove child nodes. Since our host target does not provide a mutative API for removing child nodes, we are not using this method. +除了 `appendChild` 方法,你还可以添加 `removeChild` 方法来删​​除子节点。由于我们的宿主目标不提供用于删除子节点的可变 API,因此我们没有使用此方法。 -> For this tutorial, the scope is kept limited for `Text` component. In a more practical example, you might want to validate the nesting of components too. +> 在本教程,`Text` 组件不允许嵌套其它的组件。在更实际的示例中,你可能需要验证组件的嵌套。 -#### Note +#### 注意 -- Do not track the children inside an array in your class component API. Instead, directly append them using specific host API, as React provides all the valuable information about the child (which was removed or added) +- 不要在类组件 API 中使用数组追踪子组件。相反,直接使用特定的宿主 API 添加它们,因为 React 提供了有关子节点(已删除或添加)的所有有价值的信息。 -This is correct +这是正确的 ```js class MyComponent { @@ -101,7 +99,7 @@ class MyComponent { appendChild(child) { some_platform_api.add(child) - // In browser, you may use something like: document.appendChild(child) + // 在浏览器中,我们可能会使用 document.appendChild(child) } } ``` @@ -123,7 +121,7 @@ class MyComponent { renderChildren() { for (let i = 0; i < this.children.length; i++) { - // do something with this.children[i] + // 对 this.children[i] 进行一些操作 } } @@ -133,11 +131,11 @@ class MyComponent { } ``` -- If you're rendering target does not provide a mutate method like `appendChild` and instead only lets you replace the whole "scene" at once, you might want to use the "persistent" renderer mode instead. Here's an [example host config for persistent renderer](https://github.com/facebook/react/blob/master/packages/react-native-renderer/src/ReactFabricHostConfig.js). +- 如果你的渲染目标没有提供像 `appendChild` 这样的可变方法,而是只允许你一次替换整个“场景”,你可能需要使用“持久(persistent)”渲染器模式来代替。这是一个[持久渲染器的示例宿主配置](https://github.com/facebook/react/blob/master/packages/react-native-renderer/src/ReactFabricHostConfig.js)。 ## createElement -This is similar to the `React.createElement()` for DOM as a target. +这类似于将 DOM 作为目标的 `React.createElement()`。 **`createElement.js`** @@ -145,10 +143,10 @@ This is similar to the `React.createElement()` for DOM as a target. import { Text, WordDocument } from '../components/index' /** - * Creates an element for a document - * @param {string} type Element type - * @param {Object} props Component props - * @param {Object} root Root instance + * 为文档创建一个元素 + * @param {string} type 元素类型 + * @param {Object} props 组件属性 + * @param {Object} root 根节点实例 */ function createElement(type, props, root) { const COMPONENTS = { @@ -162,11 +160,10 @@ function createElement(type, props, root) { export { createElement } ``` +我认为你可以很容易地理解在 `createElement` 方法中发生了什么。它需要传入元素类型、组件属性和根节点实例。 -I think you can easily understand what's happening inside the `createElement` method. It takes an element, props, and the root instance. - -Depending upon the type of element, we return an instance based on it else we return `undefined`. +根据元素的类型,我们返回它的实例,否则我们返回 `undefined`。 -We're done with the part two of our tutorial. We created the API for our two components (`Document` and `Text`) and a `createElement` method to create an element. In the next part, we will create a render method to flush everything to the host environment. +我们完成了教程的第二部分。我们为我们的两个组件(`Document` 和 `Text`)构建了 API,并构建了一个用于创建元素的 `createElement` 方法。在下一部分中,我们将构建一个渲染方法来将所有内容渲染到宿主环境中。 -[Continue to Part-III](./part-three.md) +[继续第三部分](./part-three.md) From b36963242856a1d77676d92e4a7d8b5fd778693c Mon Sep 17 00:00:00 2001 From: SyMind Date: Wed, 17 Nov 2021 20:17:11 +0800 Subject: [PATCH 3/6] docs: add Chinese translation for part-three --- part-three-zh_CN.md | 72 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 part-three-zh_CN.md diff --git a/part-three-zh_CN.md b/part-three-zh_CN.md new file mode 100644 index 0000000..8905858 --- /dev/null +++ b/part-three-zh_CN.md @@ -0,0 +1,72 @@ +# 第三部分 + +这是我们教程的最后一部分。我们已经完成了所有繁重的工作,创建了一个 React 调度器,为我们的调度器创建了一个公共接口,设计了组件 API。 + +现在我们只需要创建一个 `render` 方法来将所有内容渲染到宿主环境中。 + +## render + +```js + +import fs from 'fs'; +import { createElement } from '../utils/createElement'; +import { WordRenderer } from './renderer'; + +// 渲染组件 +async function render(element, filePath) { + const container = createElement('ROOT'); + + const node = WordRenderer.createContainer(container); + + WordRenderer.updateContainer(element, node, null); + + const stream = fs.createWriteStream(filePath); + + await new Promise((resolve, reject) => { + container.doc.generate(stream, Events(filePath, resolve, reject)); + }); +} + +function Events(filePath, resolve, reject) { + return { + finalize: () => { + console.log(`✨ Word document created at ${path.resolve(filePath)}.`); + resolve(); + }, + error: () => { + console.log('An error occurred while generating the document.'); + reject(); + }, + }; +} + +export default render; + +``` + +让我们看看这里发生了什么! + +**`container`** + +这是根节点实例(还记得我们调度器中的 `rootContainerInstance` 吗?)。 + +**`WordRenderer.createContainer`** + +这个方法接受一个 `root` 容器并返回当前的 Fiber(已完成的 Fiber)。记住 Fiber 是一个包含相关组件输入和输出信息的 JavaScript 对象。 + +**`WordRenderer.updateContainer`** + +这个函数接收元素、根容器、父组件、回调函数并执行一次顶层更新。 +这是根据当前 Fiber 和优先级(取决于上下文)调度更新来实现的。 + +最后,我们渲染所有子节点并通过创建写入流来生成 word 文档。 + +仍有疑惑?查看 [常见问题](./faq.md)。 + +恭喜!你已成功完成本教程。本教程的完整源代码已在此代码库 ([src](./src)) 中提供。如果你想阅读整个源代码,请按照以下顺序 - + +[`reconciler`](./src/reconciler/index.js) => [`components`](./src/components/) => [`createElement`](./src/utils/createElement.js) => [`render method`](./src/render/index.js) + +如果你喜欢阅读本教程,请 watch 或 star 这个代码库,并在 [Twitter](http://twitter.com/NTulswani) 上关注我以获取更新。 + +感谢你阅读本教程! From ee52d3ac4f12432725c103584ad0722f687bd2d5 Mon Sep 17 00:00:00 2001 From: SyMind Date: Wed, 17 Nov 2021 21:19:23 +0800 Subject: [PATCH 4/6] revise --- README-zh_CN.md | 38 ++++++++++++----------- README.md | 2 ++ part-one-zh_CN.md | 73 ++++++++++++++++++++++----------------------- part-three-zh_CN.md | 12 ++++---- part-two-zh_CN.md | 24 +++++++-------- 5 files changed, 76 insertions(+), 73 deletions(-) diff --git a/README-zh_CN.md b/README-zh_CN.md index 6daed56..26fa4ef 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -1,36 +1,38 @@ -# Building a custom React renderer +# 创建一个自定义 React 渲染器 [![Build Status](https://travis-ci.org/nitin42/Making-a-custom-React-renderer.svg?branch=master)](https://travis-ci.org/nitin42/Making-a-custom-React-renderer) +[English](./README.md) | 简体中文 + > 让我们创建一个自定义的 React 渲染器 😎

-## Introduction +## 介绍 -这是一个关于如何创建自定义 React 渲染器并将组件渲染到你需要的宿主环境的教程。教程分为三部分 —— +这是一个关于如何创建一个自定义 React 渲染器并将所有内容渲染到你的目标宿主环境中的教程。教程分为三部分 —— * **第一部分** - 创建一个 React 调度器(使用 [`react-reconciler`](https://github.com/facebook/react/tree/master/packages/react-reconciler) 包)。 -* **第二部分** - Creating a public interface to the reconciler i.e "Renderer". +* **第二部分** - 我们将创建一个用于调度器的公开方法。 -* **第三部分** - Creating a render method to flush everything to the host environment we need. +* **第三部分** - 创建渲染方法来将所有创建的组件实例渲染到我们的目标宿主环境中。 -## Brief +## 概要 -### [第一部分](./part-one.md) +### [第一部分](./part-one-zh_CN.md) 在第一部分,我们将使用 [`react-reconciler`](https://github.com/facebook/react/tree/master/packages/react-reconciler) 创建一个 React 调度器。我们将使用 Fiber 实现渲染器,因为它拥有优秀的用于创建自定义渲染器的 API。 -### [第二部分](./part-two.md) +### [第二部分](./part-two-zh_CN.md) -In part two, we will create a public interface to the reconciler i.e a renderer. We will create a custom method for `createElement` and will also architect the component API for our example. +在第二部分,我们将创建一个用于调度器的公开方法。我们将创建一个自定义 `createElement` 函数,还将为我们的示例构建组件 API。 -### [第三部分](./part-three.md) +### [第三部分](./part-three-zh_CN.md) -In part three, we will create a render method which will render our input component. +在第三部分,我们将创建渲染方法,用于渲染我们创建的组件实例。 ## 我们将创建什么? @@ -56,26 +58,26 @@ doc.generate(output); **事件** -`finalize` - It is fired after a stream has been generated successfully. +`finalize` - 在流生成成功之后触发。 -`error` - Fired when there are any errors +`error` - 在发生异常时触发。 ## 运行这个项目 -``` +```bash git clone https://github.com/nitin42/Making-a-custom-React-renderer cd Making-a-custom-React-renderer yarn install yarn example ``` -After you run `yarn example`, a docx file will be generated in the [demo](./demo) folder. +运行 `yarn example` 后,会在 [demo](./demo) 文件夹下生成一个 docx 文件。 -## Contributing +## 贡献 -Suggestions to improve the tutorial are welcome 😃. +欢迎提出改进教程的建议😃。 -**If you've completed the tutorial successfully, you can either watch/star this repo or follow me on [twitter](https://twitter.com/NTulswani) for more updates.** +**如果您已成功完成本教程,您可以 watch 或 star 此代码库或在 [twitter](https://twitter.com/NTulswani) 上关注我以获取最新的消息。** Sponsor diff --git a/README.md b/README.md index 48adb47..d0b7d56 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![Build Status](https://travis-ci.org/nitin42/Making-a-custom-React-renderer.svg?branch=master)](https://travis-ci.org/nitin42/Making-a-custom-React-renderer) +English | [简体中文](./README-zh_CN.md) + > Let's make a custom React renderer 😎

diff --git a/part-one-zh_CN.md b/part-one-zh_CN.md index 5fc1e83..4faf255 100644 --- a/part-one-zh_CN.md +++ b/part-one-zh_CN.md @@ -1,20 +1,20 @@ # 第一部分 -这是教程的第一部分。这是教程中最重要的部分,你将在其中了解 Fiber 及其结构与字段的详细说明,为渲染器创建与宿主环境相关的配置并将渲染器注入开发者工具中。 +这是教程的第一部分,也是教程最重要的部分,你将在其中了解 Fiber 及其结构与属性的详细说明,为你的渲染器创建宿主环境配置并将渲染器注入开发工具中。 -在这个部分,我们将使用 [`react-reconciler`](https://github.com/facebook/react/tree/master/packages/react-reconciler) 包创建一个 React 调度器。我们将使用 Fiber 实现渲染器。初期,React 使用 **栈渲染器**,它是在传统的 JavaScript 栈上实现。另一方面,Fiber 受代数效应和函数式思维的影响。它可以被认为是一个 JavaScript 对象,其中包含组件、输入和输出的信息。 +在这个部分,我们将使用 [`react-reconciler`](https://github.com/facebook/react/tree/master/packages/react-reconciler) 包创建一个 React 调度器。我们将使用 Fiber 实现渲染器。起初,React 使用 **栈渲染器**,它是在 JavaScript 的调用栈上实现。另一方面,Fiber 受代数效应和函数式编程思维的影响。它可以被认为是一个包含组件输入和输出的信息的 JavaScript 对象。 -在我们继续之前,我建议你阅读 [这个](https://github.com/acdlite/react-fiber-architecture) 文档 [Andrew Clark](https://twitter.com/ acdlite?lang=en)。这会让你容易些。 +在我们继续之前,我建议你阅读由 [Andrew Clark](https://twitter.com/acdlite?lang=en) 写的 [这篇](https://github.com/acdlite/react-fiber-architecture) 文章。这会让你更容易理解本教程。 让我们开始! -我们将首先安装这些依赖。 +我们首先安装这些依赖。 ```bash npm install react-reconciler fbjs ``` -让我们从 `react-reconciler` 导入 `Reconciler`,然后导入其它模块。 +让我们从 `react-reconciler` 引入 `Reconciler`,接着引入其它模块。 ```js import Reconciler from 'react-reconciler'; @@ -23,9 +23,9 @@ import emptyObject from 'fbjs/lib/emptyObject'; import { createElement } from './utils/createElement'; ``` -注意我们还导入了 `createElement` 函数。别担心,我们稍后会实现它。 +注意我们还引入了 `createElement` 函数。别担心,我们稍后会实现它。 -我们将传入 **host config** 对象到 `Reconciler` 创建一个实例。在这个对象中,我们将定义一些可以被认为是渲染器生命周期的方法(更新、添加子组件、删除子组件、提交)。 React 将管理所有非宿主组件。 +我们传入 **host config** 对象到 `Reconciler` 中来创建一个它的实例。在这个对象中,我们将定义一些可以认为是渲染器生命周期的方法(更新、添加子组件、删除子组件、提交)。 React 将管理所有非宿主组件。 ```js const WordRenderer = Reconciler({ @@ -70,7 +70,7 @@ const WordRenderer = Reconciler({ }, getRootHostContext(rootInstance) { - // 你可以使用此 'rootInstance' 从根传递数据。 + // 你可以使用此 'rootInstance' 从根节点传递数据。 }, getChildHostContext() { @@ -87,11 +87,11 @@ const WordRenderer = Reconciler({ }); ``` -让我们分析一下我们的 host config - +让我们分析一下我们的宿主配置 - **`createInstance`** -这个方法根据 `type`、`props` 和 `internalInstanceHandle` 创建一个组件实例。 +这个方法根据 `type`、`props` 和 `internalInstanceHandle` 创建组件实例。 例子 - 假设我们渲染如下内容, @@ -99,32 +99,31 @@ const WordRenderer = Reconciler({ Hello World ``` -`createInstance` 将返回元素的 `type` (' TEXT ')、`props` ( { children: 'Hello World' } )和根元素的实例(`WordDocument`)信息。 +`createInstance` 方法中将获得元素的 `type` (' TEXT ')、`props` ( { children: 'Hello World' } )和根元素的实例(`WordDocument`)信息。 **Fiber** -Fiber 是组件需要完成或已经完成的工作。最多,一个组件实例有两个 Fiber,已完成的 Fiber 和进行中的 Fiber。 +Fiber 是组件需要或已经完成的工作。最多,一个组件实例有两个 Fiber,已完成的 Fiber 和进行中的 Fiber。 -`internalInstanceHandle` 包含的信息有 `tag`、`type`、`key`、`stateNode` 和工作完成后退回的 Fiber。这个对象(Fiber)未来包含的信息有 - +`internalInstanceHandle` 包含的信息有 `tag`、`type`、`key`、`stateNode` 和工作完成后会退回去的 Fiber。这个对象(Fiber)包含的信息有 - * **`tag`** - Fiber 的类型。 -* **`key`** - 子元素的唯一标识。 -* **`type`** - 该 Fiber 关联的方法、类、模块。 +* **`key`** - 子节点的唯一标识。 +* **`type`** - 该 Fiber 关联的方法、类或模块。 * **`stateNode`** - 该 Fiber 关联的本地状态。 -* **`return`** - 当前 Fiber 执行完毕后将要退回的 Fiber(父 Fiber)。 +* **`return`** - 当前 Fiber 执行完毕后将要回退回去的 Fiber(父 Fiber)。 * **`child`** - `child`、`sibling` 和 `index` 表达 **单向列表数据结构**。 * **`sibling`** * **`index`** * **`ref`** - ref 最终用于关联这个节点。 -* **`pendingProps`** - 该属性用于标签重载时。 +* **`pendingProps`** - 该属性用于标签发生重载时。 * **`memoizedProps`** - 该属性用于创建输出。 -* **`updateQueue`** - 用于状态更新和回调的队列。 +* **`updateQueue`** - 状态更新和回调的队列。 * **`memoizedState`** - 该状态用于创建输出。 -* **`internalContextTag`** - 位运算数据结构。Fiber 使用位运算数据结构来保存有关 Fiber 及其子树的信息序列,该子树存储在相邻的计算机内存位置。该属性中的位用于确定属性的状态。称为标志的位域集合表示操作的结果或某些中间状态。React Fiber 使用 AsyncUpdates 标志来指示子树是否是异步的。 -* **`effectTag`** - 副作用标记。 +* **`subtreeFlags`** - 位标志。Fiber 使用位标志来保存有关 Fiber 及其子树的一些表示操作状态或某些中间状态。 * **`nextEffect`** - 在单向链表中快速到下一个具有副作用的 Fiber。 * **`firstEffect`** - 子树中有副作用的第一个(firstEffect)和最后一个(lastEffect)Fiber。重用此 Fiber 中已经完成的工作。 @@ -139,7 +138,7 @@ Fiber 是组件需要完成或已经完成的工作。最多,一个组件实 **`appendInitialChild`** -它用于添加子组件。如果子组件包含在父组件中(例如:`Document`),那么我们将添加所有子组件到父组件中,否则我们将在父节点上创建一个名为 `document` 的属性并将所有子节点添加给它。 +它用于添加子节点。如果子节点包含在父节点中(例如:`Document`),那么我们将添加所有子节点到父节点中,否则我们将在父节点上创建一个名为 `document` 的属性后添加所有子节点。 例子 - @@ -149,7 +148,7 @@ const data = document.render(); // 返回输出 **`prepareUpdate`** -它计算实例的差异。即使 Fiber 暂停或中止对树的一部分渲染,也可以重用这项工作。 +它计算实例的差异。即使 Fiber 暂停或中止对树的部分渲染,也可以重用这项工作。 **`commitUpdate`** @@ -157,7 +156,7 @@ const data = document.render(); // 返回输出 **`commitMount`** -渲染器挂载宿主组件,但可能会在表单自动获得焦点之后进行一些工作。仅当没有当前或备用(alternate) Fiber 时才挂载宿主组件。 +渲染器挂载宿主节点,但可能会在表单自动获得焦点之后进行一些工作。仅当没有当前或备用(alternate) Fiber 时才挂载宿主组件。 **`hostContext`** @@ -177,11 +176,11 @@ const data = document.render(); // 返回输出 **`removeChild and removeChildFromContainer`** -当我们在一个被移除的宿主组件中时,现在可以从树中移除节点。如果返回的 Fiber 是容器,那么我们使用 `removeChildFromContainer` 从容器中删除节点,否则我们使用 `removeChild`。 +从树中移除节点。如果返回的 Fiber 是容器,那么我们使用 `removeChildFromContainer` 从容器中删除节点,否则我们使用 `removeChild`。 **`insertBefore`** -它是一个 `commitPlacement` 钩子,在所有节点递归插入父节点时调用。这被抽象为 `getHostSibling` 的函数,该函数会持续搜索树,直到找到同级宿主节点(React 可能会在下一个版本中改变这种方法,因为它不是一种高效的方法,因为它会导致指数级的搜索复杂性)。 +在目标节点之前插入一个子节点。 **`appendChildToContainer`** @@ -189,15 +188,15 @@ const data = document.render(); // 返回输出 **`appendChild`** -添加子节点到父节点中。 +向目标节点添加子节点。 **`shouldSetTextContent`** -如果它返回 false,则重置文本内容。 +如果它返回 false,重置文本内容。 **`getHostContext`** -它用于标记当前的宿主上下文(根节点实例),用于发送更的内容,从而更新正在进行的 Fiber 队列(可能表示存在变化)。 +用于标记当前的宿主上下文(根元素实例),发送更新的内容,从而更新正在进行中的 Fiber 队列(可能表示存在变化)。 **`createTextInstance`** @@ -205,19 +204,19 @@ const data = document.render(); // 返回输出 **`supportsMutation`** -`True` 用于 **可变渲染器**,其中宿主目标具有像 DOM 中的 `appendChild` 这样的可变 API。 +`true` 为 **可变渲染器** 模式,其中宿主目标需要具有像 DOM 中的 `appendChild` 这样的可变 API。 -### Note +### 注意 -* 你 **不应该** 依赖 Fiber 的数据结构。将其字段视为私有。 -* 将 'internalInstanceHandle' 对象视为一个黑盒。 +* 你 **不应该** 依赖 Fiber 的数据结构。将其属性视为私有。 +* 将 'internalInstanceHandle' 对象视为黑盒。 * 使用宿主上下文方法从根节点获取数据。 > 在与 [Dan Abramov](https://twitter.com/dan_abramov) 讨论宿主配置方法和 Fiber 属性后,将上述要点添加到教程中。 ## 将第三方渲染器注入开发工具中 -你还可以将渲染器注入 react-devtools 以调试环境的宿主组件。早期,注入第三方渲染器是不可能的,但现在使用返回的 `reconciler` 实例,可以将渲染器注入 react-devtools。 +你还可以将渲染器注入 react-devtools 以调试你环境中的宿主组件。早期,是无法适配第三方渲染器的,但现在可以使用返回的 `reconciler` 实例,将渲染器注入到 react-devtools。 **使用** @@ -263,7 +262,7 @@ module.exports = CustomRenderer; const CustomRenderer = require('./reconciler') function render(element, target, callback) { - ... // 在这里,使用 CustomRenderer.updateContainer() 进行顶层更新,有关更多详细信息,请参阅 Part-IV。 + ... // 在这里,使用 CustomRenderer.updateContainer() 进行从最顶层开始的更新,有关更多详细信息,请参阅 Part-IV。 CustomRenderer.injectIntoDevTools({ bundleType: 1, // 0 for PROD, 1 for DEV version: '0.1.0', // version for your renderer @@ -273,10 +272,10 @@ function render(element, target, callback) { } ``` -我们完成了教程的第一部分。我知道仅通过阅读代码很难理解其中的某些概念。开始的时候感觉很混沌,但是继续尝试,最终它会变得清晰。刚开始学习 Fiber 架构的时候,我什么都不懂。我感到极为沮丧,但我在上述代码的每一部分都使用了 `console.log()` 并试图理解它的实现,然后出现了“芜湖 芜湖”的时刻,它最终帮助我构建了 [redocx](https://github.com/nitin42/redocx)。它是有点难以理解,但你终将会明白。 +我们完成了教程的第一部分。我知道其中的某些概念仅通过阅读代码是很难理解的。开始的时候感觉很混沌,但请继续尝试,最终它会逐渐变得清晰。刚开始学习 Fiber 架构的时候,我什么都不懂。我感到极为沮丧,但我在上述代码的每一部分都使用了 `console.log()` 并试图理解它们,然后出现了“芜湖起飞”的时刻,最终帮助我构建了 [redocx](https://github.com/nitin42/redocx)。它是有点难理解,但你终将会搞懂。 -如果你仍然有任何疑问,我在 Twitter 上的 [@NTulswani](https://twitter.com/NTulswani)。 +如果你仍然有任何疑问,我在 Twitter 上 [@NTulswani](https://twitter.com/NTulswani)。 [更多渲染器的实际示例](https://github.com/facebook/react/tree/master/packages/react-reconciler#practical-examples) -[继续第二部分]](./part-two.md) +[继续第二部分]](./part-two-zh_CN.md) diff --git a/part-three-zh_CN.md b/part-three-zh_CN.md index 8905858..9a21863 100644 --- a/part-three-zh_CN.md +++ b/part-three-zh_CN.md @@ -1,6 +1,6 @@ # 第三部分 -这是我们教程的最后一部分。我们已经完成了所有繁重的工作,创建了一个 React 调度器,为我们的调度器创建了一个公共接口,设计了组件 API。 +这是我们教程的最后一部分。我们已经完成了所有繁重的工作,创建了一个 React 调度器,为我们的调度器创建了一个公开方法,设计了组件的 API。 现在我们只需要创建一个 `render` 方法来将所有内容渲染到宿主环境中。 @@ -48,7 +48,7 @@ export default render; **`container`** -这是根节点实例(还记得我们调度器中的 `rootContainerInstance` 吗?)。 +这是根元素实例(还记得我们调度器中的 `rootContainerInstance` 吗?)。 **`WordRenderer.createContainer`** @@ -56,17 +56,17 @@ export default render; **`WordRenderer.updateContainer`** -这个函数接收元素、根容器、父组件、回调函数并执行一次顶层更新。 -这是根据当前 Fiber 和优先级(取决于上下文)调度更新来实现的。 +这个函数接收元素、根容器、父组件、回调函数并触发一次从最顶层开始的更新。 +这是根据当前 Fiber 和优先级(取决于上下文)来调度更新实现的。 最后,我们渲染所有子节点并通过创建写入流来生成 word 文档。 仍有疑惑?查看 [常见问题](./faq.md)。 -恭喜!你已成功完成本教程。本教程的完整源代码已在此代码库 ([src](./src)) 中提供。如果你想阅读整个源代码,请按照以下顺序 - +恭喜!你已成功完成本教程。本教程的完整源代码在此代码库 ([src](./src)) 中提供。如果你想阅读整个源代码,请按照以下顺序 - [`reconciler`](./src/reconciler/index.js) => [`components`](./src/components/) => [`createElement`](./src/utils/createElement.js) => [`render method`](./src/render/index.js) -如果你喜欢阅读本教程,请 watch 或 star 这个代码库,并在 [Twitter](http://twitter.com/NTulswani) 上关注我以获取更新。 +如果你喜欢阅读本教程,请 watch 或 star 这个代码库,并在 [Twitter](http://twitter.com/NTulswani) 上关注我以获取最新的消息。 感谢你阅读本教程! diff --git a/part-two-zh_CN.md b/part-two-zh_CN.md index 176feff..62efeaa 100644 --- a/part-two-zh_CN.md +++ b/part-two-zh_CN.md @@ -1,16 +1,16 @@ # 第二部分 -在上一节中,我们构建了一个 React 协调器,并了解了它如何管理渲染器的生命周期。 +在上一节中,我们创建了一个 React 调度器,并了解了它如何管理渲染器的生命周期。 -在第二部分,我们我们将为调度器创建一个公共接口。我们将设计我们组件的 API,然后将创建一个自定义版本的 `createElement` 方法。 +在第二部分,我们将创建一个用于调度器的公开方法。我们将设计我们组件的 API,然后将创建一个自定义版本的 `createElement` 方法。 ## 组件 -对于我们的例子,我们将只实现一个 `Text` 组件。`Text` 组件用于向文档添加文本。 +对于我们的例子,将只实现一个 `Text` 组件。`Text` 组件用于向文档添加文本。 -> Text 组件并不是创建特殊的文本节点。与 DOM API 相比,它具有不同的语义。 +> Text 组件并不创建特殊的文本节点。与 DOM API 相比,它具有不同的语义。 -我们将首先为我们的组件创建一个根容器(还记得协调器中的 `rootContainerInstance` 吗?)它负责用 [officegen](https://github.com/Ziv-Barber/officegen) 创建一个文档实例。 +我们将首先为我们的组件创建一个根容器(还记得调度器中的 `rootContainerInstance` 吗?)它负责用 [officegen](https://github.com/Ziv-Barber/officegen) 创建一个文档实例。 **`WordDocument.js`** @@ -58,7 +58,7 @@ export default Text; **`constructor()`** -在 `constructor` 中,我们初始化 `root` 实例和 `props`。我们还对之前在 `WordDocument.js` 中创建的 `doc` 实例创建了一个引用。使用此引用来通过向其添加文本节点来创建段落。 +在 `constructor` 中,我们初始化 `root` 实例和 `props`。我们还对之前在 `WordDocument.js` 中创建的 `doc` 实例创建了一个引用。使用此引用添加文本节点来创建段落。 例子 - @@ -82,11 +82,11 @@ appendInitialChild(parentInstance, child) { 除了 `appendChild` 方法,你还可以添加 `removeChild` 方法来删​​除子节点。由于我们的宿主目标不提供用于删除子节点的可变 API,因此我们没有使用此方法。 -> 在本教程,`Text` 组件不允许嵌套其它的组件。在更实际的示例中,你可能需要验证组件的嵌套。 +> 在本教程,`Text` 组件不允许嵌套其它的组件。在更实际的示例中,你可能需要组件的嵌套。 #### 注意 -- 不要在类组件 API 中使用数组追踪子组件。相反,直接使用特定的宿主 API 添加它们,因为 React 提供了有关子节点(已删除或添加)的所有有价值的信息。 +- 不要在类组件 API 中使用数组来追踪子组件。相反,直接使用特定宿主的 API 添加它们,因为 React 提供了相关子节点(已删除或添加)所有有价值的信息。 这是正确的 @@ -131,7 +131,7 @@ class MyComponent { } ``` -- 如果你的渲染目标没有提供像 `appendChild` 这样的可变方法,而是只允许你一次替换整个“场景”,你可能需要使用“持久(persistent)”渲染器模式来代替。这是一个[持久渲染器的示例宿主配置](https://github.com/facebook/react/blob/master/packages/react-native-renderer/src/ReactFabricHostConfig.js)。 +- 如果你的渲染目标没有提供像 `appendChild` 这样的可变方法,而是只允许你每次都替换整个“场景”,你可能需要使用“持久(persistent)”渲染器模式。这是一个[持久渲染器宿主配置的示例](https://github.com/facebook/react/blob/master/packages/react-native-renderer/src/ReactFabricHostConfig.js)。 ## createElement @@ -162,8 +162,8 @@ export { createElement } ``` 我认为你可以很容易地理解在 `createElement` 方法中发生了什么。它需要传入元素类型、组件属性和根节点实例。 -根据元素的类型,我们返回它的实例,否则我们返回 `undefined`。 +根据元素类型,我们返回它的实例,或返回 `undefined`。 -我们完成了教程的第二部分。我们为我们的两个组件(`Document` 和 `Text`)构建了 API,并构建了一个用于创建元素的 `createElement` 方法。在下一部分中,我们将构建一个渲染方法来将所有内容渲染到宿主环境中。 +我们完成了教程的第二部分。我们为我们的两个组件(`Document` 和 `Text`)创建了 API,并创建了一个用于创建元素的 `createElement` 方法。在下一部分中,我们将构建一个 `render` 方法来将所有内容渲染到宿主环境中。 -[继续第三部分](./part-three.md) +[继续第三部分](./part-three-zh_CN.md) From e2717b3693eeae81a0cdbdd23918695946eca1bd Mon Sep 17 00:00:00 2001 From: SyMind Date: Thu, 16 Dec 2021 10:11:40 +0800 Subject: [PATCH 5/6] Update part-one-zh_CN.md --- part-one-zh_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/part-one-zh_CN.md b/part-one-zh_CN.md index 4faf255..0e88ff3 100644 --- a/part-one-zh_CN.md +++ b/part-one-zh_CN.md @@ -272,7 +272,7 @@ function render(element, target, callback) { } ``` -我们完成了教程的第一部分。我知道其中的某些概念仅通过阅读代码是很难理解的。开始的时候感觉很混沌,但请继续尝试,最终它会逐渐变得清晰。刚开始学习 Fiber 架构的时候,我什么都不懂。我感到极为沮丧,但我在上述代码的每一部分都使用了 `console.log()` 并试图理解它们,然后出现了“芜湖起飞”的时刻,最终帮助我构建了 [redocx](https://github.com/nitin42/redocx)。它是有点难理解,但你终将会搞懂。 +我们完成了教程的第一部分。我知道其中的某些概念仅通过阅读代码是很难理解的。开始的时候感觉很混沌,但请继续尝试,最终它会逐渐变得清晰。刚开始学习 Fiber 架构的时候,我什么都不懂。我感到极为沮丧,但我在上述代码的每一部分都使用了 `console.log()` 并试图理解它们,然后在某一时刻“芜湖起飞”,最终帮助我构建了 [redocx](https://github.com/nitin42/redocx)。它是有点难理解,但你终将会搞懂。 如果你仍然有任何疑问,我在 Twitter 上 [@NTulswani](https://twitter.com/NTulswani)。 From f77d9713acc240d5df97c16c06933662cd3b2398 Mon Sep 17 00:00:00 2001 From: SyMind Date: Wed, 22 Dec 2021 14:30:59 +0800 Subject: [PATCH 6/6] Update part-one-zh_CN.md --- part-one-zh_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/part-one-zh_CN.md b/part-one-zh_CN.md index 0e88ff3..adfb739 100644 --- a/part-one-zh_CN.md +++ b/part-one-zh_CN.md @@ -278,4 +278,4 @@ function render(element, target, callback) { [更多渲染器的实际示例](https://github.com/facebook/react/tree/master/packages/react-reconciler#practical-examples) -[继续第二部分]](./part-two-zh_CN.md) +[继续第二部分](./part-two-zh_CN.md)