转载于:https://blog.youkuaiyun.com/sinat_17775997/article/details/77971016
react-native组件避免重复渲染
react-native若有性能问题,很可能是由于组件的重复渲染,研究下相关知识点。
转载http://www.tuicool.com/articles/vY7Vjym
export default class Home1 extends Component {
render() {
console.log('渲染根');
return (
<View style={{backgroundColor: 'white', marginTop: 100}}>
<ComponentA/>
<ComponentB/>
</View>
);
}
}
class ComponentA extends Component {
render() {
console.log('渲染A');
return (
<Text>组件A</Text>
)
}
}
class ComponentB extends Component {
render() {
console.log('渲染B');
return (
<View>
<Text>组件B</Text>
<ComponentC/>
</View>
)
}
}
class ComponentC extends Component {
render() {
console.log('渲染C');
return (
<View>
<Text>组件C</Text>
<ComponentD/>
</View>
)
}
}
class ComponentD extends Component {
render() {
console.log('渲染D');
return (
<View>
<Text>组件D</Text>
</View>
)
}
}
组件关系图如下:

测试现象:
若A被渲染,则C、D也会跟着被渲染,其他不变。
若根被渲染,所有组件都重新渲染。
若B被渲染,其他组件不变。
结论:某一组件被render,只会导致本身和所有的子组件被重新render,其他的没有影响。
问题:例如当A被渲染时,C、D的数据并未改变,甚至C、D根本就是无状态无属性组件,但它们也被重新渲染了,浪费性能。
组件是否重新渲染的决定因素是组件的生命周期方法shouldComponentUpdate的返回值,而Component组件该方法的默认实现返回值总是true,所以会被重新渲染。可以重写该方法让其根据业务需求判断是否返回true来决定是否刷新组件。但是每个组件都重写该方法很麻烦。
PureComponent组件,继承自Component,已经实现了shouldComponentUpdate,大概实现如下
function shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
}
shallowEqual实现在"node_modules/fbjs/lib/shallowEqual"处,高效判断两个对象是否相等,从而决定是否渲染页面。
将上面所有组件的父类改为PureComponent,再次测试,发现当A被渲染时,C、D也不会被渲染了,性能肯定变好。
此法虽好,但也有不少副作用,比如将B组件的实现改为如下
-
class ComponentB extends PureComponent { -
constructor(props) { -
super(props); -
this.state = { -
num: 4, -
arr: [1,2,3] -
}; -
} -
componentDidMount() { -
setInterval(() => { -
this.state.arr.push(this.state.num); -
this.setState({ -
num: this.state.num + 1, -
arr: this.state.arr -
}) -
}, 2000) -
} -
render() { -
console.log('渲染B'); -
return ( -
<View> -
<ComponentC arr={this.state.arr}/> -
<Text>{`组件B: ${this.state.num}`}</Text> -
</View> -
) -
} -
}
开个定时器,不断改变arr的值,组件C的属性发生了变化,但是由于C组件的shouldComponentUpdate判断方法shallowEqual只能对对象做浅比较,也就是判断对象的地址是否一致,这里数组本身地址并未发生变化,仅仅是内容有所变化,该方法鉴别不出来,返回的是false,页面不会重新被渲染,有大问题。
这里的解决方案有好些:
a、在C组件中重写shouldComponentUpdate,判断arr内容是否变化,决定重新渲染。
b、B组件给C组件传递属性前,将arr对象进行深拷贝(有性能问题),重新生成对象
c、利用不可变对象,我这里用的是轻量级的seamless-immutable
不可变对象有诸多优点就不说了,总之很好很强大,性能提升利器。使用之后B组件代码如下
-
class ComponentB extends PureComponent { -
constructor(props) { -
super(props); -
this.state = { -
num: 0, -
arr: Immutable([1,2,3]) -
}; -
} -
componentDidMount() { -
setInterval(() => { -
let arr = Immutable.asMutable(this.state.arr); -
arr.push(5); -
let newArr = Immutable(arr); -
this.setState({ -
num: this.state.num + 1, -
arr: newArr -
}) -
}, 2000) -
} -
render() { -
console.log('渲染B'); -
return ( -
<View> -
<ComponentC arr={this.state.arr}/> -
<Text>{`组件B: ${this.state.num}`}</Text> -
</View> -
) -
} -
}
使用就三步:
1、导入头文件,import Immutable from 'seamless-immutable'
2、数组或对象初始化时使用如Immutable([1,2,3])的方式
3、改变数组或对象时使用专门的api,比如Immutable.asMutable、Immutable.flatMap
有些组件拥有继承关系,但是顶层父类又是继承的Component,这时可以采用pure-render-decorator,给该组件添加一个装饰器扩展方法shouldComponentUpdate,这个效果跟PureComponent基本一致。
import pureRenderDecorator from 'pure-render-decorator';
@pureRenderDecorator
class ComponentA extends PureComponent {
还有个小问题,上面B组件传递到C组件的属性arr,在C组件中并没有在render方法中被使用,理论上该组件是不需要不断渲染的,但是因为shouldComponentUpdate方法返回true所以会反复渲染。当然既然B组件传递了属性arr给C,那么实际开发中arr肯定是必不可少的。我要说的是,如果在开发中C组件拿到arr不是为了渲染更新页面的目的,而是其他的比如统计之类的跟页面渲染无关的事,那么,还是需要自己重写shouldComponentUpdate,避免不必要的渲染发生。
接下来简单说下seamless-immutable中数组的实现方式:
Immutable([1,2,3]),会调用到下面这些方法
-
function makeImmutableArray(array) { // 方法A -
for (var index in nonMutatingArrayMethods) { -
if (nonMutatingArrayMethods.hasOwnProperty(index)) { -
var methodName = nonMutatingArrayMethods[index]; -
makeMethodReturnImmutable(array, methodName); // 方法B -
} -
} -
// 给数组添加上flatMap、asObject等一系列自定义方法 -
if (!globalConfig.use_static) { -
addPropertyTo(array, "flatMap", flatMap); -
addPropertyTo(array, "asObject", asObject); -
addPropertyTo(array, "asMutable", asMutableArray); -
addPropertyTo(array, "set", arraySet); -
addPropertyTo(array, "setIn", arraySetIn); -
addPropertyTo(array, "update", update); -
addPropertyTo(array, "updateIn", updateIn); -
addPropertyTo(array, "getIn", getIn); -
} -
// 让数组中的每一项都变为不可变对象 -
for (var i = 0, length = array.length; i < length; i++) { -
array[i] = Immutable(array[i]); -
} -
return makeImmutable(array, mutatingArrayMethods); // 方法C -
} -
function makeMethodReturnImmutable(obj, methodName) { // 方法B实现 -
var currentMethod = obj[methodName]; -
Object.defineProperty(obj, methodName, { -
enumerable: false, -
configurable: false, -
writable: false, -
value: Immutable(currentMethod.apply(obj, arguments)) -
}) -
} -
function makeImmutable(obj, bannedMethods) { // 方法C实现 -
for (var index in bannedMethods) { -
if (bannedMethods.hasOwnProperty(index)) { -
banProperty(obj, bannedMethods[index]); -
} -
} -
Object.freeze(obj); -
return obj; -
}
B方法:
参数obj就是数组对象,methodName为"map", "filter", "slice", "concat", "reduce", "reduceRight等。Object.defineProperty为数组重定义属性和方法,writable为false变为不可写,该方法的目的就是让数组的这些方法失去作用,外界调用不可变数组的map、concat等方法后不再有效。
C方法:bannedMethods为"push", "pop", "sort", "splice", "shift", "unshift", "reverse"等,banProperty方法的实现也是用Object.defineProperty实现,作用是外界调用不可变数组的push、pop等方法时直接报错。最后Object.freeze(obj)冻结整个数组对象,让其本身无法被修改,变为不可变对象。
A方法:包含B、C,并且给数组添加上自定义的很多方法如遍历flatMap、转换为普通数组asMutable等。array[i] = Immutable(array[i])使数组内部的每一个成员都变为不可变对象,在这里,若数组内部元素又是个数组,则会递归到该方法再次执行,直到数组内部所有对象都变为了不可变对象,基本数据类型不可变对象就是本身。
seamless-immutable使用Object.defineProperty扩展了JavaScript 的Array和Object对象来实现,只支持 Array和Object两种数据类型,API 基于与 Array 和 Object ,因此许多不用改变自己的使用习惯,对代码的入侵非常小。同时,它的代码库也非常小,压缩后下载只有 2K。相比于笨重的教科书级别的Immutable无疑更适用于react-native。
探讨React-Native组件渲染原理,分析组件重复渲染原因及优化策略,包括PureComponent使用与不可变对象实现。
978

被折叠的 条评论
为什么被折叠?



