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