1. React Hooks #

2. useState #

2.1 计数器 #

import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
    document.getElementById('root')
);
function App() {
    const [number, setNumber] = React.useState(0);
    let handleClick = () => setNumber(number + 1)
    return (
        <div>
            <p>{number}</p>
            <button onClick={handleClick}>+</button>
        </div>
    )
}
let element = <App />
DOMRoot.render(element);

2.2 src\react-dom.js #

src\react-dom.js

import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
+let hookStates = [];
+let hookIndex = 0;
+let scheduleUpdate;
function render(vdom) {
    mount(vdom, this.container);
+    scheduleUpdate = () => {
+        hookIndex = 0;
+        compareTwoVdom(this.container, vdom, vdom);
+    }
}
export function mount(vdom, container) {
    let newDOM = createDOM(vdom);
    if (newDOM) {
        container.appendChild(newDOM);
        if (newDOM.componentDidMount) {
            newDOM.componentDidMount()
        }
    }
}

+export function useState(initialState) {
+    const oldState = hookStates[hookIndex] = hookStates[hookIndex] || initialState;
+    let currentIndex = hookIndex;
+    function setState(action) {
+        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 { wrapToVdom, shallowEqual } from "./utils";
import { REACT_ELEMENT, REACT_FORWARD_REF_TYPE, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "./constants";
import { Component } from './Component';
+import * as hooks from './react-dom/client';
function createElement(type, config, children) {
    let ref;
    let key;
    if (config) {
        delete config.__source;
        delete config.__self;
        ref = config.ref;
        delete config.ref;
        key = config.key;
        delete config.key;
    }
    let props = { ...config };
    if (arguments.length > 3) {
        props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
    } else {
        props.children = wrapToVdom(children);
    }
    return {
        $$typeof: REACT_ELEMENT,
        type,
        ref,
        key,
        props,
    };
}
function createRef() {
    return { current: null };
}
function forwardRef(render) {
    var elementType = {
        $$typeof: REACT_FORWARD_REF_TYPE,
        render: render
    };
    return elementType;
}
function createContext() {
    let context = { _currentValue: undefined };
    context.Provider = {
        $$typeof: REACT_PROVIDER,
        _context: context
    }
    context.Consumer = {
        $$typeof: REACT_CONTEXT,
        _context: context
    }
    return context;
}
function cloneElement(element, newProps, children) {
    let props = { ...element.props, ...newProps };
    if (arguments.length > 3) {
        props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom)
    } else if (arguments.length === 3) {
        props.children = wrapToVdom(children);
    }
    return {
        ...element,
        props
    }
}
class PureComponent extends Component {
    shouldComponentUpdate(newProps, nextState) {
        return !shallowEqual(this.props, newProps) || !shallowEqual(this.state, nextState);
    }
}
function memo(type, compare = shallowEqual) {
    return {
        $$typeof: REACT_MEMO,
        type,
        compare
    }
}
const React = {
    createElement,
    Component,
    createRef,
    forwardRef,
    createContext,
    cloneElement,
    PureComponent,
    memo,
+    ...hooks
};
export default React;

3.useCallback+useMemo #

3.1 src\index.js #

import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
    document.getElementById('root')
);
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>
    )
}
let element = <App />
DOMRoot.render(element);

3.2 src\react-dom.js #

src\react-dom.js

import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom) {
    mount(vdom, this.container);
    scheduleUpdate = () => {
        hookIndex = 0;
        compareTwoVdom(this.container, vdom, vdom);
    }
}
export function mount(vdom, container) {
    let newDOM = createDOM(vdom);
    if (newDOM) {
        container.appendChild(newDOM);
        if (newDOM.componentDidMount) {
            newDOM.componentDidMount()
        }
    }
}

export function useState(initialState) {
    const oldState = hookStates[hookIndex] = hookStates[hookIndex] || initialState;
    let currentIndex = hookIndex;
    function setState(action) {
        let newState = typeof action === 'function' ? action(oldState) : action;
        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;
+    }
+}

