對應(yīng)源碼項(xiàng)目地址
- 托管在 github 上的項(xiàng)目鏈接:https://github.com/uncleAndyChen/react-full-stack-learning
- 托管在 gitee 上的項(xiàng)目鏈接 :https://gitee.com/uncleAndyChen/react-full-stack-learning
能學(xué)到些啥?
作為學(xué)習(xí)的例子,只有三個頁面,但是麻雀雖小,五臟俱全。通過該例子可以學(xué)習(xí)到以下知識點(diǎn):
- 項(xiàng)目的目錄結(jié)構(gòu)設(shè)計(jì)最佳實(shí)踐。
- 項(xiàng)目的 state 設(shè)計(jì)和模塊設(shè)計(jì)技巧。
- 異步獲取 API 數(shù)據(jù),以及將獲取到的數(shù)據(jù)展示到頁面上。
三個核心頁面
- 登錄頁面。
- 帖子列表頁面,僅展示帖子的基本信息。
- 帖子詳情頁面,展示帖子的詳細(xì)內(nèi)容,包括用戶的評論列表。
測試賬號
- 該bbs內(nèi)置三個用戶
- tom
- jack
- steve
- 密碼都是:123456
state 設(shè)計(jì)
{
app: {
requestQuantity: 0, // 當(dāng)前應(yīng)用中正在進(jìn)行的 API 請求數(shù)
error: null // 應(yīng)用全局錯誤信息
},
auth: {
userId: '59e5d570fe26fff867fc94c0', // 當(dāng)前登錄用戶的 id
username: 'jack' // 當(dāng)前登錄用戶的用戶名
},
ui: {
addDialogOpen: false, // 用于新增帖子的對話框的顯示狀態(tài)
editDialogOpen: false // 用于編輯帖子的對話框的顯示狀態(tài)
},
posts: {
allIds: [], // 維護(hù)數(shù)據(jù)的有序性
byId: {} // 根據(jù) id 獲取帖子的相關(guān)內(nèi)容
}
},
comments: {
byPost: {
'5c10b55d34ce789876fc00ed': [] // 帖子 id 與該帖子下的評價(jià) id 的映射。
},
byId: {} // 根據(jù)評論 id 獲取到的該條評論相關(guān)內(nèi)容。
}
},
users: {}
}
}
state 解讀
一共六個子 state
- app:記錄應(yīng)用業(yè)務(wù)狀態(tài)數(shù)據(jù)
- requestQuantity:當(dāng)前應(yīng)用中正在進(jìn)行的 API 請求數(shù)。
- error:應(yīng)用全局錯誤信息。
- auth:登錄認(rèn)證狀態(tài)
- userId:當(dāng)前登錄用戶的 id。
- username:當(dāng)前登錄用戶的用戶名。
- ui:UI 狀態(tài)數(shù)據(jù)
- addDialogOpen:用于新增帖子的對話框的顯示狀態(tài)。
- editDialogOpen:用于編輯帖子的對話框的顯示狀態(tài)。
- posts:帖子列表
- allIds:保存帖子列表的 id,維護(hù)數(shù)據(jù)的有序性。
- byId:以帖子 id 為 key 的列表,每一個子項(xiàng)為帖子的相關(guān)內(nèi)容。
- comments
- byPost 保存以某一個帖子的 id 為 key 的、該帖子 id 下的評論列表 id,即:帖子 id 與該帖子下的評價(jià) id 的映射。
- byId 與 posts 下的 byId 類似,該項(xiàng)是以評論 id 為 key 的列表,每一個子項(xiàng)為評論相關(guān)的內(nèi)容。
- users:當(dāng)前頁面相關(guān)的用戶信息列表
模塊設(shè)計(jì)
對應(yīng) state 的設(shè)計(jì),模塊設(shè)計(jì)基本上也出來了,除了對應(yīng)上面的六個子 state 都有相應(yīng)的模塊之外,還有一個 Redux 模塊。
Redux 模塊,位于 redux/modules 目錄下,各個功能相關(guān)的 reducer、action types、action creators 都定義到一個文件中。各個功能的 reducer 又通過 redux/modules/index.js 合并成一個根 reducer,以供 react-redux 創(chuàng)建 store 并進(jìn)行統(tǒng)一管理。
運(yùn)行時(shí)數(shù)據(jù)
首頁,即帖子列表頁面
看一下運(yùn)行起來的 state,僅保留了兩篇帖子的數(shù)據(jù)。
{
app: {
requestQuantity: 0,
error: null
},
auth: {
userId: '59e5d570fe26fff867fc94c0',
username: 'jack'
},
ui: {
addDialogOpen: false,
editDialogOpen: false
},
posts: {
allIds: [
'5c10b579a41380ad2bf95cfd',
'5c0f145bd76a23857943793c'
],
byId: {
'5c10b579a41380ad2bf95cfd': {
id: '5c10b579a41380ad2bf95cfd',
title: 'fs',
vote: 0,
updatedAt: '2018-12-12T07:16:10.863Z',
author: '59e5d570fe26fff867fc94c0'
},
'5c0f145bd76a23857943793c': {
id: '5c0f145bd76a23857943793c',
title: '',
vote: 0,
updatedAt: '2018-12-11T01:35:23.187Z',
author: '59e5d570fe26fff867fc94c0'
}
}
},
comments: {
byPost: {},
byId: {}
},
users: {
'59e5d22f6722f75272b3bbcf': {
id: '59e5d22f6722f75272b3bbcf',
username: 'tom'
},
'59e5d570fe26fff867fc94c0': {
id: '59e5d570fe26fff867fc94c0',
username: 'jack'
}
}
}
帖子詳情頁面
再看有三條評論的帖子,進(jìn)入詳情頁面時(shí),state 的內(nèi)容。帖子是 jack 發(fā)的,三條評論都是 tom 發(fā)的,所以users有兩個用戶的信息。
{
app: {
requestQuantity: 0,
error: null
},
auth: {
userId: '59e5d570fe26fff867fc94c0',
username: 'jack'
},
ui: {
addDialogOpen: false,
editDialogOpen: false
},
posts: {
allIds: [],
byId: {
'5c10b55d34ce789876fc00ed': {
id: '5c10b55d34ce789876fc00ed',
title: 'asd',
content: 'ads',
vote: 0,
updatedAt: '2018-12-12T07:14:37.395Z',
author: '59e5d570fe26fff867fc94c0'
}
}
},
comments: {
byPost: {
'5c10b55d34ce789876fc00ed': [
'5c1215a60dbbbd7c0a640389',
'5c12159eb50852373a0d1a9e',
'5c12151f9536aad30f94f59f'
]
},
byId: {
'5c1215a60dbbbd7c0a640389': {
id: '5c1215a60dbbbd7c0a640389',
content: 'sadf',
updatedAt: '2018-12-13T08:17:42.783Z',
author: '59e5d22f6722f75272b3bbcf'
},
'5c12159eb50852373a0d1a9e': {
id: '5c12159eb50852373a0d1a9e',
content: 'sad',
updatedAt: '2018-12-13T08:17:34.272Z',
author: '59e5d22f6722f75272b3bbcf'
},
'5c12151f9536aad30f94f59f': {
id: '5c12151f9536aad30f94f59f',
content: 'sa',
updatedAt: '2018-12-13T08:15:27.067Z',
author: '59e5d22f6722f75272b3bbcf'
}
}
},
users: {
'59e5d570fe26fff867fc94c0': {
id: '59e5d570fe26fff867fc94c0',
username: 'jack'
},
'59e5d22f6722f75272b3bbcf': {
id: '59e5d22f6722f75272b3bbcf',
username: 'tom'
}
}
}
對應(yīng)源碼結(jié)構(gòu)(模塊設(shè)計(jì))
源碼結(jié)構(gòu)與state設(shè)計(jì)是相輔相成的。
│ index.js
│
├─components // 全局通用組件
│ ├─Header
│ │ index.js
│ │ style.css
│ │
│ ├─Loading
│ │ index.js
│ │ style.css
│ │
│ └─ModalDialog
│ index.js
│ style.css
│
├─containers
│ ├─App
│ │ index.js
│ │
│ ├─Home
│ │ index.js
│ │
│ ├─Login
│ │ index.js
│ │ style.css
│ │
│ ├─Post
│ │ │ index.js // Post 容器組件
│ │ │ style.css
│ │ │
│ │ └─components // Post 專用組件
│ │ ├─CommentList
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ ├─CommentsView
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ ├─PostEditor
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ └─PostView
│ │ index.js
│ │ style.css
│ │
│ └─PostList
│ │ index.js // PostList 容器組件
│ │ style.css
│ │
│ └─components // PostList 專用組件
│ ├─PostItem
│ │ index.js
│ │ style.css
│ │
│ └─PostsView
│ index.js
│
├─images
│ like-default.png
│ like.png
│
├─redux
│ │ configureStore.js
│ │
│ └─modules
│ app.js
│ auth.js
│ comments.js
│ index.js // Redux 的根模塊,僅將其余模塊的 reducer 合并成一個根 reducer。
│ posts.js
│ ui.js
│ users.js
│
└─utils
AsyncComponent.js
connectRoute.js
date.js
request.js // 對 fetch 的封裝
SHA1.js
url.js // API 配置
關(guān)于目錄結(jié)構(gòu)設(shè)計(jì)的最佳實(shí)踐,請看:React+Redux工程目錄結(jié)構(gòu),最佳實(shí)踐
redux 模塊
redux 模塊,指的是在 redux/modules 下定義的模塊。
一個 redux 模塊,不僅包含 action types、action creators、reducers,還包含從該模塊獲取 state 數(shù)據(jù)的 selectors 函數(shù)。
selectors 函數(shù)的使命:
- 封裝:對外提供數(shù)據(jù)接口,外部調(diào)用者不需要知道內(nèi)部實(shí)現(xiàn)細(xì)節(jié),也不用關(guān)心內(nèi)部 state 的具體結(jié)構(gòu)。
- 解耦:內(nèi)部 state 結(jié)構(gòu)如果有變化,修改對外接口即可,不會影響到外部調(diào)用方,降低模塊間依賴關(guān)系,最大限度解耦。
全局 selector 函數(shù)
當(dāng)需要從多個模塊的 state 中獲取數(shù)據(jù)時(shí),最好的做法,是在 redux/module/index.js 文件中定義全局 selector 函數(shù),該 selector 再通過各個模塊的 selector 獲取需要的數(shù)據(jù)。這樣,容器組件通過調(diào)用全局 selector 函數(shù),可以非常便利地對全局?jǐn)?shù)據(jù)進(jìn)行處理。