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
相關文章
相關標籤/搜索