[譯] 深入理解 Props 和 State

SangKa發表於2018-09-18

原文連結: learnreact.design/2018/01/15/…

喜歡理由: 文筆生動 通俗易懂

特別鳴謝: 原作者 Linton Ye 的傾情校對

系列部落格: 用通俗的語言和塗鴉來解釋 React 術語

上篇文章中,我們介紹了元件、props 和 state 。

props 和 state 的區別相當明顯,確定何時使用 props 和 state 似乎也很簡單。舉個例子,屋頂的顏色自然就是 prop ,因為顏色是屋頂的固有屬性。另一方面,門的開關狀態很顯然是 state ,因為門在建立後還可以開啟或關上。然而在本文中,我們將來挑戰這一思維方式!

沒開玩笑?!?沒錯,你所看到的東西既可以是 prop,又可以是 state 。並沒有絕對的界限。我將介紹一種更有用、偏實戰的方式來思考 prop 和 state 。

學習目標

當你讀完本文後希望你能重新回到這裡,並能夠輕鬆回答以下問題:

  • props 和 state 的主要用途是什麼?
  • “state 提升”的含義是什麼?在什麼場景下需要提升 state ?

新成員

你注意到房子周圍的新成員了嗎?試試點選房門!

檢視由 focuser (@focuser) 在 CodePen 編寫的 Demo : 有貓的 React 小屋

這是一隻嗜睡的貓,門一關她就睡,只有當門再開啟的時候才會起來。如果把門關上,她立即又睡過去了。

實現貓

現在我來問你,如果實現貓的行為?先來試試吧!

先從下面的“程式碼”入手,花點時間先讀一遍。(再次重申,這並非真正的 JavaScript 程式碼,它只是以一種簡化的形式來幫助你理解概念,同時不會被 JS 中的細節所干擾。)

House:
  <div>
    <Roof />
    <Wall />
    <Window />
    <Door />
    <Cat />
  </div>

Door:
  State: status <!-- "open""closed" -->
  <div>{state.status} door</div>
  當點選門時
    如果 door.state.status 為 "open"
      將 door.state.status 修改成 "closed"
    否則
      將 door.state.status 修改成 "open"
複製程式碼

House 元件中又新增了 Cat 標籤。那麼 Cat 元件又是怎麼樣的呢?我們來定義它。

貓要麼睡、要麼醒。這似乎跟門的開關狀態很類似。或許我們同樣可以使用 state 來表示貓的狀態:

Cat:
  State: status <!-- "sleeping""awake" -->
  <div>{state.status} cat</div>
複製程式碼

Cat 元件定義好後,還需要實現的就只剩下將貓和門的狀態進行同步。門的狀態為 “open” 時,我們想要貓的狀態為 “awake”,反之為 “sleeping” 。

就這麼簡單?看看再說吧…

第一次嘗試

既然我們已經有了根據當前狀態切換門狀態的程式碼,莫不如我們就在此處切換貓的狀態:

Door:
  State: status <!-- "open""closed" -->
  <div>{state.status} door</div>
  當點選門時
    如果 state.status 為 "open"
      將 state.status 修改成 "closed"
      將 cat.state.status 修改成 "sleeping"     <!-- 錯誤的 -->
    否則
      將 state.status 修改成 "open"
      將 cat.state.status 修改成 "awake"        <!-- 錯誤的 -->
複製程式碼

不幸的是,這不起作用!還記得元件的 state 是私有資料嗎?只有在元件的內部才能訪問。其他元件,無論是父元件還是兄弟元件,都無法訪問本元件的 state 。

很遺憾,我們在 Door 元件內嘗試修改貓的狀態以失敗告終。(轉換成真正的 JavaScript 程式碼也不例外)

第二次嘗試

那麼在 Cat 元件內來修改貓的狀態如何?這次應該可以的,是吧?

Cat:
  State: status <!-- "sleeping""awake" -->
  <div>{state.status} cat</div>
  當點選門時                                    <!-- 黑人門號臉???怎麼個點選法? -->
    如果 door.state.status 為 "open"            <!-- 錯誤的 -->
      將 cat.state.status 修改成 "sleeping"
    否則
      將 cat.state.status 修改成 "awake"
複製程式碼

毫無疑問,在 Cat 元件內修改貓的狀態是沒問題的。但我們需要讀取門的狀態來決定貓的狀態是什麼。門的狀態是 Door 元件的 state ,因此無法在 Cat 元件裡訪問!

