在之前的一章我们已经了解了 React Hook 的一些基本特性/优点和一些使用方法。在这一章中,我们将深入介绍另外几个常用的钩子。
React Hooks Intro
一、类与函数的区别
React的核心是组件。在v16.8版本之前,组件的标准写法是类组件。
1.1 类组件的缺点
- 组件类代码很“重”
- 真实的 React App 由多个类按层级,一层层构成,复杂度很高
- 如果再使用 Redux,整个项目看起来会变得非常复杂
根据 Redux 的作者 Dan 的总结,组件类有以下几个缺点:
- Huge components that are hard to refactor and test.
- Duplicated logic between different components and lifecycle methods.
- Complex patterns like render props and higher-order components.
翻译一下就是:
- 大型组件很难拆分、重构和测试
- 业务逻辑存在于不同组件以及生命周期方法中,因此会导致重复逻辑
- 复杂的编程模式,如render props 和高阶组件
1.2 函数组件的优势
React 官方希望组件是一个简单的数据流管道,让开发者可以根据需求来组合管道,而不要变成一个复杂的容器。因此组件的最佳写法是函数式。
早期的函数组件无法取代类组件,是因为函数组件必须是纯函数,因此不能包含状态和生命周期方法。
而 React Hook 的出现彻底解决了这个问题,它可以让我们用函数式组件的写法,完成一个功能齐全的组件。
二、What is Hook?
React Hooks 是为函数组件提供所需要的外部功能和副作用的“钩子”。
因此我们需要什么功能,就使用什么钩子。
要注意的是,如果我们自己封装自定义的钩子,需要遵循命名规则,即钩子一律使用 use 前缀来命名,以便于识别。
接下来,我们来介绍几个 React 提供的常用的钩子。
- useState()
- useContext()
- useReducer()
- useEffect()
三、useState(): 状态钩子
由于纯函数不能有状态,所以需要把状态放在钩子里面。
useState() 函数接受状态的初始值作为参数,并返回一个数组,数组的第一个值为表示当前状态值的变量,第二个值为用来更新状态的函数(命名约定为:以set
前缀+状态的变量名)。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import React, { useState } from 'react';
export default function Button() { const [buttonText, setButtonText] = useState('Hello!'); const handleClick = () => { setButtonText('World!'); }; return ( <button onClick={handleClick}>{buttonText}</button> ); }
|
四、useContext(): 共享状态钩子
如果需要在组件间共享状态,可以使用useContext()
。
例如组件A和组件B需要共享状态,示例如下:
1 2 3 4 5 6 7 8 9
| const AppContext = React.createContext({});
<AppContext.Provider value = {{username: 'Will'}}> <div className='App'> <A /> <B /> </div> </AppContext.Provider>
|
1 2 3 4 5 6 7 8 9 10
| export default function A() { const { username } = useContext(AppContext); return ( <div className='A'> <p>Home</p> <p>{username}</p> </div> ); }
|
1 2 3 4 5 6 7 8 9 10 11
| export default function B() { const { username } = useContext(AppContext); return ( <div className='B'> <h1>B Component</h1> <p>the current user is: {username}</p> </div> ); }
|
五、useReducer(): action 钩子
React 本身并不提供状态管理工具,如果需要,通常是引入外部库,当前最流行的库非 Redux 莫属。
Redux 的核心理念是:组件发出 action 与状态管理器通信,在状态管理器收到 action 后,使用 Reducer 函数计算出最新的状态并返回其值(Reducer函数形式大致为:(state, action) => newState
)。(具体关于 Redux 的介绍,请参考我之前的关于 Redux 的文章)
随着 Redux 的流行,React 官方也加入了 useReducers()
这个钩子用来引入 Reducer 功能,从而可以更方便的使用 Redux。
**useReducer()**的基本用法:接受 Reducer 函数和状态初始值作为参数,并返回一个数组。数组的第一个值是当前状态值,第二个值是用来发送 action 的 dispatch 函数。
以计数器为例,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const myReducer = (state, action) => { switch(action.type) { case('countUp'): return { ...state, count: state.count + 1 } case ('countDown'): return { ...state, count: state.count - 1 } default: return state; } }
|
1 2 3 4 5 6 7 8 9 10 11
| function App() { const [state, dispatch] = useReducer(myReducer, { count: 0 }) return ( <div className='App'> <button onClick={() => dispatch({type: 'countUp'})}>+1</button> <button onClick={() => dispatch({type: 'countDown'})}>-1</button> <p>Count: {state.count}</p> </div> ) }
|
尽管使用 Hook 可以提供共享状态以及Reducer 函数,但是由于 React 本身并没有提供中间件(middleware)以及时间旅行(time travel),因此,如果需要使用这两个功能,还是要用 Redux。
六、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 26
| export default function Person({ personId }) { const [loading, setLoading] = useState(true); const [person, setPerson] = useState({}); useEffect(() => { setLoading(true); fetch(`https://getApi/${personId}`) .then(response => response.json()) .then(data => { setPerson(data); setLoading(false); }); }, [personId]) if (loading === true) { return <p>Loading...</p> } return ( <div> <p>Name: {person.name}</p> <p>Age: {person.age}</p> </div> ); }
|
七、创建自定义的 Hook
如上面代码示例所示,我们也可以将其封装起来以变成一个自定义的 Hook。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function usePerson(personId) { const [loading, setLoading] = useState(true); const [person, setPerson] = useState({}); useEffect(() => { setLoading(true); fetch(`https://getApi/${personId}`) .then(response => response.json()) .then(data => { setPerson(data); setLoading(true); }) }, [personId]) return [loading, person]; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export default function Person({ personId }) { const [loading, person] = usePerson(personId); if (loading === true) { return <p>Loading...</p>; } return ( <div> <p>Name: {person.name}</p> <p>Age: {person.age}</p> </div> ); }
|
Notes:以上内容借鉴了阮一峰老师的文章。