译自:https://reactjs.org/docs/lifting-state-up.html (适当进行了裁减)  

  通常我们会碰到这样的情况,当某个组件的state数据改变时,几个React组件同时都需要做出反应。这时我们推荐把相应的state值共享到这些组件最接近的父类中。让我们看下实际是怎么做的。

  在这个章节,我们将创建一个“温度计算器”,这个计算器通过给定的温度来计算水是不是沸腾了。

我们将从一个BoilingVerdice(沸水判定)组件开始。这个组件接受一个摄氏温度值作为其prop并且打印水在这个温度是否沸腾了:

function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}

下一步,我们将创建一个 Calculator(计算器)的组件。这个组件呈现一个<input>输入框让你输入一个温度值,然后将保持这个值到组件的this.state.temperature中。

此外,Calculator组件还将这个温度值传给它的子组件BoilingVerdict进行呈现。

class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
} handleChange(e) {
this.setState({temperature: e.target.value});
} render() {
const temperature = this.state.temperature;
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}

添加第二个输入

我们的新需求是,除了一个摄氏度的输入,我们还提供华氏温度的输入,并且它们之间要保持同步。

我们从Calculator组件中抽取出一个TemperatureInput(温度输入)组件。我们将添加一个新的名为scale(刻度)的prop,取值可以是“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 = {temperature: ''};
} handleChange(e) {
this.setState({temperature: e.target.value});
} render() {
const temperature = this.state.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
我们现在可以改变Calculator组件来呈现两个不同的温度输入:
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
现在我们有了两个温度输入框,但目前两入输入框不会同步,当我们改动一个输入框的温度值时,另一个输入框的值不会随着改变。这个没有达到我们的需求:我们希望这两个输入框中的值能保持同步变化。
并且在当前这个Calculator组件中我们也无法显示BoilingVerdict组件的内容。因为当前的温度值隐藏在TemperatureInput组件内部了,使得Calculator组件无法获得当前的温度值。
编写转换函数
首先,我们将编写两个函数,将摄氏度转换为华氏温度,然后返回:
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();
}

例如,tryConvert('abc'、to摄氏度)返回一个空字符串,而tryConvert('10.22'、toFahrenheit)返回'50.396'。

提升State

目前,两个TemperatureInput组件都各自保持输入的温度值在自己的local state中:

class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
} handleChange(e) {
this.setState({temperature: e.target.value});
} render() {
const temperature = this.state.temperature;
// ...

但是,我们希望这两个输入是同步的。当我们更新摄氏输入时,华氏输入应反映转换后的温度,反之亦然。

在React中,共享状态通过将需要共享的state值移动到需要它的组件的最近的共同祖先来完成。这被称为“提升State”。我们将state.temperature这个值从TemperatureInput中移除,并将其放入Calculator组件中。

如果Calculator组件拥有了共享状态,它就将成为当前两个温度输入的“真实来源”。这样就可以通过一定的代码引导使得两个温度输入子组件的值保持一致。由于这两个温度输入子组件的props都来自同一个父计算器组件,所以这两个输入将可以做到同步了。

让我们看一下详细的步骤:

我们将把TemperatureInput组件中的this.state.temperature替换为this.props.temperature。现在,让我们假设this.props.temperature已经存在,尽管将来我们需要从Calculator组件上传递它:

render() {
// Before: const temperature = this.state.temperature;
const temperature = this.props.temperature;
// ...
我们知道props是只读的。在TemperatureInput组件中,这个温度值是保存在local state中,所以只能通过调this.setState()来改变它。然而,现在这个温度值是来自父组件的prop,TemperatureInput组件将无法控制这个温度值的修改。
    在React中,通常通过让组件“受控”来解决这个问题。就像DOM <input>同时接受一个值和一个onChange props一样,TemperatureInput组件可以从它的父组件中接受温度值和onTemperatureChange函数做为温度变化的props。

现在,当TemperatureInput组件想要更新它的温度值,只要调this.props.onTemperatureChange就可以了。

handleChange(e) {
// Before: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value);
// ...

在深入研究计算器中的更改之前,让我们回顾一下对TemperatureInput组件的更改。this.state.temperature已经从local state中移除,通过this.props.temperature来替代。当我们要进行更改时,我们不再调用this.setState(),而是调用Calculator组件提供的this.props.onTemperatureChange() :

class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
} handleChange(e) {
this.props.onTemperatureChange(e.target.value);
} render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}

现在让我们转向Calculator组件,我们将存储当前输入的温度值和刻度在local state中。这是我们从输入组件中“提升”的state,它将成为两者的“真实之源”。 它是我们需要知道的所有数据的最小表示,以便两个输入组件的呈现,完整的代码如下:

const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
}; 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);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
} function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
} class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
} handleChange(e) {
this.props.onTemperatureChange(e.target.value);
} render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
} class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
} handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
} handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
} render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature; return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
} ReactDOM.render(
<Calculator />,
document.getElementById('root')
);

