Lifting State Up - 提升状态 经常的,几个组件需要映射相同的数据改变。我们推荐提升共享的state状态到它们最近的公共祖先元素。让我们看看这是如何实现的。 在这个章节,我们将创建一个温度计算器,计算在一个给定的温度,水是否会沸腾。 我们以一个叫做 'BoilingVerdict' 的组件开始。它接收 'celsius' 温度作为一个prop,并且打印是否足够使水非常。
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
} else {
return <p>The water would not boil.</p>;
}
}
接下来,我们创建一个叫做 'Calculator' 的组件。它渲染一个 <input> ,让你输入温度,并使用 'this.state.value' 来保持它的值。
另外,它根据当前输入的值来渲染 'BoilingVerdict' 。
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {value: ''};
}
handleChange(e) {
this.setState({value: e.target.value});
}
render() {
const value = this.state.value;
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input
value={value}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(value)} />
</fieldset>
);
}
}
添加第二个输入框
我们的新需求是:除了 'Celsius' 输入框,我们提供一个 'Fahrenheit' 输入框,并且它们保持同步。
我们可以通过从 'Calculator' 提取一个 'TemperatureInput' 组件来开始。我们将添加一个新的 'scale' prop给 'TemperatureInput' 组件,'scale' 可以是 'c' 或 'f':
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {value: ''};
}
handleChange(e) {
this.setState({value: e.target.value});
}
render() {
const value = this.state.value;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={value}
onChange={this.handleChange} />
</fieldset>
);
}
}
现在让我们来改变 Calculator 来渲染2个独立的温度输入框:
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
现在我们已经有2个输入框了,但是当你在它们中的一个输入温度时,另一个不更新。这与我们的需求矛盾:我们想要2者同步。
从 'Calculator' 组件中,我们也无法展示 'BoilingVerdict'。'Calculator' 不知道当前的温度,因为它隐藏在 'TemperatureInput' 的内部。
提升state状态
首先,我们写2个函数,从 'Celsius' 转换为 'Fahrenheit',然后反过来。
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
这2个函数转换数字。我们写另外一个函数,接收一个字符串 'value' 和一个转换函数名作为参数,并返回一个字符串。我们使用它来计算一个输入框的值,基于另一个输入框。
当传入一个无效的 'value',返回一个空字符串,并保证输出到第三位小数。
function tryConvert(value, convert) {
const input = parseFloat(value);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
例如,tryConvert('abc', toCelsius) 返回一个空字符串,tryConvert('10.22', toFahrenheit)返回 '50.396'
接着,我们从 'TemperatureInput' 组件中,移除 state 状态。
替代的,'TemperatureInput' 组件通过props接收 'value' 和 'onChange' 处理器(之前value是通过 state 来接收):
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({value: e.target.value});
}
render() {
const value = this.props.value;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={value}
onChange={this.handleChange} />
</fieldset>
);
}
}
如果几个组件需要访问同样的state,这是一个信息,state应该被提升到它们最近的公共祖先上来替代。在我们的案例中,公共祖先是 'Calculator'。我们将存储当前的 'value' 和 'scale' 到它的 state。
我们可能已经存在了两个输入框的值,但是它原来是没有必要的。存储最近改变的输入框的值和它代表的 'scale' 就足够了。之后,我们可以根据当前的 'value' 和 'scale' 来推断出另一个输入框的值。
2个输入框保持同步,因为它们的值可以从相同的state来计算的到:
class Calculator extends React.component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {value: '', scale: 'c'};
}
handleCelsiusChange(value) {
this.setState({scale: 'c', value});
}
handleFahrenheitChange(value) {
this.setState({scale: 'f', value});
}
render() {
const scale = this.state.scale;
const value = this.state.value;
const celsius = scale === 'f' ? tryConvert(value, toCelsius) : value;
const fahrenheit = scale === 'c' ? tryConvert(value, toFahrenheit) : value;
return (
<div>
<TemperatureInput
scale="c"
value={celsius}
onChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
value={fahrenheit}
onChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
现在,不管你编辑哪个输入框,'Calculator'组件中的 'this.state.value' 和 'this.state.scale' 都会更新。任何用户输入保留下来,一个输入框获取到用户输入到值,另一个输入框基于它,总是重新计算。
课程总结
在React应用中,任何数据都应该有一个单一的真实数据来源。通常的,为了渲染,state是第一个被添加到组件中。之后,如果其他组件也需要它,可以提升state到它们最近的祖先元素上。替代尝试同步不同组件间的state,我们应该依靠的是 'top-down data flow - 自上而下的数据流'
相比于 'two-way binding approaches - 双向绑定方法',提升state状态,涉及写更多的 'boilerplate - 样板文件' 代码,但是它有一个好处,花费更少的工作查找和杜绝bug。因为任何state存在于一些组件,组件单独可以修改它,这样bugs的表面范围就大大减少了。另外,你可以实现任何自定义逻辑来拒绝或转换用户输入。
如果一些变量,通过 'props' 和 'state' 都可以获取,最好不要使用 'state',而应该用 'props'。例如:我们进存储最后编辑的 'value' 和 它的 'scale',而不存储 'celsiusValue' 和 'fahrenheitValue'。在 'rend()' 方法中通过它们,另一个输入框的值总会被计算。这可以让我们清楚或应用于其他字段,而不会丢失用户输入的任何精度
当你在UI中发现一些错误,你可以使用 'React开发者工具' ,来检查 props,并移动树形结构,直到找到组件响应的state更新。这让你可以在源码中追踪bug
转载请注明原文地址: https://ju.6miu.com/read-676319.html