深入理解React(二) —— 數據流和事件原理


版權聲明:本文由左明原創文章,轉載請注明出處: 
文章原文鏈接:https://www.qcloud.com/community/article/158

來源:騰雲閣 https://www.qcloud.com/community

 


這個,叫做竹筧,是中日傳統禪文化中常見的庭院裝飾品,它的構造可簡單可復雜,但原理很簡單,比如這個竹筧,水從竹筧頂部入口流入內部,並按照固定的順序從上向下依次流入各個小竹筒,然后驅動水輪轉動。對於強迫症患者來說,觀賞竹筧的絕對是一種很享受的過程的最愛,你會發現這些小玩意竟然能這么流暢的協調起來,好神奇。

如果竹筧是一個組件的話,那么水就是組件的數據流。

在React中,數據流是自上而下單向的從父節點傳遞到子節點,所以組件是簡單且容易把握的,他們只需要從父節點提供的props中獲取數據並渲染即可。如果頂層組件的某個prop改變了,React會遞歸地向下遍歷整棵組件數,重新渲染所有使用這個屬性的組件。

這個是前面看到的 KM 熱點問題組件,擁有一個叫做 articles 的屬性。

在組件內部,可以通過this.props來訪問props,props是組件唯一的數據來源,對於組件來說:

props永遠是只讀的。

不要嘗試在組件內部調用setProps方法來修改props,如果你不小心這么做了,React會報錯並給出非常詳細的錯誤提示。

組件的屬性類型如果不進行聲明和驗證,那么很可能使用者傳給你的屬性值或者類型是無效的,那會導致一些意料之外的故障。好在React已經為我們提供了一套非常簡單好用的屬性校驗機制——

React有一個PropTypes屬性校驗工具,經過簡單的配置即可。當使用者傳入的參數不滿足校驗規則時,React會給出非常詳細的警告,定位問題不要太容易。

PropTypes包含的校驗類型包括基本類型、數組、對象、實例、枚舉——


以及對象類型的深入驗證等等。如果內置的驗證類型不滿足需求,還可以通過自定義規則來驗證。
如果某個屬性是必須的,在類型后面加上 isRequired 就可以了。

React的一大創新,就是把每一個組件都看成是一個狀態機,組件內部通過state來維護組件狀態的變化,這也是state唯一的作用。

state一般和事件一起使用,我們先看state,然后看看state和事件怎樣結合。
這是一個簡單的開關組件,開關狀態會以文字的形式表現在按鈕的文本上。
首先看render方法,返回了一個button元素,給button注冊了一個事件用來處理點擊事件,在點擊事件中對state的on字段取反,並執行 this.setState() 方法設置on字段的新值。一個開關組件就完成了。


組件渲染完成后,必須有UI事件的支持才能正常工作。

React通過將事件處理器綁定到組件上來處理事件。
React事件本質上和原生JS一樣,鼠標事件用來處理點擊操作,表單事件用於表單元素變化等,Rreact事件的命名、行為和原生JS差不多,不一樣的地方是React事件名區分大小寫。
比如這段代碼中,Article組件的section節點注冊了一個onClick事件,點擊后彈出alert。
有時候,事件的處理器需要由組件的使用者來提供,這時可以通過props將事件處理器傳進來。

這個是剛才那個Article組件的使用者,它提供給Article組件的props中包含了一個onClick屬性,這個onClick指向這個組件自身的一個事件處理器,這樣就實現了在組件外部處理事件回調。

這是一個React組件實現組件可交互所需的流程,render()輸出虛擬DOM,虛擬DOM轉為DOM,再在DOM上注冊事件,事件觸發setState()修改數據,在每次調用setState方法時,React會自動執行render方法來更新虛擬DOM,如果組件已經被渲染,那么還會更新到DOM中去。

這些是React目前支持的事件列表。

React的組件擁有一套清晰完整而且非常容易理解的生命周期機制,大體可以分為三個過程:初始化、更新和銷毀,在組件生命周期中,隨着組件的props或者state發生改變,它的虛擬DOM和DOM表現也將有相應的變化。

首先是初始化過程,這里會着重講,需要充分理解。
組件類在聲明時,會先調用 getDefaultProps() 方法來獲取默認props值,這個方法會且只會在聲明組件類時調用一次,這一點需要注意,它返回的默認props由所有實例共享。
在組件被實例化之前,會先調用一次實例方法 getInitialState() 方法,用於獲取這個組件的初始state。
實例化之后就是渲染,componentWillMount方法會在生成虛擬DOM之前被調用,你可以在這里對組件的渲染做一些准備工作,比如計算目標容器尺寸然后修改組件自身的尺寸以適應目標容器等等。
接下來就是渲染工作,在這里你會創建一個虛擬DOM用來表示組件的結構。對於一個組件來說,render 是唯一一個必須的方法。render方法需要滿足這幾點:
1.只能通過 this.props 或 this.state 訪問數據
2.只能出現一個頂級組件
3.可以返回 null、false 或任何 React 組件
4.不能對 props、state 或 DOM 進行修改
需要注意的是,render 方法返回的是虛擬DOM。

渲染完成以后,我們可能需要對DOM做一些操作,比如截屏、上報日志、或者初始化iScroll等第三方非React插件,可以在 componentDidMount() 方法中做這些事情。當然,你也可以在這個方法里通過 this.getDOMNode() 方法取得最終生成DOM節點,然后對DOM節點做愛做的事情,但需要注意做好安全措施,不要緩存已經生成的DOM節點,因為這些DOM節點隨時可能被替換掉,所以應該在每次用的時候去讀取。

