理解 useRef、useMemo、useCallback
memo 给子组件包裹一层memo, 父组件更新不会影响子组件更新。
useRef 可用来存储一个引用值(不会受 re-render 影响),也可用来获取 dom 节点。
useMemo 用来缓存一个值。当依赖项为空数组时,缓存值永远不会变。当有依赖性时,每次 re-render 如果依赖改变,那么将重新执行函数,将新的函数返回值作为缓存的数据。
- 传递给子组件的引用数据类型需要使用 useMemo 包裹。其实和上面说的传递函数给组件,函数要用 useCallback 包裹起来是同一个道理。函数也是引用数据类型,是一个特殊的对象!
- hook 使用 useMemo 包裹,为什么 hook 要使用 useMemo 包裹?因为我们使用 hook 本质上是在调用一个函数执行的计算结果。每次 re-render 的时候,都去执行一下 hook 函数的话,可能会造成性能上的损失。所以可以使用 useMemo 将函数执行的计算结果缓存起来。
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 35 36 37 38 39 40 41 42 43 44 45
|
import { useCallback, useMemo, useState } from 'react' import Foo from '@/components/foo' import './App.css'
function App() { const obj = useMemo(() => ({ name: 'coder', age: 28 }), [])
const [count, setCount] = useState(0)
return ( <> <button onClick={()=>setCount(count + 1)}>{count}</button> <Foo obj={obj}/> </> ) }
export default App
import { memo } from "react"
interface Props { obj: { name: string, age: number } } const Index = (props: Props) => { console.log('re-render'); return ( <button>按钮</button> ) }
export default memo(Index)
|
useCallback 是 useMemo 的语法糖,相当于返回一个函数。传递给子组件的函数使用 useCallback 包裹, re-render 时前后创建的两个函数引用地址并不一样,Object.is() => false
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 35 36 37 38 39 40 41 42 43 44 45 46 47
| import { useCallback, useState } from 'react' import Foo from '@/components/foo' import './App.css'
function App() { const request = useCallback(() => { setTimeout(() => { console.log('请求'); }, 10) }, [])
const [count, setCount] = useState(0)
return ( <> <button onClick={()=>setCount(count + 1)}>{count}</button> <Foo request={request} /> </> ) }
export default App
import { memo } from "react"
interface Props { request: () => void }
const Index = (props: Props) => { console.log('re-render'); return ( <button onClick={props.request}>按钮</button> ) }
export default memo(Index)
|
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| interface Cb{ (...arg: unknown[]):unknown }
const catchMap = new Map(); export default (key:string, callback:Cb) =>{ return async (cache=true) => { const result = cacheMap.get(key) if(cache && result) { return result; } const res = await callback() cacheMap.set(key,res)
return res } }
function App () { const [count,setCount] = useState(0); useEffect(() => { fn() })
async function fn() { const res = await getStatus()
const getStatus = useCache('getStatus', () => { const arr = [1,2,3,4] const res = [] as number[] arr.forEach(num => { console.log(num); res.push(num) });
return res })
function add () { setCount(c => c + 1) }
return ( <> <h2>==</h2> <br /> {count} <br /> <button onClick={add}>add</button> <h2>==</h2> </> ) } }
|
在函数式组件中使用 ref
- 获取当前组件内的 dom 节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { useRef, useEffect, useState } from 'react' import Foo from '@/components/foo' import './App.css'
function App() { const button = useRef(null)
useEffect(() => { console.log(button.current); }, [])
return ( <> <h2>==</h2> <button ref={button} >按钮</button> <h2>==</h2> </> ) }
export default App
|
- 获取子组件的dom节点
使用 forwardRef 转发。
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 35 36 37 38 39
|
import { useRef, useEffect } from 'react' import Foo from '@/components/foo' import './App.css'
function App() { const button = useRef(null)
useEffect(() => { console.log(button.current); }, [])
return ( <> <h2>==</h2> <Foo ref={button} /> <h2>==</h2> </> ) }
export default App
import React, { forwardRef } from "react"
interface Props { name?: string }
const Index = (props: Props, ref: any) => { return ( <button ref={ref}>name: {props.name}</button> ) }
export default forwardRef(Index)
|
- memo配合forwardRef一起使用
用 memo 包裹 forwardRef 转发后的组件,防止子组件不必要的 re-render。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { forwardRef, memo } from "react"
interface Props { name?: string }
const Index = (props: Props, ref: any) => { return ( <button ref={ref}>name: {props.name}</button> ) }
export default memo(forwardRef(Index))
|