前言

eg:代表代码对照 若文章有误,欢迎读者留言反馈

useCallback【记忆函数】

【记忆函数,实现了类似记忆组件、计算属性的功能,也能进行scu优化】

useCallback记忆函数,效果类似记忆组件memoize-one,把某个函数进行缓存,然后把当前函数体返回给你

  1. 正常情况下,父组件中当前数据改变,当前父组件无条件刷新,当然子组件也随之刷新,当前子组件依赖的数组没有发生改变,咱么就可以通过scu技术,不让他刷新

parent.jsx父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 引入useState、useCallback两个hooks
import React, { useState, useCallback } from 'react'
import Child from './Child'

// useCallback,记忆函数,效果类似记忆组件,memoize-one,把某个函数进行缓存,然后把当前函数体返回给你。
// 作用:如果子组件传入的有函数,scu会被打破。可以对函数进行useCallback缓存,这样scu就又生效了
// 函数组件内容可以看成类组件render的语法糖。
const Parent = () => {
const [num, setNum] = useState(0)
const [arr, setArr] = useState([1, 2, 3, 4])
return (
<div>
<h3>Parent</h3>
<p>{num}</p>
{ /* 正常情况下,父组件中当前数据改变,当前父组件无条件刷新,当然子组件也随之刷新 */ }
{/* 当前数据改变,当前组件无条件刷新,当然子组件也随之刷新 */}
{/* 当前子组件依赖的数组没有发生改变,咱么就可以通过scu技术,不让他刷新 */}
<button onClick={() => setNum(num + 1)}>点击+1</button>
<Child arr={arr} />
</div>
)
}

export default Parent

Child.jsx子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 引入memo高阶组件【浅比较】
import React,{memo} from 'react'

// 注意传入props来接收父组件中的数据
const Child = (props) => {
console.log('Child刷新了')
return (
<div>
<h3>Child</h3>
<p>{props.arr}</p>
</div>
)
}

// 直接使用memo高阶组件来给函数组件添加一个scu效果
export default memo(Child)
  1. 如果给子组件传递个函数进去,就算是依赖的数据和函数没有变化,scu也会被打破,子组件被动随着这个函数刷新[尽管它没执行]

问题:每次组件刷新,组件里面的执行函数都会从新创建,所以刷新前后不是同一函数

分析问题:这里多说一句,一般把函数式组件理解为class组件render函数的语法糖,所以每次重新渲染的时候,函数式组件内部所有的代码都会重新执行一遍。所以上述代码中每次render,handleClick都会是一个新的引用,所以也就是说传递给SomeComponent组件的props.onClick一直在变(因为每次都是一个新的引用),所以才会说这种情况下,函数组件在每次渲染的时候如果有传递函数的话都会重渲染子组件。

解决方法:有了useCallback就不一样了,你可以通过useCallback获得一个记忆后的函数,那么改变父组件中的num就不会影响子组件刷新,因为函数也做了缓存,注意子组件也要使用memo高阶组件

parent.jsx父组件

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
// 引入useState、useCallback两个hooks
import React, { useState, useCallback } from 'react'
import Child from './Child'

// useCallback,记忆函数,效果类似记忆组件,memoize-one,把某个函数进行缓存,然后把当前函数体返回给你。
// 作用:如果子组件传入的有数据和函数,scu会被打破。可以对函数进行useCallback缓存,这样scu就又生效了
// 函数组件内容可以看成类组件render的语法糖。
const Parent = () => {
const [num, setNum] = useState(0)
const [arr, setArr] = useState([1, 2, 3, 4])
// const handleAdd = () => {
// setArr([...arr, 5])
// }
// 使用useCallback进行scu优化[缓存了arr,如果arr发生改变就会重新刷新子组件]
const handleAdd = useCallback(() => {
setArr([...arr, 5])
}, [arr]) //第二参数有依赖性 变量改变触发第一个函数
return (
<div>
<h3>Parent</h3>
<p>{num}</p>
{ /* 正常情况下,父组件中当前数据改变,当前父组件无条件刷新,当然子组件也随之刷新 */ }
<button onClick={() => setNum(num + 1)}>点击+1</button>
{/* 当前数据改变,当前组件无条件刷新,当然子组件也随之刷新 */}
{/* 当前子组件依赖的数组没有发生改变,咱么就可以通过scu技术,不让他刷新 */}
{/* 如果给子组件传递个函数进去,就算是依赖的数据和函数没有变化,scu也会被打破,子组件被动随着这个函数刷新[尽管它没执行] */}
{/* 问题:每次组件刷新,组件里面的执行函数都会从新创建,所以刷新前后不是同一函数 */}
<Child arr={arr} handleAdd={handleAdd} />
</div>
)
}

