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

相关推荐
IT_陈寒3 分钟前
Vite 5个隐藏技巧让你的项目构建速度提升50%,第3个太香了!
前端·人工智能·后端
詩句☾⋆᭄南笙12 分钟前
HTML的盒子模型
前端·html·盒子模型
落言14 分钟前
AI 时代的工程师:懂,却非懂的时代
前端·程序员·架构
一枚攻城狮16 分钟前
前端知识点大汇总
前端
Mike_jia1 小时前
DumbAssets:开源资产管理神器,家庭与企业的高效管家
前端
Southern Wind2 小时前
Vue 3 多实例 + 缓存复用:理念及实践
前端·javascript·vue.js·缓存·html
HuangYongbiao2 小时前
Rspack 原理:webpack,我为什么不要你
前端
yinuo2 小时前
前端项目开发阶段崩溃?试试这招“Node 内存扩容术”,立马复活!
前端
前端鳄鱼崽2 小时前
【react-native-inspector】全网唯一开源 react-native 点击组件跳转到编辑器
前端·react native·react.js
用户98402276679182 小时前
【React.js】渐变环形进度条
前端·react.js·svg