状态“存在”并不是在组件内。状态是由 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 将组件从渲染树中移除并销毁它的状态
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>
)
}
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>
)
}
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
以数组上的 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)