Reack Hook

Hook简介

React 16.7.0开始推行Hook,到 React 16.8.0 Hook 稳定,Hooks开始被推广使用,它解决了传统使用生命周期而导致的相关代码逻辑分离、不相关代码逻辑混合在一个生命周期中、class中复杂的this指向、class不能被很好的压缩、class可能导致热重载不稳定

Hook为开发者提供了可以使用function创建微state,且一个state由一个对应的函数管理,还提供了专门处理副作用、实现redux、性能优化等功能,并且100%向后兼容,个人认为Hook是react未来发展的趋势,但并不意味着摒弃class。

Hook API

1、useState

useState返回一个数组,第一个值为state,第二个值为状态管理函数

值得注意的是:为了使得状态的操作函数 与其他函数区别开且和其对应的状态联系起来,在命名上有如下约定规则: set + 状态名

如有状态count,则对应状态管理函数命名为 setCount

函数签名

1
const [状态名, 状态管理函数] = useState(状态初始值);

使用实例

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

function Example() {
// 解构赋值获取useState返回的值,并给他们取名为count和setCount
const [count, setCount] = useState(0);

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

状态管理函的实参必须有返回值,且此返回值会赋值给对应状态。

除上述使用形式外,还可以如下使用

1
2
3
setCount(()=>{
return count+1;
})

对应使用场景

1
2
3
4
5
6
7
8
9
10
// 在原有state的基础上改变数据
const [person,setPerson] = useState({
name: '王老板',
age: 18
})

setState( state => ({
...person,
age: ++state.age
}) )

踩坑记录

  • useState中的数据务必是immutable数据,若两次传入同一对象则不会触发组件更新,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { useState } from 'react'
export default props => {
const [list, setList] = useState([1, 5, 3, 9])
return <>
<ul>
{list.map((item, idx) => <li key={String(idx)}>{item}</li>)}
</ul>
{/* sort 不生成副本,直接返回原数组 */}
<button onClick={()=> {setList(list.sort((a, b) => a - b))}}>sort</button>
{/* slice 返回一个新的副本数组 */}
<button onClick={()=> {setList(list.slice().sort((a, b) => a - b))}}>slice</button>
</>
}

点击sort按钮后并不会出发更新!

  • useState对应的state只要发生改变,无论组件是否使用了state,该组件都会发生更新;

    useRef所保存的值,只有在组件中被使用且发生改变时,组件才会更新;

    可以参照两者的区别,根据不同场景来判断使用哪种方式保存数据。

  • useState 是将新值直接覆盖掉旧值,而不是合并

    1
    2
    const [temp,setTemp] = useState({a: 1, b: 2});
    setTemp({a: 2}); // temp = {a: 2}

2、useEffect

使用useEffect来处理副作用

函数签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1  组件初次渲染后执行一次,依赖项每次改变时执行一次
useEffect(()=>{
//副作用动作
},[依赖项])

//2 组件初次渲染后执行一次
useEffect(()=>{
//副作用动作
},[])

//3 组件初次渲染后执行一次,组件每次更新后执行一次
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
import React, { useState, useEffect, useRef } from 'react'

export default function Example() {
const [count, setCount] = useState(0);
const [val, setVal] = useState(0);
const num = useRef(0);
useEffect(()=>{
console.log('1')
},[count]);

useEffect(()=>{
console.log('2')
},[]);

useEffect(()=>{
console.log('3')
});

return <div>
<button onClick={()=>{setCount(count + 1)}}>COUNT</button>
<button onClick={()=>{setVal(val + 1)}}>VAL</button>
<button onClick={()=>{num.current += 1}}>NUM</button>
</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//该组件初次渲染后的执行结果
1
2
3

//点击一次 COUNT按钮 后的执行结果
1
3

//点击一次 VAL按钮 后的执行结果
3

//点击一次 NUM按钮 后的执行结果

(1)处理无需清除的effect

有时候,我们只想在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。因为我们在执行完这些操作之后,就可以忽略他们了。

使用实例

​ 需求:监听url的变化来发送网络请求,保存返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { useState, useEffect } from 'react'
import ajax from '@utils/ajax'
export default function Example({ location }) {

const [data, setData] = useState({});

useEffect(()=>{
getData();
},[location]);

const getData = () => {
ajax.post().then(res => {
setData(res);
})
}
return <div>{data}</div>
}

当location发生变化时,useEffect中函数就会自动执行

(2)处理需要清除的effect

之前,我们研究了如何使用不需要清除的副作用,还有一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!

在useEffect中可选的返回一个清除函数,该清除函数会在组件卸载时自动执行,以达到清除effect的目的

函数签名

1
2
3
4
5
6
7
//useEffect的第二个参数不影响 ‘清除effect’ 动作
useEffect(()=>{
//副作用动作
return () => {
//清除effect
}
})

使用示例

1
2
3
4
5
6
7
8
9
10
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

踩坑记录

  • useEffectuseLayoutEffect中使用asyncawait语法时,要注意分离async默认返回的promiseuseEffectuseLayoutEffectcleanup函数,不要将async的返回函数返给useEffect。使用IIFE解决
1
2
3
4
5
useEffect(() => {
(async () => {
await fetchSomething();
})();
}, []);

3、useMemo与useCallback

  1. 当组件state被修改时就会触发组件的重新渲染,无论前后state是否一致
  2. 父组件更新,子组件会自动更新
  3. 组件更新时,会卸载所有function,并重新创建function

这就出现了性能问题,当更新前后状态一致时,是无需更新的。

在之前使用生命周期时,我们通常的解决方案是调用生命周期钩子函数shouldComponentUpdate来判断新老props、states是否发生变化来决定当前组件是否需要更新(原理可参见 React 的 Diff)

Hooks出现后,我们可以直接使用function的形式来创建组件状态,但function自身并没有shouldComponentUpdate判断前后状态的能力。并且,每当函数组件被调用都会执行内部的所有的逻辑,其性能损耗显而易见。

useMemo 与 useCallback 的区别与联系

实际上useCallback是基于useMemo实现的

1
2
3
function useCallback(callback, args) {
return useMemo(() => callback, args);
}
  1. useMemo是返回callback执行后的结果
  2. useCallback 是直接返回被useMemo修饰的callback函数

(1)useMemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

import React from 'react';
export default function WithoutMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');

function expensive() {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}
return <div>
<h4>{count}-{val}-{expensive()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}

在这个组件中含有一个计算量很大的函数expensive,当我们修改 conut val任意一个状态时,expensive都会被触发,尽管valexpensive的计算毫无关系。

使用useMemo来解决该问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from 'react';
export default function WithoutMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');

//使用useMemo
const expensive = useMemo(() {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
},[count])

return <div>
<h4>{count}-{val}-{expensive}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}

我们可知,expensive的计算只与count相关,则可使用useMome添加依赖值count

当且仅当count发生有效改变时才会执行相应函数,并返回缓存值给expensive

(2)useCallback

useCallback的特点、作用和用法与useMemo类似,但是他返回一个缓存的函数。

除了利用useMemouseCallback的缓存特性以达到 ”减少某函数不必要的计算“ 外,还可以利用这特性实现 “避免子组件不必要的更新”,这里以useCallback为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');

const callback = useCallback(() => {
return count;
}, [count]);
return <div>
<h4>{count}- {val}</h4>
<Child callback={callback}/>
</div>;
}

