创建自己的 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 系列结束,感谢阅读 🌹

相关推荐
. . . . .37 分钟前
shadcn组件库
前端
2501_944711431 小时前
JS 对象遍历全解析
开发语言·前端·javascript
发现一只大呆瓜1 小时前
虚拟列表:支持“向上加载”的历史消息(Vue 3 & React 双版本)
前端·javascript·面试
css趣多多2 小时前
ctx 上下文对象控制新增 / 编辑表单显示隐藏的逻辑
前端
lbb 小魔仙2 小时前
【HarmonyOS实战】React Native 表单实战:自定义 useReactHookForm 高性能验证
javascript·react native·react.js
_codemonster2 小时前
Vue的三种使用方式对比
前端·javascript·vue.js
寻找奶酪的mouse2 小时前
30岁技术人对职业和生活的思考
前端·后端·年终总结
梦想很大很大2 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
We་ct2 小时前
LeetCode 56. 合并区间:区间重叠问题的核心解法与代码解析
前端·算法·leetcode·typescript
张3蜂2 小时前
深入理解 Python 的 frozenset:为什么要有“不可变集合”?
前端·python·spring