DIY React Hooks + useState 源码实现

2020-09-02  本文已影响0人  隐号骑士

我作为一个函数组件,怎么就实现内部状态和类生命周期了呢?
现在学习一番

useState

import React from 'react';
import ReactDOM from 'react-dom';

let currentState

const render = () => {
    ReactDOM.render(
        <App/>,
      document.getElementById('root')
    );
}

function useState(initialState){
    let state = currentState || initialState 
    const setState=(targetState)=>{
        currentState = targetState;
        render()
    }
    return [state, setState]
}

function App(){
    const [count, setCount] = useState(1)
    const add = ()=>{
        setCount(count+1)
    }
    return <div>
        <div>{count}</div>
        <div onClick={add}>add</div>
    </div>
}

ReactDOM.render(
    <App/>,
  document.getElementById('root')
);

useEffects

// 修改上一个例子
let depsArray

function useEffect(cb,deps){
    const noDeps = !deps
    const noChanges = deps && depsArray ? !deps.every((e,index)=> e === depsArray[index]) : true;
    if(noDeps || noChanges){
        cb()
        depsArray = deps
    }
}

function App(){
    const [count, setCount] = useState(1)
    useEffect(()=>{
        console.log(123)
    },[count])
    const add = ()=>{
        setCount(count+1)
    }
    return <div>
        <div>{count}</div>
        <div onClick={add}>add</div>
    </div>
}

多个hooks的情况

import React from 'react';
import ReactDOM from 'react-dom';

let memoizedState = [];

let cursor = 0; 

const render = () => {
    cursor = 0
    ReactDOM.render(
        <App/>,
      document.getElementById('root')
    );
}

function useState(initialState){
    memoizedState[cursor] = memoizedState[cursor] === undefined ? initialState : memoizedState[cursor] 
    const currentCursor = cursor;
    const setState=(targetState)=>{
        memoizedState[currentCursor] = targetState;
        render()
    }
    
    return [memoizedState[cursor++], setState]
}


function useEffect(cb,deps){
    const noDeps = !deps
    const hasChanges = deps && memoizedState[cursor] ? !deps.every((e,index)=> e === memoizedState[cursor][index]) : true;
    if(noDeps || hasChanges){
        cb()
        memoizedState[cursor] = deps
    }
    cursor++
}

function App(){
    const [count, setCount] = useState(1)
    useEffect(()=>{
        console.log(123)
    },[count])
    const add = ()=>{
        setCount(count+1)
    }
    return <div>
        <div>{count}</div>
        <div onClick={add}>add</div>
    </div>
}

ReactDOM.render(
    <App/>,
  document.getElementById('root')
);

React实际的实现

首次渲染和再次渲染时,逻辑是不一样的

// react-reconciler/src/ReactFiberHooks.js
// Mount 阶段Hooks的定义
const HooksDispatcherOnMount: Dispatcher = {
  useEffect: mountEffect,
  useReducer: mountReducer,
  useState: mountState,
 // 其他Hooks
};

// Update阶段Hooks的定义
const HooksDispatcherOnUpdate: Dispatcher = {
  useEffect: updateEffect,
  useReducer: updateReducer,
  useState: updateState,
  // 其他Hooks
};
创建时
// react-reconciler/src/ReactFiberHooks.js
function mountState (initialState) {
  // 获取当前的Hook节点,同时将当前Hook添加到Hook链表中
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  // 声明一个链表来存放更新
  const queue = (hook.queue = {
    last: null,
    dispatch: null,
    lastRenderedReducer,
    lastRenderedState,
  });
  // 返回一个dispatch方法用来修改状态,并将此次更新添加update链表中
  const dispatch = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  )));
  // 返回当前状态和修改状态的方法 
  return [hook.memoizedState, dispatch];
}

Hook和Hook链表的结构

function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,
    baseState: null,
    queue: null,
    baseUpdate: null,
    next: null,
  };
  if (workInProgressHook === null) {
    // 当前workInProgressHook链表为空的话,
    // 将当前Hook作为第一个Hook
    firstWorkInProgressHook = workInProgressHook = hook;
  } else {
    // 否则将当前Hook添加到Hook链表的末尾
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

每次dispatch,创建一个update对象保存本次更新的信息,并且添加到hook对应的quene:

// react-reconciler/src/ReactFiberHooks.js
// 去除特殊情况和与fiber相关的逻辑
function dispatchAction(fiber,queue,action,) {
    const update = {
      action,
      next: null,
    };
    // 将update对象添加到循环链表中
    const last = queue.last;
    if (last === null) {
      // 链表为空,将当前更新作为第一个,并保持循环
      update.next = update;
    } else {
      const first = last.next;
      if (first !== null) {
        // 在最新的update对象后面插入新的update对象
        update.next = first;
      }
      last.next = update;
    }
    // 将表头保持在最新的update对象上
    queue.last = update;
   // 进行调度工作
    scheduleWork();
}
更新时
// react-reconciler/src/ReactFiberHooks.js
function updateState(initialState) {
  return updateReducer(basicStateReducer, initialState);
}

basicStateReducer 的实现,兼容了useReducer

// react-reconciler/src/ReactFiberHooks.js
function basicStateReducer(state, action){
  return typeof action === 'function' ? action(state) : action;
} 
// react-reconciler/src/ReactFiberHooks.js
// 去掉与fiber有关的逻辑

function updateReducer(reducer,initialArg,init) {
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;

  // 拿到更新列表的表头
  const last = queue.last;

  // 获取最早的那个update对象
  first = last !== null ? last.next : null;

  if (first !== null) {
    let newState;
    let update = first;
    do {
      // 执行每一次更新,去更新状态
      const action = update.action;
      newState = reducer(newState, action);
      update = update.next;
    } while (update !== null && update !== first);

    hook.memoizedState = newState;
  }
  const dispatch = queue.dispatch;
  // 返回最新的状态和修改状态的方法
  return [hook.memoizedState, dispatch];
}
上一篇下一篇

猜你喜欢

热点阅读