Hook学习笔记

97 阅读5分钟

hook是react16.8新加入的原生概念

将来不会取消class,hook是可选的、向后兼容的,可以渐进式使用hook。

使用hook的原因:

  • hook为组件之间复用状态逻辑提供了更好的原生途径(虽然之前也有一些别的解决方案比如render props、高阶组件,但是需要重新组织你的组件结构,很麻烦) (通过自定义hook实现)
  • 复杂组件充斥很多状态逻辑,难以理解和管理。相互关联且对照修改的部分(比如订阅和取消订阅)被拆分在不同的生命周期里。hook将组件相互关联的部分拆分成更小的函数,而并非强制按照生命周期划分。(通过effect hook实现)
  • class组件难以学习理解,且给目前的优化带来了问题。hook使得在非class的情况下可以使用更多的react特性。

hook使用注意事项:

  • 只能在函数最外层调用hook,不要在循环、条件语句或子函数中使用hook。(🤔️why? (因为hook是通过数组实现的,每次调用useState都会改变下标.react利用调用顺序正确更新状态,如果useState被包裹在循环或条件语句中,可能会引起调用顺序的错乱)

  • 只能在react的函数组件和自定义hook中调用hook。不要在其他js函数中调用。

hook怎么知道哪个state对应哪个useState?

react靠的是hook的调用顺序,每次渲染中hook调用顺序都是一样的

常见Hook

useState Hook:让函数组件使用state

useState()返回一个数组,用到了数组解构

import React, { useState } from 'react';

function Exmaple(props) {
    //声明一个叫count的state变量
    const [count, setCount] = useState(0);
    
    return(
        <div>
            //读取count
            <p>you click {count} times</p>
            
            //更新count。React重新渲染Example组件,把最新的count传给它。   
            <button onClick={() => setCount(count + 1)}>
                click me
            </button>
        </div>
    );
}

useEffect Hook:让函数组件进行副作用操作

  • 相当于class组件componentDidMount,componentDidUpdate 和 componentWillUnmount三个生命周期函数的组合

  • effect函数在浏览器渲染结束后延迟执行

1、有时我们想在react更新dom之后(包括首次渲染)运行一些代码,比如发送网络请求

import React, { useState,useEffect } from 'react';

function Exmaple(props) {
    const [count, setCount] = useState(0);
    
    //useEffect能直接读取state变量,使用了闭包机制
    useEffect(
        //每次渲染dom都会执行这个函数(包括首次渲染)
        () => {
            document.title = 'you click {count} times';
        }
    );
    
    return(
        <div>
        </div>
    );

}

2、有时是需要清除的副作用,如订阅消息。我们在effect的返回函数中进行清除工作。

注意:每次重新渲染都会清除上一个effect,生成另一个新的effect。这避免了class组件中忘记处理更新逻辑而导致的bug

import React, { useState,useEffect } from 'react';

function Exmaple(props) {
    const [isOnline, setIsOnline] = useState(null);
    
    useEffect(
        //订阅好友状态
        () => {
            ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        }
        return function cleanup() {
            //取消订阅
            ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        }
    );

}

3、有时需要跳过effect,这可以优化性能。我们可以通过给useEffect的第二个可选参数传递数组。

  • 数组中有一个元素变化,就会执行effect

  • 如果数组为空,说明effect不依赖于任何值,不需要重复执行,也就是说仅在组件挂载和卸载时执行。

  • 一般来说,传入空数组是不安全的。如果传入了空数组[],effect内部的state和props就会一直是初始值。所以我们推荐在effect内部声明需要用到的函数,这样容易看出effect依赖了哪些值。

useEffect(() => {
    document.title = 'you click {count} times';
    return () => {
        .......
    }
}, [count]);  //仅在count更改时执行effect

useRef Hook:绑定某个dom节点

useRef()创建了一个通用容器。容器的current属性保存了一个任意值

绑定某个dom节点的useRef在每次渲染时都会返回同一个ref对象

变更current属性不会引发组件重新渲染

function TextInputWithFocusButton() {
    //current属性被初始化为null
    const inputEl = useRef(null);
    const onButtonClick = () => {
        inputEl.current.focus();
    }
    return (
        <>
            <input ref = {inputEl} type = "text" />
            <button onClick = {onButtonClick}>focus the input</button>
        </>
    )
}

自定义Hook

  • 名字以use开头,内部调用其他hook的函数称为自定义hook

  • 自定义hook是一种重用状态逻辑的机制,它并不是共享state,每次使用自定义hook其中的state或副作用都是隔离的。

  • 自定义hook并不是react的特性,而是一种自然遵循hook设计的约定

下面有一个例子,FriendStatus用于显示好友的在线状态Online/Offline

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);  
  useEffect(() => {
      function handleStatusChange(status) {
           setIsOnline(status.isOnline);  
      }
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);    
      return () => {      
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);    
      };   
  });
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

FriendListItem函数用来显示好友名字,并根据状态变换颜色

import React, { useState, useEffect } from 'react';

function FriendListItem(props) {
    const [isOnline, setIsOnline] = useState(null);
    useEffect(() => {
        function handleStatusChange(status) {
          setIsOnline(status.isOnline);
        }
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
            ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
    });

    return (
        <li style={{ color: isOnline ? 'green' : 'black' }}>
          {props.friend.name}
        </li>
    );
}

可以看到上面两个函数共享逻辑和一部分代码。我们可以把共享逻辑提取到一个自定义hook中:

import React, { useState, useEffect } from 'react';

//自定义hook
function useFriendStatus(friendID) {
    const [isOnline, setIsOnline] = useState(null);
    useEffect(() => {
        function handleStatusChange(status) {
          setIsOnline(status.isOnline);
        }
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
            ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
    });
    
    return isOnline;
}

//使用自定义hook
function FriendStatus(props) {
    const isOnline = useFriendStatus(props.friend.id);
    
    if (isOnline === null) {
        return 'Loading...';
    }
    return isOnline ? 'Online' : 'Offline';
}
//使用自定义hook
function FriendListItem(props) {
    const isOnline = useFriendStatus(props.friend.id);
    
    return (
        <li style={{ color: isOnline ? 'green' : 'black' }}>
            {props.friend.name}
        </li>
    );
}

在多个hook之间传递信息

创建一个聊天接受者选择器,会显示当前好友是否在线,它和useFriendStatus之间传递信息

import React, { useState, useEffect } from 'react';

const friendList = [
    { id: 1, name: 'Phoebe' },
    { id: 2, name: 'Rachel' },
    { id: 3, name: 'Ross' },
];

function ChatRecipientPicker() {
    //useState提供最新的recipientID,所以我们可以把它传给useFriendStatus自定义hook
    const [recipientID, setRecipientID] = useState(1);
    const isRecipientOnline = useFriendStatus(recipientID);
    
    return (
        <>
            <Circle color = {isRecipientOnline ? 'green' : 'red'} />
            <select
                //当前选择的好友保存在recipientID这个state变量中每次选择新的好友更新这个state
                value = {recipientID}
                onChange = {e => setRecipientID(Number(e.target.value))}
            >
                {friendList.map(friend => (
                    <option key = {friend,id} value = {friend.id}>
                        {friend.name}
                    </option>
                ))}
            </select>
        </>
    );
}