本系列文章在实现一个 cpreact 的同时帮助你们理顺 React 框架的核心内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/PureComponent/HOC/...) 项目地址html
使用 PureComponent 是优化 React 性能的一种经常使用手段,相较于 Component, PureComponent 会在 render 以前自动执行一次 shouldComponentUpdate() 函数,根据返回的 bool 值判断是否进行 render。其中有个重点是 PureComponent 在 shouldComponentUpdate() 的时候会进行 shallowEqual(浅比较)。node
PureComponent 的浅比较策略以下:react
对 prevState/nextState 以及 prevProps/nextProps 这两组数据进行浅比较:git
1.对象第一层数据未发生改变,render 方法不会触发; 2.对象第一层数据发生改变(包括第一层数据引用的改变),render 方法会触发;github
照着上述思路咱们来实现 PureComponent 的逻辑算法
function PureComponent(props) {
this.props = props || {}
this.state = {}
isShouldComponentUpdate.call(this) // 为每一个 PureComponent 绑定 shouldComponentUpdate 方法
}
PureComponent.prototype.setState = function(updater, cb) {
isShouldComponentUpdate.call(this) // 调用 setState 时,让 this 指向子类的实例,目的取到子类的 this.state
asyncRender(updater, this, cb)
}
function isShouldComponentUpdate() {
const cpState = this.state
const cpProps = this.props
this.shouldComponentUpdate = function (nextProps, nextState) {
if (!shallowEqual(cpState, nextState) || !shallowEqual(cpProps, nextProps)) {
return true // 只要 state 或 props 浅比较不等的话,就进行渲染
} else {
return false // 浅比较相等的话,不渲染
}
}
}
// 浅比较逻辑
const shallowEqual = function(oldState, nextState) {
const oldKeys = Object.keys(oldState)
const newKeys = Object.keys(nextState)
if (oldKeys.length !== newKeys.length) {
return false
}
let flag = true
for (let i = 0; i < oldKeys.length; i++) {
if (!nextState.hasOwnProperty(oldKeys[i])) {
flag = false
break
}
if (nextState[oldKeys[i]] !== oldState[oldKeys[i]]) {
flag = false
break
}
}
return flag
}
复制代码
测试用例用 在 React 上提的一个 issue 中的案例,咱们指望点击增长按钮后,页面上显示的值可以加 1。redux
class B extends PureComponent {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.click = this.click.bind(this)
}
click() {
this.setState({
count: ++this.state.count,
})
}
render() {
return (
<div> <button onClick={this.click}>增长</button> <div>{this.state.count}</div> </div>
)
}
}
复制代码
然而,咱们点击上述代码,页面上显示的 0 分绝不动!!!性能优化
揭秘以下:app
click() {
const t = ++this.state.count
console.log(t === this.state.count) // true
this.setState({
count: t,
})
}
复制代码
当点击增长按钮,控制台显示 t === this.state.count
为 true, 也就说明了 setState 先后的状态是统一的,因此 shallowEqual(浅比较) 返回的是 true,导致 shouldComponentUpdate 返回了 false,页面所以没有渲染。框架
相似的,以下写法也是达不到目标的,留给读者思考了。
click() {
this.setState({
count: this.state.count++,
})
}
复制代码
那么如何达到咱们指望的目标呢。揭秘以下:
click() {
this.setState({
count: this.state.count + 1
})
}
复制代码
感悟:小小的一行代码里蕴藏着无数的 bug。
高阶组件(Higher Order Component) 不属于 React API 范畴,可是它在 React 中也是一种实用的技术,它能够将常见任务抽象成一个可重用的部分
。这个小节算是番外篇,会结合 cpreact(前文实现的类 react 轮子) 与 HOC 进行相关的实践。
它能够用以下公式表示:
y = f(x),
// x:原有组件
// y:高阶组件
// f():
复制代码
f()
的实现有两种方法,下面进行实践。
这类实现也是装饰器模式的一种运用,经过装饰器函数给原来函数赋能。下面例子在装饰器函数中给被装饰的组件传递了额外的属性 { a: 1, b: 2 }。
声明:下文所展现的 demo 均已在 cpreact 测试经过
function ppHOC(WrappedComponent) {
return class extends Component {
render() {
const obj = { a: 1, b: 2 }
return (
<WrappedComponent { ...this.props } { ...obj } /> ) } } } @ppHOC class B extends Component { render() { return ( <div> { this.props.a + this.props.b } { /* 输出 3 */ } </div> ) } } 复制代码
要是将 { a: 1, b: 2 } 替换成全局共享对象,那么不就是 react-redux 中的 Connect 了么?
改进上述 demo,咱们就能够实现可插拔的受控组件,代码示意以下:
function ppDecorate(WrappedComponent) {
return class extends Component {
constructor() {
super()
this.state = {
value: ''
}
this.onChange = this.onChange.bind(this)
}
onChange(e) {
this.setState({
value: e.target.value
})
}
render() {
const obj = {
onChange: this.onChange,
value: this.state.value,
}
return (
<WrappedComponent { ...this.props } { ...obj } />
)
}
}
}
@ppDecorate
class B extends Component {
render() {
return (
<div>
<input { ...this.props } />
<div>{ this.props.value }</div>
</div>
)
}
}
复制代码
效果以下图:
这里有个坑点,当咱们在输入框输入字符的时候,并不会立马触发 onChange 事件(咱们想要让事件当即触发,然而如今要按下回车键或者点下鼠标才触发),在 react 中有个合成事件 的知识点,下篇文章会进行探究。
顺带一提在这个 demo 中彷佛看到了双向绑定的效果,可是实际中 React 并无双向绑定的概念,可是咱们能够运用 HOC 的知识点结合 setState 在 React 表单中实现伪双向绑定的效果。
继承反转的核心是:传入 HOC 的组件会做为返回类的父类来使用。而后在 render 中调用 super.render()
来调用父类的 render 方法。
在 《ES6 继承与 ES5 继承的差别》中咱们提到了做为对象使用的 super 指向父类的实例。
function iiHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
const parentRender = super.render()
if (parentRender.nodeName === 'span') {
return (
<span>继承反转</span>
)
}
}
}
}
@iiHOC
class B extends Component {
render() {
return (
<span>Inheritance Inversion</span>
)
}
}
复制代码
在这个 demo 中,在 HOC 内实现了渲染劫持,页面上最终显示以下:
可能会有疑惑,使用
属性代理
的方式貌似也能实现渲染劫持呀,可是那样作没有继承反转
这种方式纯粹。
Especially thank simple-react for the guidance function of this library. At the meantime,respect for preact and react