Hook 使你在非 class 的情况下可以使用更多的 React 特性。
使用规则
-
只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用
-
如果我们想要有条件地执行一个 effect,可以将判断放到 Hook 的内部
useEffect(function persistForm() { // 👍 将条件判断放置在 effect 中 if (name !== '') { localStorage.setItem('formData', name); } });
-
对 Hook 的调用要么在一个
大驼峰法
命名的函数(视作一个组件)内部,要么在另一个useSomething
函数(视作一个自定义 Hook)中。 -
Hook 在每次渲染时都按照相同的顺序被调用。
原因
1、即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景
2、class 也给目前的工具带来了一些问题。例如,class 不能很好的压缩,并且会使热重载出现不稳定的情况。因此,我们想提供一个使代码更易于优化的 API。
好处
1、允许你在 React 函数组件中添加 state 的 Hook
特点
1、一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留
2、 返回值为:当前 state 以及更新 state 的函数。成对获取
3、 state 只在组件首次渲染的时候被创建。useState的参数只在第一次有用,作为初始值。
4、下一次重新渲染时,useState
返回给我们当前的 state
5、setCount 会重新渲染函数组件
setState会引发重新渲染,而渲染会引发useEffect的执行,
6、分离独立的 state 变量
7、使用 useEffect
调度的 effect 不会阻塞浏览器更新屏幕。
这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect
Hook 供你使用,其 API 与 useEffect
相同。
Effect HOOK
是在渲染后执行某些操作
1、渲染:包括第一次渲染的时候和每次更新后的重新渲染。
2、useEffect
放在组件内部让我们可以在 effect 中直接访问state 变量(或其他 props)(Hook 使用了 JavaScript 的闭包机制)
清除机制
3、清除机制
- 要求:在effect 中返回一个函数
- 好处:可以避免
componentDidUpdate
和componentWillUnmount
中的重复,同时让相关的代码关联更加紧密,帮助我们避免一些 bug。我们还看到了我们如何根据 effect 的功能分隔他们,这是在 class 中无法做到的 - 何时:
1、更新时
2、销毁组件时
这里的代码:
关键点:添加和移除订阅的逻辑放在一起了
function FriendStatus(props) {
// ...
useEffect(() => {
// ...
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);//------------1
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);//------------2
};
});
订阅和取消都是会配合执行的,只是可能时机不同。订阅后必然会取消的。
-
更新时:导致先进行上一个订阅删除,再进行本次订阅。执行当前 effect 之前先对上一个 effect 进行清除
-
组件销毁时:执行最后一个订阅的取消。
effect 中返回的函数名不是必须的。
effect Hook 使用同一个 API 来满足这两种情况:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);//------------ChatAPI
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);//------------ChatAPI
};
});
未更新的渲染优化
只要传递数组作为 useEffect
的第二个可选参数即可,例如:
useEffect(() => {
storeData('data', state);
const date = state.map(obj => obj.date);
const bmi = state.map(obj => obj.bmi);
let newData = { date, bmi };
setData(newData);
// [state],这里非常重要,不然setData也会造成useEffect,避免luseEffect的循环
}, [state]);
- 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组(
[]
)作为第二个参数
自定义HOOK
-
自定义 Hook 必须以 “
use
” 开头,useSomething
的命名约定可以让我们的 linter 插件在使用 Hook 的代码(如表单处理、动画、订阅声明、计时器)中找到 bug -
在两个组件中使用相同的 Hook不会共享 state ,每次调用 Hook,它都会获取独立的 state
例如:
import { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
管理内部state---useReducer
有个复杂的组件,其中包含了大量以特殊的方式来管理的内部状态
useReducer
的 Hook:
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
function Todos() {
const [todos, dispatch] = useReducer(todosReducer, []);
function handleAddClick(text) {
dispatch({ type: 'add', text });
}
// ...
}
已经将 useReducer
的 Hook 内置到 React 中