2019-02-soolx-React Hooks笔记
React Hooks
React-Hooks于React 16.8版本正式发布。它为不编写类的情况下,为组件实现state管理和其他React功能提供了可能。
Hook的优势
- 可以方便的在组件中复用逻辑
- 可以更加灵活的拆分组件
Hook 规则
- 只在顶层调用Hooks,不要在循环,条件或者嵌套函数中调用
- 仅从React组件或者自定义Hook中调用Hook
Hooks类型
基本Hooks
- useState
- useEffect
- useContent
进阶Hooks
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
使用
useState
基本用法
1 | import React, { useState } from 'react'; |
2 | |
3 | function Example() { |
4 | // Declare a new state variable, which we'll call "count" |
5 | const [count, setCount] = useState(0); |
6 | |
7 | return ( |
8 | <div> |
9 | <p>You clicked {count} times</p> |
10 | <button onClick={() => setCount(count + 1)}> |
11 | Click me |
12 | </button> |
13 | </div> |
14 | ); |
15 | } |
通过useState
声明一个状态变量,array的第一项是我们要声明的状态变量,第二项是可以用来改变该变量的方法。
使用多个状态变量时:
1 | const [age, setAge] = useState(42); |
2 | const [fruit, setFruit] = useState('banana'); |
3 | const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]); |
Lazy initial state
如果state初始值的计算成本比较高,可以采用function的形式,这样初始值只会在初始化阶段计算一次
1 | const [state, setState] = useState(() => { |
2 | const initialState = someExpensiveComputation(props); |
3 | return initialState; |
4 | }); |
useEffect
基本用法
1 | import React, { useState, useEffect } from 'react'; |
2 | |
3 | function Example() { |
4 | const [count, setCount] = useState(0); |
5 | |
6 | // Similar to componentDidMount and componentDidUpdate: |
7 | useEffect(() => { |
8 | // Update the document title using the browser API |
9 | document.title = `You clicked ${count} times`; |
10 | }); |
11 | |
12 | return ( |
13 | <div> |
14 | <p>You clicked {count} times</p> |
15 | <button onClick={() => setCount(count + 1)}> |
16 | Click me |
17 | </button> |
18 | </div> |
19 | ); |
20 | } |
Effect Hook用来实现类似于react生命周期的方法。
useEffect Hook的作用就是componentDidMount, componentDidUpdate和componentWillUnmount的结合,会在首次渲染和组件更新之后执行。
清理阶段
Effect Hook还可以return一个方法,实现清理阶段的逻辑:
1 | import React, { useState, useEffect } from 'react'; |
2 | |
3 | function FriendStatus(props) { |
4 | const [isOnline, setIsOnline] = useState(null); |
5 | |
6 | function handleStatusChange(status) { |
7 | setIsOnline(status.isOnline); |
8 | } |
9 | |
10 | useEffect(() => { |
11 | ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); |
12 | // Specify how to clean up after this effect: |
13 | return () => { |
14 | ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); |
15 | }; |
16 | }); |
17 | |
18 | if (isOnline === null) { |
19 | return 'Loading...'; |
20 | } |
21 | return isOnline ? 'Online' : 'Offline'; |
22 | } |
return的该方法会在组件卸载和每次运行这个effect之前执行,不同于componentWillUnmount。
useEffect可以根据实际的使用情况,多次调用,拆分逻辑。
1 | function FriendStatusWithCounter(props) { |
2 | const [count, setCount] = useState(0); |
3 | useEffect(() => { |
4 | document.title = `You clicked ${count} times`; |
5 | }); |
6 | |
7 | const [isOnline, setIsOnline] = useState(null); |
8 | useEffect(() => { |
9 | ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); |
10 | return () => { |
11 | ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); |
12 | }; |
13 | }); |
14 | |
15 | function handleStatusChange(status) { |
16 | setIsOnline(status.isOnline); |
17 | } |
18 | // ... |
19 | } |
useEffect还可以使用第二个参数来跳过效果,优化性能
1 | useEffect(() => { |
2 | document.title = `You clicked ${count} times`; |
3 | }, [count]); // Only re-run the effect if count changes |
该例子只有在count发生变更的时候才会执行,效果类似于
1 | componentDidUpdate(prevProps, prevState) { |
2 | if (prevState.count !== this.state.count) { |
3 | document.title = `You clicked ${this.state.count} times`; |
4 | } |
5 | } |
跳过效果也适用于清理阶段:
1 | useEffect(() => { |
2 | ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); |
3 | return () => { |
4 | ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); |
5 | }; |
6 | }, [props.friend.id]); // Only re-subscribe if props.friend.id changes |
注意,第二个参数数组中的值,请使用会随时间变化,且可以被effect使用的值。
基于前一次的状态更新
1 | function Counter({initialCount}) { |
2 | const [count, setCount] = useState(initialCount); |
3 | return ( |
4 | <> |
5 | Count: {count} |
6 | <button onClick={() => setCount(initialCount)}>Reset</button> |
7 | <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> |
8 | <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> |
9 | </> |
10 | ); |
11 | } |
useLayoutEffect
和 useEffect
相同,但在所有 DOM 修改结束后同步执行。
useContent
1 | const context = useContext(Context); |
context 变化时,hook 会触发一次 rerender。
useReducer
1 | const [state, dispatch] = useReducer(reducer, initialArg, init); |
2 | |
3 | // 传初始值 |
4 | const [state, dispatch] = useReducer(reducer, { count: initialCount }); |
5 | |
6 | // lazy 初始值 |
7 | function init(initialArg) { |
8 | return { count: initialArg }; |
9 | } |
10 | const [state, dispatch] = useReducer(reducer, initialArg, init); |
reducer 即 redux 里的 reducer,(sate, action) => newState
。
有复杂的 state 逻辑时使用,
- 多个子数据
- 返回的 state 依赖之前的 state
把 dispatch 通过 context 往下传,可解决深层组件的更新问题。(而不是把 callback 传下去)
useReducer 不会像 Redux 一样,把 reducer 执行一遍返回初始值,但可通过 useReducer(reducer, undefined, reducer)
实现。
reducer 返回相同的值时,不会触发更新,值匹配基于 Object.is 比较算法。
useRef
1 | const refContainer = useRef(initialValue); |
常见用法
1 | function TextInputWithFocusButton() { |
2 | const inputEl = useRef(null); |
3 | const onButtonClick = () => { |
4 | // `current` points to the mounted text input element |
5 | inputEl.current.focus(); |
6 | }; |
7 | return ( |
8 | <> |
9 | <input ref={inputEl} type="text" /> |
10 | <button onClick={onButtonClick}>Focus the input</button> |
11 | </> |
12 | ); |
13 | } |
特殊用法
useRef不只是用作DOM绑定。ref对象是一个通用容器,其current
属性是可变的,可以保存任何值.
1 | function Timer() { |
2 | const intervalRef = useRef(); |
3 | |
4 | useEffect(() => { |
5 | const id = setInterval(() => { |
6 | // ... |
7 | }); |
8 | intervalRef.current = id; |
9 | return () => { |
10 | clearInterval(intervalRef.current); |
11 | }; |
12 | }); |
13 | |
14 | // ... |
15 | } |
useDebugValue
1 | useDebugValue(value) |
2 | |
3 | // 延迟format,直到查看时执行 |
4 | useDebugValue(date, date => date.toDateString()); |
用于在React DevTools中显示自定义Hook的label