React 的定义是什么? - 用于构建用户界面的 Javascript 库。
可以看到 React 并没有限定,用于构建什么的用户界面,而我们在实际的使用中:使用 React 来编写 H5、PC 的 web 页面、使用 React Native 来创建移动端的原生应用、使用 Ray 来编写智能小程序的应用。
state 加上 JSX 生成了虚拟 dom,可以理解为:这个虚拟 dom 是 React 用来表示 UI 的一套数据结构,render 则决定了该虚拟 dom 最终如何渲染在目标环境中。所以,从理论上来说,我们可以使用 Javascript 加上 React 编写运行于任意平台上的界面。
今天我想要介绍的内容主题就是:自定义一个 React render,能够直接使用 React 编写运行于 canvas 中的 UI。
React 的虚拟 dom 含义,React 对虚拟 dom 有哪些操作?
要实现一个自定义 render,首先需要对 Fiber 有一个了解。本段将介绍大致介绍 React 从更新到渲染出界面要经历哪些步骤、以及 React 的虚拟 dom —— Fiber 的定义,然后以 react-dom 为例子,介绍在 React 更新的过程中将要对 Fiber 做出哪些操作,使之最终可以渲染到浏览器的屏幕上。
以下讲解以 React 18 版本为例子
import ReactDom from 'react-dom'
import App from './App'
function App() {return (<div><ul><li><span>1</span></li><li>2</li> </ul></div>)
}
const root = ReactDOM.render(<App />,
document.getElementById('root')
);
- child 指向当前 Fiber 节点的第一个子节点
- sibling 指向当前 Fiber 节点的兄弟节点
- reutrn 指向当前 Fiber 节点的父节点
React 的组件在初次渲染的时候,会生成 Fiber,这个阶段我们先称之为 reconciler。如上图所示,React组件更新是一个深度优先搜索的过程,从组件 App(这是一个 FunctionComponnet,react 在更新的过程中,对于不同类型的组件处理方式是不同的) 开始,创建 App 的 Fiber 然后继续对 App 的子组件进行类似操作,一直到文本节点 span(HostComponent)。
当前操作的组件的子组件为 null 后,react 会对当前节点执行 completeWork
的工作
completeWork 第一步
首先,对 span 这个节点执行 completeWork 操作,其类型为 HostComponent,因为该组件是初次渲染,还未生成 dom 实例,所以调用了 createInstance
方法生成 dom 实例,这里的 createInsrance
则是 react-dom render 实现的,生成目标平台实例的方法。
有没有注意到 span 标签中的文本 1 并没有生成对应的 Fiber 节点,这是 react-dom render 做的一个优化,它认为对于这种字符串类型的子节点不需要生成 Fiber,直接使用 domInstance.textContext
直接设置就可以了。在 react 的组件更新阶段这处的判断:
export function shouldSetTextContent(type: string, props: Props): boolean {return (type === 'textarea' ||type === 'noscript' ||typeof props.children === 'string' ||typeof props.children === 'number' ||(typeof props.dangerouslySetInnerHTML === 'object' &&props.dangerouslySetInnerHTML !== null &&props.dangerouslySetInnerHTML.__html != null));
}
然后在 completeWork 阶段调用了 finalizeInitialChildren
方法。该方法也是 react-dom render 实现的,在这个方法的实现中给当前 dom 节点设置了 textContent。
completeWork 第二步
到了这里已经完成了更新阶段对于 span
节点的处理,接下来 React 会处理 completeWork.return
也就是其父节点 li。
该节点也是一个 HostComponent,同样的初次渲染会调用 createInstance
以生成对应的 dom 实例,