实现 useState
src/index.js
注意:我们这里写的暂时会有数组塌陷的情况。
其实因为在原版代码里,每一个组件里都有自己的index和数组。
在原版代理里,它是把这个放在了 fiber 里。
React有一个特点,每次 setState 更新都需要从根节点开始进行一次比较,不会按需更新,这也是React 耗时长,性能差的原因,正因为次,React 搞出了 fiber,fiber 的核心就是要 diff 工作可以暂停。

import React, { Component } from './react';import ReactDOM from './react-dom';// setState参数为 值function Counter1(){let [number, setNumber] = React.useState(5);return (<div><p>Counter1:{number}</p><button onClick={() => setNumber(number+1)}>Counter1:+</button></div>)}// setState参数为 函数function Counter2(){let [number, setNumber] = React.useState(10);return (<div><p>Counter2:{number}</p><button onClick={() => setNumber(n => n+1)}>Counter2:+</button></div>)}function Counter3(){let [number, setNumber] = React.useState(15);return (<div><p>Counter3:{number}</p><button onClick={() => setNumber(number+1)}>Counter3:+</button></div>)}function App(){let [number, setNumber] = React.useState(0);return (<div><Counter1 />{number%2 === 0 && <Counter2 />}<Counter3 /><button onClick={() => setNumber(number+1)}>App:+</button></div>)}ReactDOM.render(<App />, document.getElementById('root'));
src/react.js
import { useState } from './react-dom';export { useState };const React = {useState,}export default React;
src/react-dom.js
+ let hookStates = []; // 这是一个数组,里面存放着我们所有的 hook 状态+ let hookIndex = 0; // 当前 hook 的索引+ let scheduleUpdate; // hook调度更新函数// 给根容器挂载的时候function render (vdom, container){mount(vdom, container);+ scheduleUpdate = () => {+ hookIndex = 0; //在状态修改后调试更新的时候,把 hooks 的索引重置+ compareTwoVdom(container, vdom, vdom); //虚拟DOM比较更新+ }}// 挂载function mount (vdom, container){if (!vdom) return;const dom = createDOM(vdom, container);if (dom) container.appendChild(dom);dom.componentDidMount && dom.componentDidMount();}/** 让函数组件可以使用状态** @param {*} initialState 初始状态*/export function useState(initialState){//把老的值取出来,如果没有,就使用默认值hookStates[hookIndex] = hookStates[hookIndex] ||(typeof initialState === 'function' ? initialState() : initialState);// 因为 hookIndex 一直在变,// 给每一个 useState 的 hook 定死一个不变的索引。用于 setState时 更新该hook的数据let currentIndex = hookIndex;function setState(newState){if(typeof newState === 'function') newState = newState(hookStates[currentIndex]);hookStates[currentIndex] = newState;scheduleUpdate(); //当状态改变后要重新更新应用}return [hookStates[hookIndex++], setState]; //注意,先取值,再++,把 hook 索引指向下一个 useState。}
实现 useMemo、useCallback
demo:https://www.yuque.com/zhuchaoyang/cx9erp/og7367#jncy9
src/react.js
import { useState, useCallback, useMemo } from './react-dom';export { useState, useCallback, useMemo };const React = {useCallback,useMemo,}export default React;
src/react-dom.js
/** 缓存 对象** @param {*} factory 工厂方法,该方法返回要缓存的对象* @param {*} deps 依赖数组* @returns*/export function useMemo(factory, deps){if (hookStates[hookIndex]){let [lastMemo, lastDeps] = hookStates[hookIndex];let same = deps.every((item, index) => item === lastDeps[index]);if (same){hookIndex ++;return lastMemo;} else {let newMemo = factory();hookStates[hookIndex++] = [newMemo, deps];return newMemo;}} else {let newMemo = factory();hookStates[hookIndex++] = [newMemo, deps];return newMemo;}}/** 缓存 函数** @param {*} callback 要缓存的函数* @param {*} deps 依赖数组* @returns*/export function useCallback(callback, deps){if (hookStates[hookIndex]){let [lastCallback, lastDeps] = hookStates[hookIndex];let same = deps.every((item, index) => item === lastDeps[index]);if (same){hookIndex ++;return lastCallback;} else {hookStates[hookIndex++] = [callback, deps];return callback;}} else {hookStates[hookIndex++] = [callback, deps];return callback;}}
实现 useReducer
demo:https://www.yuque.com/zhuchaoyang/cx9erp/og7367#YzW9B
src/react.js
import { useReducer } from './react-dom';export { useReducer };const React = {useReducer,}export default React;
src/react-dom.js
/** 让函数组件可以使用状态,useState 可以算作 useReducer 的一个语法糖** @param {*} initialState 初始状态*/export function useState(initialState){return useReducer(null, initialState);}export function useReducer(reducer, initialState){//把老的值取出来,如果没有,就使用默认值let lastState = hookStates[hookIndex] = hookStates[hookIndex] ||(typeof initialState === 'function' ? initialState() : initialState);//因为 hookIndex 一直在变,//给每一个 useState 的 hook 定死一个不变的索引。用于调用 setState(dispatch) 函数时 更新该hook的数据let currentIndex = hookIndex;function dispatch(action){let nextState;if (reducer){nextState = reducer(lastState, action);} else {if (typeof action === 'function') action = action(lastState);nextState = action;}hookStates[currentIndex] = nextState;scheduleUpdate(); //当状态改变后要重新更新应用}//注意,先取值,再++,把 hook 索引指向下一个 useState。return [hookStates[hookIndex++], dispatch];}
实现 useContext
demo:https://www.yuque.com/zhuchaoyang/cx9erp/og7367#tCIIp
src/react.js
export function useContext(context){return context._currentValue;}const React = {useContext,}export default React;
实现 useEffect
demo: https://www.yuque.com/zhuchaoyang/cx9erp/og7367#C082E
src/react.js
import { useEffect } from './react-dom';export { useEffect };const React = {useEffect,}export default React;
src/react-dom.js
export function compareTwoVdom(container, oldVdom, newVdom, nextDOM){// 老的有,新的没有 => 要刪除此节点if (oldVdom && !newVdom){+ if (hookStates[hookIndex]){+ let [destroyFunction] = hookStates[hookIndex];+ destroyFunction && destroyFunction();+ }}// 新旧都有虚拟DOM,但是类型type不相同 => 直接替换节点if (oldVdom && newVdom && oldVdom.type !== newVdom.type){+ if (hookStates[hookIndex]){+ let [destroyFunction] = hookStates[hookIndex];+ destroyFunction && destroyFunction();+ }}}/** useEffect** 为了保证次回调函数不是同步执行,而是在页面渲染后执行,可以把它包装成宏任务* @param {*} callback 回调函数* @param {*} deps 依赖数组*/export function useEffect(callback, deps){if (hookStates[hookIndex]){let [destroyFunction, lastDeps] = hookStates[hookIndex];let same = deps && deps.every((item, index) => item === lastDeps[index]);if (same){hookIndex ++;} else {destroyFunction && destroyFunction();setTimeout(() => {let destroyFunction = callback();hookStates[hookIndex++] = [destroyFunction, deps];})}} else {// 用 setTimeout 封装成宏任务setTimeout(() => {let destroyFunction = callback(); //执行后,返回销毁函数hookStates[hookIndex++] = [destroyFunction, deps];})}}
实现 useLayoutEffect、useRef
demo: https://www.yuque.com/zhuchaoyang/cx9erp/og7367#mB9bU
src/react.js
import { useLayoutEffect, useRef } from './react-dom';export { useLayoutEffect, useRef };const React = {useLayoutEffect,useRef,}export default React;
src/react-dom.js
/** useLayoutEffect** 为了保证次回调函数不是同步执行,而是在页面渲染之前执行,可以把它包装成微任务* @param {*} callback 回调函数* @param {*} deps 依赖数组*/export function useLayoutEffect(callback, deps){if (hookStates[hookIndex]){let [destroyFunction, lastDeps] = hookStates[hookIndex];let same = deps && deps.every((item, index) => item === lastDeps[index]);if (same){hookIndex ++;} else {destroyFunction && destroyFunction();queueMicrotask(() =>{let destroyFunction = callback();hookStates[hookIndex++] = [destroyFunction, deps];})}} else {// 用 queueMicrotask 封装成微任务queueMicrotask(() => {let destroyFunction = callback(); //执行后,返回销毁函数hookStates[hookIndex++] = [destroyFunction, deps];})}}export function useRef(initialState){hookStates[hookIndex] = hookStates[hookIndex] || {current: initialState};return hookStates[hookIndex++];}
实现 forwardRef、useImperativeHandle
demo: https://www.yuque.com/zhuchaoyang/cx9erp/og7367/edit#e38BC
src/react.js
import { forwardRef, useImperativeHandle } from './react-dom';export { forwardRef, useImperativeHandle };export function forwardRef(FunctionComponent){return class Hoc extends Component {render (){if (FunctionComponent.length < 2){console.error('Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?');}return FunctionComponent(this.props, this.ref);}}}export function useImperativeHandle(ref, handler){ref.current = handler();}const React = {forwardRef,useImperativeHandle,}export default React;
