从零实现一个mini-react(四)函数组件和useState

609 阅读2分钟

在上面基础上 我们做了 react基础渲染和协调过程

在上面基础上 我们想实现 函数组件 和 hook相关

函数组件

src/index.ts

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

type AppProps = {
  name: string;
}
/** @jsxRuntime classic */
function App(props: AppProps) {
  return (
    <div style={{background: 'salmon'}}>
      <h1>Hi {props.name}</h1>
      <h2 style={{textAlign: 'right' }}>from Didact</h2>
  </div>
  )
}
/** @jsxRuntime classic */
const element = <App name="world" />

const container = document.getElementById("root") as HTMLElement;
ReactDOM.render(element, container);

step1 改造fiber children 的获取创建

function performUnitOfWork(fiber: FiberProps): FiberProps | null | undefined {
  // 是否是函数组建
  const isFunctionComponent = fiber.type instanceof Function
  if (isFunctionComponent) {
    updateFunctionComponent(fiber)
  } else {
    updateHostComponent(fiber)
  }
  ...
}

// 更新函数组建
function updateFunctionComponent(fiber: FiberProps) {
  const children = [(fiber.type as Function)(fiber.props)]
  reconcileChildren(fiber, children)
}

// 更新原始虚拟DOM
function updateHostComponent(fiber: FiberProps) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }
  // 生成fiber
  const elements = fiber.props.children;
  reconcileChildren(fiber, elements)
}

step2 commit阶段 改造 获取dom逻辑

function commitWork(fiber: FiberBlock) {
  ...
  // const domParent = fiber.parent!.dom;
  // 因为如果是 函数组建 parent 没办法直接获取到 要通过递归获取到父节点DOM
  let domParentFiber = fiber.parent
  while (!domParentFiber?.dom) {
    domParentFiber = domParentFiber?.parent
  }
  const domParent = domParentFiber.dom

  ...
  else if (fiber.effectTag === "DELETION") {
    // 删除dom节点
    commitDeletion(fiber, domParent)
    // domParent!.removeChild(fiber.dom as Element)
  }
}


function commitDeletion(fiber: FiberProps, domParent: Element) {
  if (fiber.dom) {
    domParent.removeChild(fiber.dom)
  } else {
    commitDeletion(fiber.child as FiberProps, domParent)
  }
}

代码地址: github.com/beewolf233/…

hook useState

每个Fiber节点上 都有 hooks 节点 主要用在 type为 函数组件时

// 单个工作格类型
export type FiberProps = VDOMProps & {
  ...
  /** hooks */
  hooks?:  {
    state: any;
    queue: Function[]
  }[]
}

step1: 更新函数组建时,设置正在执行的wipFiber, 增加hook属性

// 正在执行的fiber
let wipFiber = null as FiberBlock;
let hookIndex = null as number | null;

function updateFunctionComponent(fiber: FiberProps) {
  wipFiber = fiber
  hookIndex = 0
  wipFiber.hooks = []
  const children = [(fiber.type as Function)(fiber.props)]
  reconcileChildren(fiber, children)
}

step2: 执行useState, 为wipFiber传入 hook相关, 包含老的state

export function useState<T>(initial: T) {
  // 检查是否有旧的hooks
  const oldHook =
    wipFiber!.alternate &&
    wipFiber!.alternate.hooks &&
    wipFiber!.alternate.hooks[hookIndex as number]
  // 如果有旧的,就复制到新的,如果没有初始化
  const hook = {
    state: oldHook ? oldHook.state : initial,
    queue: [],
  }
  // 是否有动作 有的话 就执行 (比如state+1 这个动作 )
  const actions = oldHook ? oldHook.queue : []
  
  actions.forEach(action => {
    hook.state = action(hook.state)
  })

  const setState = (action: Function) => {
    ...
  }
  // @ts-ignore
  wipFiber!.hooks.push(hook)
  hookIndex = hookIndex as number + 1;
  return [hook.state, setState]
}

step3 通过setState 重新 执行render 虚拟dom渲染相关

const setState = (action: Function) => {
    // @ts-ignore
    hook.queue.push(action)
    wipRoot = {
      dom: currentRoot!.dom,
      props: currentRoot!.props,
      alternate: currentRoot!,
    } as FiberProps
    nextUnitOfWork = wipRoot
    deletions = []
}

以上就完成了基本的react

代码地址: github.com/beewolf233/…

参考文章

手把手带你写一个mini版的React,掌握React的基本设计思想