React 源码阅读 -3
React.Component
class Welcome extends React.Component {render() {return <h1>Hello, {this.props.name}</h1>;
}
}
在 React
组件中,代码重用的主要方式是组合而不是继承。
在 React.Component 的子类中有个必须定义的 render() 函数。本章节介绍其他方法均为可选。
React.PureComponent
React.PureComponent
与 React.Component
很相似。两者的区别在于 React.Component
并未实现 shouldComponentUpdate()
,而 React.PureComponent
中以浅层对比prop
和 state
的方式来实现了该函数。
如果赋予 React
组件相同的 props
和 state,render()
函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent
可提高性能。
React.PureComponent
中的 shouldComponentUpdate()
仅作对象的浅层比较。如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。仅在你的 props
和 state
较为简单时,才使用 React.PureComponent
,或者在深层数据结构发生变化时调用 forceUpdate() 来确保组件被正确地更新。你也可以考虑使用 immutable
对象加速嵌套数据的比较。
此外,React.PureComponent
中的 shouldComponentUpdate()
将跳过所有子组件树的 prop
更新。因此,请确保所有子组件也都是“纯”的组件。
原理
当组件更新时,如果组件的 props
和 state
都没发生改变,render
方法就不会触发,省去 Virtual DOM
的生成和比对过程,达到提升性能的目的。具体就是 React
自动帮我们做了一层浅比较:
if (this._compositeType === CompositeTypes.PureClass) {shouldUpdate = !shallowEqual(prevProps, nextProps)
|| !shallowEqual(inst.state, nextState);
}
而 shallowEqual
又做了什么呢?会比较 Object.keys(state | props)
的长度是否一致,每一个 key
是否两者都有,并且是否是一个引用,也就是只比较了第一层的值,确实很浅,所以深层的嵌套数据是对比不出来的。
function shallowEqual(objA: mixed, objB: mixed): boolean {if (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;}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {return false;}
}
return true;
}
export default shallowEqual;
function is(x: any, y: any) {
return ((x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
// Object.is()是在 ES6 中定义的一个新方法,它与‘===’相比,特别针对 -0、+0、NaN 做了处理。Object.is(-0, +0)会返回 false,而 Object.is(NaN, NaN)会返回 true。这与 === 的判断恰好相反,也更加符合我们的预期。
https://www.imweb.io/topic/59…
使用指南
易变数据不能使用一个引用
class App extends PureComponent {
state = {items: [1, 2, 3]
}
handleClick = () => {const { items} = this.state;
items.pop();
this.setState({items});
}
render() {
return (<div>
<ul>
{this.state.items.map(i => <li key={i}>{i}</li>)}
</ul>
<button onClick={this.handleClick}>delete</button>
</div>)
}
}
会发现,无论怎么点 delete
按钮,li
都不会变少,因为 items 用的是一个引用,shallowEqual
的结果为 true
。改正:
handleClick = () => {const { items} = this.state;
items.pop();
this.setState({items: [].concat(items) });
}
不变数据使用一个引用
子组件数据
如果是基本类型, 是能够更新的.
引用类型, 则不会更新.
handleClick = () => {const { items} = this.state;
items.splice(items.length - 1, 1);
this.setState({items});
}
子组件里还是 re-render 了。这样就需要我们保证不变的子组件数据的引用不能改变。这个时候可以使用 immutable-js
函数库。
函数属性
// 1
<MyInput onChange={e => this.props.update(e.target.value)} />
// 2
update(e) {this.props.update(e.target.value)
}
render() {return <MyInput onChange={this.update.bind(this)} />
}
由于每次 render
操作 MyInput
组件的 onChange
属性都会返回一个新的函数,由于引用不一样,所以父组件的 render 也会导致 MyInput 组件的 render
,即使没有任何改动,所以需要尽量避免这样的写法,最好这样写:
// 1,2
update = (e) => {this.props.update(e.target.value)
}
render() {return <MyInput onChange={this.update} />
}
空对象、空数组或固定对象
有时候后台返回的数据中,数组长度为 0 或者对象没有属性会直接给一个 null
,这时候我们需要做一些容错:
class App extends PureComponent {
state = {items: [{ name: 'test1'}, null, {name: 'test3'}]
}
store = (id, value) => {const { items} = this.state;
items[id] = assign({}, items[id], {name: value});
this.setState({items: [].concat(items) });
}
render() {
return (<div>
<ul>
{this.state.items.map((i, k) =>
<Item style={{color: 'red'}} store={this.store} key={k} id={k} data={i || {}} />)
}
</ul>
</div>)
}
}
PureComponent
真正起作用的,只是在一些纯展示组件上,复杂组件用了也没关系,反正 shallowEqual
那一关就过不了,不过记得 props
和 state
不能使用同一个引用哦。
组件的生命的周期
生命周期地址