React Hooks 学习 - 03 useMemo、React.memo、useCallback、useRef

useMemo 钩子函数

useMemo 的行为类似 Vue 中的计算属性,可以监测某个数据的变化,根据变化值计算新值,计算出的新值可以参与视图渲染。

useMemo会缓存计算结果,如果监测值没有发生变化,即使组件重新渲染,也不会重新计算,此行为有助于避免在每个渲染上进行昂贵的计算。

使用

useMemo 接收一个计算函数和依赖项数组。

  • 计算函数:当监听的数据发生变化,执行这个函数,函数返回的值就是计算的新值
  • 依赖项数组:需要监听的数据
    • 如果不传,则会在每次渲染时执行计算函数
    • 如果传一个空数组则仅会在初始加载时执行一次

useMemo 返回的值就是计算函数返回的值。

import {
      useState, useMemo } from 'react'

function App() {
     
  const [count, setCount] = useState(0)

  const result = useMemo(() => {
     
    console.log('测试在修改 bool 时是否会重新计算')
    return count * 2
  }, [count])

  const [bool, setBool] = useState(true)

  return (
    <div>
      <span>{
     result}</span>
      <span>{
     count}</span>
      <button onClick={
     () => setCount(count+1)}>+1</button>
      <br/>
      <span>{
     bool ? '真' : '假'}</span>
      <button onClick={
     () => setBool(!bool)}>改变Bool</button>
    </div>
  );
}

export default App;

React.memo 方法

介绍使用

memo 方法是用于性能优化的高阶组件。

如果组件的 props 没有发生变化,可以阻止组件重新渲染,并直接复用最近一次渲染的结果。

import React, {
      memo, useState } from 'react'

/*
// count 每次变化都会渲染 Foo 组件
function Foo() {
  console.log('Foo 组件重新渲染了')
  return (
    
Foo 组件
) } */
// count 变化不会重新渲染 Foo 组件 const Foo = memo(function() { console.log('Foo 组件重新渲染了') return ( <div>Foo 组件</div> ) }) function App() { const [count, setCount] = useState(0) return ( <div> <span>{ count}</span> <button onClick={ () => setCount(count+1)}>+1</button> <Foo /> </div> ); } export default App;

与类组件的 shouldComponentUpdata 和 PureComponent 的区别

相同点

  • 场景:当页面数据发生改变(包括对象重新赋值导致的引用地址的改变)的时候,都会导致页面(包括组件)重新渲染,非常消耗性能。
  • 作用:当页面数据发生变化时,可以对比新旧数据,如果数据的值没有发生变化,或子组件没有依赖发生变化的数据,则可以避免组件或页面重新渲染。
  • 都是浅对比

不同点

  • shouldComponentUpdata
    • 生命周期函数,只能在类组件中使用
    • 对比数据类型:state 和 props
    • 可以自定义对比逻辑
    • 函数返回 true 组件重新渲染,返回 false 不渲染
  • React.PureComponent
    • 组件继承类
    • 继承了 PureComponent 的类组件不能使用 shouldComponentUpdata
    • 对比数据类型:state 和 props
    • 自带对比逻辑,相当于默认定义了 shouldComponentUpdata,但不能自定义对比逻辑
  • React.memo
    • 高阶组件,只能在函数型组件中使用
    • 对比数据类型:props
    • 第二个参数接收一个函数,定义对比逻辑,返回 true 不重新渲染,返回 false 重新渲染

useCallback 钩子函数

useCallback 也是用于性能优化,它可以缓存函数,使组件重新渲染时得到相同的函数实例。

向组件传递方法

import {
      useState, memo } from 'react'

const Foo = memo(function(props) {
     
  console.log('Foo 组件重新渲染了')
  return (
    <div>
      <button onClick={
     () => props.resetCount()}>重置Count</button>
    </div>
  )
})

function App() {
     
  const [count, setCount] = useState(0)

  const resetCount = () => {
     
    setCount(0)
  }

  return <div>
    <span>{
     count}</span>
    <button onClick={
     () => setCount(count+1)}>+ 1</button>
    <Foo resetCount={
     resetCount} />
  </div>
}

export default App

App 组件在 count 更新时就会重新渲染(App 函数就会重新执行),resetCount 也会重新定义,与之前传递给 Foo 的函数实例不一样,所以 Foo 就会重新下渲染。

结果就是,Foo 组件总会在未使用到的 count 的值变化时重新渲染。