export default Parent

Child.jsx子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 引入memo高阶组件【浅比较】
import React,{memo} from 'react'

// 注意传入props来接收父组件中的数据
const Child = (props) => {
console.log('Child刷新了')
return (
<div>
<h3>Child</h3>
<p>{props.arr}</p>
<button onClick={props.handleAdd}>点击改变父组件中的arr</button>
</div>
)
}

// 直接使用memo高阶组件来给函数组件添加一个scu效果
export default memo(Child)

userMemo【记忆组件,不需要scu】

useCallback的功能完全可以由useMemo所取代,如果你想通过使用useMemo返回一个记忆函数也是完全可以的
所以前面使用useCallback的例子可以使用useMemo进行改写:

1
2
3
4
5
// 使用useMemo对某个数据进行缓存,强调的是结果值
const handleAdd = () => {
setArr([...arr, 5])
}
var child = useMemo(()=><Child arr={ arr } handleAdd={ handleAdd }/>,[arr])

parent.jsx父组件

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
// 引入useState、useCallback、useMemo三个hooks
import React, { useState, useCallback, useMemo } from 'react'
import Child from './Child'

// useCallback,记忆函数,效果类似记忆组件,memoize-one,把某个函数进行缓存,然后把当前函数体返回给你。
// 作用:如果子组件传入的有数据和函数,scu会被打破。可以对函数进行useCallback缓存,这样scu就又生效了
// 函数组件内容可以看成类组件render的语法糖。
const Parent = () => {
const [num, setNum] = useState(0)
const [arr, setArr] = useState([1, 2, 3, 4])
// const handleAdd = () => {
// setArr([...arr, 5])
// }
// 使用useCallback进行scu优化[缓存了arr,如果arr发生改变就会重新刷新子组件]
// const handleAdd = useCallback(() => {
// setArr([...arr, 5])
// }, [arr]) //第二参数有依赖性 变量改变触发第一个函数

// 使用useMemo来实现useCallback的功能
const handleAdd = () => {
setArr([...arr, 5])
}
const child = useMemo(() => <Child arr={arr} handleAdd={handleAdd} />, [arr])
return (
<div>
<h3>Parent</h3>
<p>{num}</p>
{ /* 正常情况下,父组件中当前数据改变,当前父组件无条件刷新,当然子组件也随之刷新 */}
<button onClick={() => setNum(num + 1)}>点击+1</button>
{/* 当前数据改变,当前组件无条件刷新,当然子组件也随之刷新 */}
{/* 当前子组件依赖的数组没有发生改变,咱么就可以通过scu技术,不让他刷新 */}
{/* 如果给子组件传递个函数进去,就算是依赖的数据和函数没有变化,scu也会被打破,子组件被动随着这个函数刷新[尽管它没执行] */}
{/* 问题:每次组件刷新,组件里面的执行函数都会从新创建,所以刷新前后不是同一函数 */}
{/* <Child arr={arr} handleAdd={handleAdd} /> */}
{/* 使用useMemo来实现useCallback的功能,落脚点传递的是值,同时也不需要scu */}
{child}
</div>
)
}

export default Parent

Child.jsx子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react'

