React中文文档之Lifting State Up

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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值