在使用Redux前你需要知道關於React的8件事

阿輝funkyLover發表於2018-03-28

譯文,原文來自https://www.robinwieruch.de/learn-react-before-using-redux/

譯者前注: 翻譯僅作為個人學習用途,因本人水平有限,譯文中充斥著不少拙劣文法和表述,最好還是看英文原文.

狀態管理是很複雜的.檢視層工具庫,如React,允許我們在元件內部管理狀態.但它只能擴充套件到具體某一個元件.React僅僅是一個檢視層庫.最終你決定(把狀態管理)遷移到一個更為成熟的解決方案,如Redux.接下來我想在這篇文章中指出在跳上Redux的列車前,你應該瞭解清楚的有關React的內容.

通常人們會同時學習React和Redux,但這有一些缺點:

  • 他們不會遇到在僅使用本地元件狀態(this.state)時,擴充套件狀態管理時出現的問題
    • 因此他們沒法理解為什麼需要Redux這一類狀態管理工具
    • 因此他們抱怨(使用Redux時)增加了太多的樣板程式碼
  • 他們不會去學習在React中怎麼進行本地元件的狀態管理
    • 因此他們會把在Redux提供的狀態容器(state container)中管理(以及塞入)全部狀態
    • 因此他們永遠不會使用本地元件狀態管理

因為上述原因,通常建議是先學習React,然後在稍後的時間選擇加入Redux.但如果遇到擴充套件狀態管理的問題,就選擇加入Redux吧.一般那些擴充套件問題僅會在較大型的應用程式中存在,通常情況下你不需要Redux這樣的狀態管理庫.學習React之路一書中演示瞭如何使用普通的React構建應用程式,而不需要用到Redux這樣的外部依賴.

不管怎麼樣,現在你已經決定擁抱Redux了,因此這裡我列出了在使用Redux之前,你應該瞭解的有關React的內容.

熟悉React本地狀態管理

上面已經提到了最好先學習React,因此你就不能避免使用this.setState()this.state來操作本地狀態來為你的元件注入生命.你應該要能遊刃有餘地使用它們.

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
  }

  render() {
    return (
      <div>
        Counter: {this.state.counter}

        <button
          type="button"
          onClick={() => this.setState({ counter: this.state.counter + 1 })}
        />
      </div>
    );
  }
}
複製程式碼

一個React元件可以在建構函式中定義初始狀態.之後就可以通過this.setState()方法來更新狀態.狀態物件(state object)的更新過程是一次淺合併.因此你可以只更新本地狀態中特定的某一部分狀態,而其餘的狀態都不會受到影響.一旦狀態更新完,元件就會重新渲染.在上面的例子中,應用會展示更新後的值:this.state.counter.基本上在React的單向資料流中只會存在一條閉環.

React's Functional Local State(譯者注: 這裡不知道該如何翻譯)

this.setState()方法是非同步更新本地狀態的.因此你不能依賴狀態更新的時機.狀態最終都會更新的.這在大部分情況下也是沒有什麼問題的.

儘管如此,想象一下你的元件需要通過當前狀態去計算下一狀態.就如同上面的例子那樣.

this.setState({ counter: this.state.counter + 1 });
複製程式碼

用於計算的本地狀態(this.state.counter)只是當前時間的一個快照而已.因此當你用this.setState()更新本地狀態時,而本地狀態又在非同步執行更新完成之前改變了,這時你就操作了一箇舊的狀態.第一次遇到類似問題的時候或許會有點難以理解.所以用程式碼來代替千言萬語的解釋吧:

this.setState({ counter: this.state.counter + 1 }); // this.state: { counter: 0 }
this.setState({ counter: this.state.counter + 1 }); // this.state: { counter: 0 }
this.setState({ counter: this.state.counter + 1 }); // this.state: { counter: 0 }

// 更新過後的state: { counter: 1 }
// 而不是: { counter: 3 }
複製程式碼

就如你看到的那樣,當根據本地狀態更新狀態時,本地狀態作為更新狀態.這會導致bug的.這也是為什麼會有第二種更新React本地狀態的方式.

this.setState()函式可以接受一個函式作為引數而非物件.而這個回撥函式的呼叫會傳入在當下this.setState()非同步執行後的本地狀態作為引數.這個回撥執行的時候就能獲取到當前最新的,可信賴的本地狀態.

this.setState(previousState => ({ counter: previousState.counter + 1 }));
複製程式碼

那麼當你需要根據之前的本地狀態來更新時,就可以使用傳入函式給this.setState()而非物件.

另外,這也適用於依賴props的更新.在非同步執行更新之前,從父元件獲取到的props也有可能被改變過.所以傳入this.setState()的回撥會被注入第二個引數props.

