React + Redux 組件化方案

tencow 8年前發布 | 48K 次閱讀 Redux ReactNative 移動開發

React + Redux 組件化方案

在介紹組件化方案之前,先對 react 和 redux 做一個簡單介紹。

Why React

理想中的組件化,第一步應該就是組件的標簽化, 例如有一個 Header 組件,如下圖所示

無需關注組件內部的實現,我們只需要使用一個 <Header/> 標簽就能調用它,通過設置屬性的方式,來控制它的顯示的內容,和對應的事件。

class Page extends Component {
    render () {
        <div>
            <Header onAttend={click} anchorInfo={anchorInfo} members={members}/>
        </div>
    }
}

onAttend 決定點擊關注時會觸發的事件

anchorInfo 決定左側展示的主播信息

members 坐定右側展示的成員信息

借助 jsx 語法,React 已經實現上述想法。

Why Redux

在簡單的應用中,上面的組件化方案是非常清晰的,因為 <Header /> 組件被任何其他組件使用,且沒有任何副作用。

但是由于 React 的數據流向是單向的, 子組件的數據和方法只能由父級組件賦予,一旦組件嵌套層次變深,傳遞數據將會變得非常復雜。

拿上面的 Header 組件來說, 它的內部還使用了 Avatar 和 Members 兩個組件,Header 把它接受到的數據和方法,又需要傳遞給了 Avatar 和 Members 。

//Header.js
class Header extends Component {
    render () {
        <Anchor avatarInfo={this.props.AnchorInfo} onClick={this.props.click} />
        <Members members={this.props.members}/>
    }
}

當然有人會認為直接在 Header 中申明所需要的數據和方法,不再從父級獲得,這樣不就解決了深層嵌套的問題嗎,但是如此一來數據就和組件耦合到一起了,不同項目使用的 Header 的數據源一般是不同的,這意味著你需要為每個項目都要寫一個 Header,提供不同的獲取數據方式。

另一方面在假設另一個組件下載條 DownloadBar 中也有使用 anchorInfo 這個數據, 那么 DownloadBar 中也需要維護這個數據。

如果兩個組件內部的 anchorInfo 發生變化,那么都需要通知另一個組件也發生變化,因為 anchorInfo 應該是唯一的。 大型應用中不同組件共享同一個數據源的情況是常見的,如果都讓組件自身來維護一份的數據,很容易造成數據混亂。

redux 框架解決了這個問題,簡單來說,它將 react 由父級傳遞數據,變為了由一個統一的數據源 store 單向地向各個組件傳遞數據。

  • 原始的 React 架構

  • 加入了 Redux 的架構之后的

所有數據都存放在 store 中,組件內部不維護任何數據。

store 提供了 dispatch 方法來觸發改變 store 中數據。 dispatch 傳入的值被稱作 action。 dispatch(action) 之后,會進入到 store 中稱為 reducer 的處理函數,這些 reducer 會依據不同的 action 的類型,進行不同的處理,reducer 返回的值就會作為 store 中新的數據,一個 reducer 對應的是 store 中一個數據字段,每多一個reducer, store 中就多一個數據字段。數據發生改變后, store 就會通知對應的組件重新渲染。

通過 redux 框架提供的 connect 高階函數, 直接從 store 選取需要的數據和申明需要使用的方法傳入組件中,這些申明的方法是組件事件具體的邏輯的實現,例如發送請求,上報邏輯等等,所以通常調用 dispatch(action) 的邏輯也會包含在里面。

在 React 作為 UI 組件庫的基礎上,以 redux 作為狀態管理框架,我們定義了4種類型的組件。

展示組件

React 組件即為我們的展示組件。它內部不會維護任何動態的數據,除了部分只和組件本身有關的數據,例如 Video 組件中, playState(播放狀態),就是它內部才會擁有的狀態,而 src(播放源) 就必須從外部傳入。它不會包含各種事件具體的實現,只提供對應的接口(如 onClick),具體的實現都由外部調用者去決定。

存儲中心組件

存儲中心組件即為上文提到的 redux 架構中的 store。 存儲中心組件中默認定義了一些 reducer 處理函數和一些 middleware,還包含了連接 redux 和 react 的高階函數和向 store 中注入新的 reducer 的方法。

數據組件

數據組件即為 redux 架構中某個action 和 對應的 reducer 的合集。數據組件提供了各種 action 可以去調用,并且定義了對應的 action 去處理,數據組件中必須引用存儲中心組件,因為數據組件必須向 store 中注入對應的 reducer 處理函數。例如在 roomInfo 的數據組件中,提供了 enterRoom, loadRoomInfo, leaveRoom 這些 action 供調用者使用,且自動向 store 中添加了 roomInfo 這個數據。

數據組件中也會存在互相依賴的情況,例如 chatmessage 會例如 longpoll 這個數據組件,因為 chatmessage 的 reducer 中需要對 longpoll 的 action 也進行處理。

高階組件

高階組件即為經過 connect 高階組件中申明使用的展示組件和數據組件。 函數處理后的展示組件。通常情況下,被使用的組件一般都是高階組件。 高階組件確定向該展示組件傳入的屬性和方法。高階組件是和業務耦合的,復用性不強。高階組件高度聚合,而展示組件和數據組件間又充分解耦。

一個高階組件中可能包含多個數據組件,例如 Ranklist 這個展示組件,需要由提 roomInfo 和 rankList 這兩個數據組件提供數據。

