做者:Obed Parlapiano翻譯:瘋狂的技術宅javascript
原文:https://obedparla.com/code/a-...前端
未經容許嚴禁轉載java
我瞭解到,掌握了某種語言、框架或工具的人與沒有掌握的人之間的最大區別在於他們所使用的思惟模型(Mental Model)。前者擁有清晰而先進的思惟模型,然後者則沒有。react
經過良好的思惟模型,你能夠直觀地理解複雜的問題和解決方案,這比逐步的去尋求解決方案要快得多。程序員
我天天都用 React 工做,而且一直都在尋找解決難題的解決方案。我能夠經過在圍繞 React 建立的良好思惟模型來作到這一點。在本文中,我將解釋那些有助於解決問題和解決複雜性的思惟模型。面試
不管你是已經使用 React 多年的老鳥仍是剛開始使用的新手,在我看來,有用的思惟模型是使本身有信心使用它的最快方法。
思惟模型是咱們如何想象一個系統正常工做的方法。咱們經過了解系統的不一樣部分並把他們鏈接起來,這一點很重要,由於它能夠幫助咱們理解世界並解決問題。segmentfault
思惟模型的一個很好例子就是互聯網:互聯網是一個複雜的系統,有許多相互鏈接的部分,可是請考慮一下你是怎樣想象它工做方式的。個人想像是經過許多大型服務器相互鏈接的大量計算機,其中有許多中間設備對每條信息的存儲位置進行重定向。api
固然這並非一個完整的思惟模型,但足夠好,我能夠用它來解決問題,並在須要時加以改進,這就是重點:思惟模型旨在幫助咱們解決問題和理解世界。服務器
當我在 2014 年開始搭建網站時,很難理解它的工做原理。用 WordPress 構建個人博客很容易,可是我對託管、服務器、DNS、證書等等一無所知。微信
當我開始閱讀文章並嘗試一些東西(並屢次破壞個人服務器配置)時,就開始掌握這種系統來了解它的工做方式,直到最終它被創建。個人頭腦圍繞該系統創建了一個思惟模型,能夠用來與之合做。
若是有人解釋了它,並將他們的思惟模型轉移給我,我就會更快地瞭解它。在這裏我將會解釋(並展現)本身在 React 中使用的思惟模型。它將幫助你更好地理解 React,並使你成爲更好的開發人員。
React 幫助咱們比以往更輕鬆地構建複雜的交互式 UI。它還鼓勵咱們以某種方式編寫代碼,指導咱們建立更易於瀏覽和理解的應用。
React 自己是一個以簡單思想爲核心的思惟模型:對依賴類似邏輯和 UI 的程序部分進行封裝,React 將會始終確保該部分保持最新。
不管你是剛剛開始使用 React 仍是已經用了多年,擁有清晰的思惟模式可以讓你更有信心去使用它。因此我要把本身的思惟模式轉移給你,並從第一原理開始並在其基礎上進行構建。
首先爲 JavaScript 和 React 的基本構建模塊建模:函數。
這被 React 所使用的標記語言 JSX 隱藏。剝離掉 JSX 的 React 是一堆互相調用的函數。 JSX 自己就是一種實用的思惟模型,使 React 用起來更簡單、更直觀。
讓咱們分別看一下全部的部分。
React 與 JSX(JavaScript XML)一塊兒使用,JSX 是一種徹底利用 JavaScript 的功能來編寫相似 HTML 代碼的方法。 JSX 爲以直觀的方式使用嵌套函數提供了一個出色的應用思惟模型。
讓咱們忽略類組件,而將注意力集中在更常見的功能組件上。 功能組件是一個行爲與其餘 JavaScript 函數徹底相同的函數。 React 組件始終返回 JSX,而後執行並將其轉換爲 HTML。
如下是是簡單的 JSX :
const Li = props => <li {...props}>{props.children}</li>; export const RickRoll = () => ( <div> <div className='wrapper'> <ul> <Li color={'red'}>Never give you up</Li> </ul> </div> </div> );
用 Babel 編譯成純 JavaScript:
const Li = props => React.createElement('li', props, props.children); export const RickRoll = () => React.createElement( 'div', null, React.createElement( 'div', { className: 'wrapper', }, React.createElement( 'ul', null, React.createElement( Li, { color: 'red', }, 'Never give you up', ), ), ), );
若是你發現本身很難駕馭這些代碼,那麼你並不孤單,你將會了解爲何 React 團隊決定改用 JSX。
如今,請注意每一個組件做爲函數是如何調用另外一個函數的,每一個新組件是 React.createElement
函數的第三個參數。每當你編寫組件時,請記住它是正常的 JavaScript 函數,這頗有用。
React 的一個重要特徵是組件能夠有多個子組件,但只有一個父組件。我發現這很使人困惑,直到我意識到 HTML也有相同的邏輯,每一個元素必須位於其餘元素內而且能夠有不少子元素。你能夠在上面的代碼中注意到這一點,其中只有一個父級 div
包含全部子級。
prop
與函數的參數相同在使用函數時,咱們能夠用參數與該函數共享信息。對於 React 組件,咱們將這些參數稱爲 prop
(有趣的故事:我很長時間以來都沒有意識到 prop
是 properties 的縮寫)。
在本質上,prop 的行爲與函數參數徹底「同樣」,不一樣之處在於咱們經過 JSX 的更好接口與它們進行交互,而 React 爲 prop
(如 children
)提供了額外的功能。
利用這些知識,咱們能夠創建一個思惟模型來直觀地理解函數!
當我想到一個函數時,會把它想象成一個盒子,當它被調用時,這個盒子會作一些事情。它能夠返回值,也能夠不返回:
function sum(a, b) { return a + b; } console.log(sum(10, 20)); // 30 function logSum(a, b) { console.log(a + b); // 30 }
因爲組件是一種奇特的函數,因此咱們也把組件變成一個盒子,以 props
做爲原料,盒子須要建立輸出。
在執行組件時,它將會運行其具備的任何邏輯(若是有的話),並評估其 JSX。其中的任何標籤都將會變爲 HTML,並將執行全部組件,而且重複該過程,直到到達子鏈中的最後一個組件。
因爲一個組件能夠有多個子組件,但只有一個父組件,因此我把多個組件想象成一組盒子,一個盒子裝在另外一個盒子裏。每一個盒子都必須包含在一個更大的盒子中,而且裏面能夠有多個較小的盒子。
可是若是不瞭解一個組件如何與其餘組件交互,用來表示組件的盒子這一思惟模型是不完整的。
閉包是 JavaScript 中的核心概念。它們啓用了該語言的複雜功能,對於可以幫助理解 React 的良好思惟模型而言,理解閉包很是重要。
這也是初學者最苦惱的功能之一,因此在不解釋技術細節的前提下,我將向你們展現我對閉包的思惟模式。
閉包的基本描述是它是一個函數。我想像它是一個盒子,它能夠防止裏面的東西溢出,同時又容許它外面的東西進入,就像一個半透水的盒子。可是溢出到哪裏呢?
儘管閉包自己是一個框,可是任何閉包都將位於較大的框內,而最外面的框是 Window 對象。
閉包是 JavaScript 函數的特性。若是你使用了函數,則用的就是閉包。
正如我所提到的,函數是一個框,也使閉包成爲一個框。考慮到每一個函數能夠在其中包含許多其餘函數,所以閉包是函數使用其外部信息的能力,同時保持其內部的信息不會「泄漏」或由外部函數使用。
用個人思惟模型來說:我想象函數是做爲盒子中的盒子,每一個較小的盒子均可以看到外部盒子或父級盒子的信息,可是大盒子卻看不到較小盒子的信息。這就是我所能作的關於閉包的簡單而準確的解釋。
閉包很重要,由於能夠利用它們來建立一些強大的機制,而 React 則充分利用了這一點。
每一個 React 組件也是一個閉包。在組件內,你只能將 prop 從父對象傳遞到子對象,而父對象看不到子對象內部的內容,這是一項旨在使咱們程序的數據流更易於跟蹤的功能。爲了找到數據的來源,咱們一般需沿着樹結構向上查找是哪一個父級將其發送出去的。
一個很好的 React 中閉包的例子是經過子組件更新父級狀態。你可能已經作了這件事,卻沒有意識到本身正在用閉包。
首先,咱們知道父級不能直接訪問子級的信息,可是子級能夠訪問父級的信息。所以,咱們經過 props
把該信息從父級發送到子級。在這種狀況下,信息將採用函數的形式更新父級狀態。
const Parent = () => { const [count, setCount] = useState(0); return ( <div> The count is {count} <div> <ChildButtons onClick={setCount} count={count} /> </div> </div> ); }; const ChildButtons = props => ( <div> <button onClick={() => props.onClick(props.count + 1)}> Increase count </button> <button onClick={() => props.onClick(props.count - 1)}> Decrease count </button> </div> );
當 onClick
發生在 button
中時,它將執行從 props.props.onClick
接收到的函數,並用 props.count
更新值。
這裏的看法在於咱們經過子級來更新父級狀態的方式,在本例中爲 props.onClick
功能。之因此起做用,是由於該函數是在 Parent
組件做用域內(在其閉包內)「聲明」的,所以能夠訪問父級信息。一旦在子級中調用了該函數,它仍存在於相同的閉包中。
這可能很難理解,因此我認爲它是閉包之間的「隧道」。每一個都有本身的做用域,可是咱們能夠建立一種將二者鏈接的通訊隧道。
一旦瞭解了閉包如何影響咱們的組件,就能夠邁出下一步:React state。
React 的哲學很簡單:它負責處理什麼時候與如何渲染元素,而開發人員則控制怎樣進行渲染。狀態是咱們決定作什麼的工具。
當狀態被更改時,其組件將渲染並所以從新執行其中的全部代碼。咱們這樣作是爲了向用戶顯示最新被更新的信息。
在個人思惟模型中,狀態就像盒子內部的特殊屬性。它獨立於其中發生的一切。它將在第一次渲染時獲得默認值,而且始終保持最新值。
每一個變量和函數都在每次渲染上被建立,這意味着它們的值也是全新的。即便變量的值沒有改變,每次也會從新計算並從新分配。狀態不是這種狀況,只有在經過 set state
事件要求更改狀態時纔會被更改。
狀態遵循一個簡單的規則:只要被更改,狀態就會從新渲染組件及其子級。prop 遵循相同的邏輯,若是 prop 發生更改,組件將會從新渲染,可是咱們能夠經過對其進行修改來控制狀態,而 prop 更爲靜態,而且一般會根據對狀態變化的反應而進行更改。
我認爲渲染是 React 最使人困惑的部分,由於在渲染過程當中發生了不少事情,而經過查看代碼有時並不明顯。這就是爲何擁有清晰的思惟模式會對你有所幫助的緣由。
我想象用我虛構的盒子進行渲染的方式有兩種:第一種渲染使盒子存在,即狀態初始化時。第二種是從新渲染時,這時盒子是被回收從新利用的,其中大部分都是全新的,但一些重要元素仍然保持其原來的狀態。
在每一個渲染中,都會建立組件內部的全部內容,包括變量和函數,這就是爲何咱們可使用變量來存儲計算結果的緣由,由於它們將在每一個渲染中從新計算。這也是把函數做爲值不可靠的緣由,由於每一個渲染的引用(函數的值自己)都不相同。
const Thumbnail = props => ( <div> {props.withIcon && <AmazingIcon />} <img src={props.imgUrl} alt={props.alt} /> </div> );
根據組件接收的 prop ,以上內容將給出不一樣的結果。在每次 porp 更改時,React 必須從新渲染的緣由是它但願使用戶瞭解最新的信息。
可是,從新渲染後狀態不會改變,它們的值得以維持。這就是爲何盒子是「回收重利用的」而不是每次都建立全新的。在內部 React 會跟蹤每一個盒子並確保其狀態始終保持一致。這就是 React 怎樣知道什麼時候去更新組件的方式。
經過想象一個盒子被回收,我能夠了解其中的情況。對於簡單的組件而言,它很容易掌握,可是組件變得越複雜,它所接收的 prop 越多,維護的狀態也就越多,那麼清晰的思惟模型就越有用。
如今我已經分別解釋了拼圖的全部碎片,下面把它們放在一塊兒。這是我在 React 組件中使用的完整思惟模型,把它從個人想象中直接轉化爲文字。
我想象一個 React 組件是一個盒子,它在其內部包含全部信息,包括它的子級,也就是更多的盒子。
就像現實中的盒子同樣,它能夠在其中包含其餘盒子,而這些盒子中又能夠包含更多盒子。這樣每一個盒子(組件)都必須有一個父對象,而且一個父對象能夠有多個子對象。
這些盒子是半滲透性的,這意味着它們從不會把任何東西泄漏到外部,可是可使用來自外部的信息,就像屬於它們本身的同樣。我想像這表明閉包在 JavaScript 中的工做方式。
在 React 中,組件之間共享信息的方式稱爲 props
,一樣的想法也適用於函數,並被稱爲 arguments
,它們都以相同的方式工做,可是語法不一樣。
在組件內部,信息只能從父級那裏傳播到子級。換句話說,子組件能夠訪問其父組件的數據和狀態,但不能反過來,而咱們經過 prop
共享信息。
我想像這種有方向的信息共享是盒子內部的盒子。最裏面的盒子可以吸取父母的數據。
可是必須首先建立這個,而且發生在 render
上,默認值賦給 state
,就像函數同樣,該組件中的全部代碼都將會被執行。在個人思惟模型中,這等效於盒子被「建立」。
隨後的渲染或「從新渲染」將會再次執行組件中的全部代碼,從新計算變量,從新建立函數等。除了 state
外,全部內容在每一個渲染器上都是全新的。 狀態的值在渲染過程當中保持不變,只能經過 set
方法來更新。
在個人思惟模型中,我將從新渲染視爲回收盒子,由於大多數盒子是從新建立的,可是因爲 React 跟蹤組件的狀態,因此它仍然是同一個盒子。
當回收一個盒子時,其中的全部盒子,即它的子盒子也都被回收了。發生這種狀況的緣由是組件的狀態已被修改或 prop 已更改。
請記住,state 或 prop 的更改意味着用戶看到的信息已過期,React 會始終但願保持 UI 更新,以便它可以從新渲染必須顯示新數據的組件。
經過這些思惟模型,我在使用 React 時會充滿信心。它們幫我把多是迷宮的代碼可視化爲全面的思惟導圖。它還揭開了 React 的神祕面紗,並使我達到更熟悉它的水平。
一旦你開始理解它的核心原理並創造出一些用來想象代碼如何工做的方式,React 就不會那麼複雜。
我但願這篇文章對你有用!我意識到本身可以憑直覺理解 React,而把它理解爲文字是有挑戰性的。
本文給出的某些解釋只是簡化過的,例如不會在每一個渲染器上從新執行更多操做,例如 useEffect,useCallback 和 useMemo hook。我完整的思惟模型比我在一篇文章中所能解釋的更加複雜,請繼續關注本文的後續。
本文後續的第 2 部分將會重點介紹 React 的 API 的深刻模型,例如 useMemo、useCallback 和 useEffect,以及如何用思惟模型來改善 React 應用的性能。第 3 部分將會重點介紹 Context 等高級功能,以及我用於 React 的精確而完整的思惟模型的摘要。