Hook 是 React 16.8的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

1. useState

使用这个 hook 可以方便我们在函数组件中操作 state,下面例子演示和如何使用 useState 来操作基本类型、数组、对象、函数不同类型的 state。

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
import React, { useState } from 'react';

const Index = () => {
const [count,setCount] = useState(0)
const [obj,setObj] = useState({name:'Lisi'})
const [arr,setArr] = useState([1,2,3])
const [func,setFunc] = useState(()=>{
return {name: 'func'}
})
return (
<div>
<p>count: {count}</p>
<button onClick={()=>setCount(count+1)}>+1</button>
<p>name: {obj.name}</p>
<button onClick={()=>setObj({name:'Mazi'})}>changeObjectName1</button>
<button onClick={()=>setObj({
...obj,
name: 'Loali'
})}>changeObjectName2</button>
<button onClick={()=>setObj(Object.assign({},obj,{name:'Lili'}))}>changeObjectName3</button>
<p>Array: {arr}</p>
<button onClick={()=>setArr(()=>{
arr.push(4)
return [...arr]
})}>changeArray</button>
<p>func: {func.name}</p>
<button onClick={()=>setFunc({name:'function'})}>changeFuncion</button>
</div>
)
}

export default Index

注意的是,在设置 state 时候,对于数组、对象的设置应该返回一个新的数组或者对象,而对于函数设置,相当于设置函数的返回值类型。

2. useEffect

此方法用于执行相应的副作用(DOM操作、数据请求、组件更新),相当于 class 组件中的 componentDidMount、componentDidUpdate、componentWillUnmount,它采用闭包的形式,可以获取 state 和 props 在组件内部执行,在组件挂载之后执行可以达到无阻塞更新的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { useState, useEffect } from 'react';

const Index = () => {
const [count,setCount]=useState(0)

useEffect(()=>{
console.log(count)
},[count])

useEffect(()=>{ // state改变时,不执行,当离开组件时,就会执行
console.log(count)
},[])

return (
<div>
<p>count: {count}</p>
<button onClick={()=>setCount(count+1)}>+1</button>
</div>
)
}

export default Index

3. useContext

此方法接收一个 context 对象(React.createContext的返回值)并返回该 context 的当前值。当前的context值由上层组件中距离当前组件最近的<MyContext.Provider>的value prop决定。

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
import React, { useState, useContext, createContext } from 'react';

const MyContext = createContext();

const ChildContext = ()=> {
let count = useContext(MyContext)
return (<h3>{count}</h3>)
}

const Index = () => {
const [count, setCount] = useState(0)

return (
<div>
<MyContext.Provider value={count}>
<ChildContext></ChildContext>
</MyContext.Provider>
<button onClick={()=>{
setCount(count + 1)
}}>button</button>
</div>
)
}

export default Index

4. useRef

此方法返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { useState, useContext, createContext } from 'react';

const TextInputWithFocusButton = () => {
const inputEl = useRef(null)

const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
}

return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>获取Input焦点</button>
</>
);
}

export default Index

我们可使用 useRef 可以很方便的保存任何可变值;还可以结合 ref 对象以<div ref={myRef} />形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点

5. useMemo

此方法利用 js 的 Memoization 缓存机制把计算的值以 key 的形式保存在内存中,当下次在需要用到这个值的时候直接从内存中读取,而不需要在进行计算,以到达优化运行速度的目的,使用方法和 useEffect 差不多,该方法在渲染期间执行,请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。

我们可以对比一下该方法和 useEffect 的执行顺序,我们可以看到先执行 useMemo 函数,再执行 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
import React, { useState, useEffect, useMemo } from 'react';

const Index = () => {
const [count, setCount] = useState(0)

useEffect(()=>{
console.log('effect')
})
useMemo(()=>{
console.log('Memo')
})
// Memo
// effect
return (
<div>
<p>count:{count}</p>
<button onClick={()=>{
setCount(count + 1)
console.log(count)
}}>buttonCount</button>
</div>
)
}

export default Index

如果我们给它的第二个参数传入空数组,此时不更新任何状态,但要注意的是,state 的数据是改变的,只是没有更新到页面上而已,这就会导致下面代码运行的问题,当我们监听多个状态时,不监听的那个状态被改变,再触发监听的状态改变的时候,不监听的那个状态值也一并会从缓存中取出来更新到页面上。

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, { useState, useMemo } from 'react';

const Index = () => {
const [count, setCount] = useState(0)
const [num, setNum] = useState(0)

let res = useMemo(()=>{
return { count, num }
},[count])

return (
<div>
<p>count:{res.count}; num:{res.num}</p>
<button onClick={()=>{
setCount(count + 1)
console.log(count)
}}>buttonCount</button>
<button onClick={()=>{
setNum(num + 1)
console.log(num)
}}>buttonNum</button>
</div>
)
}

export default Index

当遇到需要在父子使用对应的缓存机制的时候,只需要在子组件中对父组件传过来的 props 值进行 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
// 父组件Index.js
import React, { useState, useMemo } from 'react';
import ChildMome from './ChildMome.js';

const Index = () => {
const [count, setCount] = useState(0)
const [num, setNum] = useState(0)

let res = useMemo(()=>{
return { count, num }
},[count, num])

return (
<div>
<ChildMome c={count} n={num} />
<p>count:{res.count}; num:{res.num}</p>
<button onClick={()=>{
setCount(count + 1)
console.log(count)
}}>buttonCount</button>
<button onClick={()=>{
setNum(num + 1)
console.log(num)
}}>buttonNum</button>
</div>
)
}

export default Index

// 子组件ChildMome.js
import React, { useMemo } from 'react';

const ChildMome = ({c, n}) => {

let childRes = useMemo(()=>{
return { c, n }
},[c, n])

return (
<div>
<h3>Child Memonization</h3>
<p>c:{childRes.c} ;n:{childRes.n}</p>
</div>
)
}

export default ChildMome

6. useCallback

和 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
import React, { useState, useCallback } from 'react';

const Index = () => {
const [count, setCount] = useState(0)

let callback = useCallback(()=>{
console.log(count)
return count
})
// 第二个参数传空数组不监听任何数据

return (
<div>
<p>count:{count};</p>
<h4>{callback()}</h4>
<button onClick={()=>{
setCount(count + 1)
console.log(count)
}}>buttonCount</button>
</div>
)
}

export default Index