创建自己的 React (终)

前言

接上文 创建自己的 React (下) 目前已经完成总体的功能,但是还有可以提升的地方。

使用对象作为 style 属性

ts 复制代码
/// 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 属性了

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

扁平化子数组

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

tsx 复制代码
<div>
  {posts.map(post => <p>{post}</p>)}
</div>

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

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

添加 useEffect Hook

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

  • 新增 DOM: 执行 effect 函数
  • 更新 DOM: 先取消上一次执行,再执行新的 effect 函数
  • 删除 DOM: 取消执行 effect 函数
ts 复制代码
/// 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 函数和它的依赖值。

ts 复制代码
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 了

tsx 复制代码
/// 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>
  )
}

参考

本文代码

Build your own React

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

相关推荐
CrissChan6 分钟前
Pycharm 函数注释
java·前端·pycharm
小小小小宇38 分钟前
Vue.nextTick()笔记
前端
小约翰仓鼠2 小时前
vue3子组件获取并修改父组件的值
前端·javascript·vue.js
Lin Hsüeh-ch'in2 小时前
Vue 学习路线图(从零到实战)
前端·vue.js·学习
烛阴2 小时前
bignumber.js深度解析:驾驭任意精度计算的终极武器
前端·javascript·后端
计蒙不吃鱼3 小时前
一篇文章实现Android图片拼接并保存至相册
android·java·前端
全职计算机毕业设计3 小时前
基于Java Web的校园失物招领平台设计与实现
java·开发语言·前端
啊~哈3 小时前
vue3+elementplus表格表头加图标及文字提示
前端·javascript·vue.js
小小小小宇4 小时前
前端小tips
前端