React Hooks 详解之 useState

本文详细解释了ReactHooks中useState的作用,演示其基本用法,并探讨了自定义useState实现和多useState使用中的问题及解决方案。通过实例说明了如何避免在条件语句中使用useState以防止渲染错误。
摘要由CSDN通过智能技术生成

前言

关于 react hooks 的优点,这里就不详细阐述了,大家可以去查看 文档。 本文的主要重点是详细解释各种 hooks 的使用以及阐述一些简单的 hooks 实现来帮助我们理解 hooks。其中第一个 hooks 也是使用频率最高最重要的 Hooks 就是 useState

useState

useState 的使用

Hooks 的最大的作用就是可以让你在不编写class的情况下使用state以及其他的 React 特性。而 useState 的功能就是让你在函数式组件中使用 state。 我们看下具体使用:

import React, { useState } from "react";
import ReactDom from "react-dom";

function Counter() {
  let [count, setCount] = useState(0); // 定义state:count
  return (
    <>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </>
  );
}

function render() {
  ReactDom.render(<Counter />, document.getElementById("root"));
}

render();

在上面的代码中,Counter组件是一个函数式组件,通过 useState 传入一个初始值,然后返回了 count 和 setCountcount 在组件每次被调用时都会发生变化,setCount 用于修改 count 的值,每次修改后都会触发 Count 组件的重新渲染。从上面的分析中,我们可以看到 useState 主要具有以下功能:

  1. 接受一个参数作为初始化值
  2. 返回一个数组,数组的第一个值为最新的状态 count,第二个值为一个函数用于修改状态 setCount
  3. setCount 设置后需要触发重新渲染

useState 的初步实现

根据上面分析的 useState 的功能,我们初步实现一个简单的 useState。

let state;
function useState(initialState) {
  state = state || initialState;
  function setState(newState) {
    state = newState;
    render();
  }
  return [state, setState];
}

实现思路如下:

  • 外部声明一个 state,用来接收初始值和更新后的值。为什么要定义在 useState 函数外面?是因为如果定义在函数里面,每次重新渲染时,都会将这个值设置为初始值,那样的话就拿不到最新的值了。
  • 内部定义一个函数,函数用来更新 state,并触发重新渲染。函数会接收一个 newState,并将其赋值给外部的 state,然后调用 render 函数,实现组件的重新渲染。
  • 返回一个数组。数组中是最新的 state 以及更新 state 的方法。

我们将自己实现的 useState 替换 react 的 useState,观察功能的实现。

import React from "react";
import ReactDom from "react-dom";

// 自定义的useState
let state;
function useState(initialState) {
  state = state || initialState;
  function setState(newState) {
    state = newState;
    render();
  }
  return [state, setState];
}

// Counter组件
function Counter() {
  let [count, setCount] = useState(0);
  return (
    <>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </>
  );
}

function render() {
  ReactDom.render(<Counter />, document.getElementById("root"));
}
render();

上面的代码能够正常实现原来的 useState 的一些功能,但是这存在一些问题,那就是如果有多个 useState 怎么办?如果保存多个 state?我们看如下代码?

function Counter() {
  let [count, setCount] = useState(0);
  let [num, setNum] = useState(0); // 共用一个state保存状态,修改第二个会导致第一个被覆盖。
  return (
    <>
      <p>{num}</p>
      <button onClick={() => setNum(num + 1)}>+</button>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </>
  );
}

由于我们所有的数据都共用同一个 state,因此修改其中一个会导致另外的被覆盖。为了解决这个问题,我们必须给每一个数据提供一个变量用来保存状态,从而避免冲突。解决办法是使用一个数组来进行保存。

let state = []; // state数组用来保存数据
let index = 0; // index用来对应每一个数组项
function useState(initialState) {
  let currentIndex = index; // currentIndex用来保存当前index
  state[currentIndex] = state[currentIndex] || initialState;
  function setState(newState) {
    state[currentIndex] = newState;
    render();
  }
  index += 1; // 每次修改完成之后index加1
  return [state[currentIndex], setState];
}

function render() {
  index = 0; // render时需要重新恢复index
  ReactDom.render(<Counter />, document.getElementById("root"));
}

实现思路如下:

  • state声明成数组,每一个数据对应数组的某一项
  • 声明一个索引index,每个数据对应一个索引值
  • setState通过操作索引去设置值
  • 每调用一次useState需要将 index+=1。这样的话确保多个数据具有不同的索引值
  • 返回的值也是通过索引获取
  • 每次 render 重新渲染时需要将索引 index 置为 0,确保每个数据对应的索引每次都是一致的(render 渲染组件重新渲染,组件内所有的 useState 会执行一次,每个数据又会分配一个索引,因此每次需要将 index 置为 0,确保每次的索引一致。这也是为什么 hooks 不能写在 if,while 等条件判断中)。

上面最核心的一点就是确保每个 useState 的数据对应的 index 必须一致。 也就是说:

  • 第一次渲染时,count 对应的索引值为 0,num 对应的索引值为 1。
  • 第二次渲染时,count 对应的索引值仍然为 0,num 对应的索引值为 1。
  • 第三次渲染时,count 对应的索引值仍然为 0,num 对应的索引值为 1。
  • ...

为什么需要保证 useState 的数据索引一致
我们定义多个数据,使用多次 useState,观察每次数据的索引值:

import React from "react";
import ReactDom from "react-dom";

let state = [];
let index = 0;
function useState(initialState) {
  console.log("index", index); // 观察
  let currentIndex = index;
  state[currentIndex] = state[currentIndex] || initialState;
  function setState(newState) {
    state[currentIndex] = newState;
    render();
  }
  index += 1;
  return [state[currentIndex], setState];
}

function Counter() {
  let [count, setCount] = useState(0); // 第一个useState的索引index
  let [num, setNum] = useState(0); // 第二个useState的索引index
  let [count1, setCount1] = useState(0); // 第三个useState的索引index
  let [num1, setNum1] = useState(0); // 第四个useState的索引index
  return (
    <>
      <p>{num}</p>
      <button onClick={() => setNum(num + 1)}>+</button>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </>
  );
}

function render() {
  index = 0;
  ReactDom.render(<Counter />, document.getElementById("root"));
}
render();

我们查看最终的打印顺序为:

index 0
index 1
index 2
index 3

也就是说无论什么情况下:这四个数据对应的索引始终分别为:0、1、2 和 3。 不允许出现下面这种情况。

function Counter() {
  let [count, setCount] = useState(0);
  // 在条件语句中定义useState
  if (count % 2 == 0) {
    let [count1, setCount1] = useState(0);
  }
  if (num % 2 == 0) {
    let [num1, setNum1] = useState(0);
  }
  let [num, setNum] = useState(0);

  return (
    <>
      <p>{num}</p>
      <button onClick={() => setNum(num + 1)}>+</button>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </>
  );
}

如果我们在条件语句中定义了 useState,这样的话会导致可能第一次只有两个 useState,对应的count和num的索引为0和1。但是下一次满足条件,有了四个useState了,对应的count和num的索引就变成0和3了。这样的话num的索引值发生了变化,它在不同情况下从数组中取得的值就是不一样了,不是它自身的值,这样就会导致错误。因此,uesState如果定义在条件语句中就会出现如下报错:

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render

总结

到目前为止,我们介绍了

  • useState的使用
  • useState的初步实现
  • useState实现过程中面临的问题,以及解决办法

通过上面的介绍,可以加深我们对useState的理解,当然这不是官方的实现方式,只是简化后便于理解的方式。目的只是为了帮助我们更好地使用useState。

  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值