关于React的一些基本概念
创建一个 React 自定义渲染器,你需要对React渲染的基本原理有一定的了解。所以在深入阅读本文之前,先要确保你能够理解以下几个基本概念:
1. Element
我们可以通过 JSX
或者 React.createElement
来创建 Element,用来描述我们要创建的视图节点。比如:
<button class='button button-blue'>
<b>
OK!
</b>
</button>
JSX
会被转义译为:
React.createElement(
"button",
{ class: 'button button-blue' },
React.createElement("b", null, "OK!")
)
React.createElement
最终构建出类似这样的对象:
{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
props: {
children: 'OK!'
}
}
}
}
也就是说 Element 就是一个普通的对象,描述用户创建的节点类型、props 以及 children。这些 Elements 组合成树,描述用户视图
2. Component
可以认为是 Element 的类型,它有两种类型:
- Host Component: 宿主组件,这是由渲染的平台提供的‘内置’组件,例如
ReactDOM
平台下面的DOM
节点,如div
、span
... 这些组件类型为字符串 - Composite Component: 复合组件,这是一种用户自定义的组件封装单位。通常包含自定义的逻辑、状态以及输出 Element 树。复合类型可以为类或函数
const DeleteAccount = () => (
<div>
<p>Are you sure?</p>
<DangerButton>Yep</DangerButton>
<Button color='blue'>Cancel</Button>
</div>
);
3. Instance
当 React 开始渲染一个 Element 时,会根据组件类型为它创建一个‘实例’,例如类组件,会调用new
操作符实例化。这个实例会一直引用,直到 Element 从 Element Tree 中被移除。
首次渲染
: React 会实例化一个 MyButton
实例,调用挂载相关的生命周期方法,并执行 render
方法,递归渲染下级
render(<MyButton>foo</MyButton>, container)
更新
: 因为组件类型没有变化,React 不会再实例化,这个属于‘节点更新’,React 会执行更新相关的生命周期方法,如shouldComponentUpdate
。如果需要更新则再次执行render
方法
render(<MyButton>bar</MyButton>, container)
卸载
: 组件类型不一样了, 原有的 MyButton 被替换. MyButton 的实例将要被销毁,React 会执行卸载相关的生命周期方法,如componentWillUnmount
render(<button>bar</button>, container)
4. Reconciler & Renderer
Reconciler
和 Renderer
的关系可以通过下图缕清楚.
Reconciler 的职责是维护 VirtualDOM 树,内部实现了 Diff/Fiber 算法,决定什么时候更新、以及要更新什么
而 Renderer 负责具体平台的渲染工作,它会提供宿主组件、处理事件等等。例如ReactDOM就是一个渲染器,负责DOM节点的渲染和DOM事件处理。
5. Fiber 的两个阶段 React 使用了 Fiber 架构之后,更新过程被分为两个阶段(Phase)
- 协调阶段(Reconciliation Phase) 这个阶段 React 会找出需要更新的节点。这个阶段是可以被打断的,比如有优先级更高的事件要处理时。
- 提交阶段(Commit Phase) 将上一个阶段计算出来的需要处理的副作用(Effects)一次性执行了。这个阶段必须同步执行,不能被打断
如果按照render
为界,可以将生命周期函数按照两个阶段进行划分:
- 协调阶段
constructor
componentWillMount
废弃componentWillReceiveProps
废弃static getDerivedStateFromProps
shouldComponentUpdate
componentWillUpdate
废弃render
getSnapshotBeforeUpdate()
- 提交阶段
componentDidMount
componentDidUpdate
componentWillUnmount
没理解?那么下文读起来对你可能比较吃力,建议阅读一些关于React基本原理的相关文章。
就目前而言,React 大部分核心的工作已经在 Reconciler 中完成,好在 React 的架构和模块划分还比较清晰,React官方也暴露了一些库,这极大简化了我们开发 Renderer 的难度。开始吧!
自定义React渲染器
React官方暴露了一些库供开发者来扩展自定义渲染器:
- react-reconciler - 这就是 React 的协调器, React 的核心所在。我们主要通过它来开发渲染器。
- scheduler - 合作调度器的一些 API 。本文不会用到
需要注意的是,这些包还是实验性的,API可能不太稳定。另外,没有详细的文档,你需要查看源代码或者其他渲染器实现;本文以及扩展阅读中的文章也是很好的学习资料。
创建一个自定义渲染器只需两步:
第一步: 实现宿主配置,这是react-reconciler
要求宿主提供的一些适配器方法和配置项。这些配置项定义了如何创建节点实例、构建节点树、提交和更新等操作。下文会详细介绍这些配置项
const Reconciler = require('react-reconciler');
const HostConfig = {
// ... 实现适配器方法和配置项
};
第二步:实现渲染函数,类似于ReactDOM.render()
方法
// 创建Reconciler实例, 并将HostConfig传递给Reconciler
const MyRenderer = Reconciler(HostConfig);
/**
* 假设和ReactDOM一样,接收三个参数
* render(<MyComponent />, container, () => console.log('rendered'))
*/
export function render(element, container, callback) {
// 创建根容器
if (!container._rootContainer) {
container._rootContainer = ReactReconcilerInst.createContainer(container, false);
}
// 更新根容器
return ReactReconcilerInst.updateContainer(element, container._rootContainer, null, callback);
}
容器既是 React 组件树挂载的目标
(例如 ReactDOM 我们通常会挂载到 #root
元素,#root
就是一个容器)、也是组件树的 根Fiber节点(FiberRoot)
。根节点是整个组件树的入口,它将会被 Reconciler 用来保存一些信息,以及管理所有节点的更新和渲染。
关于 Fiber 架构的一些细节可以看这些文章:
- 《译 深入React fiber架构及源码》
- 《React Fiber》 有能力的同学,可以直接看Lin Clark的演讲
HostConfig 渲染器适配
HostConfig
支持非常多的参数,完整列表可以看这里. 下面是一些自定义渲染器必须提供的参数:
interface HostConfig {
/**
* 用于分享一些上下文信息
*/
// 获取根容器的上下文信息, 只在根节点调用一次
getRootHostContext(rootContainerInstance: Container): HostContext;
// 获取子节点的上下文信息, 每遍历一个节点都会调用一次
getChildHostContext(parentHostContext: HostContext, type: Type, rootContainerInstance: Container): HostContext;
/**
* 节点实例的创建
*/
// 普通节点实例创建,例如DOM的Element类型
createInstance(type: Type, props: Props, rootContainerInstance: Container, hostContext: HostContext, internalInstanceHandle: OpaqueHandle,): Instance;
// 文本节点的创建,例如DOM的Text类型
createTextInstance(text: string, rootContainerInstance: Container, hostContext: HostContext, internalInstanceHandle: OpaqueHandle): TextInstance;
// 决定是否要处理子节点/子文本节点. 如果不想创建则返回true. 例如ReactDOM中使用dangerouslySetInnerHTML, 这时候子节点会被忽略
shouldSetTextContent(type: Type, props: Props): boolean;
/**
* 节点树构建
*/
// 如果节点在*未挂载*状态下,会调用这个来添加子节点
appendInitialChild(parentInstance: Instance, child: Instance | TextInstance): void;
// **下面都是副作用(Effect),在’提交‘阶段被执行**
// 添加子节点
appendChild?(parentInstance: Instance, child: Instance | TextInstance): void;
// 添加子节点到容器节点(根节点)
appendChildToContainer?(container: Container, child: Instance | TextInstance): void;
// 插入子节点
insertBefore?(parentInstance: Instance, child: Instance | TextInstance, beforeChild: Instance | TextInstance): void;
// 插入子节点到容器节点(根节点)
insertInContainerBefore?(container: Container, child: Instance | TextInstance, beforeChild: Instance | TextInstance,): void;
// 删除子节点
removeChild?(parentInstance: Instance, child: Instance | TextInstance): void;
// 从容器节点(根节点)中移除子节点
removeChildFromContainer?(container: Container, child: Instance | TextInstance): void;
/**
* 节点挂载
*/
// 在完成所有子节点初始化时(所有子节点都appendInitialChild完毕)时被调用, 如果返回true,则commitMount将会被触发
// ReactDOM通过这个属性和commitMount配置实现表单元素的autofocus功能
finalizeInitialChildren(parentInstance: Instance, type: Type, props: Props, rootContainerInstance: Container, hostContext: HostContext): boolean;
// 和finalizeInitialChildren配合使用,commitRoot会在’提交‘完成后(resetAfterCommit)执行, 也就是说组件树渲染完毕后执行
commitMount?(instance: Instance, type: Type, newProps: Props, internalInstanceHandle: OpaqueHandle): void;
/**
* 节点更新
*/
// 准备节点更新. 如果返回空则表示不更新,这时候commitUpdate则不会被调用
prepareUpdate(instance: Instance, type: Type, oldProps: Props, newProps: Props, rootContainerInstance: Container, hostContext: HostContext,): null | UpdatePayload;
// **下面都是副作用(Effect),在’提交‘阶段被执行**
// 文本节点提交
commitTextUpdate?(textInstance: TextInstance, oldText: string, newText: string): void;
// 普通节点提交
commitUpdate?(instance: Instance, updatePayload: UpdatePayload, type: Type, oldProps: Props, newProps: Props, internalInstanceHandle: OpaqueHandle): void;
// 重置普通节点文本内容, 这个需要和shouldSetTextContent(返回true时)配合使用,
resetTextContent?(instance: Instance): void;
/**
* 提交
*/
// 开始’提交‘之前被调用,比如这里可以保存一些状态,在’提交‘完成后恢复状态。比如ReactDOM会保存当前元素的焦点状态,在提交后恢复
// 执行完prepareForCommit,就会开始执行Effects(节点更新)
prepareForCommit(containerInfo: Container): void;
// 和prepareForCommit对应,在提交完成后被执行
resetAfterCommit(containerInfo: Container): void;
/**
* 调度
*/
// 这个函数将被Reconciler用来计算当前时间, 比如计算任务剩余时间
// ReactDOM中会优先使用Performance.now, 普通场景用Date.now即可
now(): number;
// 自定义计时器
setTimeout(handler: (...args: any[]) => void, timeout: number): TimeoutHandle | NoTimeout;
// 取消计时器
clearTimeout(handle: TimeoutHandle | NoTimeout): void;
// 表示一个空的计时器,见 深蓝互联成立于2013年,是一家物联网硬件开发及软件应用服务商,获得多次获得国家高新技术企业资质的企业。深蓝互联专注软硬件技术开发的专业性技术公司。我们从事软硬件开发十年,擅长SaaS 平台开发、APP小程序开发、软硬件结合开发,在视觉识别处理、数据架构、云计算、多线程高并发和集群、数据安全加密和防护方便有很深的技术积累。
我们拥有专业优秀的设计和技术团队,以极具创意的 UI 设计、精湛卓越的开发技术,专业的网络策划团队。公司多年来投入打造物联网SaaS平台,集成了公司研发的多款智能物联网终端(智能鲜米机、生鲜售货机、自助洗车机、小区电瓶车充电系统等)。
公司一直坚持以研发为导向,打造软硬件结合的物联网平台系统。将一直坚持提高开发的技术实力更好的为我们的客户服务!
文章来自深蓝互联http://www.szdbi.com/webxt/317.html转载请注明出处!
相关文章
无相关信息