Reactjs入门官方文档(九)【lifting-state-up】
2017-11-15 11:40
567 查看
此文章是翻译lifting-state-up这篇React(版本v15.5.4)官方文档。
在这部分,我们将要创建一个温度计算器来计算给定的温度是否使水沸腾。
我们将创建一个
然后,我们会创建一个
另外,它会当前输入值渲染
在CodePen 上尝试
开始我们可以从
现在我们可以修改
在CodePen 上尝试
现在我们有两个input,但是当你在其中一个键入temperature时,另一个不会更新。这同我们的需求相反:我们想要它们保持同步(in sync)。
我们也不能在
这两个函数转换数值。我们还要写另一个函数,它接受一个string 类型的
如果是一个无效的
例如,
但是,我们希望这两个输入是相互同步的。 当我们更新Celsisus input时,Fahrenheit input 应反映转换温度,反之亦然。
在React中,共享状态是通过将其移动到需要它的组件的最接近的共同祖先来实现的。 这被称为“提升状态(lifting state up)”。 我们将从
如果
让我们看看这是如何一步一步工作的。
首先,我们将在
我们知道props 是只读的。当
在React中,通常通过使组件“受控(controlled)”来解决。 就像DOM
现在,当
请注意,自定义组件中的
在
现在我们来看一下
我们将当前输入的
例如,如果我们在摄氏度输入中输入37,则
如果我们稍后将Fahrenheit 字段编辑为212,则
我们可以存储两个inputs 的值,但实际上是不必要的。 存储最近更改的input 的值以及它所代表的scale 就足够了。 然后,我们可以基于当前的
inputs 保持同步,因为它们的值是从相同state计算的:
在CodePen 上尝试
现在,无论是你在那一个input 进行编辑,在
让我们回顾一下编辑input时会发生什么:
React调用在DOM
当它渲染之前,
在这些方法中,
React调用
React使用
React DOM更新DOM以匹配所需的输入值。我们刚刚编辑的input 接收其当前值,另一个input 更新为转换后的temperature。
每个更新都会执行相同的步骤,以便输入保持同步。
通过专注写模版代码(boilerplater)而不是进行双向绑定,但是有一个优点,它能花费很少工作去发现和隔离bug。因为任何state 都“存在(lives)”于组件而组件可以独立改变state,确定bug 的范围也会减少。除此之外,你可以实现自定义逻辑去拒绝或改变用户输入。
如果有些东西可以来自props 或者state ,那么它就不应该存在与state 中。例如,我们存储
如果在UI 中有错误,你可以使用React Developer Tools 对props 进行检测,并向上遍历树,直到找到负责更新状态的组件。这使你可以跟踪到 bug 的源头:
状态提升(Lifting State Up)
通常,几个组件需要响应相同的数据变化。我们建议提升共享状态(lifting the shared state up)到距离它们最近的父组件。让我们看看这是如何运转的。在这部分,我们将要创建一个温度计算器来计算给定的温度是否使水沸腾。
我们将创建一个
BoilingVerdict组件。它接受一个
celsius温度作为props,然后输出是否能够使水沸腾:
function BoilingVerdict(props){ if(props.celsius >= 100){ return <p>The water would boil.</p> }else{ return <p>The water would nott boil.</p> } }
然后,我们会创建一个
Calculator组件。它渲染一个
<input>接受你的键入的温度( temperature),并将此值保存在
this.state.temperature中。
另外,它会当前输入值渲染
BoilingVerdict。
class Calculator extends Component { constructor(props){ super(props) this.state = { temperature: '' } this.handleChagne = this.handleChagne.bind(this) } handleChagne(event){ this.setState({ temperature: event.target.value }) } render(){ const temperature = this.state.temperature return ( <fieldset> <legend>Enter temperature in celsius:</legend> <input value={temperature} onChange={this.handleChagne} /> <BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset> ) } }
在CodePen 上尝试
添加第二个输入
我们的新需求是,除了Celsius input ,我们还需要提供一个Fahrenheit input,并且让它们保持同步(in sync)。开始我们可以从
Calculator组件中提取一个
TemperatureInput组件。我们将添加一个
scaleprop ,只可以接受
“c”或
“f”:
const scaleNames = { c: 'Celsius', f: 'Fahrenheit' } class TemperatureInput extends Component { constructor(props){ super(props) this.state = { temperature: '' } this.handleChange = this.handleChange.bind(this) } handleChange(event){ this.setState({ temperature: event.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去渲染两个不同的temperature inputs:
class Calculator extends Component { render(){ return ( <div> <TemperatureInput scale="c" /> <TemperatureInput scale="f" /> </div> ) } }
在CodePen 上尝试
现在我们有两个input,但是当你在其中一个键入temperature时,另一个不会更新。这同我们的需求相反:我们想要它们保持同步(in sync)。
我们也不能在
Calculator中展示
BoilingVerdict。这个
Calculator不知道当前的temperature 因为它被隐藏在
TemperatureInput中。
编写转换函数
首先,我们要写两个函数来互相转换Celsisus和
Fahrenheit。
function toCelsius(fahrenheit){ return (fahrenheit - 32 ) * 5 / 9 } function toFahrenheit(celsius){ return (celsius * 9 / 5) + 32 }
这两个函数转换数值。我们还要写另一个函数,它接受一个string 类型的
value和一个转换函数作为参数并且返回一个string。我们调用此函数来通过一个输入值获取另一个输入值。
如果是一个无效的
value它将返回一个空字符串,并且输出值保留三位小数。
function tryCovert(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', toCelsisu)将返回一个空字符串,而
tryConvert('10.22', toFahrenheit)将返回
50.369。
状态提升(Lifting State Up)
当前,两个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 } }
但是,我们希望这两个输入是相互同步的。 当我们更新Celsisus input时,Fahrenheit input 应反映转换温度,反之亦然。
在React中,共享状态是通过将其移动到需要它的组件的最接近的共同祖先来实现的。 这被称为“提升状态(lifting state up)”。 我们将从
TemperatureInput中删除本地状态,并将其移动到
Calculator中。
如果
Calculator拥有共享状态,则它将成为两个输入中当前温度的“真实来源(source of truth)”。 它可以指示他们具有彼此一致的值。 由于两个
TemperatureInput组件的props来自同一个父
Calculator组件,所以两个输入将始终保持同步。
让我们看看这是如何一步一步工作的。
首先,我们将在
TemperatureInput组件中用
this.props.temperature替换
this.state.temperature。 现在,假设
this.props.temperature已经存在,虽然我们将来需要从
Calculator传递它:
render() { // Before: const temperature = this.state.temperature const temperature = this.props.temperature }
我们知道props 是只读的。当
temperature处于本地state 时,
TemperatureInput可以调用
this.setState()来更改它。 然而,现在
temperature作为prop来自父组件,
TemperatureInput无法控制它。
在React中,通常通过使组件“受控(controlled)”来解决。 就像DOM
<input>一样,同时接受一个
value和一个
onChangeprop,所以自定义的
TemperatureInput也可以接受来自其父
Calculator的
temperature和
onTemperatureChangeprops。
现在,当
TemperatureInput想要更新其temperature 时,它会调用
this.props.onTemperatureChange:
handleChange(e) { // Before: this.setState({temperature: e.target.value}) this.props.onTemperatureChange(e.target.value) }
请注意,自定义组件中的
temperature或
onTemperatureChangeprop名称没有特殊的含义。 我们可以叫他们任何其他的东西,像命名他们为
value和
onChange这是一个常见的惯例。
onTemperatureChangeprop将与父
Calculator组件的
temperatureprop一起提供。 它将通过修改自己的本地state来处理change,从而将两个输入重新提供给新值。 我们将很快看到新的
Calculator实现。
在
Calculator中进行更改之前,让我们回顾一下对
TemperatureInput组件的更改。 我们已经从中删除了本地state,而不是读取
this.state.temperature,我们现在读取
this.props.temperature。 我们现在调用
this.props.onTemperatureChange(),而不是调用
this.setState(),这将由
Calculator提供:
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组件。
我们将当前输入的
temperature和
scale存储在本地state。 这是我们从inputs中“提升(lifted up)”的state,它们将作为他们两个的“真理之源(source of truth)”。 为了呈现两个输入,我们需要知道的所有数据的最小表示。
例如,如果我们在摄氏度输入中输入37,则
Calculator组件的state将是:
{ temperature: '37', scale: 'c' }
如果我们稍后将Fahrenheit 字段编辑为212,则
Calculator的state将为:
{ temperature: '212', scale: 'f' }
我们可以存储两个inputs 的值,但实际上是不必要的。 存储最近更改的input 的值以及它所代表的scale 就足够了。 然后,我们可以基于当前的
temperature和
scale来推断另一个input 的值。
inputs 保持同步,因为它们的值是从相同state计算的:
class Calculator extends 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' ? tryCovert(temperature, toCelsius) : temperature const fahrenheit = scale === 'c' ? tryCovert(temperature, toFahrenheit) : temperature return ( <div> <TemperatureInput scale="c" temperature={celsius} onTemperatureChange={this.handleCelsiusChange}/> <TemperatureInput scale="f" temperature={fahrenheit} onTemperatureChange={this.handleFahrenheitChange}/> <BoilingVerdict celsisu={parseFloat(celsisu)} /> </div> ) } }
在CodePen 上尝试
现在,无论是你在那一个input 进行编辑,在
Calculator中
this.state.temperature和
this.state.scale都会更新。其中的一个input 获取value,任何input 都会被保存,另一个input 都会基于这个值来更新。
让我们回顾一下编辑input时会发生什么:
React调用在DOM
<input>上指定为
onChange的函数。在我们的例子中,这是
TemperatureInput组件中的
handleChange方法。
TemperatureInput组件中的
handleChange方法使用新的所需值调用
this.props.onTemperatureChange()。其props 包括
onTemperatureChange,由其父组件
Calculator提供。
当它渲染之前,
Calculator已经指定了Celsius
TemperatureInput的
onTemperatureChange是
Calculator的
handleCelsiusChange方法,而Fahrenheit
TemperatureInput的
onTemperatureChange是
Calculator的
handleFahrenheitChange方法。因此,根据我们编辑的input,调用这两个
Calculator方法。
在这些方法中,
Calculator组件要求React通过使用新的input 值和刚刚编辑的input的当前scale 调用
this.setState()来重新渲染自身。
React调用
Calculator组件的
render方法来了解UI的外观。基于当前temperature和有效scale重新计算两个inputs的值。这里进行温度转换。
React使用
Calculator指定的新props调用各个
TemperatureInput组件的渲染方法。它会了解UI的外观。
React DOM更新DOM以匹配所需的输入值。我们刚刚编辑的input 接收其当前值,另一个input 更新为转换后的temperature。
每个更新都会执行相同的步骤,以便输入保持同步。
经验总结
在React 应用中任何数据变动都应该是基于一个“单一数据源””source of truth”。通常state 被首先加入组件去渲染。然后,如果另一个组件也需要它,那么你就可以将它们提升到它们的父组件中。你应该依赖从上到下的数据流向top-down data flow,而不是尝试在不同的组件中同步state。通过专注写模版代码(boilerplater)而不是进行双向绑定,但是有一个优点,它能花费很少工作去发现和隔离bug。因为任何state 都“存在(lives)”于组件而组件可以独立改变state,确定bug 的范围也会减少。除此之外,你可以实现自定义逻辑去拒绝或改变用户输入。
如果有些东西可以来自props 或者state ,那么它就不应该存在与state 中。例如,我们存储
temperature和
scale而不是存储
celsiusValue和
fahrenheitValue。另一个input 值总是可以在
render()方法中被计算得到。这可以让我们清晰的 应用在任何取值范围而不会失去用户输入精度。
如果在UI 中有错误,你可以使用React Developer Tools 对props 进行检测,并向上遍历树,直到找到负责更新状态的组件。这使你可以跟踪到 bug 的源头:
相关文章推荐
- React官方文档--Lifting State Up
- Reactjs入门官方文档(四)【state-and-lifecycle】
- Reactjs入门官方文档(五)【handling-events】
- Reactjs入门官方文档(六)【conditional-rendering】
- Reactjs入门官方文档(一)【jsx】
- Reactjs入门官方文档(七)【lists-and-keys】
- Reactjs入门官方文档(二)【rendering-elements】
- Reactjs入门官方文档(八)【forms】
- Reactjs入门官方文档(十一)【Thinking in React】
- React 官方文档初释义 —— State and Lifecycle(四)
- part2 react官方文档笔记09--JSX In Depth
- vue.js从入门到放弃2--官方文档阅读笔记
- React官方文档笔记之快速入门
- React.js 官方文档摘记:非受控组件
- React.js 基础入门三 ---组件状态state
- React.js 官方文档翻译
- React.js 官方文档翻译2
- React - Lifting state up 提升状态
- React官方文档--State and Lifecycle
- React.js 官方文档翻译3 代码集合