function Child({ callback }) {
const [count, setCount] = useState(() => callback());
useEffect(() => {
setCount(callback());
}, [callback]);
return <div>
{count}
</div>
}

例子中的Child组件中 是依赖于父组件传递来的callback来创建的函数,一旦父组件中的callback发生改变则Child组件就会发生更新,若父组件中的callback不使用useCallback来封装,则父组件中的任意变量发生改变都会导致callback的变化进而导致子组件不必要的更新。

此外,所有依赖state或props来创建的函数,需要用到缓存函数的地方都是useCallback的使用场景。

踩坑记录

使用lodash中的debounced

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

const Example = () => {
const [value, setValue] = useState('');

const _debounce = debounce((value)=>{
console.log(value)
},3000);

const _onChange = (e) => {
let value = e.target.value;
setValue(value)
_debounce(value);
}
return <div>
<input type="text" onChange={_onChange} value={value} />
</div>
}

export default Example

每当onChange被触发一次,都会执行setValue,进而导致刷新组件,使得每次都生成新的debounce,这就失去了函数防抖的效果。而此处的矛盾点就是“函数刷新导致生成了新的debounce”,这里就可以使用useCallback来解决。

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

const Example = () => {
const [value, setValue] = useState('');

const _debounce = useCallback(debounce((value)=>{
console.log(value);
},3000),[]);

const _onChange = (e) => {
let value = e.target.value;
setValue(value);
_debounce(value);
}
return <div>
<input type="text" onChange={_onChange} value={value} />
</div>
}

