一. memo
方法介绍
memo
方法接收两个参数,第一个参数是组件方法Component
,第二个参数是比对props
方法compare
,memo
方法返回ReactElement
对象的type
属性值
例如下面这段代码,通过memo
方法缓存HelloWorld
组件方法,当点击App
组件第一个h1
标签会修改text
属性值,触发更新渲染,当渲染HelloWorld
组件时,会比对props
是否相同,因为count
值没有变更,所以比对结果是相同的,所以不会调用HelloWorld
组件方法,即控制台不会输出console.log
,而是直接返回上次渲染结果。
当点击第二个h1
标签时会修改count
属性值,触发重新渲染,当渲染HelloWorld
组件时,比对props
不相同,重新调用HelloWorld
组件方法获取新的child ReactElement
,即控制台会输出console.log
const HelloWorld = memo(function HelloWorld({ count }: { count: number }) {
console.log(count)
return <h1>{count}</h1>
})
function App() {
const [count, setCount] = useState(0)
const [text, setText] = useState('')
return (
<div>
<h1 onClick={() => setText('are you ok?')}>text click</h1>
<h1 onClick={() => setCount(count + 1)}>count click</h1>
<h1>Text: {text}</h1>
<HelloWorld count={count} />
</div>
)
}
二. 实现memo
强烈推荐阅读手写mini React,理解React渲染原理,有助于理解本文章内容
2.1 定义memo
方法
创建elementType
对象,$$typeof
属性记录ReactElement
对象类型,type
属性记录函数组件方法,compare
属性记录比对props
方法,创建的elementType
对象会作为ReactElement
对象的type
属性值
function memo(Component, compare = null) {
const elementType = {
$$typeof: REACT_MEMO_TYPE, // Symbol.for('react.memo')
type: Component, // 记录函数组件方法
compare, // 记录比对props方法
}
return elementType
}
以这段代码为例const HelloWorld = memo(function HelloWorld({ count }) {})
,创建的ReactElement
对象结构如下
{
$$typeof: Symbol.for('react.transitional.element'),
key: null,
props: { count: 0 },
type: {
$$typeof: Symbol.for('react.memo'),
type: function HelloWorld({ count }) {},
compare: null,
},
}
2.2 创建ReactElement
对象对应的FiberNode
节点
根据ReactElement
对象的type
属性创建对应的FiberNode
节点,FiberNode
节点的tag
属性值为MemoComponent
function createFiberFromElement(element) {
let fiberTag
const { type } = element
if (typeof type === 'function') {
fiberTag = FunctionComponent
} else if (typeof type === 'string') {
fiberTag = HostComponent
} else {
// memo类型FiberNode节点
switch (type.$$typeof) {
case REACT_MEMO_TYPE:
fiberTag = MemoComponent
break
}
}
const fiber = new FiberNode(fiberTag, element.props)
fiber.key = element.key
fiber.elementType = type
coerceRef(fiber, element)
return fiber
}
2.3 调用组件方法
在构建虚拟DOM
树阶段,递归遍历到MemoComponent
类型的FiberNode
节点
- 判断旧
FiberNode
节点是否存在,如果存在则比对新旧节点prop
是否相同,相同则复用旧child FiberNode
- 如果旧
FiberNode
节点不存在或比对props
不相同,则调用组件方法获取新的child ReactElement
function updateMemoComponent(current, workInProgress) {
if (current !== null) {
// 获取比对props方法
const compare = workInProgress.elementType.compare || shallowEqual
// 获取旧属性值
const prevProps = current.pendingProps
// 获取新属性值
const nextProp = workInProgress.pendingProps
// 比对属性值是否相同,相同复用旧child FiberNode节点
if (compare(prevProps, nextProp)) {
return cloneChildFibers(current, workInProgress)
}
}
// 获取组件方法
const Component = workInProgress.elementType.type
// 调用组件方法获取新的child ReactElement
return updateFunctionComponent(current, workInProgress, Component)
}
function beginWork(workInProgress) {
// 获取旧FiberNode节点
const current = workInProgress.alternate
switch (workInProgress.tag) {
case MemoComponent:
return updateMemoComponent(current, workInProgress)
}
}
2.3.1 比对props
默认方法
通过Object.is
比对属性值是否相同
function shallowEqual(objA, objB) {
if (Object.is(objA, objB)) return true
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
)
return false
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) return false
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i]
if (
!Object.prototype.hasOwnProperty.call(objB, currentKey) ||
!Object.is(objA[currentKey], objB[currentKey])
)
return false
}
return true
}