今天咱们来写一个本身的renderer,也就是react的渲染器。开始以前,先来了解一下react的三个核心。javascript
先create-react-app建一个项目,而后安装react-reconciler,修改index.js文件,改成用咱们本身写的renderer来渲染。html
先创建一个文件,就叫renderer吧,怎么写呢,看下react-reconciler的readme.md以下:java
import Reconciler from "react-reconciler" const hostConfig = { // ... 这里写入须要使用的函数 }; const MyReconcilerInstance = Reconciler(hostConfig); const MyCustomRenderer = { render(ele, containerDom, callback){ const container = MyReconcilerInstance.createContainer(containerDom, false); MyReconcilerInstance.updateContainer( ele, container, null, callback ) } }; export default MyCustomRenderer 复制代码
而后hostConfig怎么写呢,官方已经给出了完整的列表,不过咱们不须要写那么多,先写出来须要的,以下,先把全部的函数都打上log,看一下调用顺序:node
// 这些是渲染须要的 const hostConfig = { getPublicInstance(...args) { console.log('getPublicInstance', ...args); }, getChildHostContext(...args) { console.log('getChildHostContext', ...args); }, getRootHostContext(...args) { console.log('getRootHostContext', ...args); }, appendChildToContainer(...args) { console.log('appendChildToContainer', ...args); }, prepareForCommit(...args) { console.log('prepareForCommit', ...args) }, resetAfterCommit(...args) { console.log('resetAfterCommit', ...args) }, createInstance(...args) { console.log('createInstance', ...args) }, appendInitialChild(...args) { console.log('appendInitialChild', ...args) }, finalizeInitialChildren(...args) { console.log('prepareUpdate', ...args) }, shouldSetTextContent(...args) { console.log('shouldSetTextContent', ...args) }, shouldDeprioritizeSubtree(...args) { console.log('shouldDeprioritizeSubtree', ...args); }, createTextInstance(...args) { console.log('createTextInstance', ...args); }, scheduleDeferredCallback(...args) { console.log('scheduleDeferredCallback', ...args); }, cancelDeferredCallback(...args) { console.log('cancelDeferredCallback', ...args); }, shouldYield(...args) { console.log('shouldYield', ...args); }, scheduleTimeout(...args) { console.log('scheduleTimeout', ...args); }, cancelTimeout(...args) { console.log('cancelTimeout', ...args); }, noTimeout(...args) { console.log('noTimeout', ...args); }, now(...arg){ console.log('now',...args); }, isPrimaryRenderer(...args) { console.log('isPrimaryRenderer', ...args); }, supportsMutation:true, } 复制代码
而后咱们修改App.js文件,简单的写一个计数器,大体以下:react
class App extends Component { state = { count: 1 } increment = () => { this.setState((state) => { return { count: state.count + 1 } }) } decrement = () => { this.setState((state) => { return { count: state.count - 1 } }) } render() { const { count } = this.state; return ( <div> <button onClick={this.decrement}> - </button> <span>{count}</span> <button onClick={this.increment}> + </button> </div> ) } } 复制代码
打开浏览器看一下发现并无渲染出任何东西,打开console,这些函数的调用顺序以下图,好的,那咱们开始写这些函数:git
// rootContainerInstance 根节点 咱们这里就是div#root getRootHostContext(rootContainerInstance){ return {} } 复制代码
/** * parentHostContext 从上一级节点传递过来的上下文 * type 当前节点的nodeType * rootContainerInstance 根节点 */ getChildHostContext(parentHostContext, type, rootContainerInstance){ return {} } 复制代码
/* * type 当前节点的nodeType * props 要赋予当前节点的属性 */ shouldSetTextContent(type, props) { return typeof props.children === 'string' || typeof props.children === 'number' }, 复制代码
/** * type 当前节点的nodeType * newProps 传递给当前节点的属性 * rootContainerInstance 根节点 * currentHostContext 从上级节点传递下来的上下文 * workInProgress 当前这个dom节点对应的fiber节点 */ createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress) { const element = document.createElement(type); for (const key in newProps) { const val = newProps[key]; if (key === 'className') { element.className = val } else if (key === 'style') { element.style = val } else if (key.startsWith('on')) { const eventType = key.slice(2).toLowerCase(); element.addEventListener(eventType, val); } else if (key === 'children') { if (typeof val === 'string' || typeof val === 'number') { const textNode = document.createTextNode(val); element.appendChild(textNode) } } else { element.setAttribute(key, val) } } return element }, 复制代码
/** * domElement 当前已经生成的dom节点 * type nodeType * props 属性 * rootContainerInstance 根节点 * hostContext 上下级传递过来的上下文 */ finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) { return !!props.autoFocus }, 复制代码
/** * parentInstance 上一级节点 * child 子节点 */ appendInitialChild(parentInstance, child) { parentInstance.appendChild(child); } 复制代码
// rootContainerInstance 根节点 prepareFomCommit(rootContainerInstance){} 复制代码
// container 咱们的根节点 // child 已经生成的节点 appendChildToContainer(container, child){ container.appendChild(child) } 复制代码
resetAfterCommit(){}
复制代码
好了,如今咱们的初次渲染已经大功告成了。github
而后我画了一张比较丑陋的流程图: 数组
/** * domElement 当前遍历的dom节点 * type nodeType * oldProps 旧的属性 * newProps 新属性 * rootContainerInstance 根节点 * hostContext 从上一级节点传递下来的上下文 */ prepareUpdate(domElement, type, oldProps, newProps, rootContainerInstance, hostContext) { console.log('prepareUpdate', [...arguments]); let updatePayload = null; for (const key in oldProps) { const lastProp = oldProps[key]; const nextProp = newProps[key]; if (key === 'children') { if (nextProp != lastProp && (typeof nextProp === 'number' || typeof nextProp === 'string')) { updatePayload = updatePayload || []; updatePayload.push(key, nextProp); } } else { // 其他暂不考虑 } }; return updatePayload } 复制代码
/** * domElement 对应的dom节点 * updatePayload 咱们刚才决定返回的更新 * type nodeType * oldProps 旧的属性 * newProps 新属性 * internalInstanceHandle 当前dom节点对应的fiber节点 */ commitUpdate(domElement, updatePayload, type, oldProps, newProps, internalInstanceHandle) { for (var i = 0; i < updatePayload.length; i += 2) { var propKey = updatePayload[i]; var propValue = updatePayload[i + 1]; if (propKey === 'style') { } else if (propKey === 'children') { domElement.textContent = propValue; // 其他状况暂不考虑 } else { } } }, 复制代码
woola!更新也完成了。浏览器
代码有点多,各位看官辛苦!本文全部代码都可在此处找到markdown
参考: