创建自己的 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

添加 cancelEffects 和 runEffects 方法用来执行和取消执行 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 系列结束,感谢阅读 🌹

相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万4 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋4 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁4 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95274 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大4 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师4 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学4 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端