实现 useState

src/index.js

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

sdre.gif

  1. import React, { Component } from './react';
  2. import ReactDOM from './react-dom';
  3. // setState参数为 值
  4. function Counter1(){
  5. let [number, setNumber] = React.useState(5);
  6. return (
  7. <div>
  8. <p>Counter1:{number}</p>
  9. <button onClick={() => setNumber(number+1)}>Counter1:+</button>
  10. </div>
  11. )
  12. }
  13. // setState参数为 函数
  14. function Counter2(){
  15. let [number, setNumber] = React.useState(10);
  16. return (
  17. <div>
  18. <p>Counter2:{number}</p>
  19. <button onClick={() => setNumber(n => n+1)}>Counter2:+</button>
  20. </div>
  21. )
  22. }
  23. function Counter3(){
  24. let [number, setNumber] = React.useState(15);
  25. return (
  26. <div>
  27. <p>Counter3:{number}</p>
  28. <button onClick={() => setNumber(number+1)}>Counter3:+</button>
  29. </div>
  30. )
  31. }
  32. function App(){
  33. let [number, setNumber] = React.useState(0);
  34. return (
  35. <div>
  36. <Counter1 />
  37. {number%2 === 0 && <Counter2 />}
  38. <Counter3 />
  39. <button onClick={() => setNumber(number+1)}>App:+</button>
  40. </div>
  41. )
  42. }
  43. ReactDOM.render(<App />, document.getElementById('root'));

src/react.js
  1. import { useState } from './react-dom';
  2. export { useState };
  3. const React = {
  4. useState,
  5. }
  6. export default React;

src/react-dom.js
  1. + let hookStates = []; // 这是一个数组,里面存放着我们所有的 hook 状态
  2. + let hookIndex = 0; // 当前 hook 的索引
  3. + let scheduleUpdate; // hook调度更新函数
  4. // 给根容器挂载的时候
  5. function render (vdom, container){
  6. mount(vdom, container);
  7. + scheduleUpdate = () => {
  8. + hookIndex = 0; //在状态修改后调试更新的时候,把 hooks 的索引重置
  9. + compareTwoVdom(container, vdom, vdom); //虚拟DOM比较更新
  10. + }
  11. }
  12. // 挂载
  13. function mount (vdom, container){
  14. if (!vdom) return;
  15. const dom = createDOM(vdom, container);
  16. if (dom) container.appendChild(dom);
  17. dom.componentDidMount && dom.componentDidMount();
  18. }
  19. /** 让函数组件可以使用状态
  20. *
  21. * @param {*} initialState 初始状态
  22. */
  23. export function useState(initialState){
  24. //把老的值取出来,如果没有,就使用默认值
  25. hookStates[hookIndex] = hookStates[hookIndex] ||
  26. (typeof initialState === 'function' ? initialState() : initialState);
  27. // 因为 hookIndex 一直在变,
  28. // 给每一个 useState 的 hook 定死一个不变的索引。用于 setState时 更新该hook的数据
  29. let currentIndex = hookIndex;
  30. function setState(newState){
  31. if(typeof newState === 'function') newState = newState(hookStates[currentIndex]);
  32. hookStates[currentIndex] = newState;
  33. scheduleUpdate(); //当状态改变后要重新更新应用
  34. }
  35. return [hookStates[hookIndex++], setState]; //注意,先取值,再++,把 hook 索引指向下一个 useState。
  36. }

实现 useMemo、useCallback

demo:https://www.yuque.com/zhuchaoyang/cx9erp/og7367#jncy9

src/react.js
  1. import { useState, useCallback, useMemo } from './react-dom';
  2. export { useState, useCallback, useMemo };
  3. const React = {
  4. useCallback,
  5. useMemo,
  6. }
  7. export default React;

