likes
comments
collection
share

为什么会存在React Hooks

作者站长头像
站长
· 阅读数 72

为什么会存在React Hooks

本篇汇总React在16.8版本新增的Hooks相关内容,认识什么是Hooks、有哪些API、自定义Hook以及使用规范和注意事项。帮助大家提高自身对React的新功能了解。

一、认识Hooks

1、回顾函数组件

函数组件没有实例,也不能监听各个生命周期,也无法扩展属性和方法。只是输入props,输出jsx的纯函数。也没有state。

// class 组件
class List extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        const { list } = this.props

        return <ul>{list.map((item, index) => {
            return <li key={item.id}>
                <span>{item.title}</span>
            </li>
        })}</ul>
    }
}

// 函数组件
function List(props) {
    const { list } = this.props

    return <ul>{list.map((item, index) => {
        return <li key={item.id}>
            <span>{item.title}</span>
        </li>
    })}</ul>
}

2、class组件 vs 函数组件

class组件

  • 大型组件很难拆分和重构,也很难测试。
  • 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
  • 组件类引入了复杂的编程模式,比如render props 和高阶组件。

函数组件

如果按照React函数式编程的思维来说,一个组件更像一个函数,而不像一个class,所以函数组件会看起来更简洁、符合逻辑。 但函数组件没有state,没有生命周期,还无法取代class组件。所以才有了Hooks。

3、state hook

函数组件必须要求是纯函数,不能有副作用。因此,要在函数组件使用state,不能直接使用,而是要“钩”进来,这就是Hooks,即useState。

使用useState的语法如下:

const [state, setState] = useState(initialState);

其中,state是我们定义的状态变量,initialState是状态的初始化值。setState是一个函数,用于更新状态。

举个例子,假设我们要在一个组件中使用计数器,可以这样写:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter;

在上面的例子中,我们使用useState定义了一个名为count的状态变量,并将其初始值设为0。在组件中,我们可以通过setCount函数来更新count的值。

当我们点击“Increment”按钮时,setCount函数会被调用,并将count的值加1。由于React会自动重新渲染组件,所以我们可以看到页面上的计数器会随着点击次数的增加而更新。

需要注意的是,useState返回的是一个数组,包含当前状态和更新状态的函数。在上面的例子中,我们使用了ES6的解构语法来将数组中的值分别赋给count和setCount。

4、effect hook

如何让函数组件有生命周期,例如组件经常会在didMount里发请求。

在React中,useEffect可以用来模拟组件的生命周期函数。useEffect接收两个参数,第一个参数是一个函数,称为Effect函数,它会在组件渲染后执行,第二个参数是一个数组,用于指定Effect函数依赖的变量,只有依赖的变量发生变化时,Effect函数才会重新执行。

下面是useEffect模拟生命周期函数的示例:

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