界面如下图:

现在就可以达到我们想要的效果了,两个输入框无论改变哪一个,另一个也会随着改变。其中一个温度值是用户的输入,我们会在组件内部保存下来,另一个温度值会通过计算得到。

对于在React应用程序中发生更改的任何数据,都应该有一个“真实来源”。通常,state首先被添加到需要它进行呈现的组件中。然后,如果其他组件也需要它,您可以将它提升到它们最近的共同祖先。与尝试在不同组件之间同步状态不同,您应该依赖自顶向下的数据流。

React组件State提升(译)的更多相关文章

  1. 第二章 设计高质量的React组件

    第二章 设计高质量的React组件 高质量React组件的原则和方法: 划分组件边界的原则: React组件的数据种类: React组件的生命周期. 2.1 易于维护组件的设计要素 1.高内聚:指的是 ...

  2. 28-React state提升、组件组合或继承

    Lifting State Up state提升 对于在React应用程序中更改的任何数据,应该有一个单一的数据源.通常,都是将state添加到需要渲染的组件.如果其他组件也需要它,您可以将其提升到最 ...

  3. 【译】参考手册-React组件

    react version: 15.4.2 React.Component 组件能够让你将UI拆分为多个独立自治并可重用的部分.在 React 中提供了 React.Component. 概述 Rea ...

  4. React组件系统、props与状态(state)

     多个组件合成一个组件: var style = { fontSize: 20, color: '#ff0000' }; var WebSite = React.createClass({ rende ...

  5. 说说React组件的State

    说说React组件的State React的核心思想是组件化的思想,应用由组件搭建而成, 而组件中最重要的概念是State(状态). 正确定义State React把组件看成一个状态机.通过与用户的交 ...

  6. [转] 深入理解React 组件状态(State)

    React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据. 一. 如何定义State 定义一个合适 ...

  7. React组件的State

    React组件的State 1.正确定义State React把组件看成一个状态机.通过与用户的交互,实现不同状态,然后渲染UI,让用户界面和数据保持一致.组件的任何UI改变,都可以从State的变化 ...

  8. 【05】react 之 组件state

    1.1.  状态理解 React的数据流:由父节点传递到子节点(由外到内传递),如果顶层组件某个prop改变了,React会向下传递,重新渲染所有使用过该属性的组件.除此之外React 组件内部还具有 ...

  9. 深入理解React 组件状态(State)

    React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据. 一. 如何定义State 定义一个合适 ...

随机推荐

  1. flask 钩子函数

    说明: before_request函数,就是一个装饰器,他可以把需要设置为钩子函数的代码放到视图函数执行之前执行 示例: from flask import Flask,url_for,redire ...

  2. collections 数据结构模块namedtuple

    namedtuple类 导入模块 from collections import namedtuple 使用方法及说明 #pycharm 里按住 ctrl键点击 collections可查看源码 #c ...

  3. Nginx简单手册

    Nginx 变量 变量名 注解 $arg_name 请求中的的参数名,即“?”后面的arg_name=arg_value形式的arg_name $args  请求中的参数值 $binary_remot ...

  4. HBase基础之常用过滤器hbase shell操作(转)

    创建表 create 'test1', 'lf', 'sf' lf: column family of LONG values (binary value)-- sf: column family o ...

  5. 【JS】JavaScript中Null和undefind区别

    1.undefined:只有一个值,及特殊的undefined.在使用var声明变量但未对其初始化时,这个变量的值是undefined,简言之,undefined就是表示变量申明了但未初始化时的值. ...

  6. js中数值类型相加变成拼接字符串的问题

    如题,弱类型计算需要先进行转型,例: savNum=parseInt(savNum)+parseInt(num);或者使用 number()转型

  7. Silverlight数据绑定之 绑定一个int类型的属性

    还就真心不会啊! 在类FunctionPanel中作如下定义: /// <summary> /// 鼠标状态 属性 /// </summary> public Dependen ...

  8. 1.redis安装

    一.安装 1.安装 tar fvxz redis-3.0.7.tar.gz  ln -s redis-3.0.7 redis cd redis make && make install ...

  9. 【Django】不知道为什么就是想学一下 01

    1. Django安装.项目创建及服务器连接 系统:Ubuntu 14.04.4 > cat /etc/issue //查看系统版本 安装Django > sudo pip install ...

  10. 【51nod 1288】汽油补给

    Description 有(N+1)个城市,0是起点N是终点,开车从0 -> 1 - > 2...... -> N,车每走1个单位距离消耗1个单位的汽油,油箱的容量是T.给出每个城市 ...