赞
踩
关于 react hooks 的优点,这里就不详细阐述了,大家可以去查看
文档。 本文的主要重点是详细解释各种 hooks 的使用以及阐述一些简单的 hooks 实现来帮助我们理解 hooks。其中第一个 hooks 也是使用频率最高最重要的 Hooks 就是
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 和 setCount。count 在组件每次被调用时都会发生变化,setCount 用于修改 count 的值,每次修改后都会触发 Count 组件的重新渲染。从上面的分析中,我们可以看到 useState 主要具有以下功能:
根据上面分析的 useState 的功能,我们初步实现一个简单的 useState。
- let state;
- function useState(initialState) {
- state = state || initialState;
- function setState(newState) {
- state = newState;
- render();
- }
- return [state, setState];
- }
实现思路如下:
我们将自己实现的 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 必须一致。 也就是说:
为什么需要保证 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。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。