React-useState讲解

useState

让页面"动"起来

例如实现一个 click 计数功能,普通变量无法实现。即:修改普通变量无法触发组件的更新 rerender

通过 useState 即可实现。

state 是什么

State, A component's memory ------ 这个比喻非常好!

  • props 父组件传递过来的信息
  • state 组件自己内部的状态,不对外

每次 state 变化,都会触发组件更新,从新渲染页面。

代码演示,参考 react-ts-demo 中 pages/StateDemo1.tsx

复制代码
import React, { FC, useState } from 'react'

const Demo: FC = () => {
  // let count = 0 // 普通的 js 变量,无法触发组件的更新

  const [count, setCount] = useState(0) // useState 可以触发组件的更新,
  //   const [name, setName] = useState('双越')

  function add() {
    // count++
    // setCount(count + 1)

    setCount(count => count + 1) // 使用函数,state 更新不会被合并
    setCount(count => count + 1)
    setCount(count => count + 1)
    setCount(count => count + 1)
    setCount(count => count + 1)

    // setCount(count => count + 1)
    console.log('cur count ', count) // 异步更新,无法直接拿到最新的 state 值
  }

  return (
    <div>
      <button onClick={add}>add {count}</button>
    </div>
  )
}

export default Demo

state 的特点

异步更新

代码演示

PS:setState 传入函数,可同步更新

可能会被合并

代码演示

不可变数据

state 可以是任意 JS 类型,不仅仅是值类型。

不可直接修改 state ,而要 setState 新值。

代码演示

PS:函数组件,每个更新函数从新执行,state 被重置,而不是被修改。state 可以理解为 readOnly

immer

Immer 简化了不可变数据结构的处理。特别是对于 JS 语法没那么熟悉的人。

代码演示,参考 react-ts-demo 中 pages/ImmerDemo1.tsx

复制代码
import React, { FC, useState } from 'react'
import produce from 'immer'

const Demo: FC = () => {
  //   const [userInfo, setUserInfo] = useState({ name: '柚子', age: 20 })
  //   function changeAge() {
  //     // // **不可变数据** - 不去修改 state 的值,而是要传入一个新的值 ------ 重要!
  //     // setUserInfo({
  //     //   ...userInfo,
  //     //   age: 21,
  //     // })

  //     setUserInfo(
  //       produce(draft => {
  //         draft.age = 21
  //       })
  //     )
  //   }

  const [list, setList] = useState(['x', 'y'])
  function addItem() {
    // // **不可变数据** - 不去修改 state 的值,而是要传入一个新的值 ------ 重要!
    // setList(list.concat('z'))
    // // setList([...list, 'z'])

    setList(
      produce(draft => {
        draft.push('z')
      })
    )
  }

  return (
    <div>
      <h2>state 不可变数据</h2>
      {/* <div>{JSON.stringify(userInfo)}</div>
      <button onClick={changeAge}>change age</button> */}
      <div>{JSON.stringify(list)}</div>
      <button onClick={addItem}>add item</button>
    </div>
  )
}

export default Demo

实战:List 页面使用 state

  • 使用 state
  • 使用 immer
    • push
    • 修改 isPublish

代码参考 pages/List2.tsx

复制代码
import React, { FC, useState, useEffect } from 'react'
import produce from 'immer'
import QuestionCard from './components/QuestionCard'