4. useReducer #

4.1 src\index.js #

src\index.js

import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
    document.getElementById('root')
);
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>
    )
}
let element = <Counter />
DOMRoot.render(element);

4.2 src\react-dom.js #

src\react-dom.js

import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom) {
    mount(vdom, this.container);
    scheduleUpdate = () => {
        hookIndex = 0;
        compareTwoVdom(this.container, vdom, vdom);
    }
}
export function mount(vdom, container) {
    let newDOM = createDOM(vdom);
    if (newDOM) {
        container.appendChild(newDOM);
        if (newDOM.componentDidMount) {
            newDOM.componentDidMount()
        }
    }
}
+export function useReducer(reducer, initialState) {
+    hookStates[hookIndex] = hookStates[hookIndex] || initialState;
+    let currentIndex = hookIndex;
+    function dispatch(action) {
+        let oldState = hookStates[currentIndex];
+        if (reducer) {
+            let newState = reducer(oldState, action);
+            hookStates[currentIndex] = newState;
+        } else {
+            let newState = typeof action === 'function' ? action(oldState) : action;
+            hookStates[currentIndex] = newState;
+        }
+        scheduleUpdate();
+    }
+    return [hookStates[hookIndex++], dispatch];
+}

5. useContext #

5.1 src\index.js #

src\index.js

import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
    document.getElementById('root')
);

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>
    )
}

let element = <App />
DOMRoot.render(element);

5.2 src\react-dom.js #

src\react-dom.js

import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom) {
    mount(vdom, this.container);
    scheduleUpdate = () => {
        hookIndex = 0;
        compareTwoVdom(this.container, vdom, vdom);
    }
}
export function mount(vdom, container) {
    let newDOM = createDOM(vdom);
    if (newDOM) {
        container.appendChild(newDOM);
        if (newDOM.componentDidMount) {
            newDOM.componentDidMount()
        }
    }
}
+export 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/client';
const DOMRoot = ReactDOM.createRoot(
    document.getElementById('root')
);
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>
    )
}
let element = <Counter />
DOMRoot.render(element);

6.2 src\react-dom.js #

src\react-dom.js

import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom) {
    mount(vdom, this.container);
    scheduleUpdate = () => {
        hookIndex = 0;
        compareTwoVdom(this.container, vdom, vdom);
    }
}
export function mount(vdom, container) {
    let newDOM = createDOM(vdom);
    if (newDOM) {
        container.appendChild(newDOM);
        if (newDOM.componentDidMount) {
            newDOM.componentDidMount()
        }
    }
}
+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++;
+    }
+}

7. useLayoutEffect+useRef #

7.1 事件循环 #

7.2 src\index.js #

src\index.js

import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
    document.getElementById('root')
);
const Animate = () => {
    const ref = React.useRef();
    React.useEffect(() => {
        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>
    )
}
let element = <Animate />
DOMRoot.render(element);

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++];
+}

7.4 取最新值 #

import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
    document.getElementById('root')
);
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>
    )
}
let element = <Counter />
DOMRoot.render(element);

8. forwardRef+useImperativeHandle #

8.1 src\index.js #

import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
    document.getElementById('root')
);

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>
    )
}
let element = <Parent />
DOMRoot.render(element);

8.2 src\react-dom.js #

src\react-dom.js

import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom) {
    mount(vdom, this.container);
    scheduleUpdate = () => {
        hookIndex = 0;
        compareTwoVdom(this.container, vdom, vdom);
    }
}
export function mount(vdom, container) {
    let newDOM = createDOM(vdom);
    if (newDOM) {
        container.appendChild(newDOM);
        if (newDOM.componentDidMount) {
            newDOM.componentDidMount()
        }
    }
}
+export function useImperativeHandle(ref, handler) {
+    ref.current = handler();
+}