【SORT PLAN 1】React Hooks-useState 的日常小Tip ❀

·  阅读 254
【SORT PLAN 1】React Hooks-useState 的日常小Tip ❀

有关hooks的使用,很早之前就想过要总结一下,但一直没时间(自己懒),下面就个人在平时使用过程中遇到的一些问题,做一个整理总结,这篇主要是useState的一些使用Tips

1. 基本使用

hooks让我们可以更方便的定义数据,更新数据。

当我们尝试新增一个变量的时候,会使用useState来定义变量:

const [loading, setLoading] = useState<boolean>(true);
复制代码

useState() 可以设置变量的默认值

当我们需要改变这个loading值的时候,可以使用自己定义的setLoading,并setLoading(false),但有时候可以并不能达到我们想要的效果

setLoading(false)
console.log(loading) // true
复制代码

当你在设置完setLoading(false) 后,立即打印loading,此时还是true,也就是说,setLoading(false)并没有生效。

2. 引入hooks后的执行顺序

我们知道,函数式组件中是没有生命周期这个概念的的,但是引入了hooks之后就不一样的,在某种程度上,是可以模拟一些生命周期的。

下面让我们来看一下,具体的执行顺序(就个人平时项目中使用)

  • 页面渲染

  • useState

  • useEffect

  • return

  • set函数

import React, { FC, useEffect, useState } from "react";

const App: FC = () => {
  const [count, setCount] = useState(0);
  console.log("进入App函数组件内部了", count);

  const handleClickAdd = () => {
    setCount(count + 1);
    console.log(count, "count增加了吗?");
  };

  useEffect(() => {
    console.log("进入useEffect", count);
  }, []);

  useEffect(() => {
    console.log("当count发生变化时,进入useEffect", count);
  }, [count]);

  return (
    <div className="App">
      {console.log("进入return函数了", count)}
      <div>数字:{count}</div>
      <button onClick={handleClickAdd}>点击加1</button>
    </div>
  );
};

export default App;
复制代码

页面展示:

当页面完全加载完后,代码打印的顺序为:

从下面的打印结果可以看到,

  1. 先执行的是App函数组件内部的console.log(),
  2. 之后便是执行一遍return函数
  3. 接着才会执行到useEffect
  4. 当useEffect带有依赖项后,也会执行对应的useEffect

当点击页面按钮让数字加1时,代码的打印顺序为:

  1. 首先是执行点击函数handleClickAdd,内部执行setCount()
  2. 此时我们在handleClickAdd内部继续打印console.log(count, "count增加了吗?"),可以看到count的值是没有变化的
  3. 但是,我们可以看到,代码又重新执行了一遍,和之前页面加载的情况一样,此时count成功的变为1了
  4. 继续执行到return函数里面,此时页面可以正确的展示数字1
  5. count发生改变,对应的useEffect也随之执行

3. 使用过程的一些问题

看到这里,我们开始有第一个疑问了?

3.1. set后不能立即打印正常值

为什么执行setCount之后,不能成功得到值,需要页面重新渲染一遍之后才能得到正确的结果?如何解决?

分析原因:

我们肯定会想到useState的set方法是异步的,所以这种set之后同步读取state的代码会读到旧的值。

让我们使用setTimeOut去延迟打印一下

  const handleClickAdd = () => {
    setCount(count + 1);
    setTimeout(() => {
      console.log(count, "count增加了吗?");
    }, 1000);
  };
复制代码

结果如下,count值依旧为0,而且可以看到,即便重新渲染了页面,return里面的值也都发生了改变,但是count还是没变。

其实useState返回的set方法是异步的,但是造成不能及时生效却有更深的原因:

我们知道React Hooks本质上就是函数,我们在这里读取的count变量是本轮渲染时useState的返回值,count变量的值在 const [count, setCount] = useState(0); 这句代码执行的时候就确定了为0。

我们使用setCount方法改变的是App组件中名字为count的state,但是并没有改变本轮渲染中count变量的值,所以即使是把输出count的语句放到了setTimeout里面,它依然指向不变的count变量,而非组件的count state。

那么有什么方法可以解决这个问题吗?

(说下了解的一些方法,如果有更好的欢迎评论区留言哇!)

3.1.1 方法1-使用useEffect

