Open g770728y opened 4 years ago
mobx
: context.value
引用不变, 通过proxy
驱动视图更新react.createContext
: context.value
引用将改变, 从而驱动视国更新(与state是相同的)
这二者对性能影响是不同的, 前者显然实现的是精确更新, 后者实现的是整体更新
后者带来的问题是: 一旦context发生变化, 所有useContext
的组件都将更新!!!
虽然可以使用memo
来优化, 但对于复杂深嵌套组件, 实际做起来并不简单, 顾虑太多.个人的惯用写法: 定义时:
const storeRef = useRef(new Store())
return <StoreContext.Provider value={storeRef.current}>...</StoreContext.Provider>
使用时:
const store = useContext(StoreContext);
return <div>{store.x}</div>
关键点:
useContext
始终不会引起rerender
!!!定义:
const [state, setState] = useState<Context>({});
const handleClick=() => {setState(...) }
return <Context.Provider value={state}>
...
</Context.Provider>
使用:
const store = useContext(Context);
...
这里的主要问题是: 一旦context发生了变化, 那么:
所有使用useContext的组件, 都会rerender
!!!
这是机制使然:
proxy
驱动renderer
典型场景:
const a = 'aaa';
const x = {...x, [a]: 1}
如果 编译成es5, 将编译出_objectSpread, 并使用defineProperties 方法, 性能非常低 如果 直接使用上述语法, 性能与Object.assign({}, x, [a]:1}基本相同, 都比前者快10倍 数组也尽量使用xs.concat(ys); 所以,如果 浏览器兼容性足够, 尽可能使用es6
Chrome:51 版起便可以支持 97% 的 ES6 新特性。 Firefox:53 版起便可以支持 97% 的 ES6 新特性。 Safari:10 版起便可以支持 99% 的 ES6 新特性。 IE:Edge 15可以支持 96% 的 ES6 新特性。Edge 14 可以支持 93% 的 ES6 新特性。(IE7~11 基本不支持 ES6) X5微信浏览器需要关注, 例如并不支持Array.includes
connect
高阶组件的使用, 会增大计算工作量当然, 使用connect
, 最大的好处是: 清晰, 自然, 每个组件只需关心自己的数据
所以, 在完成功能期间, 尽量大量使用connect
, 不要考虑性能问题
connect
原理说起四步:
store.state
发生改变时connect
组件的mapProps
render
试想, 一个excel
应用,有5000+
单元格, 每个单元格都connect
到store.state
当state发生改变, 则:
第2步: 计算5000个单元格的mapProps
第3步: 将这5000个newProps与oldProps进行比较
即便render
不会发生, 这10000次运算也少不了, 基本不可能达到60FPS了
这也就是为什么明明没有发生渲染, react的性能面板也会显示大量细线的原因RowGroup
-> Row
-> Cell
三级connect
这样一来:
state发生变更, 只需要考虑RowGroup的属性是否有变动, 若无变动, 则完全不刷新
若有变动, 则检查Row
, 若无变化, 则该row不刷新
若有变动, 则检查Cell
假设每行50个单元格, 共100行, 5000个单元格
修改一个单元格:
RowGroup计算1次, 渲染1次
Row计算100次, 渲染1行,
Cell计算50次, 渲染1个单元格
共计算551次, 这里的计算, 大部分可以ShallowEqual, 性能很高
基本上只需要耗费1/10的时间
甚至, 可以将RowGroup与Row之间, 再引入SubGroup, 进一步减少计算reflow
问题在开发树形excel组件
时, 发现一个问题:
无论优化得如何好, 在滚动页面时, 或点击单元格时, 总是要卡2秒
可以肯定别的优化都很好, 没有什么优化空间, 那么这个2秒是从哪里来的呢?
在chrome的性能监控里, 发现时间完全耗费在render
上, 具体是Update Render Tree
查资料, 发现其实是在重新计算布局
因为整个组件在滚动时, 并未发生引起reflow
的事件, 但确实reflow了
是什么原因呢?
一番折腾, 发现:
在容器节点上, 增加position:relative
后, 可以实现流畅滚动
没google到相关原因, 只能怀疑是css bug
这个问题后面可以深挖
<div className={styles['container']}>
<div className={styles['header']}>{header}</div>
<div style={{ overflow: 'auto', height: '100%' }}>
<div
style={{
display: 'flex',
justifyContent: 'center',
position: 'relative' , <==== 关键!!!!
height: '100%',
}}
>
<div>
{editor}
<div style={{ height: 100, width: 1 }} />
</div>
</div>
</div>
</div>
reduce
对于数据量过200以上的, 慎用reduce!!! ( 当然也可前期全部reduce, 优化时再处理 )
直接上数据说话:
idArray: T[],
idField: string = "id"
): { [id: string]: T } {
console.log("测试记录条数:", idArray.length);
console.time("idMap");
const result1 = idArray.reduce(
(acc, obj) => ({ ...acc, [(obj as any)[idField]]: obj }),
{}
);
console.timeEnd("idMap");
console.time("idMap2");
const result2 = idArray.reduce((acc, obj) => {
const _acc = Object.assign({}, acc);
const idName = (obj as any)[idField];
(_acc as any)[idName] = obj;
return _acc;
}, {});
console.timeEnd("idMap2");
console.time("idMap3");
let result = {} as any;
for (let i = 0; i < idArray.length; i++) {
const obj = idArray[i];
const idName = (obj as any)[idField];
result[idName] = obj;
}
console.timeEnd("idMap3");
console.time("idMap4");
let result4 = {} as any;
idArray.forEach(obj => {
const idName = (obj as any)[idField];
result4[idName] = obj;
});
console.timeEnd("idMap4");
console.time("idMap5");
const result5 = R.reduce(
(acc, obj) => ({ ...acc, [(obj as any)[idField]]: obj }),
{},
idArray
);
console.timeEnd("idMap5");
console.log("result5", result5);
return result;
}
结果: 出乎意料: reduce与普通forEach循环慢了数百倍:
测试记录条数: 3367
reduce+解构语法: idMap: 2620.43115234375ms
reduce+Object.assign idMap2: 2568.407958984375ms
for循环 idMap3: 0.81005859375ms
forEach循环 idMap4: 0.775146484375ms
Rambda的R.reduce idMap5: 2626.396240234375ms
区区3367条语句, reduce执行了整整2.6秒!!! 而forEach不到1ms
这一点真的跟直觉相反. rambda的方法居然快得多 对于object的修改, 使用 Object.keys(obj).forEach, 比R.map慢40倍! 同样, 直接使用Array.filter, 也比 R.filter慢80倍!
数组型组件中, 单个
item
的isSelected
在何处计算对这种高频问题, 应当形成肌肉记忆, 不要每次都要花几分钟找问题
问题
经常遇到如下代码:
每当store发生变化, selection 都要重新计算, 从而引起每个item渲染
解决
很明显, 将
isSelected
上移:优化: 在ui组合上, 维护一个全局的
SelectionWidget
回想之前做过的项目,
selection
有什么用处? 最大用处就是显示一个SelectionWidget
, 可用于:老做法: 在每个
widget
外, 套一个SelectionWidget
存在问题:
selection
一旦改变, 原则上可能影响所有组件, 性能差(不考虑优化前提下)widget
存在层级, 那么selectionWidget
可能被挡住 ( 报表设计器已遇到的问题)推荐做法: 全局
SelectionWidget
valoe-sheet
项目用上