1. React Hooks #

2. useState #

2.1 计数器 #

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

function App(){
  const[number,setNumber]=React.useState(0);
  let handleClick = ()=> setNumber(number+1)
  return (
    <div>
      <p>{number}</p>
      <button onClick={handleClick}>+</button>
    </div>
  )
}

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

2.2 src\react-dom.js #

src\react-dom.js

+let hookStates = [];
+let hookIndex = 0;
+let scheduleUpdate;
+function render(vdom, container) {
+    mount(vdom,container);
+    scheduleUpdate = ()=>{
+      hookIndex = 0;
+      compareTwoVdom(container,vdom,vdom);
+    }
+}
+export function useState(initialState){
+    hookStates[hookIndex] = hookStates[hookIndex]||initialState;
+    let currentIndex = hookIndex; 
+    function setState(newState){
+      let newState = typeof action === 'function' ? action(oldState) : action;
+      hookStates[currentIndex] = newState;
+      scheduleUpdate();
+    }
+    return [hookStates[hookIndex++],setState];
+}

2.3 src\react.js #

src\react.js

+import * as hooks from './react-dom';

const React = {
    createElement,
    Component,
    PureComponent,
    createRef,
    createContext,
    cloneElement,
    memo,
+   ...hooks
};
export default React;

3.useCallback+useMemo #

3.1 src\index.js #

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


let  Child = ({data,handleClick})=>{
  console.log('Child render');
  return (
     <button onClick={handleClick}>{data.number}</button>
  )
}
Child = React.memo(Child);

function App(){
  console.log('App render');
  const[name,setName]=React.useState('zhufeng');
  const[number,setNumber]=React.useState(0);
  let data = React.useMemo(()=>({number}),[number]);
  let handleClick = React.useCallback(()=> setNumber(number+1),[number]);
  return (
    <div>
      <input type="text" value={name} onChange={event=>setName(event.target.value)}/>
      <Child data={data} handleClick={handleClick}/>
    </div>
  )
}

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

3.2 src\react-dom.js #

src\react-dom.js

let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom,container){
   mount(vdom,container);
   scheduleUpdate = ()=>{
    hookIndex = 0;
    compareTwoVdom(container,vdom,vdom);
   }
}
export function useState(initialState){
    hookStates[hookIndex] = hookStates[hookIndex]||initialState;
    let currentIndex = hookIndex; 
    function setState(newState){
      if(typeof newState === 'function') newState=newState(hookStates[currentIndex]);
      hookStates[currentIndex]=newState;
      scheduleUpdate();
    }
    return [hookStates[hookIndex++],setState];
  }
+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;
+    }
+}
+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;
+    }
+}
const ReactDOM =  {
    render
};
export default ReactDOM;

4. useReducer #

4.1 src\index.js #

src\index.js

import React from './react';
import ReactDOM from './react-dom';
function reducer(state={number:0}, action) {
  switch (action.type) {
    case 'ADD':
      return {number: state.number + 1};
    case 'MINUS':
      return {number: state.number - 1};
    default:
      return state;
  }
}

function Counter(){
    const [state, dispatch] = React.useReducer(reducer,{number:0});
    return (
        <div>
          Count: {state.number}
          <button onClick={() => dispatch({type: 'ADD'})}>+</button>
          <button onClick={() => dispatch({type: 'MINUS'})}>-</button>
        </div>
    )
}
ReactDOM.render(
  <Counter/>,
  document.getElementById('root')
);

4.2 src\react-dom.js #

src\react-dom.js

+export function useReducer(reducer, initialState) {
+    hookStates[hookIndex] = hookStates[hookIndex] || initialState;
+    let currentIndex = hookIndex;
+    function dispatch(action) {
+        //1.获取老状态
+        let oldState = hookStates[currentIndex];
+        //如果有reducer就使用reducer计算新状态
+        if (reducer) {
+            let newState = reducer(oldState, action);
+            hookStates[currentIndex] = newState;
+        } else {
+            //判断action是不是函数,如果是传入老状态,计算新状态
+            let newState = typeof action === 'function' ? action(oldState) : action;
+            hookStates[currentIndex] = newState;
+        }
+        scheduleUpdate();
+    }
+    return [hookStates[hookIndex++], dispatch];
+}
const ReactDOM =  {
    render
};
export default ReactDOM;

5. useContext #

5.1 src\index.js #

src\index.js

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

const CounterContext = React.createContext();

function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return {number: state.number + 1};
    case 'minus':
      return {number: state.number - 1};
    default:
      return state;
  }
}
function Counter(){
  let {state,dispatch} = React.useContext(CounterContext);
  return (
      <div>
        <p>{state.number}</p>
        <button onClick={() => dispatch({type: 'add'})}>+</button>
        <button onClick={() => dispatch({type: 'minus'})}>-</button>
      </div>
  )
}
function App(){
    const [state, dispatch] = React.useReducer(reducer, {number:0});
    return (
        <CounterContext.Provider value={{state,dispatch}}>
          <Counter/>
        </CounterContext.Provider>
    )
}

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

5.2 src\react-dom.js #

src\react-dom.js

+function useContext(context){
+  return context._currentValue;
+}

6. useEffect #

6.1 src\index.js #

src\index.js