// 注意传入props来接收父组件中的数据
const Child = (props) => {
console.log('Child刷新了')
return (
<div>
<h3>Child</h3>
<p>{props.arr}</p>
<button onClick={props.handleAdd}>点击改变父组件中的arr</button>
</div>
)
}

export default Child

唯一的区别是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。所以在前面的例子中,可以返回 handleClick 来达到存储函数的目的。

所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。

从例子可以看出来,useMemo只有在第二个参数数组的值发生变化时,才会触发子组件的更新。

useRef【保存引用值】

  1. 受控组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 引入useRef、useState两个hooks
import React, { useRef, useState } from 'react'
// useRef 等同类组件间中 createRef
export default function UseRefDemo() {
var [name, setName] = useState('') // 1.受控组件的案例
const handleClick = () => {
//获取元素的值 受控组件
console.log('name', name)
}
return (
<div>
<div>用户名:<input value={name} onInput={(e) => setName(e.target.value)} type="text" /></div>
<button onClick={handleClick}>登陆</button>
</div>
)
}
  1. 非受控组件ref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 引入useRef、useState两个hooks
import React, { useRef, useState } from 'react'
// useRef 等同类组件间中 createRef
export default function UseRefDemo() {
var myName = useRef() // 1.创建一个ref案例
const handleClick = () => {
// 获取元素的值 ref
console.log('myName', myName.current.value) // 获取input元素里面的值
}
return (
<div>
<div>用户名:<input ref={myName} type="text" /></div>
<button onClick={handleClick}>登陆</button>
</div>
)
}

UseImperativeHandleDemo【透传,了解即可】

通过 useImperativeHandle 用于让父组件获取子组件内的索引,这种方式,App 组件可以获得子组件的 input 的 DOM 节点。

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
// 引入useRef、useEffect、useImperativeHandle、forwardRef四个hooks
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react"
// useImperativeHandle 透传ref,把父组件的ref传递给子组件,子组件通过hooks,跟自己的ref绑定。
// 子组件
function ChildInputComponent(props, ref) {
const inputRef = useRef(null)
// 把父组件的ref透传给子组件的ref,把父子ref进行管理
useImperativeHandle(ref, () => inputRef.current)
return <input type="text" name="child input" ref={inputRef} />
}

const ChildInput = forwardRef(ChildInputComponent)

// 父组件
function App() {
const inputRef = useRef(null)
useEffect(() => {
// inputRef.current获取子组件的实例
inputRef.current.focus()//子组件中的input框进行focus聚焦
}, [])
return (
<div>
<h3>透传</h3>
<ChildInput ref={inputRef} />
</div>
)
}
export default App

自定义hooks

自定义hooks是在react-hooks基础上的一个拓展,可以根据业务需要制定满足业务需要的hooks,更注重的是逻辑单元。通过业务场景不同,我们到底需要react-hooks做什么,怎么样把一段逻辑封装起来,做到复用,这是自定义hooks产生的初衷。

我们设计的自定义react-hooks应该是长的这样的。

1
const [ xxx , ... ] = useXXX(参数A,参数B...)

在我们在编写自定义hooks的时候,要关注的是传进去什么返回什么。返回的东西是我们真正需要的。更像一个工厂,把原材料加工,最后返回我们。

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
import React, { useState, useMemo } from 'react'

export default function Parent() {
const [num, setNum] = useState(0)
const [arr, setArr] = useState(['a', 'b', 'c', 'd'])
// 需求:通过实现一个hooks,把数据小写转大写
var newArr = useCallUpperCase(arr)
return (
<div>
<p>{num}</p>
{/* <button onClick={ ()=>setNum(num+1) }>+1</button> */}
<button onClick={() => setArr([...arr, 'e'])}>+1</button>
<ul>
{
newArr.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
}

// 自定义hooks说白了就是具有某些特殊功能的一个函数
function useCallUpperCase(arr) {
//考虑 如果arr没有发生改变,函数就可以不用重复执行
return useMemo(() => {
console.log('调用') //测试有没有缓存
return arr.map(item => {
return item.toUpperCase()
})
}, [arr])
}