react hooks--useState

概述

useState 可以使函数组件像类组件一样拥有 state,也就说明函数组件可以通过 useState 改变 UI 视图。那么 useState 到底应该如何使用,底层又是怎么运作的呢,首先一起看一下 useState 。

问题:Hook 是什么? 一个 Hook 就是一个特殊的函数,让你在函数组件中获取状态等 React 特性

使用模式:函数组件 + Hooks

特点:从名称上看,Hook 都以 use 开头

基本使用

  • 使用场景:当你想要在函数组件中,使用组件状态时 ,就要使用 useState Hook 了
  • 作用:为函数组件提供状态(state)
  • 使用步骤:
    1. 导入 useState 函数:import { useState} from 'react'
    2. 调用 useState 函数,并传入状态的初始值
    3. useState 函数的返回值中,拿到状态和修改状态的函数
    4. 在 JSX 中展示状态
    5. 在按钮的点击事件中调用修改状态的函数,来更新状态
  • 规则:
    • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用
    • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用
  • useState接受唯一一个参数,在第一次组件被调用时使用来作为初始化值

使用数组解构简化

比如,要获取数组中的元素:

  1. 原始方式:索引访问

    const arr = ['aaa', 'bbb']

    const a = arr[0] // 获取索引为 0 的元素
    const b = arr[1] // 获取索引为 1 的元素

  2. 简化方式:数组解构

    • 相当于创建了两个变量(可以是任意的变量名称)分别获取到对应索引的数组元素

      const arr = ['aaa', 'bbb']
      const [a, b] = arr

      const [state, setState] = arr

  • 使用数组解构简化 useState 的使用

    • 约定:修改状态的函数名称以 set 开头,后面跟上状态的名称

      // 解构出来的名称可以是任意名称
      const [state, setState] = useState(0)
      const [age, setAge] = useState(0)
      const [count, setCount] = useState(0)

演示示例

父传子

App.js--父组件

复制代码
import React from 'react'
import UseFun from './components/useFun'

export default function App() {
  let msg = '132'
  return (
    <div>
      <UseFun msg={msg}/>
    </div>
  )
}

src/components/useFun.js--子组件

复制代码
import React from 'react'
import PropTypes from 'prop-types'

function useFun(props) {
  return (
    <div>{props.msg}</div>
  )
}

useFun.defaultProps = {
  msg: '456'
}

useFun.propTypes = {
  msg: PropTypes.string
}

export default  useFun

计数器

App.js

复制代码
import React from 'react'
import UseFun from './components/UseFun'
import CountFun from './components/CountFun'

export default function App() {
  let msg = '132'
  return (
    <div>
      <CountFun />
    </div>
  )
}

components/CountFun.jsx

复制代码
import React from 'react'
import { useState } from 'react';

export default function CountFun() {

  let [count, setCount] = useState(0)

  function changeCount(){
    setCount(count + 1)
  }
 
  return (
    <div>
      {/* 展示状态值 */}
      <h1>useState Hook - {count}</h1>
      {/* 点击按钮,让状态值 +1 */}
      <button onClick={changeCount}>+1</button>
      <button onClick={() => changeCount()}>+1</button>
    </div>
  )
}

或下面的做法也可以

复制代码
import { useState } from 'react'

export default function const CountFun = () => {
  // 返回值是一个数组
  const stateArray = useState(0)

  // 状态值 -> 0
  const state = stateArray[0]
  // 修改状态的函数
  const setState = stateArray[1]

  return (
    <div>
      {/* 展示状态值 */}
      <h1>useState Hook -> {state}</h1>
      {/* 点击按钮,让状态值 +1 */}
      <button onClick={() => setState(state + 1)}>+1</button>
    </div>
  )
}
  • 参数:状态初始值。比如,传入 0 表示该状态的初始值为 0
    • 注意:此处的状态可以是任意值(比如,数值、字符串等),而 class 组件中的 state 必须是对象
  • 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)

useState详解

概述

状态的读取和修改:

状态的使用:1 读取状态 2 修改状态

  1. 读取状态:该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用
  2. 修改状态:
  • setCount(newValue) 是一个函数,参数表示:新的状态值
  • 调用该函数后,将使用新的状态值 替换****旧值
  • 修改状态后,因为状态发生了改变,所以,该组件会重新渲染

组件的更新过程:

函数组件使用 useState hook 后的执行过程,以及状态值的变化:

  • 组件第一次渲染:
    1. 从头开始执行该组件中的代码逻辑
    2. 调用 useState(0) 将传入的参数作为状态初始值,即:0
    3. 渲染组件,此时,获取到的状态 count 值为: 0
  • 组件第二次渲染:
    1. 点击按钮,调用 setCount(count + 1) 修改状态,因为状态发生改变,所以,该组件会重新渲染
    2. 组件重新渲染时,会再次执行该组件中的代码逻辑
    3. 再次调用 useState(0),此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1
    4. 再次渲染组件,此时,获取到的状态 count 值为:1

注意:useState 的初始值(参数)只会在组件第一次渲染时生效

也就是说,以后的每次渲染,useState 获取到都是最新的状态值。React 组件会记住每次最新的状态值!

注意点:

  • 在普通函数(方法)中,不能使用hooks
  • 在自定义的hooks中可以使用,自定义hooks必须以use开头

基本数据类型

引入useState

复制代码
import { useState } from 'react';

在函数组件内部定义state数据

复制代码
let [count, setCount] = useState(0);
let [name, setName] = useState('张三')

useState会返回一个数组,数组有两个值

  • 第一个值:代表state数据
  • 第二个值:更新这个state数据的一个函数,一般函数变量取名字通常是setXxx

可以定义多个 state值

修改数据:

