React官方文档之状态提高

状态提高

使用 react 常常会遇到几个组件须要共用状态数据的状况。这种状况下,咱们最好将这部分共享的状态提高至他们最近的父组件当中进行管理。咱们来看一下具体如何操做吧。javascript

这部份内容当中,咱们会建立一个温度计算器来计算水是否会在给定的温度下烧开。css

开始呢,咱们先建立一个名为 BoilingVerdict 的组件。它会接受 celsius 这个温度变量做为它的 prop 属性,最后根据温度判断返回内容:html

function BoilingVerdict(props) { if (props.celsius >= 100) {  return <p>水会烧开</p>; }  return <p>水不会烧开</p>; } 

接下来,咱们写一个名为 Calculator 的组件。它会渲染一个 <input> 来接受用户输入,而后将输入的温度值保存在 this.state.temperature 当中。java

以后呢,它会根据输入的值渲染出 BoilingVerdict 组件。react

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>输入一个摄氏温度</legend>  <input  value={temperature}  onChange={this.handleChange} />  <BoilingVerdict  celsius={parseFloat(temperature)} /> </fieldset> ); } } 

在 CodePen 上试试。git

添加第二个输入框

如今咱们有了一个新的需求,在提供摄氏度输入的基础之上,再提供一个华氏温度输入,而且它们能保持同步。github

咱们能够经过从 Calculator 组件中抽离一个 TemperatureInput 组件出来。咱们也会给它添加一个值为 c 或 f 的表示温度单位的 scale 属性。函数

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> ); } } 

在 CodePen 上试试。this

咱们如今有了两个输入框,可是当你在其中一个输入时,另外一个并不会更新。这显然是不符合咱们的需求的。

另外,咱们此时也不能从 Calculator 组件中展现 BoilingVerdict 的渲染结果。由于如今表示温度的状态数据只存在于 TemperatureInput 组件当中。

写出转换函数

首先,咱们写两个能够将摄氏度和华氏度互相转换的函数。

function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9; } function toFahrenheit(celsius) { return (celsius * 9 / 5) + 32; } 

这两个函数只是单纯转换数字。咱们还须要另一个函数,它接受两个参数,第一个接受字符串 temperature 变量,第二个参数则是上面编写的单位转换函数。最后会返回一个字符串。咱们会使用它来根据一个输入框的输入计算出另外一个输入框的值。

咱们最后取到输出的小数点后三位,而 temperature 输入不合法的时候,这个函数则会返回空字符串。

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(); } 

举两个例子,tryConvert('abc', toCelsius) 会返回空字符串,而 tryConvert('10.22', toFahrenheit) 会返回 '50.396'

状态提高

到这一步为止,两个TemperatureInput组件都是在本身的 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; 

可是,咱们想要的是这两个输入能保持同步。当咱们更新摄氏输入(Celsius)时,华氏度(Fahrenheit )这个框应该能显示转换后的的温度数值,反之亦然。

在React中,状态分享是经过将state数据提高至离须要这些数据的组件最近的父组件来完成的。这就是所谓的状态提高。咱们会将 TemperatureInput 组件自身保存的 state 移到 Calculator中。

若是 Calculator 组件拥有了提高上来共享的状态数据,那它就会成为两个温度输入组件的“数据源”。它会传递给下面温度输入组件一致的数据。因为两个 TemperatureInput 温度组件的props属性都是来源于共同的父组件 Calculator,它们的数据也会保持同步。

让咱们一步一步来分析如何操做。

首先,咱们在 TemperatureInput 组件中将 this.state.temperature 替换为 this.props.temperature 。从如今开始,咱们假定 this.props.temperature 属性已经存在了,不过以后仍然须要将数据从 Calculator 组件中传进去。

render() { // 以前的代码: const temperature = this.state.temperature;  const temperature = this.props.temperature; 

咱们首先知道props是只读的 这么一个事实。而以前temperature变量是被保存在其自身的 state 中的,TemperatureInput 组件只须要调用 this.setState() 就能改变它。但如今,temperature 是做为 prop 从父组件传递下来的,TemperatureInput 组件是没有控制权的。

在React中,这个问题一般是经过让组件“受控”来解决。就像 <input> 可以接受 value 和 onChange 这两个prop属性值,自定义组件 TemperatureInput 也能接受来自 Calculator 父组件的 temperature 变量和 onTemperatureChange 方法做为props属性值。

作完这些,当 TemperatureInput 组件更新它的温度数值时,就会调用 this.props.onTemperatureChange 方法。

handleChange(e) { // 以前的代码: this.setState({temperature: e.target.value});  this.props.onTemperatureChange(e.target.value); 

须要指出的是,咱们如今定义的 temperature 和 onTemperatureChange 这些prop属性的命名没有特殊含义,咱们也能够起个其余任何的名字,像是valueonChange这些只是命名习惯罢了。

onTemperatureChange 和 temperature 两个 props 属性均由父组件 Calculator 提供。父组件能够经过自身的方法来响应状态数据的改变,从而使用新的值来从新渲染两个输入框组件。不过咱们先放着,最后再来修改它。

在咱们改写 Calculator 组件以前,咱们先花点时间总结下 TemperatureInput 组件的改变。咱们将其自身的 state 从组件中移除,使用 this.props.temperature 替代 this.state.temperature ,当咱们想要响应数据改变时,使用父组件提供的 this.props.onTemperatureChange() 而不是this.setState() 方法:

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>在{scaleNames[scale]}:中输入温度数值</legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } } 

