# React 源码解读(五) hooks
# 什么是 hooks
它可以让你再不编写 class 的情况下使用
# 一些基本的 hooks
# useState
import React, { useState } from "react";
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times </p>
<button onClick={setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- count 就是一个 state,setCount 就是来控制对应的 state 的方法
- useState() 里可以设置初始值
- useState 的初始值如果是对象的话,需要注意的一点就是,每次 setCount 的时候都要传入完整的对象,否则少传的对象就会丢失
# useEffect
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
// 这个callback没有参数
console.log("componentDidMount");
}, []);
useEffect(
() => {
// // 不能改变依赖数组的state
console.log("change count");
let timer = setInterval(() => {
console.log("每秒执行");
}, 1000);
return () => {
clearInterval(timer);
};
},
[count]
);
return (
<div>
<p>You clicked {count} times </p>
<button onClick={setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
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
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
- useEffect 有两个参数,callback 和 依赖
- useEffect 的执行时机,在第一次调用 Example 函数组件的时候, useEffect 的 callBack 执行,在依赖 dependencies 的值, 发生变化的时候, callBack 执行
- 如果第二个参数为空,那么每当有 state 改变时,都会执行一次
- 如果第二个参数是空数组[],那么只在第一次调用组件的时候执行一次
- 如果第二个参数是某个 state 值,那么就会在这个依赖值变化的时候执行 callback
- 可以在 useEffect 最后 return 一个函数,这个函数会在组件卸载的时候执行,比如清除定时器
- 有个坑!不要在 callback 里更新自己依赖的 state!!!
- useEffect的目的是去处理副操作,所以useEffect会在dom渲染结束再去执行
# useCallback
+当我们定义点击事件的时候,如果直接定义的话,每次组件状态改变重新执行的时候都要重新定义一次,很不好,所以出现了 useCallback
import React, { useState, useCallback } from "react";
function Example() {
const [count, setCount] = useState(0);
const dealCount = useCallback(
() => {
// 还是之前缓存的0
setCount({
value: 1
});
// stateRef.current = count+1
// 只是添加了一个update, 还没有更新最终的值
setTimeout(() => {
// 用的还是之前的值
console.log("timer count", count);
// console.log('timer stateRef', stateRef.current)
}, 2000);
// setCount((count) => {
// return count+1
// })
},
[count]
);
return (
<div>
<p>You clicked {count} times </p>
<button onClick={dealCount}>Click me</button>
</div>
);
}
export default Example;
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
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
- useCallback 相当于把点击事件缓存了,这样在每次重新执行组件的时候就不会再次定义
- useCallback 的第二个参数目的是将所需要的 state 值传进去,如果不填的话,第一次的 state 值就会被一起缓存,后面怎么点击都不会变
- 还有一种解决方法,就是第二个参数为[],setCount 里面传入函数,也可以获取到最新的 state
const dealCount = useCallback(() => {
setCount(count => {
return count + 1;
});
}, []);
1
2
3
4
5
2
3
4
5
这里有个坑,capture value,快照变量
const dealCount = useCallback(
() => {
// 还是之前缓存的0
setCount(count + 1);
// 只是添加了一个update, 还没有更新最终的值
setTimeout(() => {
console.log("timer count", count);
}, 2000);
},
[count]
);
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 当点击触发 dealCount 方法时,经过两秒延迟输出 0 ╮(╯▽╰)╭
- setCount 执行只是把新状态添加到了 update 队列中,但是并没有更新最终值,而 setTimeout 中缓存了之前的旧值
- 换句话解释,setTimeout 是通过闭包缓存的上一轮的变量,而 setCount 之后重新执行创建了新的变量,所以就不一样了
# useMemo
import React, { memo, useState, useMemo } from "react";
function App() {
const [value, setValue] = useState(0);
const increase = useMemo(() => {
if(value > 2) return value + 1;
}, [value]);
return (
<div>
<Child value={value} />
<button
type="button"
onClick={() => {
setValue(value + 1);
}}
>
value:{value},increase:{increase || 0}
</button>
</div>
);
}
const Child = memo(function Child(props) {
console.log('Child render')
return <h1>value:{props.value}</h1>;
});
export default App;
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- useMemo()是定义一段函数逻辑是否重复执行,来处理开销较大的计算,最后返回一个值
- 传入 useMemo 的函数会在渲染期间执行,不要在这个函数内部执行与渲染无关的操作
- 如果在useMemo中执行useState之类的会产生死循环,并警告,因为useMemo是在渲染中进行的,你在其中操作DOM后,又会导致触发memo
# useRef
import React, { Component, useEffect, useRef } from 'react';
function App() {
const childRef = useRef();
useEffect(() => {
console.log('useRef')
console.log(childRef.current)
childRef.current.handleLog();
}, [])
return (
<div>
<h1>Hello World!</h1>
<Child ref={childRef} count="1"/>
</div>
)
}
// 因为函数组件没有实例,如果想用ref获取子组件的实例,子组件组要写成类组件
class Child extends Component {
handleLog = () => {
console.log('Child Component');
}
render() {
const { count } = this.props;
return <h2>count: { count }</h2>
}
}
export default App;
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
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
- 获取DOM元素的节点
- 获取子组件的实例
- 渲染周期之间共享数据的存储(state不能存储跨渲染周期的数据,因为state的保存会触发组件重渲染)
# memo
使用 React.memo 把组件包起来,可以避免不必要的刷新,类似 PureComponent
使用 memo + useCallback 能有效的调高性能
export default React.memo(Example);
1
# hooks 的结构
- memorizedState 保存 state 的值
- queue 缓存队列,储存多次更新行为
- 执行下一个 useState 对应的 hooks 对象
# memorizedState
简版的 useState 和 useEffect
let memorizedState = []; // hooks存放在这个数组
let cursor = 0; // 当前 memorizedState 下标
function useState(initialValue) {
memorizedState[cursor] = memorizedState[cursor] || initialValue;
const currentCursor = cursor;
function setState(newState) {
memorizedState[currentCursor] = newState;
render();
}
return [memorizedState[cursor++], setState]; // 返回当前state,并把cursor加1
}
function useEffect(callback, depArray) {
const hasNoDeps = !depArray;
const deps = memorizedState[cursor];
const hasChangedDeps = deps
? !depArray.every((el, i) => el === deps[i])
: true;
if (hasNoDeps || hasChangedDeps) {
callback();
memorizedState[cursor] = depArray;
}
cursor;
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let memorizedState = []; // hooks存放在这个数组
function useReducer(reducer, initialArg, init) {
let initState = void 0;
if (typeof init !== "undefined") {
initState = init(initialArg);
} else {
initState = initialArg;
}
function dispatch(action) {
memorizedState = reducer(memorizedState, action);
render();
}
memorizedState = memorizedState || initState;
return [memorizedState, dispatch];
}
function useState(initState) {
return useReducer((oldState, newState) => newState, initState);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19