缓存函数

为了避免这种不必要的重复渲染,可以使用 useCallbackresetCount 方法缓存下来,在 App 重新渲染时,获取缓存中的 resetCount 方法传递给 Foo 组件,由于缓存中的 resetCount (引用地址)未发生变化,所以 Foo 组件不会被额外渲染。

useCallback 同样可以接受一个依赖项数组作为第二个参数:

  • 如果不传,则在每次组件渲染时重新定义函数(相当于没有使用 useCallback)
  • 如果数组不为空,组件渲染时会对比数组,如果数组发生了变化则重新定义函数
  • 如果数组为空,则只会在组件挂载时执行一次。
const resetCount = useCallback(() => {
     
  setCount(0)
}, [])

const resetCount = useCallback(() => {
     
  setCount(0)
}, [setCount])

useRef 钩子函数

useRef 有两个功能:

  • 获取 DOM 元素对象
  • 保存跨组件周期的数据

获取 DOM 元素对象

useRef(initial) 会返回一个可变的 ref 对象。该对象只有一个 current 属性,初始值时 initial。

当把 ref 对象传递给组件或元素的 ref 属性后,ref 对象的 current 就指向该 DOM 元素对象。

节点变化时只会改变 ref 对象的 current 属性,不会触发组件重新渲染。

函数型组件使用 useRef

import {
      useRef } from 'react'

function App() {
     
  const box = useRef()

  return <div>
    <button onClick={
     () => console.log(box)}>获取 DIV</button>
  </div>
}

export default App

类组件使用 createRef:

import React from 'react'

class App extends React.Component {
     
  constructor(props) {
     
    super(props)
    this.box = React.createRef()
  }

  render() {
     
    return <div ref={
     this.box}>
      <button onClick={
     () => console.log(this.box)}>获取 DIV</button>
    </div>
  }
}

export default App

保存数据(跨组件周期)

useRef 返回的ref 对象在组建的整个生命周期内保持不变,每次重新渲染,都返回同一个 ref 对象。

ref 对象的 current 属性变化,并不会引发组件重新渲染。

本质上,这个 ref 对象就像是可以在其 .current 属性中保存一个可以变值的“盒子”。

所以 useRef 还可以用于保存跨组件周期的数据:

  • 即使组件重新渲染,保存的数据仍然还在。
  • 保存的数据被更改,也不会触发组件重新渲染。

useState 保存的数据的区别:useState 保存的是状态数据,当状态发生变化,会触发组件重新渲染。

停止计时器案例

在副作用函数里创建计时器,定时修改状态,点击按钮停止计时器。

下面的方式不会成功:

import {
      useState, useEffect } from 'react'

function App() {
     
  const [count, setCount] = useState(0)

  // 组件重新渲染 timerId 就会被重置
  let timerId = null

  useEffect(() => {
     
    timerId = setInterval(() => {
     
      setCount(count => count + 1)
    }, 1000)
  }, [])

  const stopCount = () => {
     
    clearInterval(timerId)
  }

  return <div>
    {
     count}
    <button onClick={
     stopCount}>停止</button>
  </div>
}

export default App

使用 useState

可以使用 useState 将 timerId 当作状态保存,避免重新渲染时被重置。

import {
      useState, useEffect } from 'react'

function App() {
     
  const [count, setCount] = useState(0)

  // 使用 useState 确保组件重新渲染后 timerId 不会被重置
  const [timerId, setTimerId] = useState(null)

  useEffect(() => {
     
    setTimerId(setInterval(() => {
     
      setCount(count => count + 1)
    }, 1000))
  }, [])

  const stopCount = () => {
     
    clearInterval(timerId)
  }

  return <div>
    {
     count}
    <button onClick={
     stopCount}>停止</button>
  </div>
}

export default App

使用 useRef

import {
      useState, useEffect, useRef } from 'react'

function App() {
     
  const [count, setCount] = useState(0)

  // 使用 useRef 保存数据,在整个生命周期都不变
  const timerId = useRef(null)

  useEffect(() => {
     
    timerId.current = setInterval(() => {
     
      setCount(count => count + 1)
    }, 1000)
  }, [])

  const stopCount = () => {
     
    clearInterval(timerId.current)
  }

  return <div>
    {
     count}
    <button onClick={
     stopCount}>停止</button>
  </div>
}

export default App

你可能感兴趣的:(react)