src/react-dom.js
  1. /** 缓存 对象
  2. *
  3. * @param {*} factory 工厂方法,该方法返回要缓存的对象
  4. * @param {*} deps 依赖数组
  5. * @returns
  6. */
  7. export function useMemo(factory, deps){
  8. if (hookStates[hookIndex]){
  9. let [lastMemo, lastDeps] = hookStates[hookIndex];
  10. let same = deps.every((item, index) => item === lastDeps[index]);
  11. if (same){
  12. hookIndex ++;
  13. return lastMemo;
  14. } else {
  15. let newMemo = factory();
  16. hookStates[hookIndex++] = [newMemo, deps];
  17. return newMemo;
  18. }
  19. } else {
  20. let newMemo = factory();
  21. hookStates[hookIndex++] = [newMemo, deps];
  22. return newMemo;
  23. }
  24. }
  25. /** 缓存 函数
  26. *
  27. * @param {*} callback 要缓存的函数
  28. * @param {*} deps 依赖数组
  29. * @returns
  30. */
  31. export function useCallback(callback, deps){
  32. if (hookStates[hookIndex]){
  33. let [lastCallback, lastDeps] = hookStates[hookIndex];
  34. let same = deps.every((item, index) => item === lastDeps[index]);
  35. if (same){
  36. hookIndex ++;
  37. return lastCallback;
  38. } else {
  39. hookStates[hookIndex++] = [callback, deps];
  40. return callback;
  41. }
  42. } else {
  43. hookStates[hookIndex++] = [callback, deps];
  44. return callback;
  45. }
  46. }

实现 useReducer

demo:https://www.yuque.com/zhuchaoyang/cx9erp/og7367#YzW9B

src/react.js
  1. import { useReducer } from './react-dom';
  2. export { useReducer };
  3. const React = {
  4. useReducer,
  5. }
  6. export default React;

src/react-dom.js
  1. /** 让函数组件可以使用状态,useState 可以算作 useReducer 的一个语法糖
  2. *
  3. * @param {*} initialState 初始状态
  4. */
  5. export function useState(initialState){
  6. return useReducer(null, initialState);
  7. }
  8. export function useReducer(reducer, initialState){
  9. //把老的值取出来,如果没有,就使用默认值
  10. let lastState = hookStates[hookIndex] = hookStates[hookIndex] ||
  11. (typeof initialState === 'function' ? initialState() : initialState);
  12. //因为 hookIndex 一直在变,
  13. //给每一个 useState 的 hook 定死一个不变的索引。用于调用 setState(dispatch) 函数时 更新该hook的数据
  14. let currentIndex = hookIndex;
  15. function dispatch(action){
  16. let nextState;
  17. if (reducer){
  18. nextState = reducer(lastState, action);
  19. } else {
  20. if (typeof action === 'function') action = action(lastState);
  21. nextState = action;
  22. }
  23. hookStates[currentIndex] = nextState;
  24. scheduleUpdate(); //当状态改变后要重新更新应用
  25. }
  26. //注意,先取值,再++,把 hook 索引指向下一个 useState。
  27. return [hookStates[hookIndex++], dispatch];
  28. }

实现 useContext

demo:https://www.yuque.com/zhuchaoyang/cx9erp/og7367#tCIIp

src/react.js
  1. export function useContext(context){
  2. return context._currentValue;
  3. }
  4. const React = {
  5. useContext,
  6. }
  7. export default React;

实现 useEffect

demo: https://www.yuque.com/zhuchaoyang/cx9erp/og7367#C082E

src/react.js
  1. import { useEffect } from './react-dom';
  2. export { useEffect };
  3. const React = {
  4. useEffect,
  5. }
  6. export default React;

