翻译|从提高性能角度优化react-redux store
原文请见
译注:一旦在React的程序中引入Redux框架,application的逻辑关注点就转移到了Redux store中.所有的与application有关的逻辑都在store中集中处理.当你对一个事物有了一定了解的时候,你会发现原来世界上有如此之多的事物不管是虚拟的,还是实际存在的,运行原理竟然高度相似.程序开发中的数据库系统和实际生活中的商店(store,supermarket)的运作方式几乎完全一样.
- 低档商店,天桥上卖针头线脑的老奶奶的货架,就是一个map,货物无序组织在一个平面上,由于货物少,存取的方法也很简单,直接手到擒来。
- 中档的便利店,货物丰富,再用平面管理就不行了,于是引入了维度,就是分类系统. 食品,饮料,报刊杂志等等,每种货物都属于一个分类,需要找到的时候,可以先找类,再找具体的货物。便利店往往都会在收银台贴出一个分类的指示内容,这个就是
索引
. 比尔.盖茨先生的计算机启蒙其实就是从图书馆分类系统开始的.所以这个分类系统蕴含着计算机数据库的初步模型。 - 更高层次的电商超市,就不在货架上存储实际的货物了,每个货架的位置这是仓库中的位置,就是完全的索引.
在程序设计中就应该要如何来组织数据. 从这个角度来看,Redux中的数据集中管理需要数据库的先进概念的支持是必须的.在React/React-native的设计中要在store的 state结构上倾注一定精力.
下面就看看这篇文章怎么来改进store从而提高React的渲染能力.
翻译开始
怎么来结构化redux的store来提高渲染性能?
本文是在React中渲染系列元素的一些优化方法的后续篇
提示: 这篇文章是专门用来优化React+Redux应用的. 并且专门用于优化react-redux v5. 由于react-redux v4的内部监听器太慢,这里的优化方法对他不起作用.
在application中存储一组元素最最普遍的方法就是作为一个数组.
可能你有一个组件需要渲染一组元素.
如果你需要更新其中之一,你就不得不更新整个列表:
每一个条目的更新都会更新整个“TargetList”的视图:
我创建了使用这种渲染方式的代码
http://codepen.io/lavrton/pen/xRgYbL
const {Stage, Layer, Circle, Group} = ReactKonva;
function generateTargets() {
return _.times(1000, (i) => {//underscore的方法
return {
id: i,
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
radius: 2 + Math.random() * 5,
color: Konva.Util.getRandomColor()
};
});
}
// for for test case our logic will be very simple
// just one action UPDATE for updating radius of target
//为了测试,只有一个action来改变目标item的半径
function appReducer(state, action) {
if (action.type === 'UPDATE') {
const i = _.findIndex(state.targets, (t) => t.id === action.id);//想找的要更新元素的索引
const updatedTarget = {//构造更新项目
...state.targets[i],
radius: action.radius
};
state = {
targets: [ //执行更新,下面的改进就在这个地方
...state.targets.slice(0, i),//js数组插入的方法
updatedTarget,
...state.targets.slice(i + 1)
]
}
}
return state;
}
const initialState = {
targets: generateTargets()
};
// create redux store
const store = Redux.createStore(appReducer, initialState);
class Target extends React.Component {
shouldComponentUpdate(newProps) {
return this.props.target !== newProps.target;
}
render() {
const {x, y, color, radius} = this.props.target;
return (
<Group x={x} y={y}>
<Circle
radius={radius}
fill={color}
/>
<Circle
radius={radius * 1 / 2}
fill="black"
/>
<Circle
radius={radius * 1 / 4}
fill="white"
/>
</Group>
);
}
}
// top component with list of targets
class App extends React.Component {
render() {
const targets = this.props.targets.map((target) => {
return <Target key={target.id} target={target} />;
});
const width = window.innerWidth;
const height = window.innerHeight;
return (
<Stage width={width} height={height}>
<Layer hitGraphEnabled={false}>
{targets}
</Layer>
</Stage>
);
}
}
const AppWrap = ReactRedux.connect((s) => ({ targets: s.targets }))(App);
ReactDOM.render(
<ReactRedux.Provider store={store}>
<AppWrap />
</ReactRedux.Provider>,
document.getElementById('container')
);
const N_OF_RUNS = 500;
const start = performance.now();
_.times(N_OF_RUNS, () => {
const id = 1;
let oldRadius = store.getState().targets[id].radius;
// update redux store
store.dispatch({ type: 'UPDATE', id, radius: oldRadius + 0.5 });
});
const end = performance.now();
console.log('sum time', end - start);
console.log('average time', (end - start) / N_OF_RUNS);
更新长度为1000的数组中的其中一条,在我的机器上需要花费大约21ms的时间.
在之前的文章中,我描述了一个做类似渲染的优化方法,就是让子组件”智能化”,在React组件的生命周期函数”shouldComponentUpdate”中添加一些逻辑.但是在store中改变state的结构方法更简单,还可以获得同样能的结果.
怎么去优化?
如果你正在使用react-redux,通过改变state的结构就你能简单的提升性能:
然后我们需要改变一下“TargetList”:
请注意:在这个实例中,我正把item ID传递到子视图中,并没有传递给整个item.在这个实例中”TargetView”不能是傻瓜组件.他需要连接到store:
正因为“TargetView”被连接到store,当“目标”数据被更新的时候,他将会自动进行更新.如果我们要更新一个列表中的一项,这种办法非常重要.”TargetList”不会被更新,因为“targetsOrder”是一样的.在同一个实例中,性能有很大的提高
http://codepen.io/lavrton/pen/ZBLrWp
const {Stage, Layer, Circle, Group} = ReactKonva;
function generateTargets() {
return _.times(1000, (i) => {
return {
id: i,
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
radius: 2 + Math.random() * 5,
color: Konva.Util.getRandomColor()
};
});
}
// for for test case our logic will be very simple
// just one action UPDATE for updating radius of target
function appReducer(state, action) {
if (action.type === 'UPDATE') {
state = {
... state,
targets: {
...state.targets,
[action.id]: {
...state.targets[action.id],
radius: action.radius
}
}
}
}
return state;
}
const initialTargets = generateTargets();
const targets = {};
initialTargets.forEach((t) => {
targets[t.id] = t;
});
const initialState = {
targetsOrder: initialTargets.map(t => t.id),
targets: targets
};
// create redux store
const store = Redux.createStore(appReducer, initialState);
class Target extends React.Component {
render() {
const {x, y, color, radius} = this.props.target;
return (
<Group x={x} y={y}>
<Circle
radius={radius}
fill={color}
/>
<Circle
radius={radius * 1 / 2}
fill="black"
/>
<Circle
radius={radius * 1 / 4}
fill="white"
/>
</Group>
);
}
}
const TargetWrap = ReactRedux.connect((s, ownProps) => ({target: s.targets[ownProps.targetId]}))(Target);
// top component with list of targets
class App extends React.Component {
render() {
const targets = this.props.targetsOrder.map((targetId) => {
return <TargetWrap key={targetId} targetId={targetId} />;
});
const width = window.innerWidth;
const height = window.innerHeight;
console.log('render list');
return (
<Stage width={width} height={height}>
<Layer hitGraphEnabled={false}>
{targets}
</Layer>
</Stage>
);
}
}
const AppWrap = ReactRedux.connect((s) => ({ targetsOrder: s.targetsOrder }))(App);
ReactDOM.render(
<ReactRedux.Provider store={store}>
<AppWrap />
</ReactRedux.Provider>,
document.getElementById('container')
);
const N_OF_RUNS = 500;
const start = performance.now();
_.times(N_OF_RUNS, () => {
const id = 1;
let oldRadius = store.getState().targets[id].radius;
// update redux store
store.dispatch({ type: 'UPDATE', id, radius: oldRadius + 0.5 });
});
const end = performance.now();
console.log('sum time', end - start);
console.log('average time', (end - start) / N_OF_RUNS);
更新一个单独的条目在我的机器上只花费了2.2ms.几乎比使用前一种state的结构快十倍.
译注:实际上是列表中每一项都根据自己的ID订阅了state的变化,当某个item的state变化是,只有单个的item可以根据ID来感知state的变化,从而进行渲染操作,其他item感知不到变化,不会发生渲染,