本文想經過本身這一年的單頁應用開發經驗,來對SPA的開發作一個總結。react
一般咱們在開發頁面時,都會拿到一份設計圖,假設咱們拿到一份這樣的設計圖redux
對於頁面的開發,我老是遵循自上而下的設計模式去開發。在這裏首先會把頁面分爲兩部分,頭部導航,和內容主體。內容主體又分爲兩部分左側關注信息以及右側的動態列表。若是按照這樣分,咱們的組件編寫會像以下這樣設計模式
<Container> <Nav /> <Body> <BodyLeft /> <BodyRight /> </Body> </Container>
這樣寫其實沒什麼問題,把全部的細節所有隱藏在組件內部,每一個組件只要處理好本身就OK。可是要知道,現現在頁面都比較複雜,通常的單頁應用都須要一個可靠的數據流去處理,不然在往後維護方面會難度巨大。這裏假設咱們使用了redux,在redux中,咱們的數據都是從頂層往下傳,通常以route頁面爲維度,去作connect,而後route頁面將所需的store以及action以props的形式分發。那就至關於,整個頁面只有route組件是智能組件,其餘組件儘量寫成木偶組件。若是按照這樣的開發模式去開發左側關注列表組件,應該是這樣的less
//BodyLeft <Container> <TopNav /> <List /> </Container>
而list組件應該多是這樣的dom
//List <Container> {data.map(item=>( <Item>{item.name}</Item> ))} </Container>
這時若是route頁面想傳遞什麼信息給左側列表,每次都要層層傳遞,少了一層,多了可能會有兩三層,這樣會寫不少沒用的信息,而且很不利於往後的維護,由於每次有更改,都要去改許多無用的地方(那些中間層)。
而我我的更傾向一種扁平化的組件設計風格。
仍是原來的頁面,若是採用扁平化的設計模式,設計出來的組件結構是這樣的模塊化
//ps:組件命名隨便取的 <Container> <Nav> <NavLeft /> <NavRight /> </Nav> <Body> <BodyLeft> <LeftTop /> <LeftList /> </BodyLeft> <BodyRight> <ArticleList /> </BodyRight> </Body> </Container>
首先最外層的佈局是能夠複用的。這也就意味着若是以後還有頁面是這樣,上下佈局,下面又是左右佈局的時候,能夠拿來用。其次是數據的傳遞不須要像以前那樣層層傳遞,能夠直接傳給想要的組件。函數
有人說route頁面爲何不能這樣寫組件化
<div> <Nav /> <div> <div className="left"> <div className="leftTop"> ... </div> <List /> </div> <div className="right"> <ArticleList /> </div> </div> </div>
也就是說把以前的那些組件換成div不就行了。可是在組件設計哲學裏,一般在connect的組件,也就是智能組件裏,是不處理與view有關的東西,智能組件只處理數據和邏輯。而於視圖相關的東西全放在木偶組件去處理。好處是隻要是邏輯處理,就會去找智能組件,而界面樣式之類,就會去找木偶組件,思路清晰,更低耦合高內聚。佈局
不少人對組件的理解是複用。其實組件化開發還有一個好處就是模塊化。模塊化能夠將一個複雜的問題劃分爲多個,簡單的問題去處理。打個比方你的能力一次只能處理一個七十分的問題,如今來了一個八十分的問題,你一次性很難處理,常常須要寫一寫,停頓一下,思考一會,而後再寫一寫,直至完成;相反若是你採用模塊化的方式去解決,直接將這個八十分的問題劃分爲四個二十分的問題,此時,你只須要先搭建一個架子,讓這個四個二十分的模塊加起來能等於八十分,接着再去處理每一個二十分的問題就OK了。這其實也秉承了自上而下的設計風格。
我一般將組件分爲四類this
智能組件
木偶組件
容器組件
高階組件
項目若是使用redux,智能組件一般是做爲connect的那個組件,他只處理該組件下全部子組件的數據邏輯;而樣式,真實的dom結構,由木偶組件來負責,他一般是stateless function,偶爾也有本身的state,但即便是state,也只處理與頁面展現有關的邏輯;容器組件很簡單,若是把一個頁面比做一我的,容器組件就是人的頭,身體,和四肢。將頁面大體分類,一般的寫法是這樣的
//Body <div className="body">{this.props.children}</div>
ps:爲何容器組件不直接寫成div
上文說過了。
若是說前三類組件都在爲頁面服務,那麼最後一個組件就是專門爲組件服務的組件。高階組件就像高階函數同樣,將被修飾的組件做爲參數,同時也能夠傳入其餘配置參數,最後返回一個被加強的組件,redux的connect
就是一個高階組件,一般寫法是這樣的:
//高階組件 const Hoc = props => WrapComponent => { return class extends Component { render() { return <WrapComponent {...props} />; } }; }; // 使用 class WrapComponent extends Component{ render(){ return <div /> } } export default Hoc()(WrapComponent)
在redux數據流管理裏,行業裏有不少最佳實踐,我這裏就當拋磚引玉。
我認爲通常頁面邏輯不是很複雜的項目,簡單的使用redux redux-thunk redux-action就夠了,若是須要處理的請求很複雜,爲了不回調地獄,可使用redux-saga。一般咱們在對數據從頂部往下分發的時候,須要以一個維度爲基點來作connect,這個維度通常是route頁面。對於router,如今也基本達成了共識,那就是router的數據狀態也是redux數據的一種,它與view無關,所以使用react-redux-router將router與redux結合在一塊兒是一個比較好的作法。
在redux裏,有一個比較有爭議的點是,關於頁面的狀態,是否要放在redux裏。有人認爲redux就應該只放數據,即後臺的數據和部分前臺本身存儲的數據;可是我認爲,咱們頁面在開發過程當中,頁面的展現邏輯一般與後臺的數據是很是耦合的,可能一個按鈕的狀態,一個icon的顏色,都與後臺的數據有關,那麼若是強行拆分,就會變成對於一個流程,咱們要先去redux裏處理後臺數據,處理好以後,再去智能組件里根據redux數據處理內部state(頁面的狀態),這樣很麻煩,也很難維護。我本身的作法是,每個流程,只對應一個action,這個action內部再去根據不一樣的參數去處理不一樣的數據,直至頁面正常反應這個操做爲止。
總的來講,咱們中不少人包括我本身,給view層賦予了太多的職能和責任,形成redux的價值沒有發揮出來,view裏跑着各類state,後期難以維護,這無疑是本末倒置的。寫這篇總結也是對我最近寫項目的一些反思,但願能有更多的人一塊兒討論,謝謝。