src/react-dom.js
  1. export function compareTwoVdom(container, oldVdom, newVdom, nextDOM){
  2. // 老的有,新的没有 => 要刪除此节点
  3. if (oldVdom && !newVdom){
  4. + if (hookStates[hookIndex]){
  5. + let [destroyFunction] = hookStates[hookIndex];
  6. + destroyFunction && destroyFunction();
  7. + }
  8. }
  9. // 新旧都有虚拟DOM,但是类型type不相同 => 直接替换节点
  10. if (oldVdom && newVdom && oldVdom.type !== newVdom.type){
  11. + if (hookStates[hookIndex]){
  12. + let [destroyFunction] = hookStates[hookIndex];
  13. + destroyFunction && destroyFunction();
  14. + }
  15. }
  16. }
  17. /** useEffect
  18. *
  19. * 为了保证次回调函数不是同步执行,而是在页面渲染后执行,可以把它包装成宏任务
  20. * @param {*} callback 回调函数
  21. * @param {*} deps 依赖数组
  22. */
  23. export function useEffect(callback, deps){
  24. if (hookStates[hookIndex]){
  25. let [destroyFunction, lastDeps] = hookStates[hookIndex];
  26. let same = deps && deps.every((item, index) => item === lastDeps[index]);
  27. if (same){
  28. hookIndex ++;
  29. } else {
  30. destroyFunction && destroyFunction();
  31. setTimeout(() => {
  32. let destroyFunction = callback();
  33. hookStates[hookIndex++] = [destroyFunction, deps];
  34. })
  35. }
  36. } else {
  37. // 用 setTimeout 封装成宏任务
  38. setTimeout(() => {
  39. let destroyFunction = callback(); //执行后,返回销毁函数
  40. hookStates[hookIndex++] = [destroyFunction, deps];
  41. })
  42. }
  43. }

实现 useLayoutEffect、useRef

demo: https://www.yuque.com/zhuchaoyang/cx9erp/og7367#mB9bU

src/react.js
  1. import { useLayoutEffect, useRef } from './react-dom';
  2. export { useLayoutEffect, useRef };
  3. const React = {
  4. useLayoutEffect,
  5. useRef,
  6. }
  7. export default React;

src/react-dom.js
  1. /** useLayoutEffect
  2. *
  3. * 为了保证次回调函数不是同步执行,而是在页面渲染之前执行,可以把它包装成微任务
  4. * @param {*} callback 回调函数
  5. * @param {*} deps 依赖数组
  6. */
  7. export function useLayoutEffect(callback, deps){
  8. if (hookStates[hookIndex]){
  9. let [destroyFunction, lastDeps] = hookStates[hookIndex];
  10. let same = deps && deps.every((item, index) => item === lastDeps[index]);
  11. if (same){
  12. hookIndex ++;
  13. } else {
  14. destroyFunction && destroyFunction();
  15. queueMicrotask(() =>{
  16. let destroyFunction = callback();
  17. hookStates[hookIndex++] = [destroyFunction, deps];
  18. })
  19. }
  20. } else {
  21. // 用 queueMicrotask 封装成微任务
  22. queueMicrotask(() => {
  23. let destroyFunction = callback(); //执行后,返回销毁函数
  24. hookStates[hookIndex++] = [destroyFunction, deps];
  25. })
  26. }
  27. }
  28. export function useRef(initialState){
  29. hookStates[hookIndex] = hookStates[hookIndex] || {current: initialState};
  30. return hookStates[hookIndex++];
  31. }

实现 forwardRef、useImperativeHandle

demo: https://www.yuque.com/zhuchaoyang/cx9erp/og7367/edit#e38BC

src/react.js
  1. import { forwardRef, useImperativeHandle } from './react-dom';
  2. export { forwardRef, useImperativeHandle };
  3. export function forwardRef(FunctionComponent){
  4. return class Hoc extends Component {
  5. render (){
  6. if (FunctionComponent.length < 2){
  7. console.error('Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?');
  8. }
  9. return FunctionComponent(this.props, this.ref);
  10. }
  11. }
  12. }
  13. export function useImperativeHandle(ref, handler){
  14. ref.current = handler();
  15. }
  16. const React = {
  17. forwardRef,
  18. useImperativeHandle,
  19. }
  20. export default React;