this.setState((prevState, props) => ...);
複製程式碼

這樣你就能保證更新狀態時所依賴的state和props是正確的.

this.setState((prevState, props) => ({ counter: prevState.counter + props.addition }));
複製程式碼

使用回撥函式時的另外一個好處是能單獨對狀態更新進行測試.簡單地把this.setState(fn)中的回撥函式提取出來並匯出(export)即可.這個回撥函式應該是一個純函式,你可以根據輸入進行簡單的輸出測試.

React的State和Props

State是元件內部維護狀態.可以作為其他元件的Props向下傳遞.那些接受Props的元件可以在內部使用Props,或者再進一步向下傳遞給它們的子元件.另外子元件接受的Props仲同樣可以是來自父元件的回撥函式.那些函式可以用於改變父元件State.基本上Props隨著元件樹往下傳遞,而State則由元件自己維護,此外通過往上層元件冒泡的函式可以改變元件中的State,而更新過後的State又以Props的形式往下傳遞.

元件可以管理很多State,這些State可以作為Props往下傳遞給子元件並且Props中可以傳遞函式給予子元件修改父元件的State.

但是,子元件並不知道Props中的函式的來源或功能.這些函式可以更新父元件的State,也可能是執行其他操作.同樣的道理,子元件也不知道它所接收的Props是來自父元件的Props,State或其他派生屬性,子元件只是單純的使用它們而已.

掌握並理解State和Props非常重要,元件樹中使用的所有屬性都可以被分為State和Props(以及根據State和Props計算得出的派生屬性).所有需要互動的部分都應作為State儲存,而其他的一切都可以作為Props在元件樹中傳遞.

在使用複雜的狀態管理工具庫之前,你應該已經試過在元件樹中往下傳遞Props.當你傳遞Props給一些根本不使用它們的元件,而又需要這些元件把Props繼續向下傳遞給最後一個使用它們的子元件時,你應該已經感覺到"需要一種更好的方式來做這件事".

提取React的State

你是否已經提取出你的本地狀態層?這是在React中擴充套件本地狀態管理最重要的策略.狀態層是可以上下提取的.

你可以向下提取的你的本地狀態,使其他元件沒法訪問.假設你有一個元件A是元件B和元件C的父元件,B和C是A的子元件,並且B,C為兄弟元件.元件A是唯一維護本地狀態(State)的元件,但是它會將State作為Props傳遞給子元件.另外也傳遞了必要的函式讓B和C能夠改變A中的State.

          +----------------+
          |                |
          |       A        |
          |                |
          |    Stateful    |
          |                |
          +--------+-------+
                   |
         +---------+-----------+
         |                     |
         |                     |
+--------+-------+    +--------+-------+
|                |    |                |
|                |    |                |
|       B        |    |        C       |
|                |    |                |
|                |    |                |
+----------------+    +----------------+
複製程式碼

現在元件A的State中有一半作為Props傳遞給C併為C所用,但B並不需要那些Props.另外,C使用其接收的Props中的函式來改變A中僅傳遞給了C的那部分State.如圖所示,元件A在幫助元件C維護著State.在大多數情況下,只需要一個元件管理其子元件所有的State即可.但是想象一下,如果元件A和元件C中間還有其他元件,而元件A依然是在維護著元件C所需的狀態,那由元件A往下傳遞的所有Props都需要遍歷元件樹才能最終到達元件C.

          +----------------+
          |                |
          |       A        |
          |                |
          |                |
          |    Stateful    |
          +--------+-------+
                   |
         +---------+-----------+
         |                     |
         |                     |
+--------+-------+    +--------+-------+
|                |    |                |
|                |    |        +       |
|       B        |    |        |Props  |
|                |    |        v       |
|                |    |                |
+----------------+    +--------+-------+
                               |
                      +--------+-------+
                      |                |
                      |        +       |
                      |        |Props  |
                      |        v       |
                      |                |
                      +--------+-------+
                               |
                      +--------+-------+
                      |                |
                      |                |
                      |        C       |
                      |                |
                      |                |
                      +----------------+
複製程式碼

這是展示提取React State的完美用例.當State僅僅用於元件C而元件A只是充當了維護的角色.這個時候對應的State片段就可以在在C中單獨管理,是可以被獨立出來的.將State狀態管理提取出來到元件C後,就不會出現傳遞Props需要遍歷整個元件樹的情況了.

          +----------------+
          |                |
          |       A        |
          |                |
          |                |
          |    Stateful    |
          +--------+-------+
                   |
         +---------+-----------+
         |                     |
         |                     |
