ReactWheels07-03Hooks全解2

代码仓库

用useReducer代替redux

一共七步

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import React, { useContext, useReducer, useEffect } from "react";
// step01 将数据集中在store里
const store = {
user: null,
books: null,
movies: null
}
// step02 将所有操作集中在 reducer里
const reducer = (state, action) => {
switch (action.type) {
case 'setUser':
return { ...state, user: action.user }
case 'setBooks':
return { ...state, books: action.books }
case 'setMovies':
return { ...state, movies: action.movies }
default:
throw new Error('no match action')
}
};

// step03 创建 Context
const Context = React.createContext(null)

function ReducerDemo1() {
// step04 创建对数据的 读/写 API
const [state, dispatch] = useReducer(reducer, store)
return (
// step05 将 step04的 读写api放到 Context提供的组件上
// step06 Context.Provider 将 Context 提供给所有组件
<Context.Provider value={{ state, dispatch }}>
<User />
<hr />
<Books />
<Movies />
</Context.Provider>
);
}

// step07 各个组件用 useContext 获取读写API
function User() {
const { state, dispatch } = useContext(Context);
useEffect(() => {
ajax("/user").then(user => {
dispatch({ type: "setUser", user: user });
});
}, []);
return (
<div>
<h1>个人信息</h1>
<div>name: {state.user ? state.user.name : ""}</div>
</div>
);
}

function Books() {
const { state, dispatch } = useContext(Context);
useEffect(() => {
ajax("/books").then(books => {
dispatch({ type: "setBooks", books: books });
});
}, []);
return (
<div>
<h1>我的书籍</h1>
<ol>
{state.books ? state.books.map(book => <li key={book.id}>{book.name}</li>) : "加载中"}
</ol>
</div>
);
}

function Movies() {
const { state, dispatch } = useContext(Context);
useEffect(() => {
ajax("/movies").then(movies => {
dispatch({ type: "setMovies", movies: movies });
});
}, []);
return (
<div>
<h1>我的电影</h1>
<ol>
{state.movies
? state.movies.map(movie => <li key={movie.id}>{movie.name}</li>)
: "加载中"}
</ol>
</div>
);
}

export default ReducerDemo1;

// 帮助函数

// 假 ajax
// 两秒钟后,根据 path 返回一个对象,必定成功不会失败
function ajax(path) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (path === "/user") {
resolve({
id: 1,
name: "hjx"
});
} else if (path === "/books") {
resolve([
{
id: 1,
name: "JavaScript 高级程序设计"
},
{
id: 2,
name: "JavaScript 精粹"
}
]);
} else if (path === "/movies") {
resolve([
{
id: 1,
name: "爱在黎明破晓前"
},
{
id: 2,
name: "恋恋笔记本"
}
]);
}
}, 2000);
});
}

useContext

  • 上下文
    • 全局变量是全局的上下文
    • 上下文是局部的全局变量
  • 使用方法
    1
    2
    3
    step1 const C = createContext(null);
    step2 <C.Provider value={{ n, setN }}> 圈定作用域
    step3 在作用域内(任意级子组件内)使用 useContext(c) 来使用上下文
  • 不是响应式的
    • 你在一个组件内将 C变化 setN
    • 另一个模块不会感知这个变化
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
import React, { createContext, useState, useContext } from 'react';

const C = createContext(null);

function UseContextDemo1() {
const [n, setN] = useState(0)
return (
<C.Provider value={{ n, setN }}>
<div className="UseContextDemo1">
<Baba />
</div>
</C.Provider>
)
}

function Baba() {
return (
<div>
我是爸爸
<Child />
</div>
)
}

function Child() {
const { n, setN } = useContext(C);
const onClick = () => {
setN(i => i + 1)
}
return (
<div>
我是儿子,我得到n:{n}
<button onClick={onClick}>+1</button>
</div>
)
}
export default UseContextDemo1

useEffect

  • 副作用

    • 对环境的改变即为副作用,如 document.title = 'xxx'
    • 不一定把 这个副作用 放在 useEffect里
    • 每次 render后执行
  • 用途

    • 作为 componentDidMount 使用 [] 作为第二个参数
    • 作为 componentDidUpdate 使用 [n] 数组内可以指定依赖,n变了才会触发
    • 作为 componentWillUnmount 使用 ,通过return 代表销毁的时候
  • 特点

    • 存在多个 useEffect,会按照出现次序执行

Demo

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import React, { useEffect, useState } from 'react';

function UseEffectDemo1() {
return (
<div className="demo1">
<Demo1 />
<Demo2 />
<Demo3 />
<Demo4 />
<Demo5 />
<Demo6 />
<Demo7 />
</div>
)
}


function Demo1() {
const [n, setN] = useState(0)

useEffect(() => {
// 任何一个变了变量变了都执行
// 第一次 和 第二三四 。。。 次
console.log('demo1:第一次 和 第二三四 。。。 次,任何一个变量变了都执行')
})

const onClick = () => {
setN(i => i + 1)
}
return (
<div className="demo1">
<h1>useEffect不传递第二个参数, 第一二三。。。次执行,任何一个变量变了都执行</h1>
n:{n}
<br />
<button onClick={onClick}>+1</button>
</div>
)
}

