翻译|从提高性能角度优化react-redux store

翻译|从提高性能角度优化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感知不到变化,不会发生渲染,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容