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 中返回一个函数
  • 好处:可以避免 componentDidUpdatecomponentWillUnmount 中的重复,同时让相关的代码关联更加紧密,帮助我们避免一些 bug。我们还看到了我们如何根据 effect 的功能分隔他们,这是在 class 中无法做到的
  • 何时:

1、更新时

2、销毁组件时

​ 这里的代码:

关键点:添加和移除订阅的逻辑放在一起了

function FriendStatus(props) {
  // ...
  useEffect(() => {
    // ...
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);//------------1
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);//------------2
    };
  });
image-20211129113811446

​ 订阅和取消都是会配合执行的,只是可能时机不同。订阅后必然会取消的。

  • 更新时:导致先进行上一个订阅删除,再进行本次订阅。执行当前 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 中