function Demo2() {
const [n, setN] = useState(0)
useEffect(() => {
// n更新之后
console.log('demo2:n更新之后')
}, [n])

const onClick = () => {
setN(i => i + 1)
}
return (
<div className="demo2">
<h1>useEffect传递第二个参数[n], 第一次执行,以及n变了执行</h1>
n:{n}
<br />
<button onClick={onClick}>+1</button>
</div>
)
}


function Demo3() {
const [x, setX] = useState(0)
const [y, setY] = useState(0)
useEffect(() => {
// x更新之后,
console.log('demo3:第一次执行,以及x更新之后才变,y改变不会触发effect')
}, [x])

const onClickX = () => {
setX(i => i + 1)
}
const onClickY = () => {
setY(i => i + 1)
}
return (
<div className="demo2">
<h1>useEffect传递第二个参数[x],第一次执行,以及x更新之后才变,y改变不会触发effect</h1>
x:{x}
<br />
y:{y}
<br />
<button onClick={onClickX}>x+1</button>
<button onClick={onClickY}>y+1</button>
</div>
)
}

function Demo4() {
const initValue = 0;
const [z, setZ] = useState(initValue)
useEffect(() => {
// 跳过第一次
if (z !== initValue) {
// 跳过第一次
console.log('demo4:通过if(z!== initValue)判断,跳过第一次,z修改后执行')
}
}, [z])

const onClick = () => {
setZ(i => i + 1)
}
return (
<div className="demo2">
z:{z}
<br />
<button onClick={onClick}>z+1</button>
</div>
)
}

function Demo5() {
const [z, setZ] = useState(0)
useEffect(() => {
console.log('demo5:useEffect 第二个参数为空数组[],只执行一次')
}, []);

const onClick = () => {
setZ(i => i + 1)
}
return (
<div className="demo5">
<h1>demo5:useEffect 第二个参数为空数组[],只执行一次</h1>
z:{z}
<br />
<button onClick={onClick}>z+1</button>
</div>
)
}

function Demo6() {
useEffect(() => {
const id = setInterval(() => {
console.log('demo6:useEffect 移除时执行 return里写一个函数')
}, 3000)
return () => {
window.clearInterval(id);
console.log('demo6:组件销毁的时候执行')
}
}, []);

return (
<div className="demo6">
<h1>demo6:useEffect 移除时执行操作 return一个函数进行操作</h1>
</div>
)
}


function Demo7() {
useEffect(() => {
console.log('demo7:1')
}, []);
useEffect(() => {
console.log('demo7:2')
}, []);

return (
<div className="demo7">
<h1>demo7:多个useEffect 按顺序执行</h1>
<br />
</div>
)
}

export default UseEffectDemo1

useLayoutEffext

Demo1

执行流程

  • App执行
  • vDom
  • 生成真实Dom
  • render完毕,改变外观 <div>0</div>
  • effect 执行=》div.innerText = 1000
1
2
3
4
5
6
7
8
9
10
11
function Demo1() {
const [n, setN] = useState(0)
useEffect(() => {
document.querySelector('#x').innerText = `n:1000`
}, [n])
return (
<div className="demo1">
<h1>useEffect</h1>
<div id="x">n:{n}</div>
</div>
)

Demo2

执行流程

  • App执行
  • vDom
  • 生成真实Dom
    • <div>0</div>
    • useLayoutEffect 执行=》div.innerText = 1000
    • 作为浏览器不会看见 n = 0 的过程
  • render完毕,改变外观 <div>1000</div>
1
2
3
4
5
6
7
8
9
10
11
12
function Demo2() {
const [n, setN] = useState(0)
useLayoutEffect(() => {
document.querySelector('#x2').innerText = `n:1000`
}, [n])
return (
<div className="demo2">
<h1>useEffect</h1>
<div id="x2">n:{n}</div>
</div>
)
}

useLayoutEffect 和 useEffect 区别

  • useLayoutEffect 是 render之前(看不到闪烁)
    • 是不是用这个更好呢?
      • 不是,因为大部分时间,我们不回去改变 dom
      • useEffect 满足不了需求的时候用 useLayoutEffect
      • 优先级更高
  • useEffect 是 render之后触发(能看见闪烁)

useLayoutEffect 和 useEffect 性能测试

  • useLayoutEffect 耗时更短
  • useEffect 耗时更长
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
function Demo3() {
const [x, setX] = useState(0)
const time = useRef(null)
const onClickX = () => {
setX(i => i + 1)
time.current = performance.now()
}

useEffect(() => {
if (time.current) {
console.log('demo3:' + (performance.now() - time.current))
}
}, [x])

return (
<div className="demo3">
<h1>useEffect的更新UI时间更长</h1>
x:{x}
<br />
<button onClick={onClickX}>x+1</button>
</div>
)
}

function Demo4() {
const [x, setX] = useState(0)
const time = useRef(null)

const onClickX = () => {
setX(i => i + 1)
time.current = performance.now()
}

useLayoutEffect(() => {
if (time.current) {
console.log('demo4:' + (performance.now() - time.current))
}
}, [x])

return (
<div className="demo4">
<h1>useLayoutEffect的更新UI时间更短</h1>
x:{x}
<br />
<button onClick={onClickX}>x+1</button>
</div>
)
}

useEffect注意事项

  • 如果 useLayoutEffect 和 useEffect 同时存在 useLayoutEffect先执行
  • 如果 用 useLayoutEffect ,你最好改变Layout
  • 为了用户体验,优先使用 useEffect(优先渲染)