import React from './react';
import ReactDOM from './react-dom';
function Counter() {
    const [number, setNumber] = React.useState(0);
    React.useEffect(() => {
        console.log('开启一个新的定时器')
        const $timer = setInterval(() => {
            setNumber(number => number + 1);
        }, 1000);
        return () => {
            console.log('销毁老的定时器');
            clearInterval($timer);
        }
    });
    return (
        <p>{number}</p>
    )
}
ReactDOM.render(<Counter />, document.getElementById('root'));

6.2 src\react-dom.js #

src\react-dom.js

+export function useEffect(callback,dependencies){
+  let currentIndex = hookIndex;
+  if(hookStates[hookIndex]){
+      let [destroy,lastDeps] = hookStates[hookIndex];
+      let same = dependencies&&dependencies.every((item,index)=>item === lastDeps[index]);
+      if(same){
+        hookIndex++;
+      }else{
+        destroy&&destroy();
+        setTimeout(()=>{
+            hookStates[currentIndex]=[callback(),dependencies];
+        });
+        hookIndex++;
+      }
+  }else{
+    setTimeout(()=>{
+        hookStates[currentIndex]=[callback(),dependencies];
+    });
+    hookIndex++;
+  }
+}
const ReactDOM =  {
    render
};
export default ReactDOM;

7. useLayoutEffect+useRef #

7.1 事件循环 #

7.2 src\index.js #

src\index.js

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

const Animate = ()=>{
    const ref = React.useRef();
    React.useLayoutEffect(() => {
      ref.current.style.transform = `translate(500px)`;//TODO
      ref.current.style.transition = `all 500ms`;
    });
    let style = {
      width: '100px',
      height: '100px',
      borderRadius: '50%',
      backgroundColor: 'red'
    }
    return (
      <div style={style} ref={ref}></div>
    )
}
ReactDOM.render(<Animate/>,document.getElementById('root'));

7.3 src\react-dom.js #

src\react-dom.js

+export function useLayoutEffect(callback,dependencies){
+    let currentIndex = hookIndex;
+    if(hookStates[hookIndex]){
+        let [destroy,lastDeps] = hookStates[hookIndex];
+        let same = dependencies&&dependencies.every((item,index)=>item === lastDeps[index]);
+        if(same){
+          hookIndex++;
+        }else{
+          destroy&&destroy();
+          queueMicrotask(()=>{
+              hookStates[currentIndex]=[callback(),dependencies];
+          });
+          hookIndex++
+        }
+    }else{
+      queueMicrotask(()=>{
+          hookStates[currentIndex]=[callback(),dependencies];
+      });
+       hookIndex++;
+    }
+}
+export function useRef(initialState) {
+    hookStates[hookIndex] =  hookStates[hookIndex] || { current: initialState };
+    return hookStates[hookIndex++];
+}

如何获取最新的state值

import React from 'react';
import ReactDOM from 'react-dom';
function Counter() {
  let valueRef = React.useRef();
  const [state, setState] = React.useState(0)
  const handleClick = () => {
    let newValue = state + 1;
    valueRef.current = newValue;
    setState(newValue)
    otherFun();
  }
  function otherFun() {
    console.log('state', valueRef.current);
  }
  return (
    <div>
      <p>state:{state}</p>
      <button onClick={handleClick}>+</button>
    </div>
  )
}
ReactDOM.render(<Counter />, document.getElementById('root'));

8. forwardRef+useImperativeHandle #

8.1 src\index.js #

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

function Child(props, ref) {
    const inputRef = React.useRef();
    React.useImperativeHandle(ref, () => (
        {
            focus() {
                inputRef.current.focus();
            }
        }
    ));
    return (
        <input type="text" ref={inputRef} />
    )
}
const ForwardChild = React.forwardRef(Child);
function Parent() {
    let [number, setNumber] = React.useState(0);
    const inputRef = React.useRef();
    function getFocus() {
        console.log(inputRef.current);
        inputRef.current.value = 'focus';
        inputRef.current.focus();
    }
    return (
        <div>
            <ForwardChild ref={inputRef} />
            <button onClick={getFocus}>获得焦点</button>
            <p>{number}</p>
            <button onClick={() => {
                debugger
                setNumber( number + 1)
            }}>+</button>
        </div>
    )
}
ReactDOM.render(<Parent/>,document.getElementById('root'));

8.2 src\react-dom.js #

src\react-dom.js

function mountClassComponent(vdom){
+    const {type, props,ref} = vdom;
    const classInstance = new type(props);
+   if(ref){
+       ref.current = classInstance;
+       classInstance.ref = ref;
+   }
    vdom.classInstance=classInstance;
    if(type.contextType){
        classInstance.context = type.contextType.Provider._value;
    }
    if(classInstance.componentWillMount)
       classInstance.componentWillMount();
    classInstance.state = getDerivedStateFromProps(classInstance,classInstance.props,classInstance.state)   
    const renderVdom = classInstance.render();
    classInstance.oldRenderVdom=vdom.oldRenderVdom=renderVdom;
    const dom = createDOM(renderVdom);
    if(classInstance.componentDidMount)
      dom.componentDidMount=classInstance.componentDidMount.bind(classInstance);
    return dom;
}

+export function useImperativeHandle(ref,handler){
+    ref.current = handler();
+}
const ReactDOM =  {
    render
};
export default ReactDOM;