React hooks (useState)

63 阅读6分钟

useState 是一个 React Hook,允许函数组件在内部管理状态。

组件通常需要根据交互更改屏幕上显示的内容,例如点击某个按钮更改值,或者输入文本框中的内容,这些值被称为状态值也就是(state)。

使用方法

useState 接收一个参数,即状态的初始值,然后返回一个数组,其中包含两个元素:当前的状态值和一个更新该状态的函数

const [state, setState] = useState(initialState)

注意事项

useState 是一个 Hook,因此你只能在 组件的顶层 或自己的 Hook 中调用它。你不能在循环或条件语句中调用它。

在严格模式中,React 将 两次调用初始化函数,以 帮你找到意外的不纯性。这只是开发时的行为,不影响生产

用法

添加一个状态

const Card = () => { let [index, setIndex] = useState(0)
let [name, setName] = useState('小满') 
let [arr, setArr] = useState([1, 2, 3]) }

React 会存储新状态,使用新值重新渲染组件,并更新 UI。

完整版案例(基本数据类型)

import { useState } from "react"
function App() {
  let [name, setName] = useState('小满') //数字 布尔值 null undefined 都可以直接赋值 一样的
  const heandleClick = () => {
    setName('大满')
  }
  return (
    <>
      <button onClick={heandleClick}>更改名称</button>
      <div>{name}</div>
    </>
  )
}
export default App

完整版案例(复杂数据类型)

数组

在React中你需要将数组视为只读的,不可以直接修改原数组,例如:不可以调用 arr.push() arr.pop() 等方法。

下面是常见数组操作的参考表。当你操作 React state 中的数组时,你需要避免使用左列的方法,而首选右列的方法

避免使用 (会改变原始数组)推荐使用 (会返回一个新数组)
添加元素 push,unshiftconcat,[...arr] 展开语法(例子)
删除元素 pop,shift,splicefilter,slice(例子)
替换元素 splice,arr[i] = ... 赋值map(例子)
排序 reverse,sort先将数组复制一份(例子)
数组新增数据

创建一个新数组,包含了原始数组的所有元素,然后在末尾添加新元素,如果想在头部添加新元素,返过来即可。

import { useState } from "react"
function App() {
  let [arr, setArr] = useState([1, 2, 3])
  const heandleClick = () => {
    setArr([...arr,4]) //末尾新增 扩展运算符
    //setArr([0,...arr]) 头部新增 扩展运算符
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div id="aaa">{arr}</div>
    </>
  )
}
export default App

数组删除数据

使用filter过滤掉不需要的元素即可。因为filter是返回一个新数组

import { useState } from "react"
function App() {
  let [arr, setArr] = useState([1, 2, 3])
  const heandleClick = () => {
    setArr(arr.filter((item) => item !== 1)) //删除指定元素
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div id="aaa">{arr}</div>
    </>
  )
}
export default App

数组替换数据

使用map筛选出需要替换的元素,然后替换为新的元素,其他元素保持不变。

import { useState } from "react"
function App() {
  let [arr, setArr] = useState([1, 2, 3])
  const heandleClick = () => {
    setArr(arr.map(item => {
      return item == 2 ? 666 : item
    }))
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div id="aaa">{arr}</div>
    </>
  )
}
export default App

指定位置插入元素

案例在2后面插入2.5,通过slice,截取前面的元素,因为slice返回一个新的数组,然后在中间插入我们需要插入的元素,然后把末尾的元素也通过slice截取出来,拼接到后面。

import { useState } from "react"
function App() {
  let [arr, setArr] = useState([1, 2, 3])
  const heandleClick = () => {
    let startIndex = 0
    let endIndex = 2;
    setArr(
      [
        ...arr.slice(startIndex, endIndex),
        2.5,
        ...arr.slice(endIndex)
      ]
    )
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div id="aaa">{arr}</div>
    </>
  )
}
export default App

排序旋转等

案例,创建一个新数组,然后通过sort排序。

import { useState } from "react"
function App() {
  let [arr, setArr] = useState([1, 2, 3])
  const heandleClick = () => {
    let newList = [...arr].map(v => v + 1) //拷贝到新数组
    newList.sort((a, b) => b - a)
    //newList.reverse()旋转
    setArr(newList)
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div id="aaa">{arr}</div>
    </>
  )
}
export default App

对象

useState可以接受一个函数,可以在函数里面编写逻辑,初始化值,注意这个只会执行一次,更新的时候就不会执行了。

在使用setObject的时候,可以使用Object.assign合并对象 或者 ... 合并对象,不能单独赋值,不然会覆盖原始对象。

import { useState } from "react"
function App() {
  const [obj, setObject] = useState(() => {
    const date = new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate()
    return {
      date,
      name: '小满',
      age: 25
    }
  })
  const heandleClick = () => {
    setObject({
      ...obj,
      name: '大满'
    })
    //setObject(Object.assign({}, obj, { age: 26 })) 第二种写法
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div>日期:{obj.date}</div>
      <div>姓名:{obj.name}</div>
      <div>年龄:{obj.age}</div>
    </>
  )
}
export default App

useState更新机制

异步机制

useState set函数是异步更新的来看下面的案例:

import { useState } from "react"
function App() {
  let [index, setIndex] = useState(0)
  const heandleClick = () => {
    setIndex(index + 1)
    console.log(index,'index') //0
  }
  return (
    <>
       <h1>Index:{index}</h1>
      <button onClick={heandleClick}>更改值</button>
      
    </>
  )
}
export default App

