單頁應用開發總結

本文想通過自己這一年的單頁應用開發經驗,來對SPA的開發做一個總結。

頁面開發模式

通常我們在開發頁面時,都會拿到一份設計圖,假設我們拿到一份這樣的設計圖

image.png

對于頁面的開發,我總是遵循自上而下的設計模式去開發。在這里首先會把頁面分為兩部分,頭部導航,和內容主體。內容主體又分為兩部分左側關注信息以及右側的動態列表。如果按照這樣分,我們的組件編寫會像如下這樣

<Container>
  <Nav />
  <Body>
    <BodyLeft />
    <BodyRight />
  </Body>
</Container>

這樣寫其實沒什么問題,把所有的細節全部隱藏在組件內部,每個組件只要處理好自己就OK。但是要知道,現如今頁面都比較復雜,一般的單頁應用都需要一個可靠的數據流去處理,否則在日后維護方面會難度巨大。這里假設我們使用了redux,在redux中,我們的數據都是從頂層往下傳,一般以route頁面為維度,去做connect,然后route頁面將所需的store以及action以props的形式分發。那就相當于,整個頁面只有route組件是智能組件,其他組件盡可能寫成木偶組件。如果按照這樣的開發模式去開發左側關注列表組件,應該是這樣的

//BodyLeft
<Container>
  <TopNav />
  <List />
</Container>

而list組件應該可能是這樣的

//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了。這其實也秉承了自上而下的設計風格。
我通常將組件分為四類

  1. 智能組件
  2. 木偶組件
  3. 容器組件
  4. 高階組件

項目如果使用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 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,后期難以維護,這無疑是本末倒置的。寫這篇總結也是對我最近寫項目的一些反思,希望能有更多的人一起討論,謝謝。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容