1. 前言
Hooks是React16.8版本中的新特性,它可以在不使用class声明的组件中使用state和React特性。
Tip: React v16.8.0已经支持Hooks。当我们进行更新时,别忘了更新其他相关依赖包,包括React DOM等。React Native将会在下一个稳定版本支持Hooks。
在使用Hooks之前我们必须知道的几件事:
- Hooks的使用完全根据我们的需要进行选择用还是不用。
- Hooks是完全向下兼容。
- Hooks现在完全可用,已经发布与v16.8.0。
- Hooks的出现并没有改变我们之前对react的理解。
- Hooks的出现并没有移除classes的计划。
那么Hooks出现的动机是什么:
- React需要为共享状态提供更好的原生途径。
- 很难重用组件之间有状态的逻辑。
- render props和高阶组件会改变组件结构。
- 复杂的组件变得难以理解。
2. State Hook(useState)
先来介绍第一个useState。可以在函数组件(function component)使用它。它在的作用是添加本地状态到当前组件。React会一直维持在这个state。useState将会有两个返回值:当前的state和一个更新state的方法。 调用useState时传入的参数表示着initial State。我们可以多次调用useState在同一个组件中,表示创建多个状态。
import React, { useState } from 'react'
const HookDemo = ({}) => {
const [count, setCount] = useState(0)
const [number, setNumber] = useState(2)
const [todos, setTodos] = useState([{text: 'huzhiwei'}])
const setHandle = () => {
todos[0].text = 'jp'
setTodos(Object.assign({}, todos))
}
return (
count: {count}
number: {number}
todos: {todos[0].text}
<button onClick={()=>setCount(count+1)}>count handler</button>
<button onClick={()=>setNumber(number+1)}>number handler</button>
<button onClick={()=>setHandle()}>todos handler</button>
)
}
复制代码
3. Effect Hook (useEffect)
我们可能执行过请求获取数据,监听,或者手动改变DOM。我们可以称为这些为副作用。因为这些操作可能会影响到其他组件或者不能在渲染组件中完成。
Effect Hook给函数组件增加了处理副作用的能力。可以理解它的能力和componentDidMount、componentDidUpdate、componentWillUnmount类似。useEffect将这些统一为一个单独的API。 如下是React文档中的例子:
import React, { useState, useEffect } from 'react'
function Example() {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = 'good body'
})
return (
<div>
<span>{ count }</span>
<button onClick={()=>setCount(count + 1)}>click me</button>
</div>
)
}
复制代码
当我们调用useEffect时,就是告诉React在刷新对DOM的更改后执行effect函数。React会在每次渲染后执行effect函数。
effect还可以选择性地指定如何通过返回函数在它们之后“清理”。例如,该组件使用一个效果来订阅朋友的在线状态,并通过取消订阅来清理:
import React, { useState, useEffect } from 'react'
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null)
function handleStatusChange(status) {
setIsOnline(status.isOnline)
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange)
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
}
})
if(isOnline === null) {
return 'Loading...'
}
return isOnline ? 'Online' : 'Offline'
}
复制代码
在这个例子中,当组件unmount的时候React将会通过ChatAPI取消订阅。在后续的render又将会重新执行这些effect。
Tip: 我们在组件中也可以像useState一样多次调用。
effect在第一次组件挂载和后面组件更新时都会执行,这样就会导致一些非必要的effect重复执行。如果我们想通过对比前后数据是否发生改变来判断是否触发effect,我们可以通过传入数组作为第二个参数:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
复制代码
如果你想要执行只运行一次的 effect(仅在组件挂载和卸载时执行),你可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不算是一种特殊情况 —— 依然遵循输入数组的工作方式。
4. 创建自定义Hooks
有些场景下,我们想复用一些关于状态的逻辑。通常会有两种常用的解决方案:高阶组件和render Props。自定义Hooks也可以做这些,而且不需要增加更多的组件。
这里有个例子使用useState和useEffect去监听好友是否在线状态。我们想重用这个监听逻辑在不同的组件中该怎么做呢?
首先抽象出这个逻辑到一个自定义组件中,叫做useFriendStatus:
import React, {useState, useEffect} from 'react'
function useFriendStatus(frientID) {
const [isOnline, setIsOnline] = useState(null)
function handleStatusChange(status) {
setIsOnline(status.isOnline)
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange)
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange)
}
})
return isOnline
}
复制代码
如上,传入friendID作为参数,然后返回是否在线。现在我们看其他组件中如何使用:
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id)
if(isOnline === null) {
return 'Loading...'
}
return isOnline ? 'online' : 'offline'
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
复制代码
这些组件的状态是完全独立的。钩子是重用有状态逻辑的一种方法,而不是状态本身。事实上,每个对钩子的调用都有一个完全独立的状态——所以您甚至可以在一个组件中两次使用相同的自定义钩子。
5. Hooks规则
- Hooks只在顶层调用,不要在循环,条件判断或者嵌套函数中调用钩子。
- 只在React的函数组件(function Component)中调用Hooks。
- 对于自定义Hooks,我们使用use开头命名。
- eslint-plugin-react-hooks该插件可以规范hooks写法。
- React 靠的是 Hook 调用的顺序来对应state和useState。