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>
{/* 条件判断 */}
{isPublished ? <span className={styles['published-span']}>已发布</span> : <span>未发布</span>}
<button
onClick={() => {
publish(id)
}}
>
发布问卷
</button>
<button
onClick={() => {
del(id)
}}
>
删除问卷
</button>
</div>
)
}
export default QuestionCard
最重要的就是:不可变数据 —— 这是 React state 的核心