在之前的一章我们已经了解了 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:以上内容借鉴了阮一峰老师的文章。