組件被初始化完成后,它的狀態會隨着用戶的操作、時間的推移、數據更新而產生變化,變化的過程是組件聲明周期的另一部分 ——

更新過程。

當組件已經被實例化后,使用者調用 setProps() 方法修改組件的數據時,組件的 componentWillReceiveProps() 方法會被調用,在這里,你可以對外部傳入的數據進行一些預處理,比如從props中讀取數據寫入state。

默認情況下,使用者調用組件的 setProps() 方法后,React會遍歷這個組件的所有子組件,進行“灌水”,將props從上到下一層一層傳下去,並逐個執行更新操作,雖然React內部已經進行過很多的優化,這個過程並不會花費多少時間,但是程序員里永遠不缺乏長期性能飢渴的同學,不用擔心,React有一個能夠解決你性能飢渴的辦法——shouldComponentUpdate()。
有時候,props發生了變化,但組件和子組件並不會因為這個props的變化而發生變化,打個比方,你有一個表單組件,你想要修改表單的name,同時你能夠確信這個name不會對組件的渲染產生任何影響,那么你可以直接在這個方法里return false來終止后續行為。這樣就能夠避免無效的虛擬DOM對比了,對性能會有明顯提升。
如果這個時候有同學仍然飢渴難耐,那么你可以嘗試 不可變數據結構(用過mongodb的同學應該懂)。
組件在更新前,React會執行componentWillUpdate() 方法,這個方法類似於前面看到的 componentWillMount()方法,唯一不同的地方只是這個方法在執行的時候組件是已經渲染過的。需要注意的是,不可以在這個方法中修改props或state,如果要修改,應當在 componentWillReceiveProps() 中修改。
然后是渲染,React會拿這次返回的虛擬DOM和緩存中的虛擬DOM進行對比,找出【最小修改點】,然后替換。
更新完成后,React會調用組件的componentDidUpdate 方法,這個方法類似於前面 componentDidMount 方法,你仍然可以在這里可以通過 this.getDOMNode() 方法取得最終的DOM節點。

香港電影結尾經常看到一個劇情,就是英雄打敗了壞人,然后警察出來擦屁股——

警察偶爾還能立功,而 componentWillUnmount 最可憐,他除了擦屁股什么也做不了。
你可以在這個方法中銷毀非React組件注冊的事件、插入的節點,或者一些定時器之類。這個過程可能容易出錯,比如綁定了事件卻沒銷毀,這個React可幫不了你,你自己約的炮,含着淚也要打完。


兩節內容講了上手React所必備的知識。
后面講價值。

直出有多快我就不多說了。
因為有虛擬DOM的存在,React可以很容易的將虛擬DOM轉換為字符串,這便使我們可以只寫一份UI代碼,同時運行在node里和和瀏覽器里。

在node里將組件HTML渲染為一段HTML一句話即可。
不過圍繞這個renderToString我們還要做一些准備工作。代碼有點多,大家做好心理准備。

這是一個express的路由方法,在這里:
1.從后台server或數據庫等來源拉取數據
2.引入要渲染的React組件
3.調用React.renderToString()方法來生成HTML
4.最后發送HTML和數據給瀏覽器

這里為了方便描述,沒有寫拉取數據的代碼,大家自行腦補。

需要注意的是這里的JSON字符串中可能出現結尾標簽或HTML注釋,可能會導致語法錯誤,這里需要進行轉義。

頁面的示例代碼本來打算用大家更熟悉的HTML,但發現代碼量太多了PPT里一頁放不下,所以換成了jade代碼,沒用過jade的同學也順便了解一下,我也順便給jade打個廣告。
這個頁面做了X個事:
1.將前面在action里生成的HTML寫到#container元素里
2.引入必須的JS文件
3.獲取action提供的數據
4.渲染組件

這就是React的服務端渲染,組件的代碼前后端都可以復用。
<有沒有沒理解清楚的同學?>

是不是感覺React挺牛逼的?大家以為React就這么點能耐嗎?

React能夠用一套代碼同時運行在瀏覽器和node里,而且能夠以原生App的姿勢運行在iOS和Android系統中,即擁有了web迭代迅速的特性,又擁有原生App的體驗。
這個姿勢叫做 React-Native。
這是React和React-Native在github上的數據,可以看出React-Native也是相當熱門——因為React-Native能夠使React的價值最大化,這個價值是什么呢——對業務來說,意味着不需要為了做終端版本就招聘和前端等量人力的終端開發,同時意味着我們成為全棧工程師有了一個捷徑。

了解iOS開發的同學都知道,水果公司對應用上架的審核效率實在讓人無力吐槽,很多團隊上一個版本還沒審核結束,下一個版本就已經做好了。而React-Native支持從網絡拉取JS,這樣iOS應用也能夠像web一樣實現快速迭代了。

這個是react-native的調試過程

作為一個沒寫過一句Object-C代碼的web前端開發,我只用了一天時間就上手了react-native,然后用了半天時間做出了一個簡單的demo頁面,可以看到react-native的生產效率還是非常高的。

單元測試顧名思義,是對各個模塊進行最小范圍的測試,容易。
我們來演示一個checkbox的單元測試過程。

看代碼

因為虛擬DOM的存在,使得react的代碼很容易做好單元測試,這是上面那段代碼的測試用例,通過karma執行后即可看到結果。

所以你可能需要這些東西


課后練習

(如果你已經看到這里了,為何不再花1分鍾思考一下上面3個問題)

上一期React技術文章:

深入理解 React (一) - JSX和虛擬DOM

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM