译自: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. nginx配置打印请求响应内容

    #放在http{}里面 log_format kyh ' [$time_local] "$request" $status \n' 'req_header:"$req_h ...

  2. 2017-12-15python全栈9期第二天第七节之数字转换成布尔值

    #!/user/bin/python# -*- coding:utf-8 -*-print(bool(2))

  3. PSi-Population Stability Index (PSI)

    python信用评分卡(附代码,博主录制) https://study.163.com/course/introduction.htm?courseId=1005214003&utm_camp ...

  4. 20165232第4次实验《Android程序设计》实验报告

    20165232第4次实验<Android程序设计>实验报告 一.实验报告封面 一.实验报告封面 课程:Java程序设计 班级:1652班 姓名:何彦达 学号:20165232 指导教师: ...

  5. 6-(基础入门篇)学会编译lua固件,固件的合成

    http://www.cnblogs.com/yangfengwu/p/9336274.html 基础教程源码链接请在淘宝介绍中下载,由于链接很容易失效,如果失效请联系卖家,谢谢 https://it ...

  6. 【MSSQL】SqlServer中delete语句表别名的问题

    1.一般情况下删除表数据的sql语句: delete from products 2.如果想给表起个别名再删除呢,就得像下面这样写了 delete products from products as ...

  7. MyBatis-Select 流程

    mybatis 版本:3.5.1 测试代码: public interface MyUserMapperAnnotation { @Select("select * from myuser ...

  8. MyBatis-Configuration

    一.引用 properties 配置文件 db.properties driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://192.168.8.136:33 ...

  9. 【leetcode-74】搜索二维矩阵

    (较简单,但犯错太多) 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值.该矩阵具有如下特性: 每行中的整数从左到右按升序排列. 每行的第一个整数大于前一行的最后一个整数. 示例 1: ...

  10. Golang入门教程(六)关键字和数据类型

    在 Go 编程语言中,数据类型用于声明函数和变量. 数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存. 一.25个关键字 二.18 ...