共计 3737 个字符,预计需要花费 10 分钟才能阅读完成。
困惑
写过 react 我的项目的同学,都经验过 react 性能优化,个别的办法是缓存某些计算,或者应用 purecomponent、memo 等办法缩小不必要的组件渲染,以及应用 context 代替 props 透传等,上面探讨一种能从开始开发我的项目时就能保障页面性能的办法。
罕用弊病办法
1.props 透传
大多数的我的项目堆砌 react 代码时,都采纳 props 一层层传递的形式,拼凑出一大堆组件代码,等到发现性能问题时再去查找瓶颈的起因。
任意地位父节点的渲染都会导致子孙节点的渲染,这些渲染有必要的也有非必要的,非必要的渲染就是导致性能危机的中央。
叶子节点的渲染可能依赖于某个 props,这就须要 props 传递不被阻断,性能很难优化。
2.pureComponent、shouldComponentUpdate
这两种形式能够用来比照 props 的变动,即便先人渲染了而本人的 props 非扭转时就不渲染,节俭性能,但这样也带来了其余的问题
2.1diff 性能损失
这两种形式是通过 diff 比拟来判断 props 是否变动的,而有些状况下因为写法等起因,props 是必然变动的,就导致了自觉的应用不仅没有带来性能晋升,反而多出了一部分 diff 计算工夫
2.2 叶子节点
如果用在某个父元素上,即便这个父元素不须要渲染,然而前面的子孙节点须要基于某个 props 来扭转渲染,所以也必然要通过这个父元素的从新渲染来达到目标
总而言之,应用这两种办法并不能准确管制所有的节点渲染必要性,可能使性能优化变得更加辣手
3.memo
应用 hooks 的同学只能通过 memo 办法来判断组件是否须要渲染,应用 memo 在简单场景下须要第二个参数的判断,弊病与下面相似,而且还会遇到一些该渲染而未渲染的坑点
4.useCallback、useMemo
配合 memo 等 diff 计算非必要渲染的伎俩,将 props 缓存起来,就是下面提到的 pureComponent 在有些状况下性能会变的更糟,起因就是在写法上没有缓存的话,就会节约 diff 计算工夫,例如
<Input onChange={e=>{setValue(e.target.value)}} />
因为 onchange 的属性是个匿名函数,每次组件渲染时,input 传入的 props 都会变,就会导致 memo、pureComponent 等优化生效。所以请将传入组件的 props 应用 useCallback 或 useMemo 包裹起来,让 props 非必要不扭转。
然而在 antd 组件中,没有应用 memo 等包裹组件,即便传入 antd 的 props 应用了 useCallback、useMemo,也不能带来性能晋升。
然而咱们本人开发时封装的组件是须要做性能优化的,在传入这类组件的 props 时,须要应用 useCallback 或 useMemo 包装。
5.context
context 办法是比较简单的性能优化策略,大量缩小 props 的透传,再配合 useContext 的应用,写法变得十分简洁。
然而 context 也没法做到准确管制渲染的必要性,因为组件订阅了 context 后只有 context 中某个值发生变化,即便没有应用这个值也会导致组件从新渲染。
6. 解决办法
6.1 应用 rxjs 准确管制组件渲染机会
先不理解 rxjs 具体的概念,把 rxjs 当做是 eventEmitters 订阅工具,先手写两个办法 eventInput(输出)、eventOutput(输入),并且把这两个办法放到 context 中,所有想要订阅 eventOutput 的组件,就应用 useContext 获取并且监听 eventOutput 事件,因为 eventInput、eventOutput 都是动态不变的函数,这就保障了 context 中的 value 不会变动(context 不寄存变动的 value 值),变动的始终是 event 中的流。达到的成果就是准确管制任意 想变动 的组件。
上图中,子孙节点也能管制任意地位的先人节点的渲染,而不扭转其余组件的渲染
6.2memo 包裹
父组件从新渲染必然导致后续子组件的渲染,所以应用 React.memo 包裹每个组件。
这里有个准则就是组件之间尽量不传递 props,只应用 rxjs 订阅须要的值。
这样就保障了能够放心使用 memo,而不放心是否有多余的 diff 和未知的坑点。所有组件没有 props 传递,只关怀本人订阅的值,只有本人订阅的值扭转了才去渲染,做到手术刀式的管制渲染。
6.3 副作用拆散出 ui
eventInput、eventOutput 从输出到输入,两头是能够设定过程的,把 ui 中的所有副作用或计算都能够放到这些过程中去,既能够保障 ui 文件体积减小,也能够让关注点拆散,ui 只做跟渲染无关的事件。举个例子:
--a.tsx
eventInput({id:1})
--hooks.ts
eventOutput.pipe(({id})=>{const res = await axios.get(id)
const colors = res.map(v=>v.color)
return colors
})
--b.tsx
eventOutput.sub(colors=>{setState(colors)
})
7. 活生生的例子
provider.jsx
import {useFetchResult,usePeriodChange} from 'useData.js'
export const context = React.createContext(({}))
const Provider = ({children}) => {const { fetchResultInput$,fetchResultOutput$} = useFetchResult()
const {periodChangeInput$} = usePeriodChange(fetchResultInput$)
return (
<context.Provider
value={{
fetchResultOutput$,
periodChangeInput$,
}}
>
{children}
</context.Provider>
)
}
export default Provider
useData.js
export const usePeriodChange = (fetchResultInput$) => {const {periodChangeInput$} = useMemo(() => {const periodChangeInput$ = new Subject()
return {periodChangeInput$}
}, [])
useEffect(()=>{
const sub = periodChangeInput$.subscribe(period=>{const params = {...period,appid:123}
fetchResultInput$.next(params)
})
return ()=>sub()
},[fetchResultInput$,periodChangeInput$])
return {periodChangeInput$}
}
export const useFetchResult = (fetchResultInput$) => {return useMemo(() => {const fetchResultInput$ = new Subject()
const fetchResultOutput$ = fetchResultInput$.pipe(
switchMap(params=>{return axios.get('/',params)
}),
map(res=>{
...
calc(res)
...
return result
})
)
return {fetchResultInput$, fetchResultOutput$}
}, [])
}
index.jsx
import Provider from './provider.jsx'
export default () => (
<Provider>
<Panel />
</Provider>
)
panel.jsx
export default () => (
<>
<Period />
<Table />
</>
)
preiod.jsx
export default () => {const {periodChangeInput$} = useContext(context)
const onChange = useCallback((period)=>{periodChangeInput$.next(period)
},[periodChangeInput$])
return <Select onChange={onChange} />
}
table.jsx
export default () => {const [data,setData] = useState([])
const {fetchResultOutput$} = useContext(context)
useEffect(()=>{const sub = fetchResultOutput$.subscribe(data=>setData(data))
return ()=>sub()
},[fetchResultOutput$])
return <Table data={data} />
}