阅读 66

React Hooks 相关笔记

这是我参与更文挑战的第10天,活动详情查看:更文挑战

Hook是什么

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

  • 完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使用 Hook。
  • 100% 向后兼容的。 Hook 不包含任何破坏性改动。
  • 现在可用。 Hook 已发布于 v16.8.0。

基础Hook(3个)

useState:

import React, { useState } from 'react';
function Example() {
  // 声明一个新的叫做 “count” 的 state 变量
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
复制代码

参数

useState 唯一的参数就是初始 state。这里的 state 不一定要是一个对象 —— 如果你有需要,它也可以是。这个初始 state 参数只有在第一次渲染时会被用到。

返回值

useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。

读取state

// class中读取state
<p>You clicked {this.state.count} times</p>
// 函数中读取state
<p>You clicked {count} times</p>
复制代码

更新state

    // class中更新state
    <button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Click me
  </button>
  
    // 函数中更新state
  <button onClick={() => setCount(count + 1)}>
    Click me
  </button>
复制代码

使用多个state变量

function ExampleWithManyStates() {
  // 声明多个 state 变量
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
}
复制代码

useEffect:

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
复制代码

useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。

可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

参数

  • 第一个参数是一个函数,是在第一次渲染以及之后更新渲染之后会进行的副作用。
    • 这个函数可能会有返回值,倘若有返回值,返回值也必须是一个函数,会在组件被销毁时执行。
  • 第二个参数是可选的,是一个数组,数组中存放的是第一个函数中使用的某些副作用属性。

useEffect在什么时候执行

默认情况下,它在第一次渲染之后每次更新之后都会执行。可以理解为 effect 发生在“每次渲染之后”,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

如果我们想在指定情况下才执行,可以给useEffect传第二个参数,用第二个参数来告诉react只有当这个参数的值发生改变时,才执行我们传的副作用函数(第一个参数)。

例如:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 只有当count的值发生变化时,才会重新执行`document.title`这一句
复制代码

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。

最常用的在componentDidMount中请求数据的场景,可以通过useEffect引入外部依赖,判断没数据或参数变化的时候进行请求。

useEffect(() => {
  fetchData(id);
}, [id]);
复制代码

React团队将倾向于把componentDidMount和componentDidUpdate不作区分,即不要再区分组件是否已经mount,使用统一的API来统一地进行状态管理。

怎么清除effect

React 会在组件卸载的时候执行清除操作,在class组件中,我们通常在componentWillUnmount生命周期中清除一些副作用,例如清除定时器、清除快捷键、清除事件监听等。

在useEffect中,提供了一个可选的功能来实现:如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它。

useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
});
复制代码

与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。

useContext

// a.js
const MyContext = createContext(null);
// 
const value = useContext(MyContext);
复制代码

Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。可以避免通过中间元素传递 props。

useContext 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。

context API 用法参考文档zh-hans.reactjs.org/docs/contex…

额外Hook(7个)

useReducer

它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
复制代码

返回一个 memoized 回调函数。

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
复制代码

返回一个 memoized 值。

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

userRef

const refContainer = useRef(initialValue);
复制代码

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。

useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

userDebugValue

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

自定义Hook

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。

与 React 组件不同的是,自定义 Hook 不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回什么(如果需要的话)。换句话说,它就像一个正常的函数。但是它的名字应该始终以 use 开头,这样可以一眼看出其符合 Hook 的规则。

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);
  // ...
  return isOnline;
}
复制代码

Hook 使用规则

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件 或 自定义Hook 中调用 Hook。不要在其他 JavaScript 函数中调用。

为什么?

 // ------------
// 首次渲染
// ------------
useState('Mary')           // 1. 使用 'Mary' 初始化变量名为 name 的 state
useEffect(persistForm)     // 2. 添加 effect 以保存 form 操作
useState('Poppins')        // 3. 使用 'Poppins' 初始化变量名为 surname 的 state
useEffect(updateTitle)     // 4. 添加 effect 以更新标题
// -------------
// 二次渲染
// -------------
useState('Mary')           // 1. 读取变量名为 name 的 state(参数被忽略)
useEffect(persistForm)     // 2. 替换保存 form 的 effect
useState('Poppins')        // 3. 读取变量名为 surname 的 state(参数被忽略)
useEffect(updateTitle)     // 4. 替换更新标题的 effect
// ...
复制代码

React 需要靠 Hook 调用的顺序来判断哪个state对应哪个useState,所以需要保证每次渲染中Hook的调用顺序都是相同的。如果将其中一个Hook放到条件语句中,就可能造成执行顺序改变,导致React无法正确地将内部 state 和对应的 Hook 进行关联。

useState('Mary')           // 1. 读取变量名为 name 的 state(参数被忽略)
// 在条件语句中使用 Hook 违反第一条规则
if (name !== '') {        // name为空的情况下,此 Hook 被忽略,导致后续顺序改变
    useEffect(function persistForm() {
        localStorage.setItem('formData', name);
    });
}
useState('Poppins')        //  2 (之前为 3)。读取变量名为 surname 的 state 失败
useEffect(updateTitle)     //  3 (之前为 4)。替换更新标题的 effect 失败
复制代码
文章分类
前端
文章标签