原文連接: learnreact.design/2018/01/15/…react
喜歡理由: 文筆生動 通俗易懂react-native
特別鳴謝: 原做者 Linton Ye 的傾情校對ionic
系列博客: 用通俗的語言和塗鴉來解釋 React 術語post
在上篇文章中,咱們介紹了組件、props 和 state 。學習
props 和 state 的區別至關明顯,肯定什麼時候使用 props 和 state 彷佛也很簡單。舉個例子,屋頂的顏色天然就是 prop ,由於顏色是屋頂的固有屬性。另外一方面,門的開關狀態很顯然是 state ,由於門在建立後還能夠打開或關上。然而在本文中,咱們未來挑戰這一思惟方式!動畫
沒開玩笑?!?沒錯,你所看到的東西既能夠是 prop,又能夠是 state 。並無絕對的界限。我將介紹一種更有用、偏實戰的方式來思考 prop 和 state 。spa
當你讀完本文後但願你能從新回到這裏,並可以輕鬆回答如下問題:翻譯
你注意到房子周圍的新成員了嗎?試試點擊房門!設計
查看由 focuser (@focuser) 在 CodePen 編寫的 Demo : 有貓的 React 小屋。code
這是一隻嗜睡的貓,門一關她就睡,只有當門再開啓的時候纔會起來。若是把門關上,她當即又睡過去了。
如今我來問你,若是實現貓的行爲?先來試試吧!
先從下面的「代碼」入手,花點時間先讀一遍。(再次重申,這並不是真正的 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 的用法。
House
組件:
House:
<div> ... <Door /> <Cat /> </div>
複製代碼
Door
和 Cat
是並排放置的。或許這就是能夠輕鬆同步它們的地方?
可是,咱們如今是在 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
組件如今定義的很好,門和貓的狀態也能完美同步。
咱們還須要修改 Door
和 Cat
組件,使用 props 來代替 state :
Door:
<div>{props.status} door</div>
Cat:
<div>{props.status} cat</div>
複製代碼
正如你所見,由於咱們想要使用來父組件的 state,在這種狀況下,爲了設置貓的狀態,門的狀態實際上是來自於 House
的,咱們能夠將相同的數據表示爲父組件的 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
組件內,某人須要作的只是按下遙控器的按鍵。
這就是咱們所須要的!下面是完整「代碼」:
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 的區別是什麼?什麼時候應該使用 state ?什麼時候應該使用 props ?
若是你還記得的話,我曾說過 props 是組件的固有屬性,它是不會改變的,而 state 是組件建立後纔有的,它是能夠改變的。當最初學習這兩個概念時,它是有幫助的。
可是,咱們剛剛建立的示例讓這一觀點變得使人困惑。不管是門的開的,仍是貓是睡着的,這理所應當應該是 state ,但咱們卻使用 props 來表示它們。這是爲何?
事實證實,在 state 和 props 的選擇問題上,仍是有很大的靈活性的。這取決於你看它的視角,你能夠採用不一樣的方式來爲組件建模。例如,當門敞開之時,你能夠說它是門的狀態,你能夠說它是房子的狀態。
感到困惑了?這是一種更有用的思考問題的方式:
當應用運行時,若是 UI 須要變化,那它必定是 state 。當點擊門時,門或開或關,那麼它必定是某處的 state 。
可是,state 並不必定是更新組件的 state 。它可能位於某個上游組件中。這徹底都取決於咱們須要在何處以及如何使用這些信息。舉個例子,咱們決定將門的狀態從 Door
組件提高到 House
組件中,是由於咱們須要在 House
組件中使用它。
另外一方面,props 只是用來向下傳遞數據的東西。就像咱們以前將門的狀態從 House
組件中向下傳遞給 Door
組件。
props 還能夠用來向下傳遞控制。例如,咱們將事件處理方法從 House
傳給 Door
。
並無,props 永遠不會改變值。我懂你的意思,門開開關關,貓睡了又醒。由於咱們如今使用 props 來表示它們,很容易讓人認爲 props 的行爲就比如 state,它們的值改變了,是這樣嗎?
這只是一個錯覺,我發現它與翻頁書的動畫至關類似。
每次房子的 state 發生改變,舊的那隻貓就會消失,而後一隻嶄新狀態的貓將從新建立。可是這一過程發生的很是之快,這就形成了咱們的視覺殘留,認爲只有一隻貓在那睡了又醒。
翻書中任何頁面上的草圖都不會移動。相似的,每隻貓在其(短暫的)生命中始終保持清醒/沉睡。
好了,咱們經過一個更復雜的示例再次學習了 props 和 state 。在這個示例中,當點擊門時,門須要切換開關狀態,同時咱們還須要將門和貓的狀態保持同步,
由於 state 是私有的,因此咱們須要將門的狀態從 Door
組件提高到 House
組件中。這樣咱們即可以在 House
組件中使用此數據來設置門和貓的狀態。咱們將此數據做爲 props 傳給 Door
和 Cat
,以便它們根據門的狀態來顯示正確的圖片。
另外一個需求是點擊門時觸發狀態的變動。由於如今門的狀態是 House
的 state,它是私有數據,只能在 Door
組件中間接來更改它。咱們在 House
組件中編寫了實際修改 state 的代碼,而後將其做爲 props 傳遞給 Door
。這相似於把電視的遙控器傳給別人。
本文中的示例或許會讓你感到一絲困惑。下面是一種用來思考 prop 和 state 的更實用的方式:
到目前爲止感受如何?若是你有任何問題或意見,請給我留言!