Skip to content

组件状态的操作

1. state为数组的处理

避免使用(会改变原数组)推荐使用(会返回新数组)
添加元素push,unshiftconcat, [...arr]
删除元素pop,shift,splicefilter,slice
替换元素splice,arr[i]=...赋值map
排序reverse,sort先将数组复制一份
jsx
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

运行结果:
Alt text 如果我们操作的数据比较复杂,往往拷贝对象或者数组后再操作更方便,但是如果数组中元素结构不止一层,浅拷贝就不能实现对象的复制后独立使用,里面的属性可能存在引用关系,这是需要第三方库来实现深度拷贝,比如lodash。

cmd
npm -i lodash

2. state为对象的处理

和数组一样,react都不让我们直接操作去改对象的数据,而是提供setXXX把我们需要的数据放入进去,交给react去替换之前的状态数据。

jsx
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:

cmd
npm i immer use-immer

安装这两个库后,在App.jsx文件中使用:

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. 惰性初始化状态值

当状态的初始值需要通过复杂计算才能得到的话,可以对其进行惰性初始化操作,用以提升性能。

jsx
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

运行结果:
Alt text 会发现computed()方法除了第一次页面加载执行,每次点击按钮都会被执行,但是computed()返回的都是固定值,后面点击按钮count状态值和computed()没有关系。

jsx
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

运行效果:
Alt text

5. 多组件状态共享

多次渲染同一个组件,每个组件都会拥有自己的state。状态是独立的,且不共享的。如果需要组件共享数据,需要状态提升,也就是将状态值放到父组件中去。

jsx
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

运行结果:
Alt text

6. 状态的重置

当组件被销毁时,所对应的状态也会被重置; 当组件位置没有发生改变时,状态是会被保留的。

jsx
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

运行结果:
Alt text

7. 状态产生计算变量

jsx
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()只会读取一次初始值:
Alt text

调用setDoubleCount可以实现doubleCount发生变化,但是比较冗余,核心在改变的其实就是count:

jsx
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

运行结果:
Alt text