likes
comments
collection
share

创建自己的 React (终)

作者站长头像
站长
· 阅读数 49

前言

使用对象作为 style 属性

/// hypereact.ts

const isStyle = key => key === "style"
const isProperty = key => key !== "children" && !isEvent(key) && !isStyle(key)

function updateDom(dom: HTMLElement, prevProps, nextProps) {
  /// ...

  const prevStyle = prevProps.style || {}
  const nextStyle = nextProps.style || {}

  // 移出旧的的样式
  Object.keys(prevStyle)
    .filter(isGone(prevStyle, nextStyle))
    .forEach(name => {
      dom.style[name] = ""
    })

  // 设置新的的样式
  Object.keys(nextStyle)
    .filter(isNew(prevStyle, nextStyle))
    .forEach(name => {
      dom.style[name] = nextStyle[name]
    })

  /// ...
}

这样就可以使用对象设置 DOM 的 style 属性了

<h1 style={{ color: "white", backgroundColor: "black", padding: "12px" }}>
Hello {value}!
</h1>

创建自己的 React (终)

扁平化子数组

不对 children 进行扁平化在渲染数组时会报错

<div>
  {posts.map(post => <p>{post}</p>)}
</div>

创建自己的 React (终)

因此需要对 children 进行扁平化,直接使用数组的 flat 方法。

export function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children
        .flat()
        .map(child =>
          typeof child === "object" ? child : createTextElement(child)
        ),
    },
  }
}

创建自己的 React (终)

添加 useEffect Hook

添加 cancelEffectsrunEffects 方法用来执行和取消执行 effect 函数,在 commitWork 方法里判断 Fiber 的 EffectTag 属性

  • 新增 DOM: 执行 effect 函数
  • 更新 DOM: 先取消上一次执行,再执行新的 effect 函数
  • 删除 DOM: 取消执行 effect 函数
/// hypereact.ts

function cancelEffects(fiber) {
  if (fiber.hooks) {
    fiber.hooks
      .filter(hook => hook.tag === "effect" && hook.cancel)
      .forEach(effectHook => {
        effectHook.cancel()
      })
  }
}

function runEffects(fiber) {
  if (fiber.hooks) {
    fiber.hooks
      .filter(hook => hook.tag === "effect" && hook.effect)
      .forEach(effectHook => {
        effectHook.cancel = effectHook.effect()
      })
  }
}

/// ...

function commitWork(fiber: Fiber) {
  /// ...

  if (fiber.effectTag === EffectTag.PLACEMENT && fiber.dom != null) {
    if (fiber.dom != null) {
      domParent.appendChild(fiber.dom)
    }
    runEffects(fiber)
  } else if (fiber.effectTag === EffectTag.UPDATE) {
    cancelEffects(fiber)
    if (fiber.dom != null) {
      updateDom(fiber.dom, fiber.alternate.props, fiber.props)
    }
    runEffects(fiber)
  } else if (fiber.effectTag === EffectTag.DELETION) {
    cancelEffects(fiber)
    commitDeletion(fiber, domParent)
    return
  }
  
  /// ...
}

添加 hasDepsChanged 方法用来判断依赖有没有更新,最后添加 useEffect 方法,参数是一个 effect 函数和它的依赖值。

const hasDepsChanged = (prevDeps, nextDeps) =>
  !prevDeps ||
  !nextDeps ||
  prevDeps.length !== nextDeps.length ||
  prevDeps.some((dep, index) => dep !== nextDeps[index])

export function useEffect(effect, deps) {
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex]

  const hasChanged = hasDepsChanged(oldHook ? oldHook.deps : undefined, deps)

  const hook = {
    tag: "effect",
    effect: hasChanged ? effect : null,
    cancel: hasChanged && oldHook && oldHook.cancel,
    deps,
  }
  
  wipFiber.hooks.push(hook)
  hookIndex++
}

这样就可以在函数式组件里面使用 useEffect Hook 了

/// App.tsx

const HelloFunctional = () => {
  const [count, setCounter] = useState(1)

  const handleClick = () => {
    setCounter(() => count + 1)
  }

  useEffect(() => {
    console.log(count)
  }, [count])

  return (
    <div>
      <h2>Hello, Functional Component</h2>
      <p>Counter: {count}</p>
      <button onClick={handleClick}>Plus</button>
    </div>
  )
}

创建自己的 React (终)

创建自己的 React (终)

参考

本文代码

Build your own React

创建自己的 React 系列结束,感谢阅读 🌹

转载自:https://juejin.cn/post/7297090176877346825
评论
请登录