function Example() {
  const [count, setCount] = useState(0);

  // 相当于 componentDidMount 和 componentDidUpdate
  useEffect(() => {
    console.log('Effect function is called');

    // 相当于 componentWillUnmount
    return () => {
      console.log('Effect cleanup function is called');
    };
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

在这个示例中,每次点击按钮时,count的值会发生变化,由于我们在useEffect的第二个参数中指定了count,因此useEffect会在每次count变化时执行。同时,由于我们在Effect函数中返回了一个函数,所以在组件卸载时,该函数会被调用,相当于componentWillUnmount生命周期函数。

二、其他Hooks API

  1. useContext: 用于在函数组件中使用 context。它接收一个 context 对象作为参数,并返回该 context 的当前值。

  2. useReducer: 用于在函数组件中使用 reducer。它接收一个 reducer 函数和一个初始状态作为参数,并返回一个数组,数组第一个元素是当前 state 的值,第二个元素是 dispatch 函数,用于触发 state 的更新。

  3. useMemo: 用于在函数组件中进行性能优化。它接收一个回调函数和一个依赖项数组作为参数,并返回回调函数的计算结果。当依赖项数组中的任何一个值发生变化时,useMemo 会重新计算回调函数的结果。

  4. useCallback: 用于在函数组件中进行性能优化。它接收一个回调函数和一个依赖项数组作为参数,并返回一个 memoized 的回调函数。当依赖项数组中的任何一个值发生变化时,useCallback 会返回一个新的 memoized 回调函数。

  5. useRef: 用于在函数组件中创建一个 ref。它返回一个可变的 ref 对象,该对象的 current 属性指向传递给 useRef 的初始值。

三、自定义Hook

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

自定义hook可以在不增加组件层级的情况下提供复用逻辑的能力。例如,如果你有两个组件需要获取相同的数据,你可以将获取数据的逻辑封装在一个自定义hook中,然后在这两个组件中使用这个自定义hook,而不是在每个组件中都写一遍相同的逻辑。

下面是一个自定义hook的示例,用于处理窗口大小的变化:

import { useState, useEffect } from 'react';

function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return windowSize;
}

在这个示例中,我们使用useState来存储窗口的宽度和高度,然后使用useEffect来订阅窗口的resize事件,并在事件处理程序中更新窗口大小。最后,我们返回窗口大小的状态对象。

使用这个自定义hook非常简单,只需要在组件中调用它即可:

function MyComponent() {
  const windowSize = useWindowSize();

  return (
    <div>
      Window size: {windowSize.width} x {windowSize.height}
    </div>
  );
}

这个自定义hook可以在多个组件中复用,而不需要在每个组件中都写一遍相同的逻辑。

四、Hooks 使用规范

1、规则

  • 只在代码最顶层使用Hook,不要在循环、判断和嵌套函数中使用。(以及不要在Hook之前return掉)
  • 只在React函数组件,或自定义Hook中使用
  • eslint 插件 eslint-plugin-react-hooks 可以解决以上问题

安装 npm install eslint-plugin-react-hooks --save-dev ,然后修改 eslint 配置文件

// ESLint 配置文件
{
  "plugins": [
    // ...此处省略 N 行...
    "react-hooks"
  ],
  "rules": {
    // ...此处省略 N 行...
    "react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
    "react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
  }
}

2、关于hook的调用顺序

在 React 中,Hook 的调用顺序非常重要,因为它会影响到组件的渲染结果。下面是 Hook 的调用顺序:

  1. 在函数组件的顶层,按照顺序调用所有的 Hook。

  2. 在每次组件更新时,也按照顺序调用所有的 Hook。

如果 Hook 的调用顺序不正确,可能会导致组件的状态出现错误,或者出现其他不可预料的问题。因此,我们需要严格按照调用顺序来使用 Hook。同时,也需要注意,每个 Hook 只能在函数组件的顶层或其他 Hook 中调用,不能在普通的 JavaScript 函数中调用。

五、Hooks组件逻辑复用

1、回顾在class组件使用的逻辑复用方法

所存在的问题如下:

  • mixin:相互依赖和相互耦合
  • 高阶组件HOC:使用过多,导致组件层级嵌套过多,不易渲染,更不易调试。并且可以劫持props,需要严格遵守约定。
  • render-props:有一定学习成本和理解成本,并且只能传函数组件。

2、使用Hooks做组件逻辑复用

在React中,常用的Hooks包括useState、useEffect、useContext、useReducer、useMemo、useCallback和useRef。这些Hooks可以用于管理组件的状态、处理副作用、共享数据和优化性能等方面。

例如,如果我们需要在多个组件中处理窗口大小变化的逻辑,可以使用自定义hook来实现复用。下面是一个处理窗口大小变化的自定义hook的示例代码:

import { useState, useEffect } from 'react';

function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return windowSize;
}

export default useWindowSize;

在上面的示例中,我们使用useState来管理窗口大小的状态,使用useEffect来处理副作用。我们还使用useEffect的返回函数来清除监听器,以避免内存泄漏。

使用这个自定义hook,我们可以在多个组件中复用窗口大小变化的逻辑,而无需重复编写相同的代码。例如,我们可以在两个不同的组件中使用这个自定义hook:

import useWindowSize from './useWindowSize';

function Component1() {
  const windowSize = useWindowSize();

  return (
    <div>
      <p>Window width: {windowSize.width}</p>
      <p>Window height: {windowSize.height}</p>
    </div>
  );
}

function Component2() {
  const windowSize = useWindowSize();

  return (
    <div>
      <p>Window width: {windowSize.width}</p>
      <p>Window height: {windowSize.height}</p>
    </div>
  );
}

在上面的示例中,我们在两个不同的组件中使用了useWindowSize自定义hook,以获取窗口大小的状态。这样,我们就可以在多个组件中复用处理窗口大小变化的逻辑。

六、React Hooks 注意事项

  1. Hook只能在函数组件和自定义Hook中使用,不能在类组件中使用。

  2. 在函数组件中使用Hook时,需要严格按照Hook的调用顺序来使用,不能在条件语句或循环语句中使用。

  3. useState、useReducer、useContext等Hook返回的state变量和dispatch函数都是稳定的,不会随着组件的重新渲染而改变。

  4. useEffect Hook用于处理副作用,需要注意副作用的依赖项,避免出现无限循环的情况。

  5. useMemo和useCallback可以用来优化组件的性能,避免重复计算和重复创建函数。

  6. useRef可以用来保存组件的状态,但是需要注意它与useState的区别,以及在函数组件中如何获取DOM元素。

  7. 自定义Hook可以用来复用组件逻辑,但是需要注意自定义Hook的命名规范和使用方式。

转载自:https://juejin.cn/post/7239206188164366397
评论
请登录