useState基础用法

2/17/2023 React

React Hook useState基础用法

# useState概念解释

第一个Hook(钩子函数)是useState,他的作用是"钩住"函数组件中自定义的变量。

"钩住"?

之前那篇"React Hook 简介"中那句话:Hook本身单词意思就是"钩子",作用就是"钩住某些生命周期函数或某些数据状态,并进行某些关联触发调用"。

"如何钩住"?在React底层代码中,是通过自定义dispatcher,采用"发布订阅模式"实现的。

关于"钩子"、"钩住"、"如何钩住"的概念以后再学习其他Hook函数的时候就不再解释啦。

# useState是来解决类组件什么问题的?

答:useState能够解决类组件所有自定义变量只能存储在this.state的问题。

举个🌰:若某个组件需要有2个自定义变量name和age,那么在类组件中只能定义如下

constructor(props) {
    super(props);
    this.state = {
        name: 'liam',
        age: 28
    }
}
1
2
3
4
5
6
7

name和age只能作为this.state的一个属性。

没有对比就没有伤害,看一下使用useState后,函数组件是如何实现上述需求的

const [name, setName] = useState("liam");
const [age, setAge] = useState(28)
1
2
  1. 函数组件本身是一个函数,不是类,因此没有构造函数constructor(props);
  2. 任何你想定义的变量都可以单独拆分出去,独立定义,互不影响;

两段代码对比之下,就会发现使用Hook的useState后,会让定义的变量相对独立、清晰简单、便于管理。

# useState函数源码

首先看一下React源码中的ReachHooks.js (opens new window)

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}
1
2
3
4
5
6

上述代码看不懂也没事,只是讲述一下如何使用Hook,不是Hook源码分析,之所以贴出源码只是显得本文比较有深度。哈哈哈

哦,对了,是不是以为这是TS代码?其实不是的,是flow,不过也可以完全将TS的泛型知识去套用到 flow 中。
在这里补充一些TypeScript常识:

  1. React本身采用TypeScript编写,还是补充点TS常识,方便对各个hook函数源码的理解(我就大概看了一下,没具体了解,目前太菜
  2. 对于useState以及其他hook函数源码,函数参数中会反复出现<S>、<T>、<P>、<L>、<I>,这些大写字母中,React约定他们对应的单词如下:
    • state -> S -> 约定表示某种"数据"
    • type -> T -> 约定表示某种"类型"
    • props -> P -> 约定表示"属性传值对应的props"
    • initial -> I -> 约定表示某个"初始值"
  3. 这种用<X>包裹起来的类型声明,在TS中成为"泛型"。理论上是可以使用任意单词的,上面那些缩写只是React自己约定单词缩写的。
  4. 对于一段TS代码,如果出现了<S>,那么后面所有的<S>都将表示"某种相同类型的数据"。对于TS的泛型相关的,多看看文档和去github搜索一些优秀的TS项目学习。

# useState基本用法

useState(value)函数会返回一个数组,该数组包含2个元素:第1个元素为我们定义的变量,第2个元素为修改该变量对应的函数名称。

代码形式:

const [variable, setVariable] = useState(value);
// ...
setVariable(newValue); //修改variable的值
1
2
3

# 拆解说明

  1. const [a,b] = [a,b] 这种形式为ES6的"解构赋值";
  2. "variable'为函数组件中自定义的变量名;
  3. "setVariable"为修改"variable"对应的函数名;
  4. "useState"为本次学习的Hook函数;
  5. "value"为变量默认值;
  6. "setVariable(newValue)"为调用setVariable并将新的值newValue赋值给variable。

# "variable"补充说明

  1. variable为变量名,实际使用中可以修改成任意变量名,比如name、age、count等等;
  2. 但是,函数组件接收父级组件属性传值的变量名为props,因此不要将变量名为props,以免混淆;
  3. 我就是要变量名定义成props,不仅不会报错还会正常执行,开心就好 😄。

# "setVariable"补充说明

  1. 该名称采用"set"+"变量名"的驼峰命名形式,只是为了提高代码可读性;
  2. 一般React项目都约定使用此种命名方式;
  3. 也可以使用自己喜欢的命名方式或者以团队规范来,但是不能以数字开头。

# "value"补充说明

  1. 必填项,不可缺省,若缺省则实际运行时会提示变量名未定义;
  2. 值的类型可以是字符串、数字、数组、对象;
  3. 值还可以是null,但不可以为undefined。

# "newValue"补充说明(非常重要)

setVariable采用"异步直接赋值"的形式,并不会像类组件中的setState()那样做"异步对比累加赋值"。

"异步"?
这里的"异步"和类组件中setState中的异步是同一个意思,都是为了优化React渲染性能而故意为之。

"直接赋值"?

  1. 在Hook中,对于简单类型数据,比如number、string类型,可以追鹅通过setVariable(newValue)直接进行赋值。
  2. 但对于复杂类型数据,比如array,object类型、若想修改其中某一个属性值而不影响其他属性,则需要先copy一份,修改某个属性后再整体赋值。具体怎么做,等过几天写下一篇的时候会写出。

# useState使用Demo

// 函数组件内定义变量name
const [name, setName] = useState("liam"); // name默认值为liam

// 在函数组件内,某些事件交互处理函数修改name的值,例如某次鼠标点击的处理函数handleClick
const handleClick = () => {
    setName("AiYang");
    // 注意一下,setName('AiYang')是异步修改的,如果此时执行console.log(name) 输出的值依然是liam
    // 之前写了一篇ahooks的一篇文章中就是为了解决这个问题 或者接下来写的 都会写到
}
1
2
3
4
5
6
7
8
9

上述代码中,我们进行了一下子操作:

  1. 声明一个变量name、修改name的方法setName、并将name默认值设置为"liam";
  2. 通过setName将name值修改为"AiYang";

注意:在一个组件中,可以不限制次数使用useState(),因此,我们可以声明多个变量,例如下面的代码:

const [name, setName] = useState("liamFE");
const [age, setAge] = useState(28);
1
2

在该代码片段中,我们分别定义了2个变量:name和age以及他们对应的修改函数setName、setAge。

# 练习题

用useState实现一个计数器,默认为0,每次点击+1。
完整🌰:

import React, {useState} from 'react';

const countComponent = () => {
    const [count, setCount] = useState(0);
    
    const handleClick = () => {
        setCount(count +1)
    }
    
    return <div onclick={handleClick}>
        {count}
    </div>
}

export default countComponent;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

在上述代码中,没有用到this,这就是函数式组件使用Hook的魅力之一,再也不用去关心烦人的this到底指向谁这个问题了。

至此,关于useState的基础用法写完了。

我发布完以后在这重新又检查了一遍,害怕那些React的高手来给小老弟上眼药水,我这只是最基础的使用以及详细讲解,希望各位大佬嘴下留情。

加油,共勉!