一、概念与基本使用
props中的所有属性都是不可变的,这使得React组件不能随着props的改变而改变。但在实际的开发中,我们更希望的是数据发生变化时,页面也会随着数据一起变化。React为我们提供了state用来解决这个问题。
state和props类似,都是一种存储属性的方式,但是不同点在于state只属于当前组件,其他组件无法访问。并且state是可变的,当其发生变化后组件会自动重新渲染,以使变化在页面中呈现。
在React中,当组件渲染完毕后,再修改组件中的变量,不会使组件重新渲染。
要使得组件可以收到变量的影响,必须在变量修改后对组件进行重新渲染。
这里我们就需要一个特殊变量,当这个变量被修改时,组件会自动重新渲染。
state相当于一个变量,
只是这个变量在React中进行了注册,
React会监控这个变量的变化,当state发生变化时,会自动触发组件的重新渲染
使得我们的修改可以在页面中呈现出来。
在函数组件中,我们需要通过钩子函数,获取state。
使用钩子 useState()
来创建state。
import {useState} from "react";
它需要一个值作为参数,这个值就是state的初始值
该函数会返回一个数组
- 数组中第一个元素,是初始值
初始值只用来显示数据,直接修改不会触发组件的重新渲染 - 数组中的第二个元素,是一个函数,通常会命名为setXxx
这个函数用来修改state,调用其修改state后会触发组件的重新渲染,并且使用函数中的值作为新的state值
现有如下组件Clock:
现在我们希望点击按钮以后,时间可以刷新直接显示一个当前的最新日期。希望页面重新渲染。但是,如果直接在clickHandler中修改date的值是无效的,像这样:
在函数中使用state我们需要使用一种钩子(hook)函数。钩子函数可以在函数组件中“勾出”React的特性,换句话说我们要用一个函数“勾出”state。
语法:
const [state, setState] = useState(initialState);
通过钩子函数useState()勾出state,useState()中需要传递一个初始值,这个值就是你希望在变量中存储的值。
函数会返回一个数组,数组中有两个元素,第一个元素是存储了值的变量,第二个元素是一个函数用来对值进行修改。
比如上边的案例,可以这样修改:
使用useState()“勾出”的变量就是一个普通变量,它里边存储了初始化的值,这个变量和其他变量没什么大区别,同样修改这个变量的值也不会对组件产生实质性的影响,所以不要尝试直接为state赋值。useState()“勾出”的函数用来修改state的值,他需要一个新的state值作为参数,调用后会触发组件的重新渲染,从而使得页面刷新,在每次的重新渲染中都会使用新的state值作为参数。
二、注意问题
引出问题
有了state,使得React组件可以随着某个值的改变而改变,我们无需再在某个值发生变化后重新手动对界面进行构建,React会替我们完成这些工作,大大降低了我们开发的难度。
但是state中还隐藏着一些不太容易发现的问题,现在假设我们需要开发一个计数器组件,这个组件非常简单,有一个按钮和一个数字,每点击一次按钮数字就会增加1,大概长成这个样子:
点击按钮以后,数字就会增加1,这个组件的实现很简单:
Counter.js
在clickHandler()中,我们调用了setCount(count+1)来对count进行更新,每次更新都是在前一次值的基础上增加1。这个代码这么写在大部分的场景下都不会带来任何的问题,但是在某些情况下就不一定了。
产生问题的原因
在React中我们通过setState()修改状态都是异步完成的,换句话说并不是调用完setState()后状态立刻就发生变化,而是需要等上一段时间,当然这段时间不会很长。
像上边的案例中state的修改虽然是异步完成的,但是由于功能比较简单,等待时间几乎可以忽略不计。但随着功能复杂度的提升,这个间隔会逐渐增多。
问题演示
假设调用setState()后1秒state的值才会真的改变,这时如果我们连续点击按钮2次,第1次点击按钮时count值是1,第2次点击速度比较快,从而两次间隔没有超过1秒,此时的count值依然是1,这就导致我点击了两次按钮,但是值只增加了1次,因为两次count+1中的count都是1。
为了演示问题,可以将上述案例的setCount()放入到一个延时调用中:
这样一来,点击按钮后1秒setCount()才会调用,如果我们在1秒内点击按钮多次,你会发现按钮数值只会增加一次,很显然我们不希望这种情况出现。
解决问题
要解决这个问题,其实也不难,在setState()时除了直接传递一个指定值以外,React还允许我们通过一个回调函数来修改state,回调函数的返回值就是新的state的值,使用回调函数的好处是,这个回调函数会确保上一次的setState()调用完成后才被调用,同时会使用最新的state值作为回调函数的第一个参数。这样一来就有效的避免了无法正确获取上一个state值的问题。
上边案例中的 setCount(count+1);
可以改成这个样子:
setCount(prevState => prevState+1);
这样一来,函数中的prevState总是上次修改后的最新state,避免再次出现点击多次按钮只修改一次的问题。总的来说,当我们修改一个state的值而需要依赖于前边的值进行计算时,最安全的方式就是通过回调函数而不是直接修改。