原文地址: itnext.io/6-tips-for-…
译文地址:github.com/xiao-T/note…
本文版权归原做者全部,翻译仅用于学习。html
我第一次学习 React 时,就知道了全部的能够提升性能的小技巧。直到如今,主要的性能优化手段就是避免协调(React 经过先后的对比来决定 DOM 是否须要更新)。react
这篇文章中,我将会列举几个简单的方法,经过简单的开发技巧提高 React 应用的性能。这并不意味着你应该一直使用这些技术,可是,知道这些老是有好处的。git
因此,咱们开始:github
父级组件每次更新,无论子组件的 props 有没有改变,它们都会随着更新。也就是说,即便子组件的 props 与以前的彻底一致,它们仍是会从新渲染。须要说明一下,我在这说的从新渲染,并非更新 DOM,而是会触发 React 的协调动做,而后来决定是否真正的更新 DOM。这个过程对性能优化尤其重要,尤为是那些大型组件树,底层上,React 不得不运行 diff 算法来检查组件树先后是否有不同的地方。web
能够继承 React.PureComponent
(利用 shouldComponentUpdate
实现的)class 来实现组件或者用高阶组件 memo
来包装你的组件。利用这些方法,你能够保证在组件 props 改变时才会更新。算法
须要注意的是:若是在比较小的组件中应用这些技术(就像下面演示的同样),将看不到有什么好处,反而会让你的应用变得有点慢(这是由于每次渲染 React 都会作一次组件的浅对比)。所以,对于那些“复杂”的组件可使用这些技术,相反,一些比较轻量的组件就须要慎重使用。数组
TLDR: 对于那些“复杂”的组件使用 React.PureComponent
, shouldComponentUpdate
或者 memo()
,可是,对于一些轻量的组件就没有必要了。若是有须要,能够把一个大型组件拆分红多个小组件,以便用 memo()
来包装。浏览器
// index.jsx
export default function ParentComponent(props) {
return (
<div>
<SomeComponent someProp={props.somePropValue}
<div>
<AnotherComponent someOtherProp={props.someOtherPropValue} />
</div>
</div>
)
}
// ./SomeComponent.jsx
export default function SomeComponent(props) {
return (
<div>{props.someProp}</div>
)
}
// --------------------------------------------
// ./AnotherComponent.jsx (1st variation)
// This component will render anytime the value of `props.somePropValue` changed
// regardless of whether the value of `props.someOtherPropValue` has changed
export default function AnotherComponent(props) {
return (
<div>{props.someOtherProp}</div>
)
}
// ./AnotherComponent.jsx (2nd variation)
// This component will render only render when its *own* props have changed
export default memo((props) => {
return (
<div>{props.someOtherProp}</div>
)
});
// ./AnotherComponent.jsx (3rd variation)
// This component will also render only render when its *own* props have changed
class AnotherComponent extends React.PureComponent {
render() {
return <div>{this.props.someOtherProp}</div>
}
}
// ./AnotherComponent.jsx (4th variation)
// Same as above, re-written
class AnotherComponent extends React.PureComponent {
shouldComponentUpdate(nextProps) {
return this.props !== nextProps
}
render() {
return <div>{this.props.someOtherProp}</div>
}
}
复制代码
对于内联对象,React 在每次渲染都会从新建立新的引用。这会致使组件每次都认为这是新的对象。所以,组件每次渲染时对比先后 props
都会返回 false
。缓存
对于不少人来讲内联样式就是一种间接引用。组件经过 prop 内联 styles
将会致使组件每次都会从新渲染(除非你自定义 shouldComponentUpdate
方法),这也会有潜在的性能问题,具体取决于组件内部是否有多个子组件。性能优化
若是,不得不使用不一样引用,有一个小技巧。好比,可使用 ES6 的扩展运算符传递多个 props 的内容。只要对象的内容是原始值(不是函数、对象或者数组)或者非原始值的“固定”引用,你均可以把它们包装成一个 prop 传递,而不是做为单独的 prop 传递。利用这种技巧可让组件在从新渲染时经过对比先后 props 带来 bail-out 的好处。
TLDR:若是,使用内联样式(或者通常的对象),你将会没法从 React.PureComponent
或者 memo()
获益。在某些状况下,你能够把须要传递的内容合并成一个对象,做为组件的 props 向下传递。
// Don't do this!
function Component(props) {
const aProp = { someProp: 'someValue' }
return <AnotherComponent style={{ margin: 0 }} aProp={aProp} />
}
// Do this instead :)
const styles = { margin: 0 };
function Component(props) {
const aProp = { someProp: 'someValue' }
return <AnotherComponent style={styles} {...aProp} />
}
复制代码
虽然,经过 prop 传递函数时匿名函数是一种很是好的方式(特别是须要其它props 做为参数调用时),可是,组件每次渲染都会获得不一样的引用。这有点上面提到的内联样式。为了保证传递给 React 组件的方法都是同一个引用,你能够在 class 中定义方法(若是,你用的基于 class 的组件)或者使用 useCallback
保证引用的一致(若是,你是用函数组件)。某些状况下,若是,你须要为函数提供不一样的参数(好比:.map
的回调函数),你能够利用 memoize 来包装函数(就像 lodash’s memoize)。这种行为称为“函数缓存”或者“监听缓存”,它能够利用浏览器内存动态保存多个函数的固定引用。
固然,有些时候内联函数比较方便,并且,也不会引发性能问题。这多是你在一些“轻量”组件上使用或者父组件每次 props 改变都须要从新渲染(所以,你不须要关心组件每次渲染时函数的引用是否是有变化)。
最后,有一件事我须要强调下:默认状况下 render-props 函数也是匿名函数。每当,把函数做为 children
组件时,你均可以在外部定义一个组件来代替这个函数,这样会保证引用的惟一性。
TLDR:尽量使用 useCallback
来绑定 props 方法,这样你就能够经过 bail-out 受益。这也适用于 render-props 返回的函数。
// Don't do this!
function Component(props) {
return <AnotherComponent onChange={() => props.callback(props.id)} />
}
// Do this instead :)
function Component(props) {
const handleChange = useCallback(() => props.callback(props.id), [props.id]);
return <AnotherComponent onChange={handleChange} />
}
// Or this for class-based components :)
class Component extends React.Component {
handleChange = () => {
this.props.callback(this.props.id)
}
render() {
return <AnotherComponent onChange={this.handleChange} />
}
}
复制代码
这条看起来和本文没多大关系,可是,减小 React 组件的大小,能够更快的显示它们。所以,若是,你以为某些内容不必初始化渲染,在初始还完成后,你能够根据须要再去加载它们。同时,也会减小应用启动时文件的大小,让应用加载更快。最后,经过拆分初始化的文件,能够把大型的工做量拆分红多个小任务,以便浏览器更快的响应。利用 React.Lazy
和 React.Suspense
能够轻松的实现文件的拆分。
TLDR: 对于那些不是实时可见的(或者没必要要),直到和用户交互后才可见的组件,能够懒加载。
// ./Tooltip.jsx
const MUITooltip = React.lazy(() => import('@material-ui/core/Tooltip'));
function Tooltip({ children, title }) {
return (
<React.Suspense fallback={children}> <MUITooltip title={title}> {children} </MUITooltip> </React.Suspense> ); } // ./Component.jsx function Component(props) { return ( <Tooltip title={props.title}> <AnotherComponent /> </Tooltip> ) } 复制代码
渲染的成本很高,尤为是 DOM 须要改变时。某些时候每次只须要显示某一组内容,好比:类型手风琴或者 tab 功能,你须要临时的 unmount 那些不可见的组件,同时,mount 可见的组件。
若是,组件的 mount/unmount 成本很高,那么这个操做可能会致使交互的延迟。对于这种状况,比较好作法是:能够经过 CSS 先隐藏组件,可是保证 DOM 存在。我意识到有些时候这并不可能,若是,同时有多个组件 mount 会带来一些问题(好比:多个组件之间共享同一个分页组件时),可是,对于其它状况,你应该选择使用刚才提到的方法。
另外,将 opacity
设置为 0 浏览器的成本**几乎为 0 **(由于,并不会引发回流),所以,尽量的不去改变 visibility
& display
。
TLDR:相比经过 unmount 隐藏组件,有时经过 CSS 隐藏组件更加好。对于那些须要花费更多时间 mount/unmount 的大型组件更加有利。
// Avoid this is the components are too "heavy" to mount/unmount
function Component(props) {
const [view, setView] = useState('view1');
return view === 'view1' ? <SomeComponent /> : <AnotherComponent />
}
// Do this instead if you' re opting for speed & performance gains
const visibleStyles = { opacity: 1 };
const hiddenStyles = { opacity: 0 };
function Component(props) {
const [view, setView] = useState('view1');
return (
<React.Fragment>
<SomeComponent style={view === 'view1' ? visibleStyles : hiddenStyles}>
<AnotherComponent style={view !== 'view1' ? visibleStyles : hiddenStyles}>
</React.Fragment>
)
}
复制代码
渲染老是不可避免的,可是,因为 React 组件是功能型组件,每次渲染组件内部的计算都会从新计算。使用 useMemo
hook 能够把那些不须要从新计算的值“缓存”起来。这样一来,那些成本巨大的计算能够利用上次渲染时的值。在这能够学到更多有关知识。
总的来讲,就是要减小组件在渲染期间的 JavaScript 的工做量,所以,主线程就不会阻塞。
TLDR:利用 useMemo
缓存那些成本巨大的计算
// don't do this!
function Component(props) {
const someProp = heavyCalculation(props.item);
return <AnotherComponent someProp={someProp} />
}
// do this instead. Now `someProp` will be recalculated
// only when `props.item` changes
function Component(props) {
const someProp = useMemo(() => heavyCalculation(props.item), [props.item]);
return <AnotherComponent someProp={someProp} />
}
复制代码
我有意的没有提到一些事情,好比:“使用生产环境构建”、“对键盘事件节流”或者“使用 web workers”,这是由于,我认为这些和 React 并无什么关系,它们更可能是与通常的 web 开发性能息息相关。这篇文章中提到的开发实践更多的是有助于提高 React 的性能,释放主线程,最终让用户感受应用响应更快。
感谢阅读:)