ReactWheels07-03Hooks全解3

代码仓库

Memo

  • React.Memo
  • Child3 可以用 React.Memo(Child)代替
  • 如果props不变,就不会再次执行一个函数组件
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 React, { useState } from 'react';
function UseMemoDemo1() {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onChangeN = () => {
setN(i => i + 1)
}
const onChangeM = () => {
setM(i => i + 1)
}
return (
<div>
没有缓存,Child组件 依赖的是m,点击按钮更新的是n,也会触发 Child从新执行
<br />
n:{n},
<br />
m:{m}
<br />
<button onClick={onChangeN}>onChangeN</button>
<br />
<button onClick={onChangeM}>onChangeM</button>
<Child data={m} />
<Child3 data={m} />
</div>
)
}

function Child(props) {
console.log('Child执行了')
console.log('Child内大量逻辑1')
console.log('Child内大量逻辑2')
console.log('Child内大量逻辑3')
return (<div>child:{props.data}</div>)
}

function Child2(props) {
console.log('Child2执行了')
console.log('Child2内大量逻辑1')
console.log('Child2内大量逻辑2')
console.log('Child2内大量逻辑3')
return (<div>child2:{props.data}</div>)
}

// 被缓存,第一次执行后,当依赖的值变了才会更新
const Child3 = React.memo(Child2)

export default UseMemoDemo1;

Memo的Bug: 如果 传递一个props.fn

  • 每次渲染的时候 父组件的 fn会重新生成一个,这样就导致,props变了, memo的组件失效了
  • 解决办法就是: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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import React, { useCallback, useMemo, useState } from 'react';
// useMemo
function UseMemoDemo2() {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onChangeN = () => {
setN(i => i + 1)
}
const onChangeM = () => {
setM(i => i + 1)
}
const onClickChild = () => { } // 这个每次执行的时候 都会重新生成一个 而且是引用的地址 ,child依赖它,它变了,child无法被缓存

/*
useMemo(()=>{
return 复用的函数
},[依赖的变量])
这个函数复用
return 就是复用的函数
第二个参数里的 [] 内是依赖变化的值
*/
const onClickChildMemo = useMemo(() => {
// return的东西 就是你要缓存的函数
return () => { }
}, [m]) // []里是依赖的东西变了才更新

/*
useCallback(fn,[m])
等价于
useMemo(()=>fn,[m])
*/
const fn = () => console.log(m)
const onClickChildMemo2 = useMemo(() => fn, [m])
const onClickChildMemo3 = useCallback(fn, [m])
return (
<div>
没有缓存,Child组件 依赖的是m,点击按钮更新的是n,也会触发 Child从新执行
<br />
n:{n},
<br />
m:{m}
<br />
<button onClick={onChangeN}>onChangeN</button>
<br />
<button onClick={onChangeM}>onChangeM</button>
<Child2 data={m} onClick={onClickChild} />
<Child4 data={m} onChilk={onClickChildMemo} />
</div>
)
}

function Child(props) {
console.log('Child执行了')
console.log('Child内大量逻辑1')
console.log('Child内大量逻辑2')
console.log('Child内大量逻辑3')
return (<div onClick={props.onClick}>child:{props.data}</div>)
}

const Child2 = React.memo(Child)

function Child3(props) {
console.log('Child3执行了')
console.log('Child3内大量逻辑1')
console.log('Child3内大量逻辑2')
console.log('Child3内大量逻辑3')
return (<div onClick={props.onClick}>child:{props.data}</div>)
}

const Child4 = React.memo(Child3)

export default UseMemoDemo2;

useMemo

特点

  • 第一个参数是 ()=>value
  • 第二个参数是 依赖 [m,n]
  • 只有依赖变了的时候,才会计算出新的 value,反之复用
