React 应用性能优化的 6 条建议

原文地址: itnext.io/6-tips-for-…
译文地址:github.com/xiao-T/note…
本文版权归原做者全部,翻译仅用于学习。html


我第一次学习 React 时,就知道了全部的能够提升性能的小技巧。直到如今,主要的性能优化手段就是避免协调(React 经过先后的对比来决定 DOM 是否须要更新)。react

这篇文章中,我将会列举几个简单的方法,经过简单的开发技巧提高 React 应用的性能。这并不意味着你应该一直使用这些技术,可是,知道这些老是有好处的。git

因此,咱们开始:github

1. 利用渲染 bail-out 技术

父级组件每次更新,无论子组件的 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>   
  }
}
复制代码

2. 避免使用内联对象

对于内联对象,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} />  
}
复制代码

3. 避免匿名函数

虽然,经过 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} />
  }
}
复制代码

4. 那些非必要的内容能够懒加载

这条看起来和本文没多大关系,可是,减小 React 组件的大小,能够更快的显示它们。所以,若是,你以为某些内容不必初始化渲染,在初始还完成后,你能够根据须要再去加载它们。同时,也会减小应用启动时文件的大小,让应用加载更快。最后,经过拆分初始化的文件,能够把大型的工做量拆分红多个小任务,以便浏览器更快的响应。利用 React.LazyReact.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> ) } 复制代码

5. 调整 CSS 避免组件强制 mount & unmount

渲染的成本很高,尤为是 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>
  )
}
复制代码

6. 缓存那些成本巨大的计算

渲染老是不可避免的,可是,因为 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 的性能,释放主线程,最终让用户感受应用响应更快。

感谢阅读:)

相关文章
相关标签/搜索