9. Lifting State Up(狀態(tài)提升)

React版本:15.4.2
**翻譯:xiyoki **

通常幾個(gè)組件需要響應(yīng)相同的數(shù)據(jù)變化。我們建議將共享狀態(tài)提升到最接近的共同祖先。讓我們看看這是如何工作的。
在本節(jié)中,我們將創(chuàng)建一個(gè)溫度計(jì)算器,用于計(jì)算水是否在給定溫度下沸騰。
我們將從名為BoilingVerdict的組件開(kāi)始。它接受celsius溫度為props,并打印是否足以將水燒開(kāi):

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的組件。它將渲染一個(gè)<input>,讓你輸入溫度,并將該值保存在this.state.value中。
此外,它用當(dāng)前值渲染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>
    );
  }
}

Adding a Second Input(增加第二個(gè)輸入)

我們新的要求是:除了攝氏度輸入框,我們還提供華氏度輸入框,并且二者是同步的。
我們把從Calculator中提取一個(gè)TemperatureInput組件作為開(kāi)始。我們將為它增加一個(gè)新scale prop,并且這個(gè)prop可以是‘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>
    );
  }
}

現(xiàn)在,我們可以改變Calculator來(lái)渲染兩個(gè)獨(dú)立的溫度輸入:

class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

現(xiàn)在我們有兩個(gè)輸入框,但當(dāng)你向其中一個(gè)輸入框輸入溫度時(shí),另一個(gè)并不會(huì)更新。這違反了我們的要求:我們希望它們保持同步。
我們也不能從Calculator中展示BoilingVerdict。Calculator也不知道當(dāng)前的溫度,因?yàn)楫?dāng)前溫度被隱藏在了TemperatureInput內(nèi)部。

Lifting State Up(狀態(tài)提升)

首先,我們將寫(xiě)兩個(gè)函數(shù)來(lái)將攝氏度轉(zhuǎn)換為華氏度,將華氏度轉(zhuǎn)換為攝氏度:

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

這兩個(gè)函數(shù)轉(zhuǎn)換數(shù)字。我們將寫(xiě)另一個(gè)函數(shù),它接受一個(gè)字符串value和一個(gè)轉(zhuǎn)換器函數(shù)作為參數(shù),并返回一個(gè)字符串。我們將使用它來(lái)計(jì)算其中一個(gè)輸入框的值,而該輸入框的值基于另一個(gè)輸入框。
它返回一個(gè)無(wú)效的空字符串value,并且它將輸出四舍五入到三位小數(shù):

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(‘a(chǎn)bc’,toCelsius)返回一個(gè)空字符串, tryConvert(’10.22’,toFahrenheit)返回‘50.396’。
接下來(lái),我們將從TemperatureInput中刪除狀態(tài)。
相反,TemperatureInput組件將接受valueonChange處理程序作為prop:

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.onChange(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>
    );
  }
}

如果幾個(gè)組件需要訪問(wèn)相同的狀態(tài),這標(biāo)志著狀態(tài)應(yīng)該被提升到最接近的共同祖先。在這個(gè)例子中最接近的祖先就是Calculator。我們將在它的狀態(tài)中存儲(chǔ)當(dāng)前的valuescale。
我們可以存儲(chǔ)兩個(gè)輸入的值,但事實(shí)證明這是不必要的。它足以存儲(chǔ)最近被更改的輸入框的值,以及其表示的scale。然后我們能基于當(dāng)前的valuescale,單獨(dú)推斷其他輸入框的值。
輸入的值保存同步,因?yàn)樗鼈兊闹祻南嗤臓顟B(tài)計(jì)算而來(lái)。

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; {/* 對(duì)狀態(tài)value作進(jìn)一步處理*/}
    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>
    );
  }
}

不論你編輯哪個(gè)輸入框,Calculator中的this.state.valuethis.state.scale都會(huì)獲得更新。其中一個(gè)輸入框獲取的值為原樣,因此任何用戶的輸入都被保留,另一個(gè)輸入框中的值總是基于它重新計(jì)算。

Lessons Learned (得到的教訓(xùn))

對(duì)于在React應(yīng)用程序中更改的任何數(shù)據(jù),應(yīng)該有一個(gè)單一的‘真實(shí)來(lái)源’。通常,首先將狀態(tài)添加到需要渲染的組件。然后,如果其他組件也需要它,你可以將其提升到最接近的共同祖先。而不是嘗試在不同組件之間同步狀態(tài),你應(yīng)該依賴于自上而下的數(shù)據(jù)流。
提升狀態(tài)涉及編寫(xiě)比雙向綁定方法更多的‘樣板’代碼。但好處是找到和隔離bug需要較少的工作。由于任何狀態(tài)存在于特定的組件中,并且該組件可以單獨(dú)改變它,所以大大減少了錯(cuò)誤的表面積。此外,你可以實(shí)現(xiàn)任何自定義邏輯以拒絕或轉(zhuǎn)換用戶輸入。
如果數(shù)據(jù)可以從props或state派生,那么它就不應(yīng)該在狀態(tài)之中。例如,我們只存儲(chǔ)了最后編輯的valuescale,而不是存儲(chǔ)兩個(gè)celsiusValuefahrenheitValue。另一個(gè)輸入的值總是可以從render()方法中計(jì)算出來(lái)。這允許我們清除或應(yīng)用四舍五入到其他字段,而不會(huì)丟失用戶輸入的任何精度。
當(dāng)你在UI中看到錯(cuò)誤時(shí),可以使用 React Developer Tools 檢查props,并向上移動(dòng)樹(shù),直到找到負(fù)責(zé)更新?tīng)顟B(tài)的組件。這使你可以跟蹤錯(cuò)誤到其來(lái)源:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,746評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,991評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,706評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,036評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,203評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,451評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,677評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,857評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,266評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,606評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,407評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,643評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • 通常,幾個(gè)組件需要根據(jù)同一個(gè)數(shù)據(jù)變化做出響應(yīng)。我們建議將這個(gè)共享的狀態(tài)提升到他們最近的一個(gè)共用祖先。讓我們看看實(shí)際...
    莫銘閱讀 908評(píng)論 0 1
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,775評(píng)論 25 708
  • 通常,一些組件需要對(duì)同一個(gè)數(shù)據(jù)做出反應(yīng),這時(shí)我們建議將這些組件中關(guān)于這個(gè)數(shù)據(jù)的State提升至和它們距離最近的父級(jí)...
    編碼的哲哲閱讀 448評(píng)論 0 0
  • 感恩這幾天的好天氣,因?yàn)樘鞖夂?,我的心情也好很多!感恩周六周日孩子們的陪伴,以解相思之苦!雖說(shuō)很忙碌但...
    心靜感恩閱讀 176評(píng)論 0 0
  • 大霧漫天風(fēng)飛揚(yáng), 遮云蔽日在何方。 鴻雁傳書(shū)思斷腸, 魂?duì)繅?mèng)繞歸故鄉(xiāng)。
    紅嘴唇卍閱讀 285評(píng)論 0 0