初识useState,你有相同的疑惑吗?

307 阅读6分钟

首先,必须称赞一下React官方文档更新了, 虽然只是beta版,但是对于用户学习V18版本的React来说,可以说是新手福音。

这说明:hook写法被广泛应用,被广大的开发者所认可,Class写法被逐渐遗弃

那么,新文档相比旧文档有什么不同呢?

  • 所有代码示例 基于 Hook 编写 而不是 class(类)。
  • 添加了 交互式示例 以及可视化的图表。
  • 指南部分包含了 挑战赛(和答案!)  以检查你的学习情况。

本文就初窥React提供新手解惑,助您少走弯路,与之共勉

为什么设置状态后不会立即更新

state状态更新

为什么调用3次+1函数后,只会增加一次呢?

/*
    你也可以理解为覆盖操作
*/
setCount1(count1 + 1)
setCount1(count1 + 1)
setCount1(count1 + 1)

// 1

React官方称state为Snapshot,翻译成中文就是快照

设置状态只会为下一次渲染更改它。请记住,state的值在渲染中永远都不会改变。简单的说:设置状态请求新的重新渲染,但不会在已经运行的代码中更改它。。如同快照一样,此时它的值是固定的

state的渲染过程如下所示:

  1. setCount1(count1 + 1): count1是0, 这样的setCount1(0 + 1)

    React 准备在下一次渲染时更改count1为 =>1

  2. setCount1(count1 + 1): count1是0, 这样的setCount1(0 + 1)

    React 准备在下一次渲染时更改count1为 => 1

  3. setCount1(count1 + 1): count1是0, 这样的setCount1(0 + 1)

    React 准备在下一次渲染时更改count1为 => 1

如果你想在重新渲染之前读取最新状态怎么办?

那么需要使用状态更新函数。也叫做“批处理

批处理

处理状态更新之前, React 会等待事件处理程序中的所有代码都已运行。这也意味着在您的事件处理程序及其中的任何代码完成之前, UI 不会更新。这种行为,也称为批处理

/** 
    代码同上
*/
setCount2(n => n + 1)
setCount2(n => n + 1)
setCount2(n => n + 1)

// 3

state的渲染过程如下所示:

  1. setCount2(n => n + 1): n => n + 1 是一个函数。

    React 将其添加到队列中。

  2. setCount2(n => n + 1): n => n + 1 是一个函数。

    React 将其添加到队列中。

  3. setCount2(n => n + 1): n => n + 1 是一个函数。

    React 将其添加到队列中。

当您useState在下一次渲染期间调用时,React 会遍历队列。之前的number状态是0,所以这就是 React 传递给第一个更新函数的参数n。然后 React 将你之前更新函数的返回值作为 传递给下一个更新函数n,依此类推:

image.png

状态更新 + 批处理更新

如果同时使用状态函数和更新函数进行渲染状态,会发生什么呢?

const [number, setNumber] = useState(0);

/** 
    代码同上
*/
setNumber(number + 5);
setNumber(n => n + 1);
setNumber(number + 10);
setNumber(n => n + 1);

 // 11

state的渲染过程如下所示:

  1. setNumber(number + 5): count1是0, 这样的setCount1(0 + 5)

    React 准备在下一次渲染时更改number为 =>5

  2. setNumber(n => n + 1): n => n + 1 是一个函数。

    React 将其添加到队列中。

  3. setNumber(number + 10): count1是0, 这样的setCount1(0 + 10)

    React 准备在下一次渲染时更改number为 =>10

  4. setNumber(n => n + 1): n => n + 1 是一个函数。

    React 将其添加到队列中。

异步代码中获取当前的state变量

异步代码中读取最新的状态,我们会发现,状态就像快照一样工作,所以你不能从像超时这样的异步操作中读取最新的状态

我们点击按钮后,重新编辑输入框的内容, 我们会发现,显示的是点击按钮时的内容 而不是当前的内容

我们需要使用ref存储当前的信息

状态变量