此时index应该打印1,但是还是0,因为我们正常编写的代码是同步的,所以会先执行,而set函数是异步的所以后执行,这么做是为了性能优化,因为我们要的是结果而不是过程。

内部机制

当我们多次以相同的操作更新状态时,React 会进行比较,如果值相同,则会屏蔽后续的更新行为。自带防抖的功能,防止频繁的更新。 案例:

import { useState } from "react"
function App() {
  let [index, setIndex] = useState(0)
  const heandleClick = () => {
    setIndex(index + 1) //1
    setIndex(index + 1) //1
    setIndex(index + 1) //1
    console.log(index,'index')
  }
  return (
    <>
       <h1>Index:{index}</h1>
      <button onClick={heandleClick}>更改值</button>
      
    </>
  )
}
export default App


结果是1并不是3,因为setIndex(index + 1)的值是一样的,后续操作被屏蔽掉了,阻止了更新。

为了解决这个问题,你可以向setIndex 传递一个更新函数,而不是一个状态。

import { useState } from "react"
function App() {
  let [index, setIndex] = useState(0)
  const heandleClick = () => {
    setIndex(index => index + 1) //1
    setIndex(index => index + 1) //2
    setIndex(index => index + 1) //3
  }
  return (
    <>
      <h1>Index:{index}</h1>
      <button onClick={heandleClick}>更改值</button>

    </>
  )
}
export default App

  1. index => index + 1 将接收 0 作为待定状态,并返回 1 作为下一个状态。
  2. index => index + 1 将接收 1 作为待定状态,并返回 2 作为下一个状态。
  3. index => index + 1 将接收 2 作为待定状态,并返回 3 作为下一个状态。

现在没有其他排队的更新,因此 React 最终将存储 3 作为当前状态。

按照惯例,通常将待定状态参数命名为状态变量名称的第一个字母,例如 prevIndex 或者其他你觉得更清楚的名称。

自己的完整例子:

import { useState } from 'react'
import './App.css'

function App() {
  let str = 'this is a unResponsive variable'
  const [st,setSt]=useState(0)
  const addNum=(n:number)=>{
    setSt(n+1)
  }
  const unResonsivefn=()=>{
    str="set unResponsive variable"
    console.log(str);
  }
  const [arr,setArr]=useState<number[]>([1,2,3])
  const setInEffectiveArr=()=>{
    arr.push(4)
    setArr(arr)
    // setArr([...arr,4]) // 这样写就有效果了,这样是规范的写法
  }

  const [obj, setObject] = useState(() => {
    const date = new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate()
    return {
      date,
      name: '小满',
      age: 25
    }
  })
  const heandleClick = () => {
    setObject({
      ...obj,
      name: '大满'
    })
    //setObject(Object.assign({}, obj, { age: 26 })) 第二种写法
  }
  const [index, setIndex] = useState(0)
  const heandleClickAsn = () => {
    // 这样设计的目的是为了性能优化,批量更新
    // setIndex(1) // React的setState是异步的
    // setIndex(2) // React的setState是异步的
    // setIndex(3) // React的setState是异步的
    setIndex(index + 1) // React的setState是异步的
    console.log(index,'index') //0 同步任务先走
  }

  return (
    <>
    <div style={{textAlign: "left"}}>数组:</div>
      <div>
        <div>非响应式变量:</div>
        <button onClick={() => unResonsivefn()}>
          {str}
        </button>
      </div>
      <div>
        <div>响应式变量:</div>
        <button onClick={() => addNum(st)}>
          st 是响应式变量,每次点击都会变化 {st}
        </button>
      </div>
      <div>
        <div>操作无效数组:</div>
        <button onClick={() => setInEffectiveArr()}>
          arr 是响应式变量,但是直接操作无效
          {arr}
        </button>
      </div>
      <div>
        <div>新增有效数组项:</div>
        <button onClick={() => setArr([...arr,4])}>
          arr 是响应式变量,使用规范写法有效
          {arr}
        </button>
      </div>
      <div>
        <div>删除数组项:</div>
        <button onClick={() => setArr(arr.filter((item) => item !== 2))}>
          arr 是响应式变量,使用规范写法有效
          {arr}
        </button>
      </div>
      <div>
        <div>修改数组项:</div>
        <button onClick={() => setArr(arr.map((item) => item === 3 ? 9 : item))}>
          arr 是响应式变量,使用规范写法有效
          {arr.toString()}
        </button>
      </div>
      <div>
        指定位置插入元素
        <button onClick={() => {
          const newArr = [...arr];
          newArr.splice(1, 0, 8); // 在索引1的位置插入8
          setArr( newArr );
        }}>
          arr 是响应式变量,使用规范写法有效
          {arr.toString()}
        </button>
      </div>
      <div>
        排序后更新数组
        <button onClick={() => {
          const newArr = [...arr].sort((a, b) => a - b); // 创建新数组并排序
          setArr(newArr);
        }}>
          arr 是响应式变量,使用规范写法有效
          {arr.toString()}
        </button>
      </div>
      <div style={{textAlign: "left"}}>对象:</div>
      <button onClick={heandleClick}>更改值</button>
      <div>日期:{obj.date}</div>
      <div>姓名:{obj.name}</div>
      <div>年龄:{obj.age}</div>
      <div>set的异步机制:看控制台输出index还是0</div>
      <h1>Index:{index}</h1>
      <button onClick={heandleClickAsn}>更改值</button>

    </>
  )
}

export default App