// 组件是一个函数(执行返回 JSX 片段),组件初次渲染执行这个函数
// 任何 state 更新,都会触发组件的更新(重新执行函数)
const List2: FC = () => {
  useEffect(() => {
    console.log('加载 ajax 网络请求')

    return () => {
      console.log('销毁')
    }
  }, []) // 无依赖,组件初次渲染时执行

  // const [count, setCount] = useState(0)
  const [questionList, setQuestionList] = useState([
    { id: 'q1', title: '问卷1', isPublished: false },
    { id: 'q2', title: '问卷2', isPublished: true },
    { id: 'q3', title: '问卷3', isPublished: false },
    { id: 'q4', title: '问卷4', isPublished: true },
  ])

  // useEffect(() => {
  //   console.log('question list changed')
  // }, [questionList])

  // useEffect(() => {
  //   console.log('count changed')
  // }, [count, questionList])

  function add() {
    // setCount(count + 1)

    const r = Math.random().toString().slice(-3)
    // setQuestionList(
    //   // 新增 concat
    //   questionList.concat({
    //     id: 'q' + r,
    //     title: '问卷' + r,
    //     isPublished: false,
    //   })
    // )

    // immer 的方式
    setQuestionList(
      produce(draft => {
        draft.push({
          id: 'q' + r,
          title: '问卷' + r,
          isPublished: false,
        })
      })
    )
  }

  function deleteQuestion(id: string) {
    // // 不可变数据
    // setQuestionList(
    //   // 删除 filter
    //   questionList.filter(q => {
    //     if (q.id === id) return false
    //     else return true
    //   })
    // )

    // immer 的方式
    setQuestionList(
      produce(draft => {
        const index = draft.findIndex(q => q.id === id)
        draft.splice(index, 1)
      })
    )
  }

  function publishQuestion(id: string) {
    // setQuestionList(
    //   // 修改 map
    //   questionList.map(q => {
    //     if (q.id !== id) return q

    //     return {
    //       ...q,
    //       isPublished: true,
    //     }
    //   })
    // )

    // immer 的方式
    setQuestionList(
      produce(draft => {
        const q = draft.find(item => item.id === id)
        if (q) q.isPublished = true
      })
    )
  }

  return (
    <div>
      <h1>问卷列表页2</h1>
      <div>
        {questionList.map(question => {
          const { id, title, isPublished } = question
          return (
            <QuestionCard
              key={id}
              id={id}
              title={title}
              isPublished={isPublished}
              deleteQuestion={deleteQuestion}
              publishQuestion={publishQuestion}
            />
          )
        })}
      </div>
      <div>
        <button onClick={add}>新增问卷</button>
      </div>
    </div>
  )
}

export default List2

代码参考 /components/QuestionCard

复制代码
import React, { FC, useEffect } from 'react'
import classnames from 'classnames'
// import './QuestionCard.css'
import styles from './QuestionCard.module.scss'

// ts 自定义类型
type PropsType = {
  id: string
  title: string
  isPublished: boolean
  deleteQuestion?: (id: string) => void
  publishQuestion?: (id: string) => void
}

// FC - functional component
const QuestionCard: FC<PropsType> = props => {
  const { id, title, isPublished, deleteQuestion, publishQuestion } = props

  function publish(id: string) {
    publishQuestion && publishQuestion(id)
  }

  function del(id: string) {
    deleteQuestion && deleteQuestion(id)
  }

  // useEffect(() => {
  //   console.log('question card mounted')

  //   return () => {
  //     console.log('question card unmounted', id) // 销毁
  //   }

  //   // 生命周期:创建,更新(state 变化),销毁
  // }, [])

  // let itemClassName = 'list-item'
  // if (isPublished) itemClassName += ' published'
  // // 逻辑稍微复杂

  // const itemClassName = classnames('list-item', { published: isPublished })
  // const itemClassName = classnames({
  //   'list-item': true,
  //   published: isPublished,
  // })

  const listItemClass = styles['list-item']
  const publishedClass = styles.published
  const itemClassName = classnames({
    [listItemClass]: true,
    [publishedClass]: isPublished,
  })

  return (
    <div key={id} className={itemClassName}>
      <strong>{title}</strong>
      &nbsp;
      {/* 条件判断 */}
      {isPublished ? <span className={styles['published-span']}>已发布</span> : <span>未发布</span>}
      &nbsp;
      <button
        onClick={() => {
          publish(id)
        }}
      >
        发布问卷
      </button>
      &nbsp;
      <button
        onClick={() => {
          del(id)
        }}
      >
        删除问卷
      </button>
    </div>
  )
}

export default QuestionCard

最重要的就是:不可变数据 ------ 这是 React state 的核心

相关推荐
xjt_09011 分钟前
浅析Web存储系统
前端
foxhuli22939 分钟前
禁止ifrmare标签上的文件,实现自动下载功能,并且隐藏工具栏
前端
青皮桔1 小时前
CSS实现百分比水柱图
前端·css
失落的多巴胺1 小时前
使用deepseek制作“喝什么奶茶”随机抽签小网页
javascript·css·css3·html5
DataGear1 小时前
如何在DataGear 5.4.1 中快速制作SQL服务端分页的数据表格看板
javascript·数据库·sql·信息可视化·数据分析·echarts·数据可视化
影子信息1 小时前
vue 前端动态导入文件 import.meta.glob
前端·javascript·vue.js
青阳流月1 小时前
1.vue权衡的艺术
前端·vue.js·开源
样子20181 小时前
Vue3 之dialog弹框简单制作
前端·javascript·vue.js·前端框架·ecmascript
kevin_水滴石穿1 小时前
Vue 中报错 TypeError: crypto$2.getRandomValues is not a function
前端·javascript·vue.js
翻滚吧键盘1 小时前
vue文本插值
javascript·vue.js·ecmascript