了解状态
在React中,State
是一种用来存储组件内部状态的变量。
- 与上一章的
Props
在组件间传递状态相对的,状态这一变量代表了组件内部的情况,可以被看做组件的“内存”。
- 作为组件内部情况的代表,每当状态的量发生改变时,React会重新渲染整个组件。
综上,状态作为一种持久化的局部变量,允许我们在更改组件内容的同时重新渲染整个组件。
使用状态,就意味着我们用声明式的代码构造了用户界面,并让其随着时间推移发生变化。
状态工作流程
- 我们定义组件的状态变量,并在处理用户交互逻辑的
handler
函数中更新状态。
- 当用户与界面交互时,触发
handler
并更新状态。React检测到状态更新,于是重新渲染组件。
状态与道具
使用状态
何时使用状态
状态基本语法
1
2
3
4
| const [value, SetValue] = useState("init value");
console.log(value); // init value
SetValue("next value");
console.log(value) // next value
|
我们使用React的useState()
函数生成一个状态的数组。数组第一位是状态变量本身,可以使用useState()
函数的传入值来指定其初始值;数组第二位是更新状态变量的函数。实际应用中,要想重新渲染组件,就要使用数组中的更新函数来对状态进行更新。将状态定义成let
或var
都是无效的操作。
我们知道状态代表了组件内部的状态,因此在React中,状态只能在组件内开头定义。
实际使用中,我们还会使用更安全的方法来更新状态变量。
1
| SetValue((value)=>value+1);
|
派生状态
实际应用过程中,有些变量可以由状态计算得到,并在重新渲染的过程中得到更新。我们可以创建这种类型的派生状态
。
1
2
3
4
5
6
7
8
9
| const [date,SetDate] = useState(new Date());
const month = date.getMonth()
const exdate = date.getDate()
const day = date.getDay()
return (
<p>Today is {day} of {month} {exdate}.</p>
)
|
实际编写过程中,为了减少状态管理的难度,我们应该尽量创建派生状态。
控制元素
我们可以使用状态量和设置状态函数来控制一些HTML元素的value
和handler
。
1
2
3
4
5
| <input
type={"text"}
value={count}
onChange={(e) => handleInputChange(e)}
/>
|
在这个HTML的input
文本框元素中,我们将其文本值设置为count
状态,并在其值改变时调用handleInputChange()
函数,并在其中更新count
状态。
1
2
3
4
| function handleInputChange(e) {
const t = e.target.value;
setCount(t);
}
|
状态提升
实际开发过程中,我们会在不同组件中创建大量的状态变量。然而由于React的单向通信原则,我们无法在各个组件之间使用道具来传递状态变量。因此,要让各个组件之间的状态互相同步,我们必须要将某个组件的状态提升到父节点上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| function Count({ count, setCount }) {
function handleInputChange(e) {
const t = e.target.value;
setCount(t);
}
return (
<div style={{ textAlign: "center" }}>
<button onClick={() => setCount((c) => Number(c) - 1)}>-</button>
<input
type={"text"}
value={count}
onChange={(e) => handleInputChange(e)}
/>
<button onClick={() => setCount((c) => Number(c) + 1)}>+</button>
</div>
);
}
|
在这个Count
组件中,我们使用到了count
状态,但是却没有在Count
组件内定义它,而是在它的父组件App
内定义它。
1
2
3
4
5
6
7
8
9
10
11
12
| function App() {
const [step, setStep] = useState(0);
const [count, setCount] = useState(0);
return (
<>
<Step step={step} setStep={setStep} />
<Count count={count} setCount={setCount} />
<DateP step={step} count={count} />
<Reset setStep={setStep} setCount={setCount} step={step} count={count} />
</>
);
}
|
这样,所有App
组件的子组件都可以使用count
状态。当Count
组件更新count
状态时,所有App
组件中的子组件也将正常重新渲染。
状态实战
我们制作一个日历程序,可以通过多种交互方式改变日期的描述。
首先分析程序中的状态。可以发现:
step
和count
会根据交互而改变,并在不同的组件中被更新;- 真实日期可以根据
step
和count
计算得出。
因此,我们在父组件App
中定义step
和count
状态,并将其作为道具传递给子组件。
1
2
3
4
5
6
7
8
9
10
11
12
| function App() {
const [step, setStep] = useState(0);
const [count, setCount] = useState(0);
return (
<>
<Step step={step} setStep={setStep} />
<Count count={count} setCount={setCount} />
<DateP step={step} count={count} />
<Reset setStep={setStep} setCount={setCount} step={step} count={count} />
</>
);
}
|
- 在
DataP
组件中,我们用step
和count
计算日期;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| function DateP({ step, count }) {
let input = step * count;
let date = new Date();
let temp = date.getDate() + input;
date.setDate(temp);
date = date.toDateString();
return (
<p style={{ margin: 10, textAlign: "center" }}>
{input !== 0 ? Math.abs(input) + " " : ""}
{input !== 0 ? (input > 0 ? "Days from today " : "Days ago ") : "Today "}
{input >= 0 ? "is" : "was"} {date}
</p>
);
}
|
在Step
和Count
组件中,我们控制HTML元素,将状态和交互绑定在一起。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| function Step({ step, setStep }) {
function handleInputChange(e) {
const n = e.target.value;
setStep(n);
}
return (
<div style={{ textAlign: "center" }}>
<input
type={"range"}
min={"1"}
max={"10"}
value={step}
onChange={(e) => handleInputChange(e)}
/>
<span style={{ margin: 10 }}>Step: {step}</span>
</div>
);
}
function Count({ count, setCount }) {
function handleInputChange(e) {
const t = e.target.value;
setCount(t);
}
return (
<div style={{ textAlign: "center" }}>
<button onClick={() => setCount((c) => Number(c) - 1)}>-</button>
<input
type={"text"}
value={count}
onChange={(e) => handleInputChange(e)}
/>
<button onClick={() => setCount((c) => Number(c) + 1)}>+</button>
</div>
);
}
|
最后,我们添加一个Reset
按钮组件,按下时就清空count
和step
状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| function Reset({ setStep, setCount, step, count }) {
function handleReset() {
setStep(0);
setCount(0);
}
return (
(step !== 0 || count !== 0) && (
<div style={{ display: "flex", justifyContent: "center" }}>
<button onClick={handleReset}>Reset</button>
</div>
)
);
}
|