export default Example

这样每次组件刷新后执行的debounce都是同一个函数,进而使得debounce的防抖效果生效。

4、useContext与useReducer

(1)useContext

实现同一子树下所有节点可统一共享子树根节点的数据

函数签名

1
const value = useContext(MyContext);

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

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。

别忘记 useContext 的参数必须是 context 对象本身

  • 正确: useContext(MyContext)
  • 错误: useContext(MyContext.Consumer)
  • 错误: useContext(MyContext.Provider)

调用了 useContext 的组件总会在 context 值变化时重新渲染。

使用实例:

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, { useContext } from 'react'
//创建Context对象,并附默认值 {count: 1}
const MyContext = React.createContext({ count: 1 })
const [count, setState] = useState(1)

export default function Example() {

const Son = () => {
return <div>
<Grandson></Grandson>
</div>
}

const Grandson = () => {
//使用useContext获取Context对象
const obj = useContext(MyContext);
return <div>
Grandson---{obj.count}
</div>
}
return <MyContext.Provider value={{ count: count }}>
<button onClick={() => { setState(count + 1) }}>ADD</button>
<Son></Son>
</MyContext.Provider>
}

当点击按钮改变count时,所有消费者组件都会随之发生更新,这也就达到了跨层级组件直接共享数据的目的。

(2)useReducer

函数签名

1
2
3
4
// reducer就是平时redux那种reducer函数
// initialState 初始化的state状态
// init 一个函数用于惰性计算state初始值
const [state, dispatch] = useReducer(reducer, initialArg, init);

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(统一管理数据,并对action加以限制)

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。

initialArg作为state的初始值,若存在init,则init会将initalArg处理后的值作为state的初始值

reducer对应于dispatch,使用dispatch所传入的实参对应于reduceraction

使用实例

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


const initialState = 0;

const init = (initialCount) => {
return { count: initialCount }
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'RESET':
return init(action.payload)
default:
throw new Error();
}
}

export default props => {
const [state, dispatch] = useReducer(reducer, initialState, init);
return <div>
Count: {state.count}
<br />
<button onClick={() => dispatch({ type: 'decrement' })}>SUB</button>
<br />
<button onClick={() => dispatch({ type: 'increment' })}>ADD</button>
<br />
<button onClick={() => dispatch({ type: 'RESET', payload: initialState })}>RESET</button>
</div>
}

(3)useContext与useReducer结合使用

结合使用useContext和useReducer以实现共享数据的统一管理和共享数据安全性保证

使用实例

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

export default props => {
const MyContext = React.createContext({ count: 1 });

const init = (initalCount) => {
return { count: initalCount }
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'RESET':
return init(action.payload)
default:
throw new Error();
}
}

const initialCount = 0;
const [state, dispatch] = useReducer(reducer, initialCount, init);

const Son1 = () => {
return <Grandson1></Grandson1>
}

const Son2 = () => {
const obj = useContext(MyContext);
return <div>
Son2---{obj.count}
</div>
}