+--------+-------+    +--------+-------+
|                |    |                |
|                |    |                |
|       B        |    |                |
|                |    |                |
|                |    |                |
+----------------+    +--------+-------+
                               |
                      +--------+-------+
                      |                |
                      |                |
                      |                |
                      |                |
                      |                |
                      +--------+-------+
                               |
                      +--------+-------+
                      |                |
                      |                |
                      |        C       |
                      |                |
                      |     Stateful   |
                      +----------------+
複製程式碼

此外,元件A中的State也應有所改變,它只管理自己以及其最接近的子元件的必要State.

提取State不僅可以往下,也可以反過來往上提取:提升狀態.假設你有父元件A,元件B和C都為其子元件.A和B以及A和C之間又多少個元件並不重要,但是這次元件C已經管理了它所需的所有State.

          +----------------+
          |                |
          |       A        |
          |                |
          |                |
          |    Stateful    |
          +--------+-------+
                   |
         +---------+-----------+
         |                     |
         |                     |
+--------+-------+    +--------+-------+
|                |    |                |
|                |    |                |
|       B        |    |                |
|                |    |                |
|                |    |                |
+----------------+    +--------+-------+
                               |
                      +--------+-------+
                      |                |
                      |                |
                      |        C       |
                      |                |
                      |    Stateful    |
                      +----------------+
複製程式碼

如果元件B需要元件C中所管理的State呢?這個時候元件C中的State不能共享給元件B,因為State只能作為Props向下傳遞.這就是為什麼你需要提升State.你可以把元件C中的State網上提取,直到B和C的共同父元件(A),如果元件B需要用到元件C中管理的所有狀態,則元件C甚至應該變成無狀態元件.而所有的State可以在A中管理,但在B和C之間共享.

          +----------------+
          |                |
          |       A        |
          |                |
          |                |
          |    Stateful    |
          +--------+-------+
                   |
         +---------+-----------+
         |                     |
         |                     |
+--------+-------+    +--------+-------+
|                |    |                |
|                |    |        +       |
|       B        |    |        |Props  |
|                |    |        v       |
|                |    |                |
+----------------+    +--------+-------+
                               |
                      +--------+-------+
                      |                |
                      |                |
                      |        C       |
                      |                |
                      |                |
                      +----------------+
複製程式碼

提取State讓你能夠僅僅通過React就能擴充套件你的狀態管理.當更多的元件需要用到特定的State時,可以往上提取State,直到需要訪問該State的元件的公共元件.此外,本地狀態管理依然保持著可維護性,因為一個元件根據自身需求管理儘可能多的狀態,換言之如果元件或其子元件不需要該State的話,則可以往下提取State放置在需要的地方.

你可以在官方文件中閱讀更多關於關於提取State的資訊.

React高階元件(HOC)

高階元件是React中一種高階設計模式.你可以使用它來抽象功能,並將其作為其他多個元件的可選功能重用.高階元件接受一個元件和其他可選配置作為引數並返回一個增強版本的元件.它建立在Javascript的高階函式(返回函式的函式)的原則之上.

如果你並不十分了解高階元件的概念,我推薦你閱讀一下高階元件的簡單介紹.裡面通過React的條件渲染用例來講解高階元件的概念.

高階元件概念在後面會顯得尤為重要,因為在使用像Redux這樣的庫的時候,你將會遇到很多高階元件.當需要使用Redux這一類庫將狀態管理層和React的檢視層"連線"起來時.你通常會使用一個高階元件來處理這層關係(如react-redux中的connect高階元件).

這也同樣適用於其他狀態管理庫,如MobX.在這些庫中使用了高階元件來處理狀態管理層和檢視層的連線.

React上下文(Context)

React的Context上下文很少被使用,我不會建議去使用它,因為Context API並不穩定,而且使用它還UI增加應用程式的複雜性.不過儘管如此,還是很有必要理解它的功能的.

所以為什麼你應該要了解Context呢?Content用於在元件樹上隱式地傳遞屬性.你可以在父元件的某個地方宣告屬性,並在元件樹下的某個子元件中選擇再次獲取該屬性.然而如果通過Props傳遞的話,所有不需要使用那些資料的元件都需要顯式地往下傳遞.這些元件位於父元件的上下文和最終消費該Props的子元件的上下文之間.所以Context是一個無形的容器.你可以在元件樹中找到它.所以,為什麼你應該要了解Context呢?

通常在使用複雜的狀態管理工具庫時,例如Redux和MobX,你需要將狀態管理層粘合到React的檢視層上.這也是為什麼你需要了解React高階元件的原因.這其中的粘合層允許你訪問State並進行修改,而State本身通常是在某種狀態容器中進行管理的.