在之前写项目的时候我尝试过在useEffect中写,当count发生改变的时候,执行该useEffect,此时再读取count就是正确的值了; 这个就是上面写到的这种:

  useEffect(() => {
    console.log("当count发生变化时,进入useEffect", count);
  }, [count]);
复制代码

3.1.2 方法2-利用set函数支持的一个函数,用旧的值计算新的值返回

下面👇🏻一种方法可以看一下:

setCount((prevCount) => {
      console.log("count增加了吗", prevCount);
      return prevCount;
    });
复制代码

useState hook返回的set方法除了 setState(newState); 这种调用方法,还可以接受一个函数参数,用旧的state值来计算出新的state返回:

setState(prevState => {
  const newState = computeUpdatedState(prevState);
  return newState;
});
复制代码

这种调用方法的函数参数传入的prevState参数,总是指向最新的state值,我们可以放心使用。

注意我们这里使用setState方法只是为了取到最新的state值,并不是真的要改变state,所以在实例代码中直接返回pervFoo就可以:

setCount((prevCount) => {
      console.log("count增加了吗", prevCount);
      return prevCount;
});
复制代码

根据React文档的描述:

If your update function returns the exact same value as the current state, the subsequent rerender will be skipped completely.

返回原来的state值不会引发重新渲染,所以这种方法不会带来性能问题,可以放心使用。

3.2. 引用类型修改(页面不刷新)

修改了数组,但是页面没有刷新


import React, {useState,useEffect} from 'react';
import './App.css';


function App() {
  const [arr, updateArr] = useState<any>(['1','4',4,'8']);
    const addList = () => {
        arr.push(1);
        updateArr(arr);
        console.log(arr,'arr')
    };
    return (
        <div>
            {
                arr.map((item:any, index:any) => (
                    <p key={index}>{index} {item}</p>
                ))
            }
            <button onClick={addList}>添加List</button>
        </div> 
    );
}

export default App;
复制代码

3.2.1 方法1-扩展运算符

updateArr([...arr]);
复制代码

有时候也会遇到修改对象属性的时候,也是可以通过这种方式进行修改

3.2.2 方法2-immer 统一处理的state

immer: 获得不可变的reducer,即一个深拷贝后又修改的全新的state

了解:github.com/immerjs/imm…

Immer 是 mobx 的作者写的一个 immutable 库,核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构,简单易用、体量小巧、设计巧妙,满足了我们对JS不可变数据结构的需求。

  • 理解这个hooks useImmerState(一个自定义hooks)
import { useState } from 'react';
import type { Draft } from 'immer';
import produce from 'immer';

/**
 * useImmerState  Immer
 */
export function useImmerState<T>(defaultState: T): [T, (func: (draft: Draft<T>) => void) => void] {
  const [state, setState] = useState<T>(defaultState);

  function setImmerState(func: (draft: Draft<T>) => void) {
    setState((s) =>
      produce(s, (d) => {
        return func(d);
      }),
    );
  }
  return [state, setImmerState];
}
复制代码
  • 如何使用
import { useImmerState } from '@common/hooks/useImmerState';

const getInitialState = () => {
  return {
    componentsList: [] as IComponent<ComponentType>[],
    isShowEmpty: false,
    isShowNotice: false,
  };
};


const [state, setState] = useImmerState(getInitialState());


const { componentsList, isShowEmpty, isShowNotice } = state;


 setState((d) => {
       d.componentsList = data;
       d.isShowEmpty = arr.length === 0;
       d.isShowNotice = noticeArr.length > 0;
});

复制代码

有关hooks中useState的一些使用小Tip就暂时整理到这里啦,hooks使用起来非常方便,当如果使用不当就会造成像重复渲染之类的问题,特别是使用useEffect的时候,之后有时间也要整理一下useEffect的使用,不过都是个人的一些看法哈。

当然也不乏useCallback,useMemo这样的用来提高页面性能,避免重复渲染的hooks,也是值得去学习的,(可能我在项目中用的并不是很多,不过也要看具体情况使用)

今天就到这啦

我是婧大,一名前端小学崽,希望和你一起学习一起进步。🙆🙆🙆

加油!wx:lj18379991972 欢迎👏🏻一起交流学习

文章肯定有写的不好的地方,可以评论区指正❤。

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改