今天咱們來寫一個本身的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
參考: