React组件State提升(译)
译自: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提升(译)的更多相关文章
- 第二章 设计高质量的React组件
		
第二章 设计高质量的React组件 高质量React组件的原则和方法: 划分组件边界的原则: React组件的数据种类: React组件的生命周期. 2.1 易于维护组件的设计要素 1.高内聚:指的是 ...
 - 28-React state提升、组件组合或继承
		
Lifting State Up state提升 对于在React应用程序中更改的任何数据,应该有一个单一的数据源.通常,都是将state添加到需要渲染的组件.如果其他组件也需要它,您可以将其提升到最 ...
 - 【译】参考手册-React组件
		
react version: 15.4.2 React.Component 组件能够让你将UI拆分为多个独立自治并可重用的部分.在 React 中提供了 React.Component. 概述 Rea ...
 - React组件系统、props与状态(state)
		
多个组件合成一个组件: var style = { fontSize: 20, color: '#ff0000' }; var WebSite = React.createClass({ rende ...
 - 说说React组件的State
		
说说React组件的State React的核心思想是组件化的思想,应用由组件搭建而成, 而组件中最重要的概念是State(状态). 正确定义State React把组件看成一个状态机.通过与用户的交 ...
 - [转] 深入理解React 组件状态(State)
		
React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据. 一. 如何定义State 定义一个合适 ...
 - React组件的State
		
React组件的State 1.正确定义State React把组件看成一个状态机.通过与用户的交互,实现不同状态,然后渲染UI,让用户界面和数据保持一致.组件的任何UI改变,都可以从State的变化 ...
 - 【05】react 之 组件state
		
1.1. 状态理解 React的数据流:由父节点传递到子节点(由外到内传递),如果顶层组件某个prop改变了,React会向下传递,重新渲染所有使用过该属性的组件.除此之外React 组件内部还具有 ...
 - 深入理解React 组件状态(State)
		
React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据. 一. 如何定义State 定义一个合适 ...
 
随机推荐
- Java基础内部类、包的声名、访问修饰符、代码块整理
			
内部类 14.1内部类概念 将类写在其他类的内部,可以写在其他类的成员位置和局部位置,这时写在其他类内部的类就称为内部类.其他类也称为外部类. 内部类分为成员内部类与局部内部类. 我们定义内部类时,就 ...
 - 剑指Offer_编程题_16
			
题目描述 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则. /* struct ListNode { int val; struct ListNode *n ...
 - slave have equal MySQL server UUIDs
			
在部署MySQL主从复制架构的时候,碰到了"Last_IO_Error: Fatal error: The slave I/O thread stops because master and ...
 - ELASTICSEARCH 搜索的评分机制
			
从我们在elasticsearch复合框输入搜索语句到结果显示,展现给我们的是一个按score得分从高到底排好序的结果集.下面就来学习下elasticsearch怎样计算得分. Lucene(或 El ...
 - 015、调试Dockerfile(2019-01-04 周五)
			
参考https://www.cnblogs.com/CloudMan6/p/6853329.html Dockerfile构建镜像的过程 1.从base镜像运行一个容器 2.执行一条指令,对容 ...
 - tedu训练营day04
			
1.猜拳:import randomlist = ['石头','剪刀','布']y = input('''(0) 石头(1) 剪刀(2) 布请出拳(0/1/2):******************* ...
 - Linux 用top命令查看CPU和内存使用情况
			
直接 top 回车 PID:进程的ID USER:进程所有者 PR:进程的优先级别,越小越优先被执行 NInice:值 VIRT:进程占用的虚拟内存 RES:进程占用的物理内存 SHR:进程使用的共享 ...
 - Android文字转语音引擎(TTS)使用
			
百度网盘下载地址 密码:3si0资源来源:https://blog.csdn.net/Sqq_yj/article/details/82460580?utm_source=blogxgwz4 简单比较 ...
 - 4-24日 collections模块  random模块  time模块   sys模块  os模块
			
1, collections模块 在内置数据类型(dict.list.set.tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter.deque.defaultdi ...
 - FTP之二
			
username=admin password=123 ip=192.168.14.117 port=21 参考:http://blog.csdn.net/yelove1990/article/det ...