2019-05-16
React Hooks
Hooks In React.ts
约半年前 React 大力推广其新出的 Hooks API,而最近沉迷 TS 不能自拔,因此很多地方我直接 Hooks + TS 来写了。

# useState

利用 useState 可以用来声明变化的状态,比如一个 点击计数:
00import * as React from "react";
01
02export function Counter() {
03    const [count, setCount] = React.useState(0);
04    const inc = () => setCount(count + 1);
05    
06    return (
07        <div onClick={ inc }>Click Me: { count }</div>
08    )
09}
当调用 setCount 的时候, Counter 会重新渲染,此时的 count 是调用 setCount 的那个参数,用来设置新的 count 以得到最新的状态对应的视图。
React 通过 React Hooks 调用的顺序来推断状态的值及其 dispatcher (即上面的 setCount),比如在下面代码中,程序根据 count 的状态来生成 test test2
00import * as React from "react";
01
02export function Counter() {
03    const [count, setCount] = React.useState(0);
04
05    let temp1: [number, (n: number) => void];
06    let temp2: [number, (n: number) => void];
07
08    if (count >= 3) {
09        temp1 = React.useState(0);
10        temp2 = React.useState(6);
11    } else {
12        temp2 = React.useState(6);
13        temp1 = React.useState(0);
14    }
15
16    return (
17        <div>
18            <div onClick={() => {
19                setCount(count + 1);
20            }}>Click Me: { count }</div>
21            
22            {/* test1 counter */}
23            <div onClick={() => {
24                temp1[1](temp1[0] + 1);
25            }}>test1: { temp1[0] }</div>
26
27            {/* test2 counter */}
28            <div onClick={() => {
29                temp2[1](temp2[0] + 1);
30            }}>test2: { temp2[0] }</div>
31        </div>
32    );
33}
上述程序,一开始显示:
00Click Me: 0
01test1: 0
02test2: 6
而后点击 Click Me 直到 3 的时候,结果会变成这样:
00Click Me: 3
01test1: 6
02test2: 0
说明 React 里用 useState 的调用序来作为其状态的 key。(在传统类组件里是通过 state 上的键名来唯一确定某个状态)

# useEffect

useEffect 用于监听各种副作用,比如在 counter 中 count 初始值延迟初始化为一个随机数:
00function Hello() {
01    const [count, setCount] = React.useState(0);
02    const inc = () => setCount(count + 1);
03
04    // 副作用函数
05    React.useEffect(() => {
06        console.log('init');
07        const r = Math.round(Math.random() * 10);
08        setTimeout(() => {
09            setCount(r);
10        }, 500);
11    }); // 注意这里没有写 deps 依赖
12
13    return (
14        <div onClick={ inc }>
15            Click Me: { count }
16        </div>
17    );
18}
这个初始值取决于一个随机数,但是实际运行中,却是每 0.5s 触发一次副作用函数,其原因在于我们没有指定其副作用的依赖,这需要设置 useEffect 来告诉副作用应该何时运行:
00function Hello() {
01    const [count, setCount] = React.useState(0);
02    const inc = () => setCount(count + 1);
03
04    // 副作用函数
05    React.useEffect(() => {
06        console.log('init');
07        const r = Math.round(Math.random() * 10);
08        setTimeout(() => {
09            setCount(r);
10        }, 500);
11    }, []  /*
12        注意这个空数组,它表明这个副作用没有任何依赖
13        此时这个副作用退化为类组件中的 componentDidMount 生命周期钩子 
14    */);
15
16    return (
17        <div onClick={ inc }>
18            Click Me: { count }
19        </div>
20    );
21}
useEffect 的第二个参数叫做 依赖列表 它用于指定副作用的依赖,当列表中的项变化的时候会触发副作用重新刷新。上面这种情况意味着副作用不依赖任何对象,此时副作用函数退化为类组件中的 componentDidMount 生命周期钩子。在下面的例子里,我们以 count 本身作为依赖对象:
00function Hello() {
01    const [count, setCount] = React.useState(0);
02    const inc = () => setCount(count + 1);
03
04    // 副作用函数
05    React.useEffect(() => {
06        console.log('Count Changed'); 
07        ajax('/set-new-count', { count });
08    }, [ count ]);
09
10    return (
11        <div onClick={ inc }>
12            Click Me: { count } { text }
13        </div>
14    );
15}
在上面的例子里,指定 count 为依赖对象使得该副作用域变成 count watcher 了,在这个副作用里面我们可以吧 count 传到服务器,但是,这会出现另外一种情况,如果用户疯狂连续触发 inc 呢?这样可能会疯狂对服务器发起请求,此时需要利用副作用函数的返回值来消除作用域,可以这样处理副作用函数,用于消除其对组件、对程序的影响:
00function Hello() {
01    const [count, setCount] = React.useState(0);
02    const inc = () => setCount(count + 1);
03
04    // 副作用函数
05    React.useEffect(() => {
06        console.log('Count Changed');
07
08        const t = setTimeout(() => {
09            ajax('/set-new-count', { count });
10        });
11
12        return () => clearTimeout(t);
13    }, [ count ]);
14
15    return (
16        <div onClick={ inc }>
17            Click Me: { count } { text }
18        </div>
19    );
20}
顺便提一下,组件的 props 也可以作为依赖对象:
00function Test(props: any) {
01    React.useEffect(() => {
02        console.log('props changed');
03    }, [ props ]);
04
05    return <div>...</div>
06}
至此,我们已可以使用 useStateuseEffect 来完成类组件做的事了。

# Context

利用 useContext 纯函数组件里也可以使用 React Context:
00import * as React from "react";
01
02type MsgCtx = {
03    msg: string;
04    setMsg: (msg: string) => void
05}
06
07const msgCtx = React.createContext({} as MsgCtx);
08
09export function App() {
10    return (
11        <MsgCtxProvider>
12            <MyInnerComponent />
13        </MsgCtxProvider>
14    )
15}
16
17function MsgCtxProvider(props: React.PropsWithChildren<{}>) {
18    const [msg, setMsg] = React.useState('');
19
20    const inner = (
21        <div>
22            <div>{ msg }</div>
23            { props.children }
24        </div>
25    );
26
27    return (
28        <msgCtx.Provider value={{ msg, setMsg }}>
29            { inner }
30        </msgCtx.Provider>
31    );
32}
33
34function MyInnerComponent() {
35    const ctx = React.useContext(msgCtx);
36
37    return (
38        <div onClick={ () => {
39            ctx.setMsg('hello, msg');
40        } }>
41            click here set msg:
42        </div>
43    )
44}
Context 的意义在于将另外一个组件里的 hooks 给其他组件用,从而达到 lift state up 的效果(上述代码中将 msg setMsg 这两个 hooks 写在了 MsgCtxProvider 里,MyInnerComponent 可以通过 useContext 来使用这两个 hooks (注:context 也可以作为依赖列表)。

# Todo App Example

以下是利用 Hooks API 去实现 TODO 应用的全部代码,可以参考一下。




回到顶部