webpack阶段
路由阶段
1 .路由懒加载
2 .React.lazy 目前只支持默认导出(default exports)
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
//必须搭配这个东西
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
颗粒化控制可控组件
1 .可控组件的DOM值收到react数据的state控制,一旦react的state控制数据状态发生变化,触发了render,如果父组件内容比较简单,还好,但是如果父组件复杂,还会影响到子组件,那就非常的复杂了
1.1 .但由于协调算法的优化, 触发render不一定触发真实dom的更新
2 .举例
1 .父组件启动了一个定时器,这就是每一秒都会更新整个自己的组件,如果这个里面调用了别的组件,那么别的组件也是会一起触发更新的.
2 .他的思路是这样,把setState的单独做成最小的粒度组件,不然他有子组件,这样自然就不会影响到别的
3 .目前这里兄弟组件之间是不会互相影响的.
3 .只要父组件的render了,那么默认情况下就会触发子组件的render过程,子组件的render过程又会触发它的子组件的render过程,一直到React元素(即jsx中的<div>这样的元素.并不是只有传入props才会导致变化
4 .shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
return nextState.someData !== this.state.someData
}
state里的数据这么多,还有对象,还有复杂类型数据,react的理念就是拆分拆分再拆分,这么多子组件,我要每个组件都去自己一个一个对比吗??不存在的,这么麻烦,要知道我们的终极目标是不劳而获-_-
//hook里面也不能用
//弊端:如果存在很多子孙组件的话,会有很大的工作量,并且可能会漏加
5 .React.PureComponent
无论组件是否是 PureComponent,如果定义了 shouldComponentUpdate(),那么会调用它并以它的执行结果来判断是否 update。在组件未定义 shouldComponentUpdate() 的情况下,会判断该组件是否是 PureComponent,如果是的话,会对新旧 props、state 进行 shallowEqual 比较,一旦新旧不一致,会触发 update。
* 浅判等 只会比较到两个对象的 ownProperty 是否符合 [源码](https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js#L39)
尽量将复杂类型数据(ArrayList)所关联的视图单独拆成PureComonent有助于提高渲染性能,比如表单、文本域和复杂列表在同一个 render() 中,表单域的输入字段改变会频繁地触发 setState() 从而导致 组件 重新 render()。而用于渲染复杂列表的数据其实并没有变化,但由于重新触发 render(),列表还是会重新渲染。
//目前来看
import React from 'react';
import { immutableRenderDecorator } from 'react-immutable-render-mixin';
@immutableRenderDecorator
class Test extends React.Component {
render() {
return <div></div>;
}
}
hook下面
1 .父子组件完全没有任何依赖,父组件不需要传给子组件数据和方法
对于函数组件来说并没有这个生命周期可以调用,因此想实现性能优化只能通过 React.memo(<Component />) 来做,这种做法和继承 PureComponent 的原理一致。
React.memo 包一下
import React from 'react'
export default React.memo(()=>{
console.log('TopSon 更新')
return (
<div>
h11
</div>
)
})
//算一个外部优化,这样甚至都不会触发子组件的任何逻辑,不会执行任何业务逻辑
2 .父组件传值给子组件,这个需要内外搭配,内部就是useMemo()
const TopSon=useMemo(()=>{
console.log('111')
return topSon
},[])
//不能通过这样生成一个新的带有缓存的组件,他的操作只能起作用在有state变化的.只会执行部分逻辑.
1 .对于变化的值,这里添加依赖
建立独立的请求渲染单元
1 .把页面,分为请求数据战术部分和基础部分,不需要请求数据,直接写好的.
2 .对于一些逻辑不是很复杂的数据展示部分,可以使用一种独立组件,独立请求数据,独立控制渲染的模式.这样一个数据请求发生的重现渲染,也不会影响其他的组件.每一部分抽取出来,形成独立的渲染单元,每个组件都独立数据请求到独立渲染
3 .虽然react有很好的diff算法去协调相同的节点,但是长列表,或者说直接不渲染是最好的结果了
4 .无论从class声明,还是fun声明的无状态,都有一套自身的优化机制,shouldComponentUpdate还是hook中的useMemo,useCallback,都可以根据自身情况,定制一个符合场景的渲染条件,使得依赖数据请求组件形成一个自己的,小的渲染,环境,可以根据这个小的环境自定义优化机制.
immetable.js
1 .提高对象的比较性能,pureComponent只能对对象进行浅比较,对于对象的数据类型,无法比较
2 .这里是如何和hook里面结合起来的呢,hook的useMemo依赖的都是后面的数据,无法拿到前后的数据啊,或者说每一个数据难道还要保存两份版本,然后专门用这个函数diff吗?
3 .本质就是useMemo的第二个参数能否diff到object的变化.
写法上的小细节
1 .绑定事件不要写箭头函数,因为箭头函数每次渲染都会创建一个新的事件处理器,导致子组件每次都会重新渲染
2 .循环正确使用key
1 .不要使用index做key,每次还是会全部diff
2 .不要用index拼接其他字段:如果有元素移动或者删除,失去对应关系之后,剩下的节点都不能得到有效的复用
3 .一定要使用唯一的id来做key
3 .基本上所有的函数都用useMemo,useCallback包起来做缓存
//一次操作3个click函数
const [ handerClick1 , handerClick2 ,handerClick3] = useMemo(()=>{
const fn1 = ()=>{
/* 一些操作 */
}
const fn2 = ()=>{
/* 一些操作 */
}
const fn3= ()=>{
/* 一些操作 */
}
return [fn1 , fn2 ,fn3]
},[])
避免重复渲染
1 .学会使用批量更新
1 .批量更新失败的场景 异步场景之后操作多个state
handerClick=()=>{
setTimeout(() => {
this.setState({ a:a+1 })
this.setState({ b:b+1 })
this.setState({ c:c+1 })
}, 0)
}
const handerClick = () => {
Promise.resolve().then(()=>{
setB( { ...b } )
setC( c+1 )
setA( a+1 )
})
}
//常见场景举例,一次ajax请求之后,获得多个state,想要通过多个useState改变状态,会造成多次渲染页面,为了解决这个问题,我们可以手动批量更新
const handerClick = () => {
Promise.resolve().then(()=>{
unstable_batchedUpdates(()=>{
setB( { ...b } )
setC( c+1 )
setA( a+1 )
})
})
}
react-dom 中提供了unstable_batchedUpdates方法进行手动批量更新。这个api更契合react-hooks,
2 .合并state
3 .useMemo,React,memo隔离渲染单元
<button onClick={ ()=> setNumber(number + 1) } >点击</button>
<ul>
{
useMemo(()=>(list.map(item=>{
console.log(1111)
return <li key={ item.id } >{ item.name }</li>
})),[ list ])
}
</ul>
{ useMemo(()=> <ChildrenComponent />,[]) }
//竟然可以写在这个里面
4 .不是所有的值都需要用state
class Demo extends React.Component{
text = 111
componentDidMount(){
const { a } = this.props
/* 数据直接保存在text上 */
this.text = a
}
render(){
/* 没有引入text */
return <div>{'hello,world'}</div>
}
}
//压根渲染没用到的地方就不需要
5 .useCallBack :useCallback就是 callback加了一个memoize。我们接着往下看
海量数据优化,时间分片,虚拟列表
1 .时间分片的概念,就是一次性渲染大量数据,初始化的时候会出现卡顿等现象。我们必须要明白的一个道理,js执行永远要比dom渲染快的多。 ,所以对于大量的数据,一次性渲染,容易造成卡顿,卡死的情况
2 .,就是用setTimeout把任务分割,分成若干次来渲染。一共40000个数据,我们可以每次渲染100个, 分次400渲染。
3 .setTimeout 可以用 window.requestAnimationFrame() 代替,会有更好的渲染效果。 我们demo使用列表做的,实际对于列表来说,最佳方案是虚拟列表,
失效原因
1 .在react的event handler内部多次setState会被batch为一次更新
2 .但是在一个异步的事件循环里面多次setState,react不会batch
3 .可以使用React.unstable_batchedUpdates来强制batch
fn3 = () => {
// 模拟一个异步操作,真实业务里面可能是网络请求等
setTimeout(
unstable_batchedUpdates(() => {
this.setState({ a: Math.random() });
this.setState({ a: Math.random() });
}),
0
);
};
4 .原因 简单来解释,React 的更新是基于 Transaction(事务)的,Transacation 就是给目标执行的函数包裹一下,加上前置和后置的 hook (有点类似 koa 的 middleware),在开始执行之前先执行 initialize hook,结束之后再执行 close hook,这样搭配上 isBatchingUpdates 这样的布尔标志位就可以实现一整个函数调用栈内的多次 setState 全部入 pending 队列,结束后统一 apply 了。
但是 setTimeout 这样的方法执行是脱离了事务的,react 管控不到,所以就没法 batch 了。
5 .vue实现:是因为 vue 采用了 nexttick 的方式,利用 EventLoop,将一个同步事件循环过程中所有修改合并,它本质上属于延迟的批量更新
6 .如果有用react-redux可以用batch代替unstable_batchedUpdates。batch底层还是调用的unstable_batchedUpdates。
import { batch } from 'react-redux';
useMemo,.useCallback
1 .如果传给子组件的派生状态或函数,每次都是新的引用,那么 PureComponent 和 React.memo 优化就会失效。所以需要使用 useMemo 和 useCallback 来生成稳定值,并结合 PureComponent 或 React.memo 避免子组件重新 Render
发布者订阅者模式跳过中间组件的渲染阶段
1 .公共数据放在reedux等库上面,只在用到的时候在取,而不需要经过不用他的中间组件传递值来浪费资源,也就是中间的组件没有用到数据,只是起了一个传递的作用,这里是不需要的
防抖函数
懒加载
懒渲染
1 .懒渲染指当组件进入或即将进入可视区域时才渲染组件。常见的组件 Modal/Drawer 等,当 visible 属性为 true 时才渲染组件内容,也可以认为是懒渲染的一种实现
2 .中判断组件是否出现在可视区域内是通过 react-visibility-observer[30]
动画库直接修改DOM,跳过组件Render
1 .react-spring的动画实现,当一个动画启动后,每次动画属性改变不会引起组件重新 Render ,而是直接修改了 dom 上相关属性值
2 .
避免在DIdMount,didUpdate中更新State
1 .所有提交阶段的钩子里面不要进行setState
2 .在提交阶段更新组件的State,会再次触发组件的更新流程,造成两倍耗时.一般有以下场景
1 .计算并更新组件的派生状态.
2 .useLayoutEffect()函数
React Profiler定位render过程瓶颈
1 .开启「记录组件更新原因」
2 .点击面板上的齿轮,然后勾选「Record why each component rendered while profiling.」,如下图。
3 .然后点击面板中的虚拟 DOM 节点,右侧便会展示该组件重新 Render 的原因。