我的掘金:https://juejin.im/post/5c988b…
1. 前言
Hooks 是 React16.8 版本中的新特性,它可以在不使用 class 声明的组件中使用 state 和 React 特性。
Tip: React v16.8.0 已经支持 Hooks。当我们进行更新时,别忘了更新其他相关依赖包,包括 React DOM 等。React Native 将会在下一个稳定版本支持 Hooks。
在使用 Hooks 之前我们必须知道的几件事:
Hooks 的使用完全根据我们的需要进行选择用还是不用。
Hooks 是完全向下兼容。
Hooks 现在完全可用。
Hooks 的出现并没有改变我们之前对 react 的理解。
Hooks 的出现并没有移除 classes 的计划。
那么 Hooks 出现的动机是什么:
很难重用组件之间有状态的逻辑
复杂的组件变得难以理解
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。