# 0.前言
创建项目
create-react-app hook-ts-demo --template typescript
# 一.函数式组件中使用
- 方式一
import React from "react";
type UserInfo = {
name: string,
age: number,
};
export const User = ({ name, age }: UserInfo) => {
return (
<div className="App">
<p>{name}</p>
<p>{age}</p>
</div>
);
};
const user = <User name="vortesnail" age={25} />;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 也可以通过以下方式使用有类型约束的函数式组件:
import React from "react";
type UserInfo = {
name: string,
age: number,
};
export const User: React.FC<UserInfo> = ({ name, age }) => {
return (
<div className="User">
<p>{name}</p>
<p>{age}</p>
</div>
);
};
const user = <User name="vortesnail" age={25} />;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上述代码中不同之处在于:
export const User = ({ name, age }: UserInfo) => {};
export const User: React.FC<UserInfo> = ({ name, age }) => {};
2
- 使用函数式组件时需要将组件申明为
React.FC类型,也就是Functional Component的意思,另外props需要申明各个参数的类型,然后通过泛型传递给React.FC。
虽然两种方式都差不多,但我个人更喜欢使用 React.FC 的方式来创建我的有类型约束的函数式组件,它还支持children 的传入,即使在我们的类型中并没有定义它:
export const User: React.FC<UserInfo> = ({ name, age, children }) => {
return (
<div className="User">
<p>{name}</p>
<p>{age}</p>
<div>{children}</div>
</div>
);
};
const user = (
<User name="vortesnail" age={25}>
I am children text!
</User>
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 我们也并不需要把所有参数都显示地解构:
export const User: React.FC<UserInfo> = (props) => {
return (
<div className="User">
<p>{props.name}</p>
<p>{props.age}</p>
<div>
{/*仍可以拿到 children*/}
{props.children}
</div>
</div>
);
};
const user = (
<User name="vortesnail" age={25}>
I am children text!
</User>
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 二.useState 中使用
怎么使用 useState?
const [count, setCount] = useState < number > 0;
- 参数为基本类型时的常规使用:
import React, { useState } from "react";
const Counter: React.FC<{ initial: number }> = ({ initial = 0 }) => {
const [count, setCount] = useState < number > initial;
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>加</button>
<button onClick={() => setCount(count - 1)}>减</button>
</div>
);
};
export default Counter;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 参数为对象类型时的使用:
import React, { useState } from "react";
type ArticleInfo = {
title: string,
content: string,
};
const Article: React.FC<ArticleInfo> = ({ title, content }) => {
const [article, setArticle] = useState < ArticleInfo > { title, content };
return (
<div>
<p>Title: {article.title}</p>
<section>{article.content}</section>
<button
onClick={() =>
setArticle({
title: "下一篇",
content: "下一篇的内容",
})
}
>
下一篇
</button>
</div>
);
};
export default Article;
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
- 在我们的参数为对象类型时,需要特别注意的是,
setXxx并不会像this.setState合并旧的状态,它是完全替代了旧的状态,所以我们要实现合并,可以这样写(虽然我们以上例子不需要):
setArticle({
title: "下一篇",
content: "下一篇的内容",
...article,
});
2
3
4
5
# 三.useEffect 中使用
为啥使用 useEffect?
你可以把 useEffect看做 componentDidMount, componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
怎么使用 useEffect?
useEffect(() => {
...
return () => {...}
},[...])
2
3
4
- 每当状态改变时,都要重新执行
useEffect的逻辑:
import React, { useState, useEffect } from "react";
let switchCount: number = 0;
const User = () => {
const [name, setName] = useState < string > "";
useEffect(() => {
switchCount += 1;
});
return (
<div>
<p>Current Name: {name}</p>
<p>switchCount: {switchCount}</p>
<button onClick={() => setName("Jack")}>Jack</button>
<button onClick={() => setName("Marry")}>Marry</button>
</div>
);
};
export default User;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 即使每次状态都改变,也只执行第一次 useEffect 的逻辑:
useEffect(() => {
switchCount += 1;
}, []);
2
3
- 根据某个状态是否变化来决定要不要重新执行:
const [value, setValue] = useState < string > "I never change";
useEffect(() => {
switchCount += 1;
}, [value]);
2
3
4
因为 value 我们不会去任何地方改变它的值,所以在末尾加了[value]后,useEffect内的逻辑也只会执行第一次,相当于在 class 组件中执行了componentDidMount ,后续的 shouldComponentUpdate 返回全部是false。
- 组件卸载时处理一些内存问题,比如清除定时器、清除事件监听:
useEffect(() => {
const handler = () => {
document.title = Math.random().toString();
};
window.addEventListener("resize", handler);
return () => {
window.removeEventListener("resize", handler);
};
}, []);
2
3
4
5
6
7
8
9
10
11
# 四.useRef 中使用
为啥使用 useRef?
它不仅仅是用来管理DOM ref的,它还相当于this, 可以存放任何变量,很好的解决闭包带来的不方便性。
怎么使用 useRef?
const [count, setCount] = useState < number > 0;
const countRef = useRef < number > count;
2
- 闭包问题:想想看,我们先点击 加 按钮 3 次,再点 弹框显示 1 次,再点 加 按钮 2 次,最终
alert会是什么结果?
import React, { useState, useEffect, useRef } from "react";
const Counter = () => {
const [count, setCount] = useState < number > 0;
const handleCount = () => {
setTimeout(() => {
alert("current count: " + count);
}, 3000);
};
return (
<div>
<p>current count: {count}</p>
<button onClick={() => setCount(count + 1)}>加</button>
<button onClick={() => handleCount()}>弹框显示</button>
</div>
);
};
export default Counter;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
结果是弹框内容为 current count: 3,为什么?
当我们更新状态的时候, React会重新渲染组件, 每一次渲染都会拿到独立的count 状态, 并重新渲染一个 handleCount 函数. 每一个 handleCount 里面都有它自己的count。
- 那如何显示最新的当前
count呢?
const Counter = () => {
const [count, setCount] = useState < number > 0;
const countRef = useRef < number > count;
useEffect(() => {
countRef.current = count;
});
const handleCount = () => {
setTimeout(() => {
alert("current count: " + countRef.current);
}, 3000);
};
//...
};
export default Counter;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 因为变更
.current属性不会引发组件重新渲染,根据这个特性可以获取状态的前一个值:
const Counter = () => {
const [count, setCount] = useState < number > 0;
const preCountRef = useRef < number > count;
useEffect(() => {
preCountRef.current = count;
});
return (
<div>
<p>pre count: {preCountRef.current}</p>
<p>current count: {count}</p>
<button onClick={() => setCount(count + 1)}>加</button>
</div>
);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们可以看到,显示的总是状态的前一个值:
- 操作
Dom节点,类似createRef():
import React, { useRef } from "react";
const TextInput = () => {
const inputEl = useRef < HTMLInputElement > null;
const onFocusClick = () => {
if (inputEl && inputEl.current) {
inputEl.current.focus();
}
};
return (
<div>
<input type="text" ref={inputEl} />
<button onClick={onFocusClick}>Focus the input</button>
</div>
);
};
export default TextInput;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 五.useMemo 中使用
为啥使用 useMemo?
从 useEffect 可以知道,可以通过向其传递一些参数来影响某些函数的执行。 React 检查这些参数是否已更改,并且只有在存在差异的情况下才会执行此。
useMemo做类似的事情,假设有大量方法,并且只想在其参数更改时运行它们,而不是每次组件更新时都运行它们,那就可以使用 useMemo来进行性能优化。
记住,传入useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于useEffect 的适用范畴,而不是 useMemo。
怎么使用 useMemo?
function changeName(name) {
return name + "给name做点操作返回新name";
}
const newName = useMemo(() => {
return changeName(name);
}, [name]);
2
3
4
5
6
7
- 常规使用,避免重复执行没必要的方法:我们先来看一个很简单的例子,以下是还未使用
useMemo的代码:
import React, { useState, useMemo } from "react";
// 父组件
const Example = () => {
const [time, setTime] = useState < number > 0;
const [random, setRandom] = useState < number > 0;
return (
<div>
<button onClick={() => setTime(new Date().getTime())}>
获取当前时间
</button>
<button onClick={() => setRandom(Math.random())}>获取当前随机数</button>
<Show time={time}>{random}</Show>
</div>
);
};
type Data = {
time: number,
};
// 子组件
const Show: React.FC<Data> = ({ time, children }) => {
function changeTime(time: number): string {
console.log("changeTime excuted...");
return new Date(time).toISOString();
}
return (
<div>
<p>Time is: {changeTime(time)}</p>
<p>Random is: {children}</p>
</div>
);
};
export default Example;
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
在这个例子中,无论你点击的是 获取当前时间 按钮还是 获取当前随机数 按钮,<Show />这个组件中的方法changeTime都会执行。
但事实上,点击 获取当前随机数 按钮改变的只会是 children 这个参数,但我们的 changeTime 也会因为子组件的重新渲染而重新执行,这个操作是很没必要的,消耗了无关的性能。
- 使用 useMemo 改造我们的
<Show />子组件:
const Show: React.FC<Data> = ({ time, children }) => {
function changeTime(time: number): string {
console.log("changeTime excuted...");
return new Date(time).toISOString();
}
const newTime: string = useMemo(() => {
return changeTime(time);
}, [time]);
return (
<div>
<p>Time is: {newTime}</p>
<p>Random is: {children}</p>
</div>
);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这个时候只有点击 获取当前时间 才会执行 changeTime 这个函数,而点击 获取当前随机数 已经不会触发该函数执行了。
- 你可能会好奇,
useMemo能做的难道不能用useEffect来做吗? 答案是否定的!如果你在子组件中加入以下代码:
const Show: React.FC<Data> = ({ time, children }) => {
//...
useEffect(() => {
console.log("effect function here...");
}, [time]);
const newTime: string = useMemo(() => {
return changeTime(time);
}, [time]);
//...
};
2
3
4
5
6
7
8
9
10
11
12
13
你会发现,控制台会打印如下信息:
changeTime excuted...
effect function here...
2
正如我们一开始说的:传入 useMemo的函数会在渲染期间执行。 在此不得不提React.memo ,它的作用是实现整个组件的 Pure功能:
const Show:React.FC<Data> = React.memo(({ time, children }) => {...}
所以简单用一句话来概括 useMemo 和 React.memo的区别就是:前者在某些情况下不希望组件对所有 props 做浅比较,只想实现局部Pure功能,即只想对特定的props做比较,并决定是否局部更新。
# 六.useCallback 中使用
为啥使用 useCallback? useMemo 和 useCallback 接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于useMemo 返回的是函数运行的结果, useCallback返回的是函数。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
怎么使用 useCallback?
function changeName(name) {
return name + "给 name 做点操作返回新 name";
}
const getNewName = useMemo(() => {
return changeName(name);
}, [name]);
2
3
4
5
6
7
- 将之前
useMemo的例子,改一下子组件以下地方就 OK 了:
const Show: React.FC<Data> = ({ time, children }) => {
//...
const getNewTime = useCallback(() => {
return changeTime(time);
}, [time]);
return (
<div>
<p>Time is: {getNewTime()}</p>
<p>Random is: {children}</p>
</div>
);
};
2
3
4
5
6
7
8
9
10
11
12
13
# 七.useReducer 中使用
为什么使用 useReducer?
有没有想过你在某个组件里写了很多很多的 useState 是什么观感?比如以下:
const [name, setName] = useState < string > "";
const [islogin, setIsLogin] = useState < boolean > false;
const [avatar, setAvatar] = useState < string > "";
const [age, setAge] = useState < number > 0;
//...
2
3
4
5
怎么使用 useReducer?
import React, { useState, useReducer } from "react";
type StateType = {
count: number,
};
type ActionType = {
type: "reset" | "decrement" | "increment",
};
const initialState = { count: 0 };
function reducer(state: StateType, action: ActionType) {
switch (action.type) {
case "reset":
return initialState;
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
}
function Counter({ initialCount = 0 }) {
const [state, dispatch] = useReducer(reducer, { count: initialCount });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
}
export default Counter;
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
# 八.useContext 中使用
为啥使用 useContext? 简单来说 Context 的作用就是对它所包含的组件树提供全局共享数据的一种技术。
怎么使用 useContext?
export const ColorContext = React.createContext({ color: '#1890ff' })
const { color } = useContext(ColorContext)
// 或
export const ColorContext = React.createContext(null)
<ColorContext.Provider value='#1890ff'>
<App />
</ColorContext.Provider>
// App 或以下的所有子组件都可拿到 value
const color = useContext(ColorContext) // '#1890ff'
2
3
4
5
6
7
8
9
- 根组件注册,所有子组件都可拿到注册的值:
import React, { useContext } from "react";
const ColorContext = React.createContext < string > "";
const App = () => {
return (
<ColorContext.Provider value="#1890ff">
<Father />
</ColorContext.Provider>
);
};
const Father = () => {
return <Child />;
};
const Child = () => {
const color = useContext(ColorContext);
return (
<div style={{ backgroundColor: color }}>Background color is: {color}</div>
);
};
export default App;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 配合
useReducer实现Redux的代替方案:
import React, { useReducer, useContext } from "react";
const UPDATE_COLOR = "UPDATE_COLOR";
type StateType = {
color: string,
};
type ActionType = {
type: string,
color: string,
};
type MixStateAndDispatch = {
state: StateType,
dispatch?: React.Dispatch<ActionType>,
};
const reducer = (state: StateType, action: ActionType) => {
switch (action.type) {
case UPDATE_COLOR:
return { color: action.color };
default:
return state;
}
};
const ColorContext =
React.createContext <
MixStateAndDispatch >
{
state: { color: "black" },
};
const Show = () => {
const { state, dispatch } = useContext(ColorContext);
return (
<div style={{ color: state.color }}>
当前字体颜色为: {state.color}
<button
onClick={() =>
dispatch && dispatch({ type: UPDATE_COLOR, color: "red" })
}
>
红色
</button>
<button
onClick={() =>
dispatch && dispatch({ type: UPDATE_COLOR, color: "green" })
}
>
绿色
</button>
</div>
);
};
const Example = ({ initialColor = "#000000" }) => {
const [state, dispatch] = useReducer(reducer, { color: initialColor });
return (
<ColorContext.Provider value={{ state, dispatch }}>
<div>
<Show />
<button
onClick={() =>
dispatch && dispatch({ type: UPDATE_COLOR, color: "blue" })
}
>
蓝色
</button>
<button
onClick={() =>
dispatch && dispatch({ type: UPDATE_COLOR, color: "lightblue" })
}
>
轻绿色
</button>
</div>
</ColorContext.Provider>
);
};
export default Example;
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
# 参考
[1] https://zhuanlan.zhihu.com/p/36116211
[2] 案例中的代码详见:https://github.com/Carloin/hooks-ts-demo (opens new window)