const Grandson1 = () => {
const obj = useContext(MyContext);
return <div>
Grandson1---{obj.count}
</div>
}
return <MyContext.Provider value={{ count: state.count }}>
<button onClick={() => dispatch({ type: 'decrement' })}>SUB</button>
<br />
<button onClick={() => dispatch({ type: 'increment' })}>ADD</button>
<br />
<button onClick={() => dispatch({ type: 'RESET', payload: initialCount })}>RESET</button>
<Son1></Son1>
<Son2></Son2>
</MyContext.Provider>
}

我们现在已经完成了useContext与useReducer的配合使用,我们现在将代码抽离出来,使其可以在任何组件中都可以快速调用

目录结构

1
2
3
4
5
6
7
8
9
10
-example
-components
-Son
-index.js
-Grandson
-index.js
-store
-index.js
-reducer.js
-index.js

使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// example\store\index.js
import React, { useReducer, useContext } from 'react'
import { defaultState, reducer, init } from './reducer'

const MyContext = React.createContext();

const Context = props => {

//将useReducer的返回值 [state,dispatch] 作为参数
const contextValue = useReducer(reducer, defaultState, init);

return <MyContext.Provider value={contextValue}>
{props.children}
</MyContext.Provider>
}

const useMyContext = () => {
return useContext(MyContext)
}

export {
Context,
useMyContext
}
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
// example\store\reducer.js
export const defaultState = {
count: 0
}

export const init = (initalState) => {
return {
...initalState,
count: initalState.count
}
}

export const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return {
...state,
count: state.count + 1
};
case 'decrement':
return {
...state,
count: state.count - 1
};
case 'RESET':
return init(defaultState)
default:
throw new Error();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// example\components\Grandson\index.js
import React from 'react'
import { useMyContext } from '../../store'
export default props => {
const [state, dispatch] = useMyContext();
return <div>
Grandson --- {state.count}
<button onClick={() => { dispatch({ type: 'increment' }) }}>Grandson ADD</button>
<button onClick={() => { dispatch({ type: 'decrement' }) }}>Grandson SUB</button>
<button onClick={() => { dispatch({ type: 'RESET' }) }}>Grandson RESET</button>
</div>
}
1
2
3
4
5
6
7
// example\components\Son\index.js
import React from 'react'
import Grandson from '../Grandson'

export default props => {
return <Grandson></Grandson>
}
1
2
3
4
5
6
7
8
9
//example\index.js
import React from 'react'
import Son from './components/Son'
import { Context } from './store'
export default props => {
return <Context>
<Son></Son>
</Context>
}

5、useRef

函数签名

1
const refContainer = useRef(initialValue);

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

使用实例

1
2
3
4
5
6
import React from 'react'

export default props => {
const refContainer = useRef(null);
return <div ref={refContainer}></div>
}

除此之外,我们可以使子组件接受父组件的ref,让父组件有控制组件的能力,这里要借助forwardRef

使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// father
import React, { useRef } from 'react'
import Son from './components/Son'
export default props => {
const refContainer = useRef(null);
const changeInput = () => {
refContainer.current.value = '啊,我被改了!'
}

return <div >
<button onClick={changeInput}>changeInput</button>
<Son ref={refContainer}></Son>
</div>
}
1
2
3
4
5
6
7
8
// Son
import React, { forwardRef } from 'react'
const Son = (props,ref) => {
return <div >
<input ref={ref}></input>
</div>
}
export default forwardRef(Son)

useRef 声明常量以保存可变值

除了使用useRef绑定DOM,它另一个重要作用就是声明一个常量保存可变值,绕开React的Capture Value特性

保存常量这一功能实际上是基于useMemo实现的

1
2
3
4
> function useRef (initialValue) {
> return useMemo (() => ({ current : initialValue }), []);
> }
>

可见 就是初始化的时候创建一个{current:initialValue},不依赖任何数据,需要手动赋值修改

使用实例

1
2
3
4
5
6
7
8
9
10
11
12
import React, { useRef } from 'react'
export default props => {
const count = useRef(0);
const add = () => {
count.current += 1;
}

return <div >
<h1>{count.current}</h1>
<button onClick={add}>ADD</button>
</div>
}

踩坑记录

  • 注意与useState的区别与联系