1
2
3
4
5
6
7
8
9
10
11
12
/*
useMemo(()=>{
return 复用的函数
},[依赖的变量])
这个函数复用
return 就是复用的函数
第二个参数里的 [] 内是依赖变化的值
*/
const onClickChildMemo = useMemo(() => {
// return的东西 就是你要缓存的函数
return () => { }
}, [m]) // []里是依赖的东西变了才更新

注意

  • 如果你的 value是个函数你就要这样
1
useMemo(()=>(x)=>{console.log(1)}},[依赖1,依赖2])
  • useCallback的优化
1
2
3
4
5
6
7
8
9
10
// 两个箭头看着很懵逼
useMemo(()=>(x)=>{console.log(x)}},[依赖1,依赖2])

// useCallback 你可以这样
useCallback((x)=>{console.log(x)}},[依赖1,依赖2])


useCallback(fn,[m])
等价于
useMemo(()=>fn,[m])

useRef

  • 当你需要一个值,在组件不断render的时候保持不变(addr)
  • 初始化 const count = useRef(0)
  • 读取 count.current
  • 为什么需要 current?
    • 为了保证多次 useRef 都是同一个值(只有引用能做到)
    • count = {current:0} 地址101
    • 下次 +1 后 count = {current:1} 地址101

扩展vue3内容

  • vue3的ref 改变ref的值,会触发UI更新

    1
    2
    3
    4
    5
    6
    // 初始化
    cosnt count = ref(0)
    // 读取
    count.value
    // 更新,它会触发UI更新,vue3会自动 render
    count.value +=1
  • 而 react的 ref不会触发 UI更新

想要给React的ref改变的时候触发UI更新咋办?

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
import React, { useEffect, useRef, useState } from 'react';
function UseRefDemo1() {
const count = useRef(0)
const [_, set_] = useState(null)
const onChangeN = () => {

count.current += 1
}
const onChangeNUpdatedUI = () => {
count.current += 1
set_(Math.random())
}
useEffect(() => {
console.log(count)
})
return (
<div>
count:{count.current},
<br />
<button onClick={onChangeN}>onChangeRef不更新UI</button>
<button onClick={onChangeNUpdatedUI}>onChangeRef更新UI</button>
</div>
)
}

export default UseRefDemo1;

forwardRef : 仅限函数组件

  • props无法传递 ref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { useRef } from "react";
import ReactDOM from "react-dom";

function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button2 ref={buttonRef}>按钮</Button2>
{/* 看浏览器控制台的报错 */}
</div>
);
}

const Button2 = props => {
return <button className="red" {...props} />;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
  • 实现ref传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { useRef } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button3 ref={buttonRef}>按钮</Button3>
</div>
);
}

const Button2 = (props,ref)=>{
return <button className="red" ref={ref} {...props} />;
}

// React.forwardRef 可以额外多传递一个参数
// 把ref参数 转发给 Button2
const Button3 = React.forwardRef(Button2);

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

useRef

  • 可用来引用DOM,避免来 docuemt.querySelector
  • 也可以引用普通对象

forwardRef

  • 由于 props不能传递 ref,所以需要 forwardRef
  • 为什么props不包含 ref呢?,因为大部分时候不需要

useImperativeHandle

  • 名字起的稀巴烂
  • 应该叫做 setRef
  • 用于自定义 ref的属性
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
import React, {
useRef,
useEffect,
useImperativeHandle,
createRef
} from "react";

function SetRefDemo() {
const buttonRef = useRef(null);
useEffect(() => {
console.log(buttonRef.current);
});
return (
<div className="App">
<Button2 ref={buttonRef}>按钮</Button2>
<button
className="close"
onClick={() => {
console.log(buttonRef);
buttonRef.current.x();
}}
>
</button>
</div>
);
}

const Button2 = React.forwardRef((props, ref) => {
const realButton = createRef(null);
const setRef = useImperativeHandle;
// 对 ref进行自定义的包装返回
setRef(ref, () => {
return {
x: () => {
realButton.current.remove();
},
realButton: realButton
};
});
return <button ref={realButton} {...props} />;
});

export default SetRefDemo;