高階組件可能不會引入任何數據組件的方法,只需 import 對應的數據組件,將reducer 注入進 store

import '@tencent/now-data-roomInfo'

接入組件

  1. 申明存儲中心組件。
  2. 申明合適的高階組件。
  3. 如果沒有對應的高階組件,則申明展示組件和數據組件,創建為新的高階組件。
  4. 如果沒有對應的展示組件,則創建一個需要的展示組件。回到step2
  5. 如果沒有對應的數據組件,則創建一個需要的數據組件。回到step3
  6. 編寫入口文件,引入各個高階組件。

實際開發時我們的樣子可能是這樣的

  1. 我們接到了一個新的需求,其中大致布局和之前的項目完全一致,改變的點有,這個業務只在 手q 中執行,而且視頻的數據源由一個新的 CGI 提供。
  2. 確認我們需要的組件在這個例子中,需要用的組件有:
  3. Header 頭部
  4. Video 視頻
  5. Message 消息
  6. Bubble 點贊
  7. ToolPanel 工具面板

  8. 在 tnpm 上查找高階組件,發現以下高階組件

  9. now-highorder-bubble
  10. now-highorder-message
  11. now-highorder-toolpanel
  12. now-highorder-header
  13. now-highorder-video

其中可以直接使用的組件有

  • now-highorder-bubble
  • now-highorder-message
  • now-highorder-toolpanel
    通過 tnpm 安裝對應組件
tnpm install @tencent/now-highorder-message @tencent/now-highorder-toolpanel 
@tencent/now-highorder-bubble

now-highorder-header 定義的 onClose 事件只能在 NOW APP 中才能執行, 所以不能使用。

now-highorder-video 中引用的數據組件使用的 CGI 數據是一個舊版 CGI 數據 ,也不能使用。

  1. 在項目中自定義一個新的 header 高階組件, 使用的展示組件和數據組件與 now-highorder-header 中的一樣,任然是 now-display-header(展示組件) 和 now-data-header(數組組件), 只是通過 connect 鏈接的時候,onClose 傳入的方法 為新的方法。

    通過 tnpm 安裝對應的展示組件和數據組件

    tnpm install @tencent/now-data-roomInfo @tencent/now-display-header

    創建新的 Header 高階組件 now-highorder-header2 ``` import Header from '@tencent/now-display-header' //引入展示組件 import roomInfo from '@tencent/now-data-roomInfo' //引入數據組件 import connect from 'react-redux'

export default connect((state) => { const { roomInfo } = state

return {
    roomInfo
}

}, (dispatch) => { return { onClose: () => { _.mqq('close') //手q中改為調用 mqq 提供的 close 接口 } } })(Header)

4. 在項目中自定義一個新的 video 的高階組件,使用的展示組件為現有的 now-display-header, 因為使用了一個新的 CGI, 先新建一個的數據組件 now-data-videoinfo_v2,數據組件必須引用 now-store 中的 addReducer 方法,向store中注入新的字段。

now-data-videoinfo_v2

import { addReducer } from '@tencent/now-store';

export function loadVideo(roomId) { //定義action函數 ... }

function videoInfo (state = { // 定義 reducer處理函數 url: '', }, action) { ... }

addReducer({ // 向store中注入新的數據 videoInfo })

在新的 video 高階組件中引入,這個數據組件和 now-display-video 
通過 tnpm 安裝對應的展示組件

tnpm install @tencent/now-display-video

創建新的高階組件 now-highorder-video2

import Video from '@tencent/now-display-video' //引入展示組件 import {loadVideo} from 'now-data-videoinfo_v2' //引入申明的數據組件

export default connect((state) => { const { url, } = state.videoInfo

return {
    src: url
}

}, (dispatch) => { return { onLoad: () => { return dispatch(loadVideo()) } } })(Video)

5. 編寫入口文件 index.js
引入現有的和剛新建的組件,組裝頁面。

import React, { Component } from 'react' 引入基礎框架 import { Provider, connect } from 'react-redux'

import Store from '@tencent/now-store'; //引入管理組件

import Header from './now-highorder-header2' //引用高階組件 import Video from './now-highorder-video2' import Message from '@tencent/now-highorder-message' import Bubble from '@tencent/now-highorder-bubble' import ToolPanel from '@tencent/now-highorder-toolpanel'

class PageContainer extends Component { //創建 react 根組件 render () { return (

//引用各個組件 <Header /></p>

<Video />

        <Message />      

        <Bubbles />

        <ToolPanel />
    </div>
)

}</code></pre>

}

const store = new Store() //實例化管理組件

const Root = connect(function(state) {

return state; })(PageContainer);

ReactDOM.render(

 

, document.getElementById('container') ) //渲染 React

```

例如上面代碼,需要通過 import 組件 將reducer 注入進 store 即可。

架構的優勢

  1. 組件的引用簡單。
  2. 展示組件和數據組件之間的分離實現了低耦合,而連接兩者的高階組件實現了高內聚。
  3. 全部由 tnpm 管理,模塊管理方便。
  4. 即使使用了不同了數據管理架構,也可以直接使用展示組件。

一些待解決的問題

  1. 公用的 css 無法管理,需要引入新的構建工具
  2. 開發調試不方便,無法單獨獨立的開發一個組件
  3. 組件文檔缺失。
  4. 缺乏測試用例,組件迭代后不能保證可靠性。

 

來自:http://imweb.io/topic/57c531bc6227a4f55a8872c2

 

Save

 本文由用戶 tencow 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!