手写react全家桶 (ts) - 持续更新

335 阅读3分钟

使用ts手写react,实现其API

仓库地址:https://github.com/xuxiaozhou/my-react

食用方法

git clone https://github.com/xuxiaozhou/my-react
yarn install
yarn build

# 测试代码目录:./demo

已实现的React全家桶功能

  • hook
  • lifecycle
  • state,props
  • event
  • Component,PureComponent,FunctionComponent
  • react-router
  • react-router-dom
  • history
  • redux

实现细节

挂载流程

  • ReactDOM.render(vdom,container)
  • 通过vdom进行挂载 mount(vdom,container)
  • 通过vdom创建真实dom (createDOM)
    • 类组件
      • new出实例 -> 生命周期 constructor
      • 调用getDerivedStateFromProps获取最新的部分state, 设置到实例上
      • 调用render方法获取renderVdom(要渲染的虚拟dom) -> 生命周期 render
      • 将 renderVdom 递归调用 createDOM
    • 函数组件
      • 调用函数组件获取renderVdom(要渲染的虚拟dom)
      • 将 renderVdom 递归调用createDOM
    • HTML元素
      • 创建真实dom
      • 处理props
      • 递归children
        • 数组遍历调用 mount
        • 对象直接调用 mount
      • 将真实dom插入container容器 -> 生命周期:componentDidMount

事件机制

在react函数处理事件,生命周期处理函数都是setState都是批量更新模式,等到函数同步执行完毕够一次性更新

  • 将事件处理函数(listener)绑定到真实dom身上,后续通过event.target获取
  • 将事件绑定到document身上,统一用dispatchEvent作为所有事件的处理函数
  • dispatchEvent
    • 进入批量更新处理模式: updateQueue.isBatchingUpdate = true
    • 创建合成事件
    • 通过event.target向上递归,获取当前事件名的绑定事件
    • 执行批量更新 updateQueue.batchUpdate()

setState

前置

  • 维护一个全局updateQueue更新队列
  • 类组件执行初始化时,会初始化一个updater

真实流程

  • updater.addState
  • 判断当前的的更新模式
    • 批量更新
      • 将当前updater添加到 updateQueue 的updaters即可
    • 立即执行
      • 执行updater.updateClassComponent
  • 批量更新模式:
    • 事件函数处理完成后:updateQueue.batchUpdate
    • 遍历所有的updateQueue.updaters
    • 执行updater.updateClassComponent
    • 执行完清空updaters
    • 重制为立即更新模式

更新模式

  • 执行getDerivedStateFromProps,获取新的部分state
  • 设置最新的state和props
  • 执行shouldComponentUpdate
    • 返回false
      • 更新结束
    • 返回true
      • 执行render获取最新的renderVdom
      • 执行getSnapshotBeforeUpdate
      • 新旧renderVdom进行dom-diff
      • 执行componentDidUpdate

调用类组件的forceUpdate

forceUpdate的更新流程不走shouldComponentUpdate,直接走更新流程

  • 执行getDerivedStateFromProps获取最新的部分state
  • 设置最新的state和props
  • 执行render获取最新renderVdom
  • 执行getSnapshotBeforeUpdate
  • 新旧renderVdom进行dom-diff
  • 执行componentDidUpdate

卸载阶段

  • componentWillUnmount

PureComponent和Component

实现shouldComponentUpdate,对新旧props和新旧state进行浅层比较。 如果相同则返回true,不走更新流程 如果不同则返回false,走更新流程

export abstract class PureComponent extends Component {
  // @ts-ignore
  shouldComponentUpdate(nextProps, nextState) {
    //  true -> 不更新
    //  false -> 更新
    return shallowEqual(this.props, nextProps) && shallowEqual(this.state, nextState);
  }
}

React.createContext

将 上下文数据 挂载到Provider函数的静态属性_value上

function createContext() {
  // 提供, 是函数组件
  function Provider(props) {
    // 保持_value的对象指针
    if (!Provider._value) {
      Provider._value = props.value || {};
    } else {
      Object.assign(Provider._value, props.value);
    }

    return props.children;
  }

  function Consumer(props) {
    props.children(Provider._value);
  }

  return { Provider, Consumer };
}

在类组件上使用 static contextType = xxx

在new 类组件 时候如果类组件有contextType静态方法则进行赋值。由于是Provider._value不会修改引用地址,则后续拿到的都是最新的值

// react-dom/src/index mountClassComponent
// 进行赋值
instance.context = Type.contextType.Provider._value

React.memo

使用React.PureComponent进行包装,在shouldComponentUpdate对新旧props进行比较

function memo(FunctionComponent) {
  return class ClassComponent extends PureComponent {
    render() {
      return FunctionComponent(this.props);
    }
  };
}

React.createRef

function createRef() {
  return { current: null };
}

赋值阶段

如果ref绑定到DOM元素

// react-dom/src/index.ts
// 挂载的时候进入赋值
if (props.ref) {
  props.ref.current = realDOM;
}

如果ref绑定到类组件元素

// react-dom/src/index.ts mountClassComponent
if (ref) {
  ref.current = instance;
}