但是如何使這個狀態容器能夠被所有粘合上React元件所訪問呢?這是由React Context來完成的.在最頂層的元件,一般是React應用的根元件,你應在React Context中宣告狀態容器,以便在元件樹下的每個元件都能進行隱式訪問.整個過程都是通過React的提供者模式(Provider Pattern)完成的.

當然這也並不意味著在使用Redux一類的庫時你需要自己處理React Context上下文.這類工具庫已經為你提供瞭解決方案,使所有元件都可以訪問狀態容器.當你的狀態可以在不同的元件中訪問而不必擔心狀態容器來自哪裡的時後,這個底層實現的機制是什麼,為什麼這樣做的有效的,這都是很有必要去了解的事實.

React Stateful元件(帶狀態的元件)

React中有兩種宣告元件的方式: ES6類元件和函式(不帶狀態)元件.一個不帶狀態的函式元件僅僅是一個接收Props並返回JSX的函式.其中不保持任何的State也不會觸發任何React生命週期函式.顧名思義就是無狀態的.

function Counter({ counter }) {
  return (
    <div>
      {counter}
    </div>
  );
}
複製程式碼

另一方面,即React類元件是可以保持State和能出發宣告周期函式的.這些元件能訪問this.state和呼叫this.setState()方法.這就說明了ES類元件是能帶狀態的元件.而如果他們不需要保持本地State的話,也可以是無狀態元件.通常無狀態的類元件也會需要使用宣告周期函式.

class FocusedInputField extends React.Component {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
    this.input.focus();
  }

  render() {
    return (
      <input
        type="text"
        value={this.props.value}
        ref={node => this.input = node}
        onChange={event => this.props.onChange(event.target.value)}
      />
    );
  }
}
複製程式碼

結論是隻有ES6類元件是可以帶狀態的,但是他們也可以是無狀態的.而函式元件則是無狀態的.

此外,還可以使用高階元件來新增狀態到React元件上.你可以編寫自己的高階元件來管理狀態,或者使用像recompose工具庫中的withState高階元件.

import { withState } from `recompose`;

const enhance = withState('counter', 'setCounter', 0);

const Counter = enhance(({ counter, setCounter }) =>
  <div>
    Count: {counter}
    <button onClick={() => setCounter(n => n + 1)}>Increment</button>
    <button onClick={() => setCounter(n => n - 1)}>Decrement</button>
  </div>
);
複製程式碼

當使用高階元件時,你可以選擇傳遞任意區域性狀態到React元件中去.

容器與展示模式(容器元件與展示元件)

因為Dan Abramov的博文,容器和演示模式變得流行了起來.如果你對此並不十分了解,現在正是深入學習的時機.基本上它會將元件分為兩類:容器元件和展示元件.容器元件負責描述元件是如何工作的,展示元件負責元件內容的展示.容器元件一般是一個類元件,因為容器元件是需要管理本地狀態的.而展示元件是一個無狀態函式元件,因為一般只用於展示Props和呼叫從父元件傳遞過來的函式.

在深入Redux之前,理解這種模式背後的原理是很有意義的.當你使用狀態管理的工具庫時,你會把元件和State連線起來.那些元件並不在意應該怎麼去展示內容,而更多是描述如何起效的.因此那些元件就是容器元件.再具體一點,你應該會經常聽到連線元件(connected component)當一個元件和狀態管理層連線起來之後.

MobX還是Redux?

縱觀所有狀態管理庫,Redux是最流行的一個,但是MobX也是一個很有價值的替代品.這兩個庫都遵循不同的哲學和程式設計正規化.

在你決定使用它們之前,請確保你清楚瞭解本文中解釋的有關React的內容.你應該對能坦然面對在僅使用React的情況下進行本地狀態管理,還能瞭解各種React的概念並擴充套件你的狀態管理.此外,確保在你的應用在未來會變得更加龐大時,才需要去擴充套件狀態管理的解決方案.也許提取State或使用React Context應用提供者模式(provider pattern)就可以解決你的問題了.

因此,如果決定邁上Redux和MobX的道路,可以閱讀下面的文章以做出更好的決定:Redux or MobX: An attempt to dissolve the Confusion.文章中有效的對比了兩個庫的差異,並提供了一些學習和應用他們的建議.或者看下Tips to learn React + Redux來了解Redux吧.

後記

希望這篇文章為你理清了再應用像Redux一類的庫之前,你應該學習和了解的內容.目前,我正在寫一個關於Redux和本地狀態管理的書,內容包括Redux和MobX.如果不想錯過的話,你可以點這進行訂閱.

譯者後注: 希望我拙劣的翻譯沒有為你理解本文增加難度,再說一次最好還是看英文原文,如有改進建議請大方聯絡,我必虛心受教.

相關文章