React 導讀(六)
來自專欄 小擼伴讀系列
閱讀完之前的第四和第五章,分享了 Dialog
和 Table
組件的一點設計,還有一些小組件的代碼都已經上傳到 Github-smarty 上去了,能夠自己翻閱看一下,接著我們開始整合這些組件來合成一個業務模塊。
一、理解容器組件、展示組件
在現在流行的方案中,Redux 的出現迎來了容器組件、展示組件等概念的流行,其實在這之前 Flux 的方案已經有了這類劃分,Redux 應該是讓它更知名了。
容器組件(containers/employeeManage/index.jsx
):又稱充血組件,主要用於一個頂層組件的數據獲取,以及一些副作用數據、業務邏輯的處理,我這裡的狀態訂閱代碼都放在此處。
展示組件(components/employee/**/*.jsx
):又可以稱貧血組件,跟容器組件不同的是它幾乎沒有業務邏輯,只是通過傳遞的 props
進行渲染,更像一個純函數,(props) => component
。
這裡多聯想一下,如果把這裡的組件類型,脫離 View 層的限制去連接後端的分層設計,其實他們也是有充血模型和貧血模型的,但是唯一不同的是,後端的分層只是對數據的操作,可以簡單分為:
充血模型:(Domain + Bussiness Logic) => End 貧血模型:(Bussiness Logic + Service) => (Domain Model) => End
上面主要是領域模型(Domain)以及業務邏輯(Bussiness Logic)的關係,區別的話簡單可以理解成業務邏輯存在的位置,這裡就跟前端的組件劃分是一樣的,如何更好的劃分[業務邏輯]
就是設計和開發組件的重點。
二、容器組件代碼
容器就有一點打包的趣味,容器+組件,就是打包一些組件在一起,那麼我們需要打包哪些東西呢?
我們需要打包業務模塊的所有功能的入口組件,比如這裡我的功能有:- 模塊名稱
- 搜索、按鈕等操作欄
- 人員表格
- 刪除、添加、編輯彈框
入口代碼就這樣子:
<div className="mod"> <ModTitle>員工管理</ModTitle> <EmployeeHeader onAdd={this.handleAdd} /> <EmployeeTable loading={this.state.isLoadingData} data={this.state.list} onEdit={this.handleEdit} onDelete={this.handleDelete} /> {/* 各種彈框 */} <EmployeeDeleteDialog visible={this.state.visibleDeleteDialog} data={this.state.currentSelected} onClose={this.handleCloseDeleteDialog}/> <EmployeeAddDialog visible={this.state.visibleAddDialog} onClose={this.handleCloseAddDialog} /> <EmployeeEditDialog visible={this.state.visibleEditDialog} data={this.state.currentSelected} onClose={this.handleCloseEditDialog} /></div>
從上面的代碼可以看出,我這裡的容器組件 render
的內容都是一些包裝了的自定義組件,有很多 props
的傳遞。那麼這些 props
的值在容器組件中是如何獲取的呢?那就要用到之前我們解釋數據流程的內容了,這裡溫習一下主要代碼:
// 1. 先看一下我們容器組件依賴的狀態 stateconst state = { list: [], currentSelected: null, visibleDeleteDialog: false, visibleAddDialog: false, visibleEditDialog: false, isLoadingData: false,};// 2. 這裡的 state 我是都放在 Store 中進行託管的,所以我們在容器組件構造器裡面需要獲取初始值constructor() { super(); // Store 有一個獲取狀態的 getter this.state = EmployeeStore.getState();}
這樣我們的數據就初始化好了,接下來就是監聽來更改狀態,然後 props 裡面的值就有了更新。
componentDidMount() { EmployeeStore.on(loadingData, this.handleLoadingData); EmployeeStore.on(updateList, this.handleUpdateList); // 第一次先請求一次員工列表 EmployeeStore.getList();}// 只要 emit(updateList, newData) 就會觸發 React 的更新方法 setState 這樣整個組件數據就重新更新了handleUpdateList(list) { this.setState(prevState => { return { list: list, isLoadingData: false, }; });}
三、彈框
其實容器組件要做的工作我這裡就差不多了,因為功能很簡單。那麼我這裡只是處理了列表更新,對於列表數據的刪除和添加等功能都沒有做,這些副作用其實我是放在了彈框裡面去做,無論你放在哪裡都是可以的,看你項目的情況。我們這裡只看一下添加彈框的內容,添加彈框是一個業務類型的組件,依賴的是 Dialog
基礎組件的功能。
// addDialog/index.jsxrender() { if(!this.props.visible) { return null; } const footer = ( <DialogFooter onSubmit={this.handleSubmit} onClose={this.handleClose} submitText="添加" closeText="關閉" /> ); return ( <Dialog className="employeeAddDialog" renderHeader={() => <DialogHeader title="添加成員" />} renderFooter={() => footer}> <div className="addDialog"> <Input onChange={this.handleChangeName} placeholder="請輸入姓名" /> <Input onChange={this.handleChangeDays} placeholder="請輸入天數" /> <Input onChange={this.handleChangeAge} placeholder="請輸入年齡" /> </div> </Dialog> );}
上面的代碼可以看出來,其實主要就是多了三個輸入框來進行交互,最後這個添加彈框是有自己的狀態的,不是一個純展示組件,他的 state
如下:
state = { formData: { name: , age: 0, sex: null, days: 0, }};
這裡我喜歡包裹一層 formData,用來單獨就值我要跟後端對接的數據,可能需求還有變化,需要添加的數據並不想影響我的表單提交數據,而且更新也相對方便一點。
那麼彈框的提交和關閉是怎麼弄的呢?
handleSubmit(e) { const {formData} = this.state; // addEmployee 方法會 emit(updateList, newData) 來更新容器裡面的狀態 EmployeeStore.addEmployee(formData); // 樂觀交互 this.handleClose();}handleClose(e) { // 把關閉狀態給外面控制 this.props.onClose();}
這裡可以看到關閉是通過 props
傳遞的一個容器方法來做的,因為彈框總體上來說,都是不具備重業務邏輯的一個組件,所以渲染的控制對外開放會更好,因為有時候萬一你彈框是默認就要顯示的就很彆扭:this.state.visible = this.props.visible
這種代碼就很蛋疼。所以乾脆控制都讓外面來做,要改變的給一個 onChange
的介面就行。
這裡需要注意的就是添加彈框繼承的是
React.PureComponent
來減少不必要的渲染,會進行一個淺的數據 Diff 控制更新。
細心的朋友可能會發現,我的 Input
組件寫得比較噁心,為什麼呢?我綁定了三個不同的 handleChangeXXX
這種方法,那麼如何優化呢?可以思考一下哦~
四、展示表格組件
在這裡我將表格做成了純粹靠外界數據進行渲染的展示組件,將業務邏輯和行為幾乎都給了容器組件去做,先來看一下目錄結構:
├── dataTable│ ├── config.js // 表格的配置,主要是 columns、格式化方法│ ├── dataTable.css│ └── index.jsx // 表格組件
根據之前的需求,表格這裡具有:表頭
和 內容
兩部分,這裡表頭能夠根據我們的配置進行初始化:
// 具體的配置內容能夠在 Github 上看一下import config from ./config;const headers = config.table.map((row, index) => { let width = row.width; return <th onClick={row.onSort} style={row.style} className={row.className} width={width} key={`header-${index}`}>{row.title}</th>;});headers.push(<th width="160" key="opts">操作</th>);
表頭初始化好了,開始初始化內容,表格的內容這裡按行進行初始化,依賴了一個 SimpleRow
的業務組件來減少業務表格的代碼量:
const rows = this.props.data.map((item, i) => { return ( <SimpleRow key={item.id} tableConfig={config.table} row={item} index={i}> <td key={`opt-${i}`}> <Button style={{marginRight: 5}} color="blue" onClick={() => this.props.onEdit(item)}>編輯</Button> <Button color="red" onClick={() => this.props.onDelete(item)}>刪除</Button> </td> </SimpleRow> );});
這樣我們的一個表格就搞定了~加入 Table
組件就能夠渲染一個簡單職工列表了:
<div className="mod-table employeeTable"> <TableLoading loading={loading} /> <Table> <Table.Header> {headers} </Table.Header> <Table.Body> {rows} </Table.Body> </Table></div>
這裡可以看到還有一個 TableLoading
組件,這也是一個偏業務的組件,主要就是用於表格載入數據時候的一個 Loading 行為。最後這個表格是搞定了,但是代碼還是複雜了一點,那麼怎麼才能更簡單的讓別人用呢?讓代碼更少,O__O "…這個可以再思考一下哦~可以再進行一次封裝。
PS: 有什麼東西是分層搞不定的...如果搞不定,再分一層。哈哈,當然這是一個段子。
今天就寫到這裡吧,其實導讀系列就先結束了,這裡涉及的東西不多,但是對最初用 React 來編寫代碼還算比較有幫助的,後面會繼續深入一點的話題,比如
庫類:熱火朝天的數據狀態管理、純函數、流式、單向數據流、表單、驗證器等等吧~
腳手架:如何搭建一個自己心儀的玩具~
框架:如何更工程的去開發 React 等內容~
好玩的一些開發組件的理念~
當 TypeScript 遇上 React 呀這種。
想到的時候有時間就寫寫。
PS: 系列寫了六集 =.= 還蠻神奇的,我姓協音就是六。
推薦閱讀:
※關於在react中request到底是應該寫在哪裡?
※前端自學,目前可以用react寫一些項目,但是不知道目前現在在前端上的水平,希望可以獲得指點?
※在react-router4中進行代碼拆分(基於webpack)
※前端每周清單第 22 期:ES8 正式發布、React 與 GraphQL 開發指南和性能優化,Vue.js 2.4.0 發布
※redux源碼分析