上文谈到了 React Hooks 诞生的背景和缘由,它是 React 团队为了补充加强纯函数组件所开发的一套组件 Api,是从 React v16.8 引入的又一里程碑性的新特性。今天就来总结一下常见的 React Hooks 和它们的使用方法、可替代类组件的场景等。
Hooks are functions that let you “hook into” React state and lifecycle features from function components
可以看出,官方解释 React Hooks 本质上是一些钩子函数。
对于常见的功能,React为我们提供了一些常用的钩子,当然有特殊需要,我们也可以写自己的钩子。下面是React为我们提供的默认的四种最常用钩子
- useState()
- userContext()
- userReducer()
- useEffect()
钩子都带有use
前缀,React约定,钩子一律用 use
前缀命名。所以,你自定义钩子也都要命名为useXXX。
1. useState()
我们知道,纯函数组件没有状态,useState()
用于为函数组件引入状态。下面使用 Hooks 写一个最简单的计数器:
const Counter = () => {
const [count, setCount] = useState(0)
const add = () => {
setCount(count + 1)
}
return (
<>
<p>{count}</p>
<button onClick={add}> +1 </button>
</>
)
}
useState()
,它接受状态的初始值作为参数,即上例中计数的初始值 0,它返回一个数组,其中数组第一项为一个变量,指向状态的当前值。类似this.state
;第二项是一个 set 函数,用来更新状态,类似setState
。该函数的命名,约定为set
前缀加状态变量名。useState 接受的这个初始值可以是一个数字、字符串或对象,甚至可以是一个函数。当初始值是一个函数的时候,这个函数只会在这个组件初始渲染的时候执行。
需要注意的是如果 state 是一个对象,使用 setXXX 的时候不会像类组件的 setState 那样自动合并对象。要达到这种效果,需要把原 state 做一个展开,可以这样写:
setState(prev => {
return {...prev, ...updatedValues};
});
另外一个跟类组件的 setState 相似的一点是,当新传入的值跟之前的值一样时,不会触发更新。
2. useEffect()
使用这个 Hooks 之前需要先理解下什么是副作用。网络请求或者 DOM 操作都是副作用的例子,Effect Hook 就是专门用来处理副作用的。在纯函数组件里写副作用代码需要格外小心,否则非常容易出 bug。
类组件中,副作用代码可以写在componentDidMount
和componentDidUpdate
等生命周期钩子中;而函数式组件没有这些生命周期钩子,为了在函数组件中实现近似的功能,就需要使用 useEffect
Hook 来实现:
const LogOut = () => {
const [ count, setCount ] = useState(0)
useEffect(() => {
console.log(`You clicked ${count} times`)
})
return (
<>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}> +1 </button>
</>
)
}
useEffect
会在每次 DOM 渲染后执行,不会阻塞页面渲染。它可实现componentDidMount
、componentDidUpdate
和componentWillUnmount
三个生命周期函数的执行时机。
useEffect(() => {}, [array])
useEffect()
接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect的依赖项。只要这个数组发生变化,useEffect()
就会执行。当第二项省略不填时,useEffect()
会在每次组件渲染时执行。这一点类似于类组件的componentDidUpdate
生命周期。
当副作用只需要在组件挂载的时候和卸载的时候执行,则第二项可填为空数组 []
,类似于类组件的componentDidMount
生命周期和componentWillUnmount
的组合。如果第二项填为空数组 [],useEffect 中的 props 和 state 将始终保持其初始值。
useEffect(() => {
console.log('mount')
return () => {
console.log('unmount')
}
}, [])
useEffect(() => {
console.log(`You clicked ${count} times`)
}, [count])
这样只有在 count 改变的时候才执行 Effect 里的函数。
如果需要模拟componentWillUnmount
生命周期的操作,也就是在组件卸载前消除相关副作用,可以使用如下代码:
useEffect(() => {
//执行副作用
console.log(`you clicked ${count} times`);
return () => {
//消除副作用
//some code...
}
})
当第二个参数依赖项不为空数组时,前一个函数中 return 的函数部分会在再次执行 useEffect 前执行。