likes
comments
collection
share

五个避免 React 组件重渲染的方法

作者站长头像
站长
· 阅读数 22

原文地址:medium.com/bitsrc/5-wa…

原文作者:Chameera Dulanga

React 组件从开始到现在已经发展了很长一段时间。虽然如此,许多开发者发现还是很难去减少不必要的重渲染。但实际上是有很多方法去避免这个问题的。

在这篇文档,我会讨论 5 个方法去避免 React 组件中不必要的重渲染。

使用 useMemo() 和 useCallback() 钩子记忆

这个记忆可以使得组件仅在 props 数据发生变化时才进行重渲染。通过这个技巧,开发者可以避免无用的渲染并减少应用程序中的计算负载。

react 提供了两个钩子去实现记忆:

  • useMemo()
  • useCallback()

在输入相同的情况下,无需重新计算这些钩子,通过缓存并返回相同的结果来减少重渲染。当输入有变化时,缓存就会失效并渲染新的组件状态。

useMemo()

为了更好的理解如何使用 useMemo() 钩子,让我们来看下两数相乘的一个例子:

const multiply = (x,y) => {
  return x*y
}

上面的这个方法不管输入是什么,每次被调用时都会计算结果并重新渲染组件。但是,如果我们使用了 useMemo() 钩子,当输入是相同的情况下且结果被保存在缓存中时,我们可以避免组件的重新渲染。

const cachedValue = useMemo(() => multiply(x, y), [x, y])

现在,计算的结果会被保存在 cachedValue 变量中,并且除非输入发生了变化 useMemo() 钩子每次都会返回这个结果。

useCallback()

useCallback() 是另一个实现记忆的 react 钩子。但是,和 useMemo() 不同,这个钩子不缓存结果。相反,它记住的是所提供给它的回调函数。

例如,考虑一个具有可点击项目列表的组件。

import { useCallback } from 'react';
export function MyParent({ item }) {
  const onClick = useCallback(event => {
    console.log('Clicked Item : ', event.currentTarget);
  }, [item]);
  return (
    <Listitem={item} onClick={onClick}
    />
  );
}

在上面这个例子中,useCallback() 记住了 onClick 的回调。因此,当用户重复点击相同的项目时,不会导致组件的重新渲染。

使用 React Query 来优化 API 调用

在 React 应用中我们经常会使用 useEffect()钩子用来做异步获取数据的操作。但是,在每次渲染时useEffect() 都会执行获取数据,并且在大多数情况下,它一直加载相同的数据。

作为解决方案,我们可以使用 React Query 库去缓存响应的数据。当我们调用 API 时,React Query 会在请求之前先从缓存中返回数据。然后,它会从服务端检索数据,如果发现没有可用的新数据,它将阻止组件的重渲染。

import React from 'react'
import {useQuery} from 'react-query'
import axios from 'axios'

async function fetchArticles(){
  const {data} = await axios.get(URL)    
  return data
}

function Articles(){
  const {data, error, isError, isLoading } = useQuery('articles', fetchArticles)
 
  if(isLoading){
    return <div>Loading...</div>
  }
  if(isError){
    return <div>Error! {error.message}</div>
  }
  return(
    <div>
      ...
    </div>
  )
}
export default Articles

React Query 库目前在 NPM 上的每周下载量超过 60 万次,并且在 Githhub 上有 1.3k+ 的 star。

使用 Reselect 创建有记忆能力的 Selector

Reselect 是一个用于创建有记忆能力 selector 的三方包。它通常会与 Redux 的 store 搭配使用,并且它能很好的减少不必要的重渲染。

  • Reselect selectors 能够计算派生数据
  • Reselect selectors 不会重新计算除非传入的参数变化了
  • 它们可以作为其他 selector 的输入

Reselect 提供一个名叫 createSelector 的 api,这个 api 可以生成具有记忆能力的函数。为了更好的理解,可以看下面给出的这个例子。

import { createSelector } from 'reselect' 
...
const selectValue = createSelector(
  state => state.values.value1,
  state => state.values.value2,
  (value1, value2) => value1 + value2
)
...

在这个例子中 createSelector 将两个 selector 作为输入,然后返回一个具有记忆能力的版本。在更改值之前,不会使用这个记忆版本再次计算 selector。

Reselect 库目前在 NPM 上的每周下载量超过两百万次,并且在 Githhub 上有 18.4k+ 的 star。

使用 useRef() 代替 useState()

在 React 应用中useState()钩子被广泛的用于当状态发生变化时重渲染组件。但是,在一些场景中我们需要在不重新渲染组件的情况下,跟踪状态的变化。

但是,如果我们使用 useState() 钩子,我们不能达到跟踪状态变化而不触发组件的重渲染。

function App() {
  const [toggle, setToggle] = React.useState(false)
  const counter = React.useRef(0)
  console.log(counter.current++)
  return (
    <button onClick={() => setToggle(toggle => !toggle)} > 
      Click 
    </button>
  )
}
ReactDOM.render(<React.StrictMode><App /></React.StrictMode>, document.getElementById('mydiv'))

在上面这个例子中,每当值发生变化时,都会导致组件的重渲染。但是 counter 计算器保留了它的值,因为它是一个可变的标识。由于我们使用的是 useRef(),它只会引起单个渲染。但是,如果我们使用的是 useState(),在每次切换时它都会导致两次渲染。

使用 React Fragments

如果你以前使用过 React,你就会知道 React 需要在组件外用单个父元素包裹组件。虽然这并不会导致重渲染,但你知道这会影响到整体组件的渲染时间吗?

作为解决方案,可以使用 React Fragment 来包裹组件,它会减少 DOM 的负载,进而加快渲染时间,并减少内存的使用。

const App= () => {
  return (
    <React.Fragment><p>Hello<p/><p>World<p/></React.Fragment>
  );
};

总结

在这篇文章中,我讨论了 5 个不同的方法用于在 React 组件中阻止不必要的重渲染。大多数解决方案都是通过缓存,你也可以使用内置的 React 钩子或者三方库来实现它们。

此外,这些方法也能提升你的应用性能,以防止不必要的重渲染,同时减少内存开销。