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;
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;
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 的值变化时重新渲染。
为了避免这种不必要的重复渲染,可以使用 useCallback
将 resetCount
方法缓存下来,在 App 重新渲染时,获取缓存中的 resetCount
方法传递给 Foo 组件,由于缓存中的 resetCount
(引用地址)未发生变化,所以 Foo 组件不会被额外渲染。
useCallback
同样可以接受一个依赖项数组作为第二个参数:
const resetCount = useCallback(() => {
setCount(0)
}, [])
或
const resetCount = useCallback(() => {
setCount(0)
}, [setCount])
useRef
有两个功能:
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
将 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
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