React | 2020-06-21 16:33:04 652次 1次
本篇实现类组件渲染更新,hooks 的简单实现。
一、类组件
import React from './react'; import ReactDOM from './react-dom'; class ClassCounter extends React.Component { constructor(props) { super(props); this.state = { number: 0 }; } onClick = () => { this.setState(state => ({ number: state.number + 1 })); } render() { return ( <div id="counter"> <span>{this.state.number}</span> <button onClick={this.onClick}>加1</button> </div> ) } } ReactDOM.render(<FunctionCounter name="计数器" />, document.getElementById('root'));
假设我们有一个简单的计数器组件,继承自 React.component,所以需要在 react.js 中添加这个类:
... import { Update } from './UpdateQueue'; ... ... class Component { constructor(props) { this.props = props; } setState(payload) {//可能是对象,也可能是一个函数 let update = new Update(payload); //updateQueue其实是放在此类组件对应的fiber节点的 internalFiber this.internalFiber.updateQueue.enqueueUpdate(update); scheduleRoot();//从根节点开始调度 } } Component.prototype.isReactComponent = {};//类组件 ...
UpdateQueue.js:
export class Update { constructor(payload) { this.payload = payload; } } //数据结构是一个单链表 export class UpdateQueue { constructor() { this.firstUpdate = null; this.lastUpdate = null; } enqueueUpdate(update) { if (this.lastUpdate === null) { this.firstUpdate = this.lastUpdate = update; } else { this.lastUpdate.nextUpdate = update; this.lastUpdate = update; } } forceUpdate(state) { let currentUpdate = this.firstUpdate; while (currentUpdate) { let nextState = typeof currentUpdate.payload === 'function' ? currentUpdate.payload(state) : currentUpdate.payload; state = { ...state, ...nextState }; currentUpdate = currentUpdate.nextUpdate; } this.firstUpdate = this.lastUpdate = null; return state; } }
实现类组件的时候,定义的那个组件类型仅仅是一个标识,虽然也是一个fiber节点,但是其无法去更新,需要把自己的 render中 return 出的节点挂载到自己父级上,继续完善 scheduler.js:
... function beginWork(currentFiber) { ... } else if (currentFiber.tag === TAG_CLASS) {//类组件 updateClassComponent(currentFiber); } } function updateClassComponent(currentFiber) { if (!currentFiber.stateNode) {//类组件 stateNode 组件的实例 // new ClassCounter(); 类组件实例 fiber双向指向 currentFiber.stateNode = new currentFiber.type(currentFiber.props); currentFiber.stateNode.internalFiber = currentFiber; currentFiber.updateQueue = new UpdateQueue(); } //给组件的实例的state 赋值 currentFiber.stateNode.state = currentFiber.updateQueue.forceUpdate(currentFiber.stateNode.state); let newElement = currentFiber.stateNode.render(); const newChildren = [newElement]; reconcileChildren(currentFiber, newChildren); } ... ... //这里因为更新节点时候 ClassCounter这个fiber不需要更新 function updateDOM(stateNode, oldProps, newProps) { if (stateNode && stateNode.setAttribute) setProps(stateNode, oldProps, newProps); } ... ... function reconcileChildren(currentFiber, newChildren) {//[A1] ... while (newChildIndex < newChildren.length || oldFiber) { ... if (newChild && typeof newChild.type === 'function' && newChild.type.prototype.isReactComponent) { tag = TAG_CLASS;// } ... if (sameType) { if (oldFiber.alternate) {//说明至少已经更新一次了 ... newFiber.updateQueue = oldFiber.updateQueue || new UpdateQueue(); ... } else { newFiber = { ... updateQueue: oldFiber.updateQueue || new UpdateQueue(), ... } } } else { if (newChild) {//看看新的虚拟DOM是不是为null newFiber = { ... updateQueue: new UpdateQueue(), ... } } ... } ... } } ... function commitWork(currentFiber) { ... // 跳过继承的这个类组件,因为它只是一个标识 while (returnFiber.tag !== TAG_HOST && returnFiber.tag !== TAG_ROOT && returnFiber.tag !== TAG_TEXT) { returnFiber = returnFiber.return; } ... if (currentFiber.effectTag === PLACEMENT) {//新增加节点 let nextFiber = currentFiber; if (nextFiber.tag === TAG_CLASS) { return; } // 如果要挂载的节点不是DOM节点,比如说是类组件Fiber,一直找第一个儿子,直到找到一个真实DOM节点为止 while (nextFiber.tag !== TAG_HOST && nextFiber.tag !== TAG_TEXT) { nextFiber = currentFiber.child; } domReturn.appendChild(nextFiber.stateNode); //删除节点的时候需要跳过ClassCounter,直接找子节点 } else if (currentFiber.effectTag === DELETION) {//删除节点 return commitDeletion(currentFiber, domReturn); } ... } function commitDeletion(currentFiber, domReturn) { if (currentFiber.tag == TAG_HOST || currentFiber.tag == TAG_TEXT) { domReturn.removeChild(currentFiber.stateNode); } else { commitDeletion(currentFiber.child, domReturn) } }
二、HOOKS函数组件
添加完这些逻辑,实现类组件的渲染更新,接下来继续实现 hooks 的 useReducer 和 useState 功能,函数组件直接调用即可
const ADD = 'ADD'; function reducer(state, action) { switch (action.type) { case ADD: return { count: state.count + 1 }; default: return state; } } function FunctionCounter() { const [numberState, setNumberState] = React.useState({ number: 0 });//0 const [countState, dispatch] = React.useReducer(reducer, { count: 0 });//0 return ( <div> <div id="counter1"> <span>{numberState.number}</span> <button onClick={() => setNumberState({ number: numberState.number + 1 })}>加1</button> </div> <div id="counter2"> <span>{countState.count}</span> <button onClick={() => dispatch({ type: ADD })}>加1</button> </div> </div> ) } ReactDOM.render(<FunctionCounter name="计数器" />, document.getElementById('root'));
... let workInProgressFiber = null;//正在工作中的fiber let hookIndex = 0;//hooks索引 ... function beginWork(currentFiber) { ... } else if (currentFiber.tag === TAG_FUNCTION_COMPONENT) {//函数组件 updateFunctionComponent(currentFiber); } } function updateFunctionComponent(currentFiber) { workInProgressFiber = currentFiber; hookIndex = 0; workInProgressFiber.hooks = []; const newChildren = [currentFiber.type(currentFiber.props)]; reconcileChildren(currentFiber, newChildren); } ... export function useReducer(reducer, initialValue) { let newHook = workInProgressFiber.alternate && workInProgressFiber.alternate.hooks && workInProgressFiber.alternate.hooks[hookIndex]; if (newHook) { newHook.state = newHook.updateQueue.forceUpdate(newHook.state); } else { newHook = { state: initialValue, updateQueue: new UpdateQueue()// 空的更新队列 } } const dispatch = action => {//{type:'ADD'} let payload = reducer ? reducer(newHook.state, action) : action; newHook.updateQueue.enqueueUpdate( new Update(payload) ); scheduleRoot(); } workInProgressFiber.hooks[hookIndex++] = newHook; return [newHook.state, dispatch]; } export function useState(initialValue) { return useReducer(null, initialValue); }
react 的 hooks 简单模拟实现完成,具体代码请移步 源码地址https://github.com/wineSu/react-Hooks
1人赞