#状态管理

#步骤

  • 用 State 响应输入(不同状态不同 ui)
  • 选择 State 结构
  • 在组件间共享状态:提取到公共父级
  • 对 state 进行保留和重置
  • 迁移状态逻辑至 Reducer 中
  • 使用 Context 深层传递参数(Context 是 props 不是状态)
  • 使用 Reducer 和 Context 拓展你的应用

#对 state 进行保留和重置

#渲染树(fiber)

状态“存在”并不是在组件内。状态是由 React 保存的。React 通过组件在渲染树中的位置将它保存的每个状态与正确的组件关联起来

#相同位置的相同组件

位于相同位置的相同组件,对 React 来说,它是同一个 例如:(这里也可以<Counter isFancy={isFancy} />

import { useState } from 'react'

export default function App() {
  const [isFancy, setIsFancy] = useState(false)
  return (
    <div>
      {isFancy ? <Counter isFancy={true} /> : <Counter isFancy={false} />}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={(e) => {
            setIsFancy(e.target.checked)
          }}
        />
        使用好看的样式
      </label>
    </div>
  )
}

对 React 来说重要的是组件在渲染树中的位置,而不是在 JSX 中的位置,因为 React 看到的是调用函数组件后的结果

下面例子也是相同组件:

import { useState } from 'react'

export default function App() {
  const [isFancy, setIsFancy] = useState(false)
  if (isFancy) {
    return (
      <div>
        <Counter isFancy={true} />
        <label>
          <input
            type="checkbox"
            checked={isFancy}
            onChange={(e) => {
              setIsFancy(e.target.checked)
            }}
          />
          使用好看的样式
        </label>
      </div>
    )
  }
  return (
    <div>
      <Counter isFancy={false} />
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={(e) => {
            setIsFancy(e.target.checked)
          }}
        />
        使用好看的样式
      </label>
    </div>
  )
}

你可以认为它们有相同的“地址”:根组件 App 的第一个子组件 div 的第一个子组件。

#相同位置的不同组件

不同的组件类型进行切换时,React 将组件从渲染树中移除并销毁它的状态

#相同位置如何重置 state

  • 将组件渲染在不同的位置
  • 使用 key 赋予每个组件一个明确的身份

#将组件渲染在不同的位置

import { useState } from 'react'

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true)
  return (
    <div>
      {isPlayerA && <Counter person="Taylor" />}
      {!isPlayerA && <Counter person="Sarah" />}
      <button
        onClick={() => {
          setIsPlayerA(!isPlayerA)
        }}
      >
        下一位玩家!
      </button>
    </div>
  )
}

#使用 key 赋予每个组件一个明确的身份

import { useState } from 'react'

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true)
  return (
    <div>
      {isPlayerA ? (
        <Counter key="Taylor" person="Taylor" />
      ) : (
        <Counter key="Sarah" person="Sarah" />
      )}
      <button
        onClick={() => {
          setIsPlayerA(!isPlayerA)
        }}
      >
        下一位玩家!
      </button>
    </div>
  )
}

#使用 key 重置表单

import { useState } from 'react'
import Chat from './Chat.js'
import ContactList from './ContactList.js'

export default function Messenger() {
  const [to, setTo] = useState(contacts[0])
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={(contact) => setTo(contact)}
      />
      <Chat contact={to} />
    </div>
  )
}

在点击 ContactList 切换 to 时,Chat 因为位置相同,会保留相同的输入框状态

但是每个人的输入框内容应该是不同的

使用 key 来修复这个问题<Chat key={to.id} contact={to} />

#条件渲染位置问题

import { useState } from 'react'

export default function App() {
  const [showHint, setShowHint] = useState(false)
  return (
    <div>
      {showHint && (
        <p>
          <i>提示:你最喜欢的城市?</i>
        </p>
      )}
      <Form />
      {showHint ? (
        <button
          onClick={() => {
            setShowHint(false)
          }}
        >
          隐藏提示
        </button>
      ) : (
        <button
          onClick={() => {
            setShowHint(true)
          }}
        >
          显示提示
        </button>
      )}
    </div>
  )
}

此处 Form 被认为是同一位置的,因为&&条件 true 时渲染后面内容,false 时等同于 null 与 undefined,虽然不渲染,但是仍旧在渲染树中占位

#迁移状态逻辑至 Reducer 中

对于拥有许多状态更新逻辑的组件来说,事件处理程序可能过于分散可以将组件的所有状态更新逻辑整合到一个外部函数中,这个函数叫作 reducer

为什么称之为 reducer

以数组上的 reduce() 方法命名的

reduce() 允许你将数组中的多个值 “累加” 成一个值

接受 目前的结果 和 当前的值,然后返回 下一个结果

React 中的 reducer 和这个是一样的

接受 目前的状态 和 action ,然后返回 下一个状态

调用这两个 reducer 都会返回下一个

所以可以直接使用 reduce()获取最终状态

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [
        ...tasks,
        {
          id: action.id,
          text: action.text,
          done: false,
        },
      ]
    }
    case 'changed': {
      return tasks.map((t) => {
        if (t.id === action.task.id) {
          return action.task
        } else {
          return t
        }
      })
    }
    case 'deleted': {
      return tasks.filter((t) => t.id !== action.id)
    }
    default: {
      throw Error('未知 action: ' + action.type)
    }
  }
}

let initialState = []

let actions = [
  { type: 'added', id: 1, text: '参观卡夫卡博物馆' },
  { type: 'added', id: 2, text: '看木偶戏' },
  { type: 'deleted', id: 1 },
  { type: 'added', id: 3, text: '打卡列侬墙' },
]

let finalState = actions.reduce(tasksReducer, initialState)