React 18中hook函数详解之useState和useEffect

前言

React创建组件的方式有三种,分别是函数式组件、类组件,还有createElement组件。react v16.8版本之前函数式组件是没有状态的。但是,自16.8以后得版本有个hook函数,函数式组件也有了状态,反而类组件没有多少人写了,原因在于生命周期很麻烦,也难记。笔者近几年写React项目已经很少使用类组件了。接下来,详细探讨下hook函数,为什么会有那么神奇的效果?

一 、常用的Hook有哪些?

React Hooks 是 React v16.8 之后推出的函数式组件状态管理方案。它是为了解决状态复用、类组件写法麻烦等原因而提出的,Hook函数本质是闭包。Hooks 主要是利用闭包来保存状态,使用链表保存一系列 Hooks,将链表中的第一个 Hook 与 Fiber 关联。在 Fiber 树更新时,就能从 Hooks 中计算出最终输出的状态和执行相关的副作用。Hook函数带来便利有逻辑复用、业务代码更聚合、写法更简洁。

常用的Hook函数useState、useEffect、useRef、useCallback、useMemo、useReducer、useLayoutEffect

二、useState详解

useState是React自带的一个Hook函数,使用useState可声明内部状态变量。useState接收的参数为状态初始值或状态初始化方法,它返回一个数组。数组的第一项是当前状态值,每次渲染其状态值可能都会不同;第二项是可改变对应状态值的set函数,在useState初始化后该函数不会变化。

useState的类型为:

function useState(initialState:S|(() => S )): [S,Dispatch >];

initialState仅在组件初始化时生效,后续的渲染将忽略initialState:

const [value, setValue] = useState("");
const [count, setCount] = useState(value);

如上例中的value,当初始值传入另一个状态并初始化后,另一个状态函数将不再依赖value的值。

在 class 中,我们需要调用 this.setState() 来更新 count 值,在函数式组件中,我们已经有了 setCount 和 count 变量,所以我们不需要 this:

import {useState} from "react";
const Example = () => {
  const [count, setCount] = useState(0);
  return (
    
) }

类似于setState,单击按钮时调用setCount更新了状态值count。当调用setCount后,组件会重新渲染,count的值会得到更新。

当传入初始状态为函数时,其仅执行一次,类似于类组件中的构造函数:

useState返回的更新函数也可使用函数式更新:

setCount(preCount => preCount + 1)

如果新的state需要依赖先前的 state 计算得出,那么可以将回调函数当作参数传递给setState。该回调函数将接收先前的state,并将返回的值作为新的state进行更新。

在组件生命周期或React合成事件中,setState是异步;在setTimeout或者原生dom事件中,setState是同步。

为什么react大部分情况setState是异步的呢?假如所有setState是同步的,意味着每执行一次setState时(有可能一个同步代码中,多次setState),都重新vnode diff + dom修改,这对性能来说是极为不好的。如果是异步,则可以把一个同步代码中的多个setState合并成一次组件更新。

三、useEffect详解

useEffect函数会在组件渲染完毕后,执行和渲染无关的副作用,如数据获取,设置订阅以及手动更改 React 组件中的 DOM 等。

有了useEffect,我们可以在函数组件中实现 像类组件中的生命周期那样某个阶段做某件事情,具有:

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

基本用法:

useEffect(() => {
    console.log('这是一个不含依赖数组的useEffect,每次render都会执行!')
})

useEffect接受一个回调函数和一个可选的依赖项数组。回调函数会在组件挂载、更新和卸载时执行,根据依赖项的不同情况选择执行。如果依赖项为空数组,那么回调函数只会在组件挂载和卸载时执行一次。如果依赖项中包含某个状态或属性,那么只有在这个状态或属性发生变化时才会执行回调函数。 

useEffect的规则

  • 没有传第二个参数时,在每次 render 之后都会执行 useEffect中的内容
  • useEffect接受第二个参数来控制跳过执行,下次 render 后如果指定的值没有变化就不会执行
  • useEffect 是在 render 之后浏览器已经渲染结束才执行
  • useEffect 的第二个参数是可选的,类型是一个数组
  • 根据第二个参数的不同情况,useEffect具有不同作用

下面是useEffect的一些常见用法:

1、获取数据和执行网络请求:

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

function MyComponent() {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios.get('/api/data');
      setData(result.data);
    }
    fetchData();
  }, []);

  return (
    
{data.map((item) =>
{item.name}
)}
); } export default MyComponent;

2、订阅事件:

import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    const subscription = myEventEmitter.subscribe('event', () => {
      // 处理事件逻辑
    });

    return () => {
      subscription.unsubscribe();
    };
  }, []);

  return 
My Component
; }

3、执行清理操作:

import { useEffect, useState } from 'react';

function MyComponent() {
  const [timer, setTimer] = useState(null);

  useEffect(() => {
    const id = setInterval(() => {
      // 处理定时器逻辑
    }, 1000);

    setTimer(id);

    return () => {
      clearInterval(timer);
    };
  }, []);

  return 
My Component
; }

4、使用第三方库:

import { useEffect } from 'react';
import moment from 'moment';

function MyComponent() {
  useEffect(() => {
    moment.locale('zh-cn');
  }, []);

  return 
{moment().format('LLLL')}
; }

需要注意的是,useEffect回调函数中的操作可能会对应用程序的性能产生影响。如果回调函数执行的操作很耗费资源,那么可能会导致应用程序变慢。因此,需要谨慎使用useEffect,并在需要时进行性能优化。

在使用React Hooks时,需要遵守以下准则及特性要求。

  • 只在顶层使用Hooks。不要在循环、条件或嵌套函数中调用Hooks,确保总是在React函数组件的顶层调用它们。

  • 不要在普通的JavaScript函数中调用Hooks。仅在React的函数组件中调用Hooks,以及在自定义Hook中调用其他Hooks。

你可能感兴趣的:(react,react.js,javascript,前端,useState,useEffect)