React允许用户使用多个state变量保存state,同时也允许设置单个state来编写程序。

如何去构建state的结构,可以说仁者见仁,智者见智。没有正确错误之分, 但是良好的state可以让组件可读性更强,更容易修改和调试

单一状态变量 vs 状态变量对象

我们以登陆作为功能分析,去分析如何定义state

  1. 提交输入用户名
  2. 提交用户输入的密码
  3. 提交用户是否记住密码
  4. 用户选择接受条款,启用 登陆 按钮

首先,我们定义一个form对象,去存储提交的信息,并将其中的变量赋初值

const [form, setForm] = useState({})

首先,用户名和密码统一成一个变量。

然后,问题来了,记住密码是否作为单独的变量,是否接受条款是否作为单独的变量?

在这里,我觉得以功能作为划分,所有的提交信息放入到一起,而记住密码通常是应用程序特定的,而与用户信息无关,需要单独去处理这个逻辑

const [form, setForm] = useState({
    name: '',
    password: '',
    comfirmPassword: ''
})
const [isRemember, setIsRemember] = useState(false)
const [isAccept, setIsAccept] = useState(false)

迭代到V2.0,业务需求进行了调整。

  1. 新增:Root和IMA user
  2. IMA user需要添加公司账号
  3. IMA user不能记住密码

于是,是否将Root和IMA用户权限作为单独的变量 就产生分歧了?

正常来说是添加到state对象中的,但是不排除root具有特殊的行为和权限,作为单独的状态对象可能更合适。

迭代到3.0...

所以说,使用多个state或者单一state,并不是绝对的。是根据业务逻辑和场景进行划分的。但不管怎样: 合并功能类似或者同步更新的状态变量,是最优解

冗余对象和重复对象 vs 变量

从状态中删除冗余和重复数据有助于确保其所有部分保持同步,尽可能的让你的状态变得简单

常见的冗余对象定义:

// fullName = firstName + lastName
const [fullName, setFullName] = useState('')
// length
const [total, setTotal] = useState(0)

很多时候,冗余对象都可以通过设置变量去解决

const fullName = firstName + ' ' + lastName;
const total = array.length;

更新状态数组和对象

更新状态对象

更新对象数组,推荐使用解构赋值

setUser({
    ...user,
    name: e.target.value
})

更新状态数组

image.png

更新数组时不要使用改变数组的方法,也不要去改变原数组

// 新增
setArr([...arr, {id: 1, name: e.target.value}])

// 编辑
let newArr = arr.map(item => {
    if(item.id == id){
        return {
            ...item,
            name: e.target.value
        }
    } else {
        return item
    }
})
setArr(newArr)

// 删除
let newArr = arr.filter(item => item.id != id)
setArr(newArr)

useState vs useImmer

我们发现,编辑操作的时候,代码非常臃肿,有没有简洁的代码呢?

import { useImmer } from 'use-immer';

const [todos, updateTodos] = useImmer(initialTodos);

function handleChangeTodo(nextTodo) {
    updateTodos(draft => {
      const todo = draft.find(t =>
        t.id === nextTodo.id
      );
      todo.title = nextTodo.title;
      todo.done = nextTodo.done;
    });
}

image.png

useImmer需要安装use-immer npm才能运行 只有来自 state 的数据会被 draft

性能优化

useMemo 缓存优化

useMemo用于优化代码, 可以让对应的函数只有在依赖发生变化时才返回新的值

useMemo主要用于缓存计算。比如:计算商品的价格,前面所讲的fullName计算,有点类似Vue的computed

function sum() {
    return a + b;
}

// 只有在a或者b的值变化时才会触发sum函数执行
const result = useMemo(() => {sum()}, [a, b])

useMemo返回的是return返回的内容

useCallback

使用方式与useMemo类似,只是返回的是一个函数

useCallback返回的是函数

结语

React使用上限很高,下限很低,useState算是习的入门, 开启了组件化编程之旅。

那么祝您React编程愉快