6、useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用

使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//father
import React, { useRef } from 'react'
import Son from './components/Son'
export default props => {
const refContainer = useRef(null);
const changeInput = () => {
refContainer.current.focus()
// console.log(refContainer)
}

return <div >
<button onClick={changeInput}>changeInput</button>
<Son ref={refContainer}></Son>
</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Son
import React, { useRef, forwardRef, useImperativeHandle } from 'react'

function Son(props, ref) {
//新建一个ref 将此ref绑定在本组建内的input上
const inputRef = useRef();
//将 使得本组件input获取焦点的方法赋给父组件传递来的ref上
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
}

export default forwardRef(Son);

7、useLayoutEffect

1
useLayoutEffect( () => { }, [ 依赖项 ] );

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。

8、useDebugValue

1
useDebugValue(value)

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

1
2
3
4
5
6
7
8
9
10
11
12
> function useFriendStatus(friendID) {
> const [isOnline, setIsOnline] = useState(null);
>
> // ...
>
> // 在开发者工具中的这个 Hook 旁边显示标签
> // e.g. "FriendStatus: Online"
> useDebugValue(isOnline ? 'Online' : 'Offline');
>
> return isOnline;
> }
>

自定义Hook

在自定义函数中封装原有的Hook

约定该函数的名称为 use开头

官网案例

由于isOnline的值是监听网络的结果决定的,则只需要暴露state即可

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

function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);

useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});

return isOnline;
}

我的案例

当自定义一个Hook,和普通Hook一样使用时,也可以将state对应的操作函数暴露出来

1
2
3
4
5
6
7
import React, { useState } from 'react'
//自定义Hook
export default function useFriendStatus(value) {
const [isOnline, setIsOnline] = useState(value || false);

return [isOnline, setIsOnline];
}

使用

1
2
3
4
5
6
7
8
9
10
import React from 'react'
import { useFriendStatus } from './hooks';
export default props => {
const [online, setOnline] = useFriendStatus(false);

return <div>
{online ? 'true' : 'false'}
<button onClick={() => { setOnline(!online) }}>SWITCH</button>
</div>
}

Hook的规则

1、只在组件顶层中使用Hook

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useStateuseEffect 调用之间保持 hook 状态的正确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { useState } from 'react'

function Example(props) {
const [count, setCount] = useState(0); //Yes

if(props.id){
// const [count, setCount] = useState(0); //No
}

const fn = () => {
// const [count, setCount] = useState(0); //No
}

return <div></div>
}

2、只在React 函数中调用Hook

不要在普通的 JavaScript 函数中调用 Hook。你可以:

  • 在 React 的函数组件中调用 Hook
  • 在自定义 Hook 中调用其他 Hook

遵循此规则,确保组件的状态逻辑在代码中清晰可见。

3、其他

  1. Hook在函数组件中使用(这里不称为 无状态组件),在class组件中是不起作用的
  2. 所有的Hook都是在DOM更新之后执行的

经验笔记

  1. useState将函数入参给useState时,该函数是在DOM渲染前执行的

    1
    2
    3
    4
    const [value,setValue] = useState(()=>{
    console.log('笨鸟先飞');
    return 0
    })
  2. useEffect是在DOM渲染结束后执行的

  3. useLayoutEffect与DOM渲染同步进行,在此函数中获取DOM,会导致偶发性报错

  4. 组件不要声明在另一个组件内

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 错误
    const FatherComponent = (props) => {
    const SonComponent = () => { return <div></div>}
    return <div>
    <SonComponent></SonComponent>
    </div>
    }

    // 正确
    const SonComponent = () => { return <div></div>}

    const FatherComponent = (props) => {
    return <div>
    <SonComponent></SonComponent>
    </div>
    }

参照文章:

  1. React官方文档
  2. useMemo与useCallback使用指南
  3. useReducerde使用和原理

文章还会继续更新完善

最后更新: 2019年12月03日 16:13

原始链接: https://HowlCN1997.github.io/2019/10/16/React Hook/

× 请我吃糖~
打赏二维码