如今让咱们来看看 Calculator 组件。

咱们将会在它的 state 中存储以前输入框组件的 temperature 和 scale 值,这是从输入框组件中“提高”上来的 state,它将会成为两个输入框组件的“数据源”。这是咱们所须要的可以从新渲染而且表示两个不一样输入组件的最基本的数据。

举个例子,假如咱们在摄氏度输入框中输入37,那么 Calculator 的 state 就是:

{ temperature: '37', scale: 'c' } 

若是咱们以后在华氏度输入框输入212,那么 Calculator 的状态数据就会是:

{ temperature: '212', scale: 'f' } 

其实咱们能够一块儿保存两个输入的值,但这么作彷佛没有必要。保存最近 改变的值和所需标识的温标单位就足够了。咱们能够只需基于当前的 temperature 和 scale 计算出另外一个输入框中的值。

如今这两个输入框中的值能保持同步了,由于它们使用的是经过同一个 state 计算出来的值。

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> ); } } 

在 Codepen 上试试。

如今,不管你编辑哪个输入框,Calculator 组件中 this.state.temperature 和 this.state.scale 都会更新。其中之一的输入框获得用户原样输入的值,另外一个输入框老是显示基于这个值计算出的结果。

让咱们梳理下编辑输入框时所发生的一系列活动:

  • React在DOM原生组件<input>上调用指定的onChange函数。在本例中,指的是TemperatureInput组件上的handleChange函数。
  • TemperatureInput组件的handleChange函数会在值发生变化时调用this.props.onTemperatureChange()函数。这些props属性,像onTemperatureChange都是由父组件Calculator提供的。
  • 当最开始渲染时,Calculator组件把内部的handleCelsiusChange方法指定给摄氏输入组件TemperatureInputonTemperatureChange方法,而且把handleFahrenheitChange方法指定给华氏输入组件TemperatureInputonTemperatureChange。两个Calculator内部的方法都会在相应输入框被编辑时被调用。
  • 在这些方法内部,Calculator组件会让React使用编辑输入的新值和当前输入框的温标来调用this.setState()方法来重渲染自身。
  • React会调用Calculator组件的render方法来识别UI界面的样子。基于当前温度和温标,两个输入框的值会被从新计算。温度转换就是在这里被执行的。
  • 接着React会使用Calculator指定的新props来分别调用TemperatureInput组件.React也会识别出子组件的UI界面。
  • React DOM 会更新DOM来匹配对应的值。咱们编辑的输入框获取新值,而另外一个输入框则更新通过转换的温度值。

一切更新都是通过一样的步骤,于是输入框能保持同步的。

经验教训

在React应用中,对应任何可变数据理应只有一个单一“数据源”。一般,状态都是首先添加在须要渲染数据的组件中。此时,若是另外一个组件也须要这些数据,你能够将数据提高至离它们最近的父组件中。你应该在应用中保持 自上而下的数据流,而不是尝试在不一样组件中同步状态。

状态提高比双向绑定方式要写更多的“模版代码”,但带来的好处是,你也能够更快地寻找和定位bug的工做。由于哪一个组件保有状态数据,也只有它本身可以操做这些数据,发生bug的范围就被大大地减少了。此外,你也可使用自定义逻辑来拒绝或者更改用户的输入。

若是某些数据能够由props或者state提供,那么它颇有可能不该该在state中出现。举个例子,咱们仅仅保存最新的编辑过的temperaturescale值,而不是同时保存 celsiusValue 和 fahrenheitValue 。另外一个输入框中的值老是能够在 render() 函数中由这些保存的数据计算出来。这样咱们能够根据同一个用户输入精准计算出两个须要使用的数据。

当你在开发UI界面遇到问题时,你可使用 React 开发者工具来检查props属性,而且能够点击查看组件树,直到你找到负责目前状态更新的组件。这能让你到追踪到产生 bug 的源头。

Monitoring State in React DevTools
相关文章
相关标签/搜索