React中文文档之Lifting State Up

    xiaoxiao2021-08-14  189

    Lifting State Up - 提升状态 经常的,几个组件需要映射相同的数据改变。我们推荐提升共享的state状态到它们最近的公共祖先元素。让我们看看这是如何实现的。 在这个章节,我们将创建一个温度计算器,计算在一个给定的温度,水是否会沸腾。 我们以一个叫做 'BoilingVerdict' 的组件开始。它接收 'celsius' 温度作为一个prop,并且打印是否足够使水非常。 function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } else { return <p>The water would not boil.</p>; } } 接下来,我们创建一个叫做 'Calculator' 的组件。它渲染一个 <input> ,让你输入温度,并使用 'this.state.value' 来保持它的值。 另外,它根据当前输入的值来渲染 'BoilingVerdict' 。 class Calculator extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = {value: ''}; } handleChange(e) { this.setState({value: e.target.value}); } render() { const value = this.state.value; return ( <fieldset> <legend>Enter temperature in Celsius:</legend> <input value={value} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(value)} /> </fieldset> ); } } 添加第二个输入框 我们的新需求是:除了 'Celsius' 输入框,我们提供一个 'Fahrenheit' 输入框,并且它们保持同步。 我们可以通过从 'Calculator' 提取一个 'TemperatureInput' 组件来开始。我们将添加一个新的 'scale' prop给 'TemperatureInput' 组件,'scale' 可以是 '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 = {value: ''}; } handleChange(e) { this.setState({value: e.target.value}); } render() { const value = this.state.value; const scale = this.props.scale; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={value} onChange={this.handleChange} /> </fieldset> ); } } 现在让我们来改变 Calculator 来渲染2个独立的温度输入框: class Calculator extends React.Component { render() { return ( <div> <TemperatureInput scale="c" /> <TemperatureInput scale="f" /> </div> ); } } 现在我们已经有2个输入框了,但是当你在它们中的一个输入温度时,另一个不更新。这与我们的需求矛盾:我们想要2者同步。 从 'Calculator' 组件中,我们也无法展示 'BoilingVerdict'。'Calculator' 不知道当前的温度,因为它隐藏在 'TemperatureInput' 的内部。 提升state状态 首先,我们写2个函数,从 'Celsius' 转换为 'Fahrenheit',然后反过来。 function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9; } function toFahrenheit(celsius) { return (celsius * 9 / 5) + 32; } 这2个函数转换数字。我们写另外一个函数,接收一个字符串 'value' 和一个转换函数名作为参数,并返回一个字符串。我们使用它来计算一个输入框的值,基于另一个输入框。 当传入一个无效的 'value',返回一个空字符串,并保证输出到第三位小数。 function tryConvert(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', toCelsius) 返回一个空字符串,tryConvert('10.22', toFahrenheit)返回 '50.396' 接着,我们从 'TemperatureInput' 组件中,移除 state 状态。 替代的,'TemperatureInput' 组件通过props接收 'value' 和 'onChange' 处理器(之前value是通过 state 来接收): class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.setState({value: e.target.value}); } render() { const value = this.props.value; const scale = this.props.scale; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={value} onChange={this.handleChange} /> </fieldset> ); } } 如果几个组件需要访问同样的state,这是一个信息,state应该被提升到它们最近的公共祖先上来替代。在我们的案例中,公共祖先是 'Calculator'。我们将存储当前的 'value' 和 'scale' 到它的 state。 我们可能已经存在了两个输入框的值,但是它原来是没有必要的。存储最近改变的输入框的值和它代表的 'scale' 就足够了。之后,我们可以根据当前的 'value' 和 'scale' 来推断出另一个输入框的值。 2个输入框保持同步,因为它们的值可以从相同的state来计算的到: class Calculator extends React.component { constructor(props) { super(props); this.handleCelsiusChange = this.handleCelsiusChange.bind(this); this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this); this.state = {value: '', scale: 'c'}; } handleCelsiusChange(value) { this.setState({scale: 'c', value}); } handleFahrenheitChange(value) { this.setState({scale: 'f', value}); } render() { const scale = this.state.scale; const value = this.state.value; const celsius = scale === 'f' ? tryConvert(value, toCelsius) : value; const fahrenheit = scale === 'c' ? tryConvert(value, toFahrenheit) : value; return ( <div> <TemperatureInput scale="c" value={celsius} onChange={this.handleCelsiusChange} /> <TemperatureInput scale="f" value={fahrenheit} onChange={this.handleFahrenheitChange} /> <BoilingVerdict celsius={parseFloat(celsius)} /> </div> ); } } 现在,不管你编辑哪个输入框,'Calculator'组件中的 'this.state.value' 和 'this.state.scale' 都会更新。任何用户输入保留下来,一个输入框获取到用户输入到值,另一个输入框基于它,总是重新计算。 课程总结 在React应用中,任何数据都应该有一个单一的真实数据来源。通常的,为了渲染,state是第一个被添加到组件中。之后,如果其他组件也需要它,可以提升state到它们最近的祖先元素上。替代尝试同步不同组件间的state,我们应该依靠的是 'top-down data flow - 自上而下的数据流' 相比于 'two-way binding approaches - 双向绑定方法',提升state状态,涉及写更多的 'boilerplate - 样板文件' 代码,但是它有一个好处,花费更少的工作查找和杜绝bug。因为任何state存在于一些组件,组件单独可以修改它,这样bugs的表面范围就大大减少了。另外,你可以实现任何自定义逻辑来拒绝或转换用户输入。 如果一些变量,通过 'props' 和 'state' 都可以获取,最好不要使用 'state',而应该用 'props'。例如:我们进存储最后编辑的 'value' 和 它的 'scale',而不存储 'celsiusValue' 和 'fahrenheitValue'。在 'rend()' 方法中通过它们,另一个输入框的值总会被计算。这可以让我们清楚或应用于其他字段,而不会丢失用户输入的任何精度 当你在UI中发现一些错误,你可以使用 'React开发者工具' ,来检查 props,并移动树形结构,直到找到组件响应的state更新。这让你可以在源码中追踪bug
    转载请注明原文地址: https://ju.6miu.com/read-676319.html

    最新回复(0)