组件状态的操作
1. state为数组的处理
避免使用(会改变原数组) | 推荐使用(会返回新数组) | |
---|---|---|
添加元素 | push,unshift | concat, [...arr] |
删除元素 | pop,shift,splice | filter,slice |
替换元素 | splice,arr[i]=...赋值 | map |
排序 | reverse,sort | 先将数组复制一份 |
import { useState } from 'react'
function App() {
const [list, setList] = useState([
{id: 1, name: '张三'},
{id: 2, name: '李四'},
{id: 3, name: '王五'}
])
const clickHandler1 = () => {
// 加在最后
// setList([...list, {id:4, name:'赵六'}])
// 加在前面
// setList([...list, {id:4, name:'赵六'}])
// 加在中间
setList([...list.slice(0, 1), {id:4, name:'赵六'}, ...list.slice(1)])
}
const clickHandler2 = () => {
// 删除其中的一个元素
setList([...list.slice(1)])
}
const clickHandler3 = () => {
// 修改中的一个元素中的属性
setList(list.map((item) => {
if(item.id==2){
return {...item, name: 'jack'}
}else {
return item
}
}))
}
const clickHandler4 = () => {
// 如果数组或者对象里面数据只有一层,可以只用浅拷贝就像
const newList = [...list]
newList.reverse()
setList(newList)
}
return (
<div>
hello App
<div>
<button onClick={clickHandler1}>点击添加</button>
</div>
<div>
<button onClick={clickHandler2}>点击删除</button>
</div>
<div>
<button onClick={clickHandler3}>点击修改元素</button>
</div>
<div>
<button onClick={clickHandler4}>点击排序</button>
</div>
<div>
<ul>
{
list.map((item) => <li key={item.id}>{item.name}</li>)
}
</ul>
</div>
</div>
)
}
export default App
运行结果: 如果我们操作的数据比较复杂,往往拷贝对象或者数组后再操作更方便,但是如果数组中元素结构不止一层,浅拷贝就不能实现对象的复制后独立使用,里面的属性可能存在引用关系,这是需要第三方库来实现深度拷贝,比如lodash。
npm -i lodash
2. state为对象的处理
和数组一样,react都不让我们直接操作去改对象的数据,而是提供setXXX把我们需要的数据放入进去,交给react去替换之前的状态数据。
import { useState } from 'react'
import { cloneDeep } from 'lodash'
function App() {
const [info, setInfo] = useState(
{id: 1, name: '张三', age: 23}
)
const clickHandler1 = () => {
// 如果不整体修改,只改其中一个属性,会被react整体替换,导致只有一个属性,比如age
// setInfo({
// age: 30
// })
// 整体替换,属性比较多的时候,使用...比较灵活
// setInfo({
// ...info,
// name: jack
// })
// ...只是实现了浅拷贝, 使用lodash工具库里面的cloneDeep实现深度拷贝
let newInfo = cloneDeep(info)
newInfo.name = 'jack'
setInfo(newInfo)
}
return (
<div>
hello App
<div>
<button onClick={clickHandler1}>点击修改名字</button>
</div>
<div>
{
JSON.stringify(info, null, 2)
}
</div>
</div>
)
}
export default App
提示
lodash工具库虽然可以实现深度拷贝,但是深度拷贝的底层是forin+递归, 递归是一个非常耗性能的操作,如果只需要改一个复杂大对象里面的一个属性,深拷贝将会非常耗时,也就是说深拷贝不保证运行期间的执行性能。
3. 使用immer操作状态
immer库提供更方便的方式去操作state值,间接解决深拷贝的问题(换了种办法),需要提前安装immer:
npm i immer use-immer
安装这两个库后,在App.jsx文件中使用:
import { useImmer } from 'use-immer'
function App() {
// 不再使用useState, 代替为useImmer
const [info, setInfo] = useImmer( {
id: 1, name: {first:'zhang', last:'san'}, age: 23
})
const clickHandler1 = () => {
setInfo((draft)=>{
// draft是一个数据代理对象,代理info
// 可以直接修改draft对象,不用拷贝
draft.name.first = 'jack'
})
}
return (
<div>
hello App
<div>
<button onClick={clickHandler1}>点击修改名字</button>
</div>
<div>
{
JSON.stringify(info, null, 2)
}
</div>
</div>
)
}
export default App
4. 惰性初始化状态值
当状态的初始值需要通过复杂计算才能得到的话,可以对其进行惰性初始化操作,用以提升性能。
import { useState } from 'react'
function computed(n){
console.log("执行computed。。。")
return n+1+2+3
}
function App() {
const [count, setCount] = useState(computed(0))
const clickHandler = () => {
setCount(count + 1)
}
return (
<div>
hello App
<div>
<button onClick={clickHandler}>点击</button>
<span style={{marginLeft: '20px'}}>{count}</span>
</div>
</div>
)
}
export default App
运行结果: 会发现computed()方法除了第一次页面加载执行,每次点击按钮都会被执行,但是computed()返回的都是固定值,后面点击按钮count状态值和computed()没有关系。
import { useState } from 'react'
function computed(n){
console.log("执行computed。。。")
return n+1+2+3
}
function App() {
// 使用箭头函数初始化
const [count, setCount] = useState(()=>computed(0))
const clickHandler = () => {
setCount(count + 1)
}
return (
<div>
hello App
<div>
<button onClick={clickHandler}>点击</button>
<span style={{marginLeft: '20px'}}>{count}</span>
</div>
</div>
)
}
export default App
运行效果:
5. 多组件状态共享
多次渲染同一个组件,每个组件都会拥有自己的state。状态是独立的,且不共享的。如果需要组件共享数据,需要状态提升,也就是将状态值放到父组件中去。
import { useState } from 'react'
function Button({onClick, value}){
return (
<div>
<button onClick={onClick}>点击</button><span style={{marginLeft: '10px'}}>{value}</span>
</div>
)
}
function App() {
const [value, setValue] = useState(0)
const handleClick = (e) => {
setValue(value + 1)
}
return (
<div>
<Button onClick={handleClick} value={value}/>
<Button onClick={handleClick} value={value}/>
</div>
)
}
export default App
运行结果:
6. 状态的重置
当组件被销毁时,所对应的状态也会被重置; 当组件位置没有发生改变时,状态是会被保留的。
import { useState } from 'react'
function Button({style}){
var [count, setCount] = useState(0)
const onClick = (e) => {
setCount(count + 1)
}
return (
<div>
<button style={style} onClick={onClick}>点击</button><span style={{marginLeft: '10px'}}>{count}</span>
</div>
)
}
function App() {
const [isStyle, setStyle] = useState(false)
const handleClick = (e) => {
setStyle(!isStyle)
}
return (
<div>
hello App
<button onClick={handleClick}>点击添加样式</button>
{/* 状态重置情况1: 不同的结构体*/}
{/*{isStyle && <Button style={{borderColor: 'red'}} />}*/}
{/* 状态重置情况2: 相同结构体,但是额外添加了key属性*/}
{isStyle ? <Button key="2" style={{borderColor: 'red'}}/> : <Button />}
</div>
)
}
export default App
运行结果:
7. 状态产生计算变量
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
const [doubleCount, setDoubleCount] = useState(count*2)
const handleClick = (e) => {
setCount(count + 1)
}
return (
<div>
hello App
<button onClick={handleClick}>点击</button>
<span style={{ marginLeft: '10px' }}>{count}</span>
<span style={{ marginLeft: '10px' }}>{doubleCount}</span>
</div>
)
}
export default App
会发现doubleCount在页面按钮点击后并没有改变,原因在于useState()只会读取一次初始值:
调用setDoubleCount可以实现doubleCount发生变化,但是比较冗余,核心在改变的其实就是count:
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
// const [doubleCount, setDoubleCount] = useState(count*2)
const doubleCount = count * 2 // 类似Vue中的计算属性
const handleClick = (e) => {
setCount(count + 1)
// setDoubleCount((count + 1)*2)
}
return (
<div>
hello App
<button onClick={handleClick}>点击</button>
<span style={{ marginLeft: '10px' }}>{count}</span>
<span style={{ marginLeft: '10px' }}>{doubleCount}</span>
</div>
)
}
export default App
运行结果: