react渲染性能优化

178 阅读4分钟

useMemo

作用:它在每次重新渲染的时候能够缓存计算的结果

看个场景

下面我们的本来的用意是想基于count的变化计算斐波那契数列之和,但是当我们修改num状态的时候,斐波那契求和函数也会被执行,显然是一种浪费

// useMemo
// 作用:在组件渲染时缓存计算的结果import { useState } from 'react'function factorialOf(n) {
  console.log('斐波那契函数执行了')
  return n <= 0 ? 1 : n * factorialOf(n - 1)
}
​
function App() {
  const [count, setCount] = useState(0)
  // 计算斐波那契之和
  const sumByCount = factorialOf(count)
​
  const [num, setNum] = useState(0)
​
  return (
    <>
      {sum}
      <button onClick={() => setCount(count + 1)}>+count:{count}</button>
      <button onClick={() => setNum(num + 1)}>+num:{num}</button>
    </>
  )
}
​
export default App

useMemo缓存计算结果

思路: 只有count发生变化时才重新进行计算

import { useMemo, useState } from 'react'function fib (n) {
  console.log('计算函数执行了')
  if (n < 3) return 1
  return fib(n - 2) + fib(n - 1)
}
​
function App() {
  const [count, setCount] = useState(0)
  // 计算斐波那契之和
  // const sum = fib(count)
  // 通过useMemo缓存计算结果,只有count发生变化时才重新计算
  const sum = useMemo(() => {
    return fib(count)
  }, [count])
​
  const [num, setNum] = useState(0)
​
  return (
    <>
      {sum}
      <button onClick={() => setCount(count + 1)}>+count:{count}</button>
      <button onClick={() => setNum(num + 1)}>+num:{num}</button>
    </>
  )
}
​
export default App

React.memo

作用:允许组件在props没有改变的情况下跳过重新渲染

组件默认的渲染机制

默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染

// memo
// 作用:允许组件在props没有改变的情况下跳过重新渲染import { useState } from 'react'function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is son</div>
}
​
function App() {
  const [, forceUpdate] = useState()
  console.log('父组件重新渲染了')
  return (
    <>
      <Son />
      <button onClick={() => forceUpdate(Math.random())}>update</button>
    </>
  )
}
​
export default App

使用React.memo优化

机制:只有props发生变化时才重新渲染 下面的子组件通过 memo 进行包裹之后,返回一个新的组件MemoSon, 只有传给MemoSon的props参数发生变化时才会重新渲染

import React, { useState } from 'react'const MemoSon = React.memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
})
​
function App() {
  const [, forceUpdate] = useState()
  console.log('父组件重新渲染了')
  return (
    <>
      <MemoSon />
      <button onClick={() => forceUpdate(Math.random())}>update</button>
    </>
  )
}
​
export default App

props变化重新渲染

import React, { useState } from 'react'const MemoSon = React.memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
})
​
function App() {
  console.log('父组件重新渲染了')
​
  const [count, setCount] = useState(0)
  return (
    <>
      <MemoSon count={count} />
      <button onClick={() => setCount(count + 1)}>+{count}</button>
    </>
  )
}
​
export default App

props的比较机制

对于props的比较,进行的是‘浅比较’,底层使用 Object.is 进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,React并不关心对象中的具体属性

1.传递个简单类型的prop prop变化时组件重新渲染 2.传递一个引用类型的prop 比较的是新值和旧值的引用是否相等 当父组件的函数重新执行时,实际上形成的是新的数组引用

简单类型

image.png

<MemoSon count={num}> 子组件不会重新渲染 ;

<MemoSon count={count}> 子组件重新渲染

复杂类型

import React, { useState } from 'react'const MemoSon = React.memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
})
​
function App() {
  // const [count, setCount] = useState(0)
  const [list, setList] = useState([1, 2, 3])
  //const list = useMemo(()=>{
  //    return [1,2,3]
  //},[]) // 保证引用稳定-> useMemo 组件渲染的过程中缓存一个值 子组件不会重新渲染
  return (
    <>
      <MemoSon list={list} />
      <button onClick={() => setList([1, 2, 3])}>
        {JSON.stringify(list)}
      </button>
    </>
  )
}
​
export default App

说明:虽然俩次的list状态都是 [1,2,3] , 但是因为组件App俩次渲染生成了不同的对象引用list,所以传给MemoSon组件的props视为不同,子组件就会发生重新渲染

useCallback缓存函数

当给子组件传递一个引用类型prop的时候,即使我们使用了memo 函数依旧无法阻止子组件的渲染,其实传递prop的时候,往往传递一个回调函数更为常见,比如实现子传父,此时如果想要避免子组件渲染,可以使用 useCallback缓存回调函数

useCallback缓存之后的函数可以在组件渲染时保持引用稳定,也就是返回同一个引用

// useCallBackimport { memo, useCallback, useState } from 'react'const Input = memo(function Input({onChange}) {
  console.log('Son组件渲染了')
  return <input type="text" onChange={(e) => onChange(e.target.value)} />
})
​
function App() {
    //传给子组件的函数
    const changeHandler = useCallback((value) => console.log(value), [])
    //触发父组件重新渲染的函数
    const [ count, setCount] = useState(0)
​
  return (
    <div className = "App">
          {/* 把函数作为prop传给子组件 */}
      <Input onChange={changeHandler} />
      <button onClick={() => setCount(count+1)}>{count}</button>
    </div>
  )
}
​
export default App