?
This document uses PHP Chinese website manual Release
通常,幾個組件需要反映相同的變化數(shù)據(jù)。我們建議將共享狀態(tài)提升至最接近的共同祖先。讓我們看看這是如何運作的。
在本節(jié)中,我們將創(chuàng)建一個溫度計算器,用于計算在給定溫度下水是否沸騰。
我們將從一個叫做組件開始BoilingVerdict
。它接受celsius
溫度作為支柱,并打印是否足夠煮沸水:
function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p>;}
接下來,我們將創(chuàng)建一個名為的組件Calculator
。它呈現(xiàn)一個<input>
讓你輸入溫度,并保持其價值this.state.temperature
。
此外,它呈現(xiàn)BoilingVerdict
當(dāng)前輸入值。
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> ); } }
我們的新要求是,除了攝氏溫度輸入外,我們還提供華氏溫度輸入,并且它們保持同步。
我們可以從提取TemperatureInput
組件開始Calculator
。我們將添加一個新的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 = {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> ); } }
我們現(xiàn)在可以改變Calculator
以呈現(xiàn)兩個單獨的溫度輸入:
class Calculator extends React.Component { render() { return ( <div> <TemperatureInput scale="c" /> <TemperatureInput scale="f" /> </div> ); } }
在 CodePen 上試用它。
我們現(xiàn)在有兩個輸入,但是當(dāng)你在其中一個輸入溫度時,另一個不會更新。這與我們的要求相矛盾:我們希望保持同步。
我們也無法顯示BoilingVerdict
從 Calculator
。在Calculator
不知道當(dāng)前的溫度,因為它是藏在里面的TemperatureInput
。
首先,我們將編寫兩個函數(shù)將攝氏溫度轉(zhuǎn)換為華氏溫度,然后返回:
function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9; } function toFahrenheit(celsius) { return (celsius * 9 / 5) + 32; }
這兩個函數(shù)轉(zhuǎn)換數(shù)字。我們將編寫另一個函數(shù),它將字符串temperature
和轉(zhuǎn)換器函數(shù)作為參數(shù)并返回一個字符串。我們將使用它來計算基于其他輸入的一個輸入的值。
它會在無效的情況下返回一個空字符串temperature
,并將輸出保留為小數(shù)點后第三位:
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
組件都獨立地將其值保持在本地狀態(tài):
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; // ...
但是,我們希望這兩個輸入互相同步。當(dāng)我們更新攝氏溫度輸入時,華氏溫度輸入應(yīng)反映轉(zhuǎn)換后的溫度,反之亦然。
在 React 中,共享狀態(tài)是通過將它移動到需要它的組件的最接近的共同祖先來完成的。這被稱為“提升狀態(tài)”。我們將從當(dāng)?shù)貒抑幸瞥?code>TemperatureInput并將其移入Calculator
。
如果Calculator
擁有共享狀態(tài),它將成為兩個輸入中當(dāng)前溫度的“真值源”。它可以指導(dǎo)他們都有相互一致的價值觀。由于兩個TemperatureInput
組件的道具來自同一個父Calculator
組件,因此兩個輸入始終保持同步。
讓我們看看這是如何一步一步工作。
首先,我們將替換this.state.temperature
用this.props.temperature
的TemperatureInput
部件?,F(xiàn)在,讓我們假裝this.props.temperature
已經(jīng)存在,盡管我們需要Calculator
在未來將它傳遞出去:
render() { // Before: const temperature = this.state.temperature; const temperature = this.props.temperature; // ...
我們知道道具是只讀的。當(dāng)temperature
在當(dāng)?shù)氐臓顟B(tài),TemperatureInput
可以打電話this.setState()
來改變它。但是,現(xiàn)在temperature
來自父母的道具,TemperatureInput
它無法控制它。
在 React 中,通常通過將組件“控制”來解決這個問題。就像 DOM <input>
接受a value
和onChange
prop一樣,自定義也可以TemperatureInput
接受它的父項temperature
和onTemperatureChange
道具Calculator
。
現(xiàn)在,當(dāng)TemperatureInput
想要更新其溫度時,它會調(diào)用this.props.onTemperatureChange
:
handleChange(e) { // Before: this.setState({temperature: e.target.value}); this.props.onTemperatureChange(e.target.value); // ...
注意:對自定義組件中的任一
temperature
或onTemperatureChange
名稱沒有特殊含義。我們可以稱其他任何東西,比如說它們的名字value
,onChange
這是一個通用的慣例。
onTemperatureChange
支柱將與一起提供temperature
由父支柱Calculator
組件。它將通過修改其自身的本地狀態(tài)來處理更改,從而使用新值重新呈現(xiàn)兩個輸入。我們Calculator
很快就會看到新的實施。
在深入了解變化之前Calculator
,讓我們回顧一下對TemperatureInput
組件的更改。我們已經(jīng)從中刪除了當(dāng)?shù)氐膰遥皇情喿xthis.state.temperature
,我們現(xiàn)在閱讀this.props.temperature
。this.setState()
我們現(xiàn)在打電話給我們this.props.onTemperatureChange()
,而不是打電話給我們,這將由以下人員提供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> ); } }
現(xiàn)在我們來看看這個Calculator
組件。
我們將存儲當(dāng)前的輸入temperature
并scale
處于本地狀態(tài)。這是我們從投入中“提起來”的狀態(tài),它將成為兩者的“真相之源”。它是我們?yōu)榱顺尸F(xiàn)兩個輸入而需要知道的所有數(shù)據(jù)的最小表示。
例如,如果我們在攝氏度輸入中輸入37,則Calculator
組件的狀態(tài)將為:
{ temperature: '37', scale: 'c'}
如果我們稍后編輯華氏場為212,那么Calculator
將會是:
{ temperature: '212', scale: 'f'}
我們可以存儲兩個輸入的值,但事實證明這是不必要的。存儲最近更改的輸入的值以及它所表示的比例就足夠了。然后,我們可以基于當(dāng)前temperature
和scale
單獨推斷另一個輸入的值。
輸入保持同步,因為它們的值是從相同的狀態(tài)計算得出的:
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 上試用它。
現(xiàn)在,無論您編輯哪個輸入,this.state.temperature
和this.state.scale
在Calculator
獲取更新。其中一個輸入按原樣得到值,因此任何用戶輸入都會保留,而另一個輸入值總是基于此重新計算。
讓我們回顧一下編輯輸入時會發(fā)生的情況:
React 調(diào)用onChange
在 DOM 上指定的函數(shù)<input>
。在我們的例子中,這是組件中的handleChange
方法TemperatureInput
。
組件中的handleChange
方法使用新的期望值TemperatureInput
調(diào)用this.props.onTemperatureChange()
。它的道具,其中包括onTemperatureChange
,由其母公司提供的Calculator
。
當(dāng)它先前渲染中,Calculator
已經(jīng)指定onTemperatureChange
了攝氏的TemperatureInput
是Calculator
的handleCelsiusChange
方法,以及onTemperatureChange
所述華氏TemperatureInput
是Calculator
的handleFahrenheitChange
方法。所以Calculator
根據(jù)我們編輯的輸入來調(diào)用這兩個方法。
在這些方法中,Calculator
組件要求 React 通過調(diào)用this.setState()
新的輸入值和我們剛剛編輯的輸入的當(dāng)前比例重新呈現(xiàn)自己。
React 調(diào)用Calculator
組件的render
方法來了解 UI 的外觀。根據(jù)當(dāng)前溫度和活動比例重新計算兩個輸入的值。溫度轉(zhuǎn)換在這里執(zhí)行。
React 用它們指定的新道具調(diào)用render
各個TemperatureInput
組件的方法Calculator
。它了解他們的用戶界面應(yīng)該是什么樣子。
React DOM 更新 DOM 以匹配所需的輸入值。我們剛剛編輯的輸入接收其當(dāng)前值,另一個輸入更新為轉(zhuǎn)換后的溫度。
每次更新都經(jīng)歷相同的步驟,以使輸入保持同步。
對于在 React 應(yīng)用程序中更改的任何數(shù)據(jù),應(yīng)該有一個“真相源”。通常,首先將狀態(tài)添加到需要渲染的組件中。然后,如果其他組件也需要它,可以將它提升到最接近的共同祖先。與其試圖在不同組件之間同步狀態(tài),您應(yīng)該依賴自頂向下的數(shù)據(jù)流。
提升狀態(tài)涉及編寫比雙向綁定方法更多的“樣板”代碼,但作為一個好處,查找和隔離錯誤需要較少的工作。由于任何狀態(tài)“存在于”某個組件中,并且該組件本身可以改變它,所以錯誤的表面積大大降低。另外,您可以實現(xiàn)任何自定義邏輯來拒絕或轉(zhuǎn)換用戶輸入。
如果某件事可以從道具或狀態(tài)中推導(dǎo)出來,那么它可能不應(yīng)該處于這個狀態(tài)。例如,而不是存儲既celsiusValue
和fahrenheitValue
,我們只是存儲上次編輯temperature
和scale
。其他輸入的值可以始終由render()
方法中的值來計算。這讓我們可以清除或應(yīng)用舍入到其他字段,而不會丟失用戶輸入的任何精度。
當(dāng)您在 UI 中發(fā)現(xiàn)錯誤時,可以使用 React Developer Tools 檢查道具并向上移動樹,直到找到負(fù)責(zé)更新狀態(tài)的組件。這可以讓你追蹤錯誤來源。