当前位置:   article > 正文

React Hooks 学习 - 03 useMemo、React.memo、useCallback、useRef_useref usememo usecallback

useref usememo usecallback

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

React.memo 方法

介绍使用

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

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

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

/*
// count 每次变化都会渲染 Foo 组件
function Foo() {
  console.log('Foo 组件重新渲染了')
  return (
    <div>Foo 组件</div>
  )
}
*/

// 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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

与类组件的 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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

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

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

缓存函数

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

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

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

const resetCount = useCallback(() => {
  setCount(0)
}, [setCount])
  • 1
  • 2
  • 3

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

类组件使用 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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

保存数据(跨组件周期)

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

使用 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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

使用 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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/411811
推荐阅读
相关标签
  

闽ICP备14008679号