上一小节我们学习了 react
中类组件的优化方式,对于 hooks
为主流的函数式编程,react
也提供了优化方式 memo
方法,本小节我们来了解下它的用法和实现原理。
这里我们接着上一小节的实现,添加函数组件
// src/index.js
function FunctionCounter(props) {
console.log('FunctionCounter render')
return <div>FunctionCount: {props.count}</div>
}
// memo 使用方式
const MemoFunctionCounter = React.memo(FunctionCounter)
...
render() {
return <div>
<MemoFunctionCounter count={this.state.number} />
<ClassCounter count={this.state.number} />
<input defaultValue={1} ref={this.amountRef} />
<button onClick={this.handleClick}>+</button>
</div>
}
...
我们打印下 memo
返回的是什么:
可以看到返回了一个 react
元素,元素类型是 react.memo
,type
对应我们传入的函数组件,compare
对应属性的判断方式,默认值就是类组件中的 shallowEqual
方法进行浅比较,因为函数组件中没有状态,所以只考虑属性。
前面我们提到过,react
元素就是一个对象,所以这里同样我们要对组件的挂在和更新进行处理,就跟 Provider
和 Consumer
处理一样的。说到这里相信跟下来的小伙伴脑袋里已经有了大概的思路。
constants.js
添加新的元素类型// src/constants.js
export const REACT_MEMO = Symbol('react.mome')react
中添加方法// src/react.js
// 从打印得知返回一个对象
function memo(type, compare = null) {
return {
$$typeof: REACT_MEMO,
compare,
type, // 传入的函数组件
}
}
理论上这里就已经实现了 memo
方法,但是我们还要对组件的挂载和更新进行判断处理
memo
类型挂载处理// src/react-dom.js
//createDOM
...
if (type && type.$$typeof === REACT_MEMO) {
return mountMemoComponent(vdom)
}
...
function mountMemoComponent(vdom) {
// 这里的 vdom 的 memo 方法返回的自身 vdom,即 <MemoFunctionCounter count={this.state.number}
// type 对应的就是我们打印出来的值,type.type 就是我们传入的 函数组件,大家可以打印自行查看
const {type: { type: FunctionCounter }, props} = vdom
// 这里记录一下旧的属性
vdom.prevProps = props
// 执行我们传入的函数组件返回要渲染的 vdom
let renderVdom = FunctionCounter(props)
vdom.oldRenderVdom = renderVdom
return createDOM(renderVdom)
}
memo
类型更新处理// src/react-dom.js
// updateElement
...
if (oldVdom.type.$$typeof === REACT_MEMO) {
updateMemoComponent(oldVdom, newVdom)
}
...
function updateMemoComponent(oldVdom, newVdom) {
let { type: {compare}, prevProps } = oldVdom
compare = compare || shallowEqual // 默认值
if(compare(prevProps, newVdom.props)) {
// 如果属性相同,不用更新,把oldVdom属性复制到newVdom
newVdom.prevProps = prevProps
newVdom.oldRenderVdom = oldVdom.oldRenderVdom
} else {
// 需要更新
let oldDOM = findDOM(oldVdom)
let parentDOM = oldDOM.parentNode
const {type: {type: FunctionCounter}, props} = newVdom
// 使用新的 vdom 渲染
let renderVdom = FunctionCounter(props)
// diff 对比,比较的是真实要渲染的 dom,不是直接的 oldVdom
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom)
// 同理赋值
newVdom.prevProps = props
newVdom.oldRenderVdom = renderVdom
}
}
使用我们自己的库执行,发现会有个小报错:
错误的原因是因为我们没有对空值进行过滤,导致 diff
对比时获取不到属性,我们做下简单的处理:
// src/react-dom.js
// updateChildren
...
// 我们对数组进行下 filter 过滤
oldVChildren = (Array.isArray(oldVChildren) ? oldVChildren : [oldVChildren]).filter(el => typeof el !== 'undefined' && el !== null);
newVChildren = (Array.isArray(newVChildren) ? newVChildren : [newVChildren]).filter(el => typeof el !== 'undefined' && el !== null);
...
再次点击按钮执行,可以看到效果和原生库一样。这两节内容都是讲 react
针对优化渲染内部做的处理,大家可以对比着看。当然如果工作中要求不是很高,也可以忽略,因为 IE
已经淘汰了,现有的主流浏览器性能都很 ok
。如有错误欢迎指正!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。