解決辦法

呃!太蹩腳了。要保持門和貓的狀態同步,我們必須要在某處能同時訪問兩者。但看上去資料是通過設計而對外隱藏的。如果來解決此難題呢?

解決辦法就是需要我們靈活地理解 state 和 props 的用法。

提升門的 state

House 元件:

House:
  <div>
    ...
    <Door />
    <Cat />
  </div>
複製程式碼

DoorCat 是並排放置的。或許這就是可以輕鬆同步它們的地方?

但是,我們現在是在 House 元件內。與之前的嘗試同理,在這裡是沒辦法讀取 Door 的 state 或者改變 Cat 的 state 。

但如果我們使用 props 來替代 state 呢?

House:
  <div>
    ...
    <Door status="open" />
    <Cat status="awake" />
  </div>
複製程式碼

當門關上時:

House:
  <div>
    ...
    <Door status="closed" />
    <Cat status="sleeping" />
  </div>
複製程式碼

當然,門的狀態不會是固定的值,它會隨時間而改變。我們用 doorStatus 來表示門的狀態。

House:
  <div>
    ...
    <Door status={doorStatus} />
    <Cat status={如果 doorStatus 為 'open' 值為 'awake' 否則為 'sleeping'} />
  </div>
複製程式碼

這不就解決同步的問題了嘛。順便問下,這個會變化的值 doorStatus 是什麼?在元件中什麼是可以改變的?沒錯,正是 state 。

House:
  State: doorStatus <!-- 'open''closed' -->
  <div>
    ...
    <Door status={state.doorStatus} />
    <Cat status={如果 state.doorStatus 為 'open' 值為 'awake' 否則為 'sleeping'} />
  </div>
複製程式碼

太棒了!House 元件現在定義的很好,門和貓的狀態也能完美同步。

我們還需要修改 DoorCat 元件,使用 props 來代替 state :

Door:
  <div>{props.status} door</div>
Cat:
  <div>{props.status} cat</div> 
複製程式碼

正如你所見,因為我們想要使用來父元件的 state,在這種情況下,為了設定貓的狀態,門的狀態其實是來自於 House 的,我們可以將相同的資料表示為父元件的 state,並將資料作為 props 傳遞給子元件。通常,這被稱之為 state 提升。我們將 state 移至元件的更高層級處。

[譯] 深入理解 Props 和 State

更改房子的 state

現在門和貓的狀態通過房子的 state 進行連線。如果想開門或喚醒貓的話,我們需要更改 House 元件的 state 。

問題來了,哪裡是唯一可以更新 House 的 state 的地方?就在 House 元件內,沒錯吧?

但是,我們想要在 Door 裡來觸發這次更改。也就是說,我們想要的效果是隻有當點選門時才開門,而不是點選整個房子或窗戶等。

所以 Door 元件需要做些改動:

Door:
  <div>{props.status} door</div>
  當點選門時 
    做某件事來修改 `House` 的 state
複製程式碼

但等等,之前不是說不能在 Door 元件內修改 House 的 state 嗎?

沒錯。我們沒辦法直接修改 House 的 state 。但並不等於說不能間接地修改。看下面…

House 元件內,我們來寫程式碼以實際修改它的 state :

House:
  State: doorStatus <!-- 'open''closed' -->
  toggleDoorStatus:
    如果 state.doorStatus 為 'open'
      將 state.doorStatus 修改成 'closed'
    否則
      將 state.doorStatus 修改成 'open'
  ...
複製程式碼

此刻,我們還未指定何時執行這段程式碼。我們只是給了它一個名字 (toggleDoorStatus),以便稍後通過名稱來找到它執行。

然後將 toggleDoorStatus 作為 prop 傳遞給 Door 元件:

House:
  ...
  <div>
    ...
    <Door ... onClickAction={toggleDoorStatus} />
    ...
  </div>
複製程式碼

Door 元件中,我們只需執行這個點選操作即可:

Door:
  <div>{props.status} door</div>
  當點選門時 
    執行 props.onClickAction <!-- 實際執行的是名為 "toggleDoorStatus" 的程式碼-->
複製程式碼

這就像把電視遙控器傳遞給其他人一樣。某人在 Door 元件內按下了遙控器按鍵。House 元件裡的電視機就會換臺或加大音量。

將會發生什麼取決於傳給 Door 的遙控器是什麼。它可能控制的是房間裡的電視、空調或高保真音響系統。在 Door 元件內,某人需要做的只是按下遙控器的按鍵。

