创建自己的 React (终)
前言
使用对象作为 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>
扁平化子数组
不对 children
进行扁平化在渲染数组时会报错
<div>
{posts.map(post => <p>{post}</p>)}
</div>
因此需要对 children
进行扁平化,直接使用数组的 flat
方法。
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
函数
/// 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 系列结束,感谢阅读 🌹
转载自:https://juejin.cn/post/7297090176877346825