状态
- 一、如何设计组件状态的步骤
- 二、状态构造原则
- 1. 组相关状态
- 2. 避免矛盾/互斥状态
- 3. 避免多余状态
- 4. 不要把props放进state,除非你特别想要阻止更新
- 三、状态保存/重置
- 1. 相同位置的相同组件保留状态
- 2. 同一位置不同元素reset状态
一、如何设计组件状态的步骤
- 第一步:识别组件的不同视觉状态
- 第二步:确定是什么触发了这些状态变化
- 第三步:用useState代表状态
- 第四步:移除任何非必要的状态变量
- 第五步:连接事件处理程序以设置状态
二、状态构造原则
SSoT:单一数据源原则,
封装就是将强依赖的变量组织封装在一起
1. 组相关状态
如果同一时间需要更新多个状态,可以合并在单个状态中,如下:
const [x,setX]=useState(0)
const [y,setY]=useState(0)
⬇️
const [position, setPosition] = useState(
{ x: 0, y: 0 }
)
2. 避免矛盾/互斥状态
const [isSending, setIsSending] = useState(false)
const [isSent, setIsSent] = useState(false)
isSending
和isSent
不可能同时为true,所以最好用一个status
状态来代替
⬇️
const [status, setStatus] = useState('sending'); //useState('sent')
3. 避免多余状态
遵循单一数据源原则,可以计算出的值不要再设为状态
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
function handleFirstNameChange(e) {
setFirstName(e.target.value);
setFullName(e.target.value + ' ' + lastName);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
setFullName(firstName + ' ' + e.target.value);
}
fullName
是一个计算值,可以根据firstName
和lastName
计算得出,不用设为状态
⬇️
const fullName = firstName + ' ' + lastName;
4. 不要把props放进state,除非你特别想要阻止更新
在父组件改变props.color
时,以下代码不会重新渲染,状态仅在第一次渲染期间初始化。
export default function Clock(props) {
const [color, setColor] = useState(props.color);
return (
<h1 style={{ color: color }}>
{props.time}
</h1>
);
}
如果需要重新渲染 ⬇️
export default function Clock(props) {
const color = props.color
return (
<h1 style={{ color: color }}>
{props.time}
</h1>
);
}
- 避免状态嵌套太深,需要重构数据结构
三、状态保存/重置
状态保存在 React 内部,React 通过组件在 UI 树中的位置将其持有的每个状态与正确的组件相关联。
1. 相同位置的相同组件保留状态
import { useState } from 'react';
// 当改变状态isPlayerA时,Counter因位置不变,保留之前的状态
export default function App() {
const [isPlayerA, setIsPlayerA] = useState(true);
return (
<div>
{isPlayerA ? (
<Counter person="Taylor" />
) : (
<Counter person="Sarah" />
)}
</div>
)
}
两种重置状态的方法
- 在不同位置渲染组件
{isPlayerA &&
<Counter person="Taylor" />
}
{!isPlayerA &&
<Counter person="Sarah" />
}
- 给每个组件一个明确的标识key
{isPlayerA ? (
<Counter person="Taylor" key="Taylor" />
) : (
<Counter person="Sarah" key="Sarah" />
)}
2. 同一位置不同元素reset状态
(当您在同一位置渲染不同的组件时,它会重置其整个子树的状态)
当状态isFancy
改变时,因为Counter
组件被包裹的元素不同,整个子tree都会重新渲染,组件内部的状态会重置,
import { useState } from 'react';
export default function App() {
const [isFancy, setIsFancy] = useState(false);
return (
<div>
{isFancy ? (
<div>
<Counter isFancy={true} />
</div>
) : (
<section>
<Counter isFancy={false} />
</section>
)}
</div>
)
}
ui tree变化如右图所示:
演示如链接:
https://codesandbox.io/s/meowu5?file=%2FApp.js&utm_medium=sandpack
这就是为什么不应该嵌套组件函数,每点击一次按钮,输入状态就消失:
这是因为每次渲染都会创建不同的 函数,你在同一个位置渲染不同的组件,所以 React 重置下面的所有状态。
总是在顶层声明组件函数,不要嵌套它们的定义。
import { useState } from 'react';
export default function MyComponent() {
const [counter, setCounter] = useState(0);
function MyTextField() {
const [text, setText] = useState('');
return (
<input
value={text}
onChange={e => setText(e.target.value)}
/>
);
}
return (
<>
<MyTextField />
<button onClick={() => {
setCounter(counter + 1)
}}>Clicked {counter} times</button>
</>
);
}