vuepress-theme-vdoing/docs/《React》笔记/01.核心概念/09.状态提升.md

184 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 状态提升 (共享状态)
date: 2021-03-26 09:56:40
permalink: /pages/f0e3d2/
categories:
- 《React》笔记
- 核心概念
tags:
- React
author:
name: xugaoyi
link: https://github.com/xugaoyi
---
# 09. 状态提升 (共享状态)
通常,**多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去**。
在 React 中,**将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。这就是所谓的“状态提升”**
两个输入框共享数据的例子:
```jsx
const scaleNames = {
c: '摄氏度',
f: '华氏度'
};
// 转摄氏度
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
// 转华氏度
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
// 转换,为空时返回空,否则返回保留三位小数的浮点数
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
// Math.round返回一个数字四舍五入后的整数
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
// 水是否会沸腾
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>水会沸腾.</p>;
}
return <p>水不会沸腾.</p>;
}
// 子组件 - 输入框
class TemperatureInput extends React.Component {
constructor(props) {
super(props); // 接收父组件传入props
this.handleChange = this.handleChange.bind(this); // 绑定回调函数并修正this
}
// 处理change
handleChange(e) {
// e是合成事件对象通过e.target.value 取值
// 调用父组件传入的onTemperatureChange函数并传值
this.props.onTemperatureChange(e.target.value);
// 当子组件输入框值改变时调用父组件的onTemperatureChange方法并传出值。
// 另外onTemperatureChange命名方式`在<子组件>变更`
}
render() {
// 接收父组件传入的温度值
const temperature = this.props.temperature;
// 接收父组件传入的衡量方式
const scale = this.props.scale;
return (
<fieldset>
<legend>输入温度-{scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
// 父组件 - 计算器
class Calculator extends React.Component {
constructor(props) {
super(props); // 接收父组件传入props
// 绑定事件回调并修正this
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
// 创建初始状态值
this.state = {temperature: '', scale: 'c'};
}
// 处理`摄氏度`变更
handleCelsiusChange(temperature) {
// temperature接收到子组件传来的参数并通过setState修改状态
this.setState({scale: 'c', temperature});
}
// 处理`华氏度`变更
handleFahrenheitChange(temperature) {
// temperature接收到子组件传来的参数并通过setState修改状态
this.setState({scale: 'f', temperature});
}
// 渲染函数每当state改变都会调用
render() {
// 取得当前state下的值
const scale = this.state.scale;
const temperature = this.state.temperature;
// 根据scale值取得相应的温度数据
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
// 返回渲染的元素
// 插入子组件TemperatureInput传入相应的参数onTemperatureChange指定为当前组件的回调函数
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
// 渲染DOM
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
```
[**在 CodePen 上尝试**](https://codepen.io/gaearon/pen/WZpxpz?editors=0010)
::: note
1. 父组件给所有子组件传入state的值
2. 子组件修改值时调用父组件的方法并把值传出
3. 父组件接收到值之后修改state
4. state被修改之后重新执行render函数并回到第1步
:::
### 小结
- 任何可变数据应当只有一个相对应的唯一“数据源”
- 通常state 都是首先添加到需要渲染数据的组件中去
- 然后,如果其他组件也需要这个 state那么你可以将它提升至这些组件的最近共同父组件中
- 你应当依靠[自上而下的数据流](https://zh-hans.reactjs.org/docs/state-and-lifecycle.html#the-data-flows-down),而不是尝试在不同组件间同步 state。
- “存在”于组件中的任何 state仅有组件自己能够修改它
- 如果某些数据可以由 props 或 state 推导得出,那么它就不应该存在于 state 中。如上例中经过tryConvert方法转换的后的值。
### React开发者工具debug
当你在 UI 中发现错误时,可以使用 [React 开发者工具](https://github.com/facebook/react/tree/master/packages/react-devtools) 来检查问题组件的 props并且按照组件树结构逐级向上搜寻直到定位到负责更新 state 的那个组件。