React Hooks基本使用

109 阅读4分钟

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

为什么需要Hook?

我们先来看一下类组件对比函数式组件有什么优势:

  • 类组件可以定义自己的状态(state),而函数式组件不可以,因为函数式组件每次调用都会产生新的临时变量;

  • 类组件有自己的生命周期函数,我们可以在对应的周期函数中完成不同的逻辑;

    • 比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次;
    • 函数式组件没有生命周期函数
  • 类组件可以在状态改变是只会重新执行render函数,而函数式组件在重新渲染时,整个函数都会被执行。

但是类组件也存在问题:

  • 复杂组件变得难以理解:

    • 我们在刚开始编写类组件时,组件起初很简单,但是随着业务的增多,组件会越来越复杂。
    • 例如组件经常在componentDidmount中获取数据,设置事件监听等,又要在componentWillUnmount中清除。
    • 对于这样的类组件非常难拆分,因为他们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码复杂度。
  • 难以理解的 class

    • 我们还发现ES6的class是学习React的一个障碍。
    • 我们必须要搞清楚this到底指向谁,所以要花很多精力去学习this。
  • 在组件之间复用状态逻辑很难

    • 我们也许熟悉React中一些代码复用的解决方案,比如高阶组件。
    • 但是这些方案会很麻烦,使代码难以理解。

Hook的出现可以解决上面提到的这些问题。他可以让我们在编写class的情况下使用state以及一些其他的React特性。

通俗一点讲: Hook的出现,把类组件的优势吸收进函数式组件里面来,对函数式组件做了一些增强,又保留函数式组件的简洁性。

使用state Hook

我们通过一个计数器的案列,来对比一下函数式组件结合hook与类组件的对比:

// hook
import React, { useState } from 'react';
​
export default function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);
​
  return (
    <div>
      <h1>当前计数: {count}</h1>
      <button onClick={() => setCount(count + 1)}>
        +1
      </button>
    </div>
  );
}
// 类组件
export default class Example extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  increment(){
      this.setState({ count: this.state.count + 1 })
  }
  render() {
    const { count } = this.state;
    return (
      <div>
        <p>当前计数:{count}</p>
        <button onClick={() => this.increment()}>
          +1
        </button>
      </div>
    );
  }
}

通过上面的一个简单的案例,是不是觉得使用hook让代码简洁多了。

Hook是什么

Hook是一个特殊的函数。它可以让我们"钩入" React state以及生命周期等特性。

useState做了什么

  • useState帮助我们定义了一个”state变量“,useState是一种新方法,他与class里面的this.state提供的功能完全相同。
  • 一般来说,在函数退出后变量就会”消失“,而state中的变量会被React保留。

useState需要哪些参数

useState接受唯一一个参数,在组件第一次被调用时来作为初始值。(如果没有传递参数,那么初始值为undefined)。

useState方法的返回值是什么

useState的返回值是一个数组,数组中是当前的state以及更新state的函数。const [count, setCount] = useState() 这与 class 里面 this.state.count 和 this.setState 类似。

使用Effect Hook

  • Effect Hook可以让你在函数式组件中执行”副作用“操作;
  • 网络请求,设置订阅,手动更新DOM都属于”副作用“;

下面我们来看这个例子,为计数器增加了一个小功能:将 document 的 title 设置为包含了点击次数的消息。

import React, { useState, useEffect } from 'react';
​
export default function Example() {
  const [count, setCount] = useState(0);
​
  useEffect(() => {
    document.title = `当前计数:${count}`;
  });
​
  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect分析:

  • useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数。
  • 默认情况下,组件第一次渲染和每次更新之后,都会执行这个函数。
  • 如果熟悉类组件的生命周期函数,可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

需要清除的Effect

我们在编写组件的时候,有些”副作用“是需要被清除的。例如:订阅外部数据源

使用useEffect清除副作用的案例:

import React, { useState, useEffect } from 'react';
​
export default function Example() {
  const [count, setCount] = useState(0);
​
  useEffect(() => {
    console.log("订阅数据行为");
    
    return function clean(){   //如果effect 返回一个函数,React 将会在执行清除操作时调用它
        console.log("取消订阅")
    }
  });
​
  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

为什么要返回一个函数

  • 这是 effect 可选的清除机制。
  • 每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。
  • 它们都属于 effect 的一部分。

React何时清除effect

1679933322831.png

  • React 会在组件卸载的时候执行清除操作。
  • React 会在执行当前 effect 之前对上一个 effect 进行清除操作。

使用多个Effect

前面说过,网络请求,设置订阅,手动修改DOM都属于”副作用“。Hook允许我们按照代码的用途分离它们,来实现简化代码逻辑的目的,而不是写在一起。

export default function Example(){
  const [counter, setCounter] = useState(10);
  // 副作用代码
  useEffect(()=>{
    // 修改dom
  })
  useEffect(()=>{
      // 设置订阅行为
    console.log("开始订阅数据");
    return ()=>{
        console.log("取消订阅");
        // 取消监听
    }
  })
  useEffect(()=>{
    // 发起网络请求
  })
  return (
    <div>
      <h1>App Counter:{counter}</h1>
      <button onClick={() => setCounter(counter + 1)}>+1</button>
    </div>
  );
}

React将按照effect声明的顺序依次调用组件中的每一个effect。

Effect性能优化

默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但这会导致两个问题:

  1. 某些代码我们只希望执行一次即可,类似于类组件中componentDidMount和componentWillUnmount中完成的事情;
  2. 多次执行也会导致一定的性能问题;

useEffect实际上接受两个参数,参数二是一个数组,判断该useEffect在哪些state发生变化时,才重新执行;

useEffect(() => {
  document.title = `当前计数: ${count}`;
}, [count]); // 仅在 count 更改时更新

注意:如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。

Hook规则

  1. 只在函数最顶层使用,不要在循环、条件或者子函数中调用。
  2. 只在React函数中调用,不要在普通的js函数中调用。