复制代码
function changeName() {
    setName('李四');
}

完整代码:

复制代码
import React from 'react'
import { useState } from 'react';

function useFun(props) {
  let [msg, setMsg] = useState('张三')

  function changeMsg(){
    setMsg('李四')
  }

  return (
    <div>
      <p>msg:{msg}</p>
      <button onClick={changeMsg}>修改msg</button>
    </div>
  )
}

export default  useFun

引用数据类型

定义对象数据

复制代码
let [user, setUser] = useState({
    name: 'zs',
    age: 20
})

修改对象

复制代码
function changeUserName() {
    setUser({
        name: 'ls',
    })
}

这样修改对象,会导致其他属性丢失,因为它没有自动合并对象

正确方式:

复制代码
function changeUserName() {
    setUser({
        ...user,
        name: 'ls',
    })
}

可以将整个对象解构扩展,然后将修改的属性覆盖原来属性

注意:

修改state数据第一个参数可以是函数

复制代码
setUser((prevState) => {
    console.log(prevState)
    return {
        ...prevState,
        name: 'ls'
    }
})

这个函数和类组件的setState的第一个参数是一致的

修改数据函数没有第二个参数

修改数据的函数仍然是异步的,不是同步的。

完整代码:

复制代码
import React from "react";
import { useState } from "react";

function useFun(props) {
  let [msg, setMsg] = useState("张三");
  let [user, setName] = useState({
    name: "zs",
    age: 20,
  });

  function changeMsg() {
    setMsg("李四");
  }

  const changeName = () => {
    // setName({
    //   ...user,
    //   name: 'ls'
    // })
    //或如下写法:
    setName((prevState) => {
      console.log(prevState);
      return {
        ...prevState,
        name: "ls",
      };
    });
  };

  return (
    <div>
      <p>msg:{msg}</p>
      <button onClick={changeMsg}>修改msg</button>
      <hr />
      <p>name::{user.name}</p>
      <p>age: {user.age}</p>
      <button onClick={changeName}>修改name</button>
    </div>
  );
}

export default useFun;

为函数组件添加多个状态

问题:如果一个函数组件需要多个状态,该如何处理?

回答:调用 useState Hook 多次即可,每调用一次 useState Hook 可以提供一个状态。

注意:useState Hook 多次调用返回的 [state, setState] 相互之间,互不影响。

注意:React Hooks 只能直接出现在 函数组件 中,不能嵌套在 if/for/其他函数中

否则就会报错:React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render

React 的 useState 这个 Hook 被条件性(放在一个条件判断中)的调用了。

React Hooks 必须要每次组件渲染时,按照相同的顺序来调用所有的 Hooks。

  • 为什么会有这样的规则? 因为 React 是按照 Hooks 的调用顺序来识别每一个 Hook,如果每次调用的顺序不同,导致 React 无法知道是哪一个 Hook
  • 通过开发者工具可以查看到。

如何监听 state 变化?

类组件 setState 中,有第二个参数 callback 或者是生命周期componentDidUpdate 可以检测监听到 state 改变或是组件更新。

那么在函数组件中,如何怎么监听 state 变化呢?这个时候就需要 useEffect 出场了,通常可以把 state 作为依赖项传入 useEffect 第二个参数 deps ,但是注意 useEffect 初始化会默认执行一次。

具体可以参考如下 Demo :

dispatch更新特点

上述讲的批量更新和 flushSync ,在函数组件中,dispatch 更新效果和类组件是一样的,但是 useState 有一点值得注意,就是当调用改变 state 的函数dispatch,在本次函数执行上下文中,是获取不到最新的 state 值的,把上述demo 如下这么改:

结果:0,0,0

原因很简单,函数组件更新就是函数的执行,在函数一次执行过程中,函数内部所有变量重新声明,所以改变的 state ,只有在下一次函数组件执行时才会被更新。所以在如上同一个函数执行上下文中,number 一直为0,无论怎么打印,都拿不到最新的 state 。

useState注意事项

在使用 useState 的 dispatchAction 更新 state 的时候,记得不要传入相同的 state,这样会使视图不更新。比如下面这么写:

如上例子🌰中,当点击按钮后,发现视图没有改变,为什么会造成这个原因呢?

在 useState 的 dispatchAction 处理逻辑中,会浅比较两次 state ,发现 state 相同,不会开启更新调度任务; demo 中两次 state 指向了相同的内存空间,所以默认为 state 相等,就不会发生视图更新了。

解决问题: 把上述的 dispatchState 改成 dispatchState({...state}) 根本解决了问题,浅拷贝了对象,重新申请了一个内存空间。

相关推荐
musk12123 分钟前
electron 打包太大 试试 tauri , tauri 安装打包demo
前端·electron·tauri
翻滚吧键盘32 分钟前
js代码09
开发语言·javascript·ecmascript
万少1 小时前
第五款 HarmonyOS 上架作品 奇趣故事匣 来了
前端·harmonyos·客户端
OpenGL1 小时前
Android targetSdkVersion升级至35(Android15)相关问题
前端
rzl021 小时前
java web5(黑马)
java·开发语言·前端
Amy.Wang1 小时前
前端如何实现电子签名
前端·javascript·html5
海天胜景1 小时前
vue3 el-table 行筛选 设置为单选
javascript·vue.js·elementui
今天又在摸鱼1 小时前
Vue3-组件化-Vue核心思想之一
前端·javascript·vue.js
蓝婷儿1 小时前
每天一个前端小知识 Day 21 - 浏览器兼容性与 Polyfill 策略
前端
百锦再2 小时前
Vue中对象赋值问题:对象引用被保留,仅部分属性被覆盖
前端·javascript·vue.js·vue·web·reactive·ref