更新 -> 创建和删除
- type 不一致
删除旧的 创建新的
- demo
import React from './core/React.js'
let showBar = false
function Counter() {
const foo = <div>foo</div>
const bar = <p>bar</p>
function handleShowBar() {
showBar = !showBar
React.update()
}
return (
<div>
Counter
<div>{showBar ? bar : foo}</div>
<button onClick={handleShowBar}>showBar</button>
</div>
)
}
function App() {
return (
<div>
hi-mini-react
<Counter></Counter>
</div>
)
}
export default App
上面我们每次点击 showBar 的时候希望 bar 和 foo 来回切换,但是现在代码每次点击都会添加新的不会把之前的旧的删除
方法:
把我们需要删除的节点存到一个数组里,然后在 commitRoot 的时候统一处理
+ let deletions = []
const initChildren = (fiber, children) => {
children.forEach((child, index) => {
const isSameType = oldFiber && oldFiber.type === child.type
let newFiber
if (isSameType) {} else {
newFiber = {
type: child.type,
props: child.props,
child: null, // child 和 sibling 初始化我们不知道
sibling: null,
parent: fiber,
dom: null,
effectTag: 'placement'
}
+ if (oldFiber) {
+ deletions.push(oldFiber)
+ }
}
}
+ const commitDeletion = (fiber) => {
+ fiber.parent.dom.removeChild(fiber.dom)
+ }
const commitRoot = () => {
// 这里为啥不是root.props.children
+ deletions.forEach(commitDeletion)
...
+ deletions = []
}
问题:如果我们的节点里面有函数组件就会报错,因为 FC 没有 dom,所以我们应该删除它的 child,也就是递归调用 commitDeletion 但是 child 的父级是 FC 依然没有dom 所以要继续找它父级的父级是否有dom
const commitDeletion = (fiber) => {
if (fiber.dom) {
let fiberParent = fiber.parent
while(!fiberParent.dom) {
fiberParent = fiberParent.parent
}
fiberParent.dom.removeChild(fiber.dom)
} else {
commitDeletion(fiber.child)
}
}
- 新的比老的短
多出来的节点需要删除掉
- demo
import React from './core/React.js'
let showBar = false
function Counter() {
const foo = (
<div>
foo
<div>child</div>
</div>
)
const bar = <p>bar</p>
function handleShowBar() {
showBar = !showBar
React.update()
}
return (
<div>
Counter
<button onClick={handleShowBar}>showBar</button>
<div>{showBar ? bar : foo}</div>
</div>
)
}
function App() {
return (
<div>
hi-mini-react
<Counter></Counter>
</div>
)
}
export default App
因为我们处理的时候是根据新节点的 children 来遍历的
children.forEach((child, index) => {
// 开始对比
const isSameType = oldFiber && oldFiber.type === child.type
let newFiber
if (isSameType) {
// update
newFiber = {
type: child.type,
props: child.props,
child: null, // child 和 sibling 初始化我们不知道
sibling: null,
parent: fiber,
// 更新不会新创建 dom
dom: oldFiber.dom,
alternate: oldFiber,
effectTag: 'update'
}
} else {
// 添加
newFiber = {
type: child.type,
props: child.props,
child: null, // child 和 sibling 初始化我们不知道
sibling: null,
parent: fiber,
dom: null,
effectTag: 'placement'
}
if (oldFiber) {
deletions.push(oldFiber)
}
}
if (oldFiber) {
// 多个子级
oldFiber = oldFiber.sibling
}
if (index === 0) {
fiber.child = newFiber
} else {
// 如果不是第一个孩子就需要绑定到sibling,也就是上一个孩子的sibling 上,所以我们需要知道上一个孩子
prevChild.sibling = newFiber
}
// 考虑到我们还需要设置 parent.sibling,因为我们是从上往下获取的,所以work肯定是顶层也就是 parent,我们只能给 child 设置,
// 但是如果直接在child 上加就会破坏原有结构,所以我们单独维护一个newWork 对象,
prevChild = newFiber
})
console.log(oldFiber)
我们最后一次遍历的是遍历的心节点的倒数第一个div,它的children 里的的child 是bar,我们的 oldFiber 指向的是之前的也就是 foo,遍历完成 oldFiber 被赋值成了一个新的兄弟节点也就是老的下面的倒数第一个 div
if (oldFiber) {
deletions.push(oldFiber)
}
问题:如果我们有多个自己节点
const foo = (
<div>
foo
<div>child</div>
<div>child2</div>
</div>
)
我们除了要删除它,还要删除它的兄弟节点
while (oldFiber) {
deletions.push(oldFiber)
oldFiber = oldFiber.sibling
}
- edge case
- demo
import React from './core/React.js'
let showBar = false
function Counter() {
const bar = <p>bar</p>
function handleShowBar() {
showBar = !showBar
React.update()
}
return (
<div>
Counter
<button onClick={handleShowBar}>showBar</button>
<div>{showBar && bar}</div>
</div>
)
}
function App() {
return (
<div>
hi-mini-react
<Counter></Counter>
</div>
)
}
export default App
解决方式:判断如果是 false 就不处理
const initChildren = (fiber, children) => {
if (isSameType) {
} else {
+ if (child) {
newFiber = {
type: child.type,
props: child.props,
child: null, // child 和 sibling 初始化我们不知道
sibling: null,
parent: fiber,
dom: null,
effectTag: 'placement'
}
}
}
- demo
<div>
Counter
<div>{showBar && bar}</div>
<button onClick={handleShowBar}>showBar</button>
</div>
问题:因为我们第二个节点是 false,我们如果是false的时候不会赋值 newFiber 所以 pervChild = newFiber 就是 null
if (newFiber) {
prevChild = newFiber
}
- 优化更新
问题:更新子组件的时候,其他不相关的组件也会重新执行,造成了浪费
import React from './core/React.js'
let countFoo = 1
function Foo() {
console.log('foo return')
function handleClick() {
countFoo++
React.update()
}
return (
<div>
<h1>foo</h1>
{countFoo}
<button onClick={handleClick}>click</button>
</div>
)
}
let countBar = 1
function Bar() {
console.log('bar return')
function handleClick() {
countBar++
React.update()
}
return (
<div>
<h1>bar</h1>
{countBar}
<button onClick={handleClick}>click</button>
</div>
)
}
let countRoot = 1
function App() {
console.log('app return')
function handleClick() {
countRoot++
React.update()
}
return (
<div>
hi-mini-react: {countRoot}
<button onClick={handleClick}>click</button>
<Foo></Foo>
<Bar></Bar>
</div>
)
}
export default App
每次 update 的时候 三个组件都会重新 render
我们之前的更新逻辑是更新的时候重新指定根节点,从根节点一步步的执行,直到把整棵树执行完成
改进:只需要更新我们操作的那个组件的树,比如 Foo组件和它下面的节点,所以我们需要知道当前组件的开始节点和结束节点
开始节点:Foo
结束节点:当处理到兄弟节点的时候也就是Bar 的时候
获取开始节点:
在 updateFunctionComponent 中
const updateFunctionComponent = (fiber) => {
wipFiber = fiber
}
const update = () => {
console.log(wipFiber, 'www')
}
点击 Foo 但是我们会发现打印的却是 Bar,因为 wiperFiber 每次会被覆盖
解决方法:使用闭包
const update = () => {
let currentFiber = wipFiber
return () => {
console.log(currentFiber)
nextWorkOfUnit = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot
}
wipRoot = nextWorkOfUnit
}
}
改造 demo
function Foo() {
console.log('foo return')
const update = React.update()
function handleClick() {
countFoo++
update()
}
return (
<div>
<h1>foo</h1>
{countFoo}
<button onClick={handleClick}>click</button>
</div>
)
}
const update = () => {
let currentFiber = wipFiber
return () => {
wipRoot = {
...currentFiber,
alert: currentFiber
}
// wipRoot = {
// dom: currentRoot.dom,
// props: currentRoot.props,
// alternate: currentRoot
// }
nextWorkOfUnit = wipRoot
}
}
处理结束点
处理时机在赋值下一个节点的时候
nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit)
if (wipRoot?.sibling?.type === nextWorkOfUnit?.type) {
nextWorkOfUnit = null
}
https://github.com/wanglifa/mini-react/commit/3cb79f8d640207023b67f8a063553f56c2519dff