[譯] 深入理解 Props 和 State

這就是我們所需要的!下面是完整“程式碼”:

House:
  State: doorStatus <!-- 'open' or 'closed' -->
  toggleDoorStatus:
    如果 state.doorStatus 為 'open'
      將 state.doorStatus 修改成 'closed'
    否則
      將 state.doorStatus 修改成 'open'
  <div>
    ...
    <Door status={state.doorStatus} onClickAction={toggleDoorStatus} />
    <Cat status={如果 state.doorStatus 為 'open' 值為 'awake' 否則為 'sleeping'} />
  </div>
Door:
  <div>{props.status} door</div>
  當點選門時
    執行 props.onClickAction
Cat:
  <div>{props.status} cat</div>  
複製程式碼

再次審視 Props 和 State

現在讓我們重溫幾個問題,props 和 state 的區別是什麼?何時應該使用 state ?何時應該使用 props ?

何時使用 state ? 何時使用 props ?

如果你還記得的話,我曾說過 props 是元件的固有屬性,它是不會改變的,而 state 是元件建立後才有的,它是可以改變的。當最初學習這兩個概念時,它是有幫助的。

但是,我們剛剛建立的示例讓這一觀點變得令人困惑。無論是門的開的,還是貓是睡著的,這理所應當應該是 state ,但我們卻使用 props 來表示它們。這是為什麼?

事實證明,在 state 和 props 的選擇問題上,還是有很大的靈活性的。這取決於你看它的視角,你可以採用不同的方式來為元件建模。例如,當門敞開之時,你可以說它是門的狀態,你可以說它是房子的狀態。

一種更有用的理解方式

感到困惑了?這是一種更有用的思考問題的方式:

  • State: 如果 UI 需要更改就表示某處肯定會有 state
  • Props: 用來傳遞資料、傳遞控制

當應用執行時,如果 UI 需要變化,那它一定是 state 。當點選門時,門或開或關,那麼它一定是某處的 state 。

但是,state 並不一定是更新元件的 state 。它可能位於某個上游元件中。這完全都取決於我們需要在何處以及如何使用這些資訊。舉個例子,我們決定將門的狀態從 Door 元件提升到 House 元件中,是因為我們需要在 House 元件中使用它。

另一方面,props 只是用來向下傳遞資料的東西。就像我們之前將門的狀態從 House 元件中向下傳遞給 Door 元件。

props 還可以用來向下傳遞控制。例如,我們將事件處理方法從 House 傳給 Door

本示例中的 props 是否會改變值?

並沒有,props 永遠不會改變值。我懂你的意思,門開開關關,貓睡了又醒。因為我們現在使用 props 來表示它們,很容易讓人認為 props 的行為就好比 state,它們的值改變了,是這樣嗎?

這只是一個錯覺,我發現它與翻頁書的動畫相當相似。

[譯] 深入理解 Props 和 State

每次房子的 state 發生改變,舊的那隻貓就會消失,然後一隻嶄新狀態的貓將重新建立。但是這一過程發生的非常之快,這就造成了我們的視覺殘留,認為只有一隻貓在那睡了又醒。

翻書中任何頁面上的草圖都不會移動。類似的,每隻貓在其(短暫的)生命中始終保持清醒/沉睡。

總結

好了,我們通過一個更復雜的示例再次學習了 props 和 state 。在這個示例中,當點選門時,門需要切換開關狀態,同時我們還需要將門和貓的狀態保持同步,

因為 state 是私有的,所以我們需要將門的狀態從 Door 元件提升到 House 元件中。這樣我們便可以在 House 元件中使用此資料來設定門和貓的狀態。我們將此資料作為 props 傳給 DoorCat,以便它們根據門的狀態來顯示正確的圖片。

另一個需求是點選門時觸發狀態的變更。因為現在門的狀態是 House 的 state,它是私有資料,只能在 Door 元件中間接來更改它。我們在 House 元件中編寫了實際修改 state 的程式碼,然後將其作為 props 傳遞給 Door 。這類似於把電視的遙控器傳給別人。

本文中的示例或許會讓你感到一絲困惑。下面是一種用來思考 prop 和 state 的更實用的方式:

  • State: 如果 UI 需要更改就表示某處肯定會有 state
  • Props: 用來傳遞資料、傳遞控制

到目前為止感覺如何?如果你有任何問題或意見,請給我留言!

相關文章