dva 是基于 redux 最佳實(shí)踐 實(shí)現(xiàn)的 framework颊亮,簡(jiǎn)化使用 redux 和 redux-saga 時(shí)很多繁雜的操作
數(shù)據(jù)流向
數(shù)據(jù)的改變發(fā)生通常是通過(guò)用戶交互行為或者瀏覽器行為(如路由跳轉(zhuǎn)等)觸發(fā)的,當(dāng)此類行為會(huì)改變數(shù)據(jù)的時(shí)候可以通過(guò) dispatch 發(fā)起一個(gè) action,如果是同步行為會(huì)直接通過(guò) Reducers 改變 State ,如果是異步行為(副作用)會(huì)先觸發(fā) Effects 然后流向 Reducers 最終改變 State,所以在 dva 中狗唉,數(shù)據(jù)流向非常清晰簡(jiǎn)明,并且思路基本跟開源社區(qū)保持一致
Modul
Subscription
Subscriptions 是一種從 源 獲取數(shù)據(jù)的方法涡真,它來(lái)自于 elm分俯。
Subscription 語(yǔ)義是訂閱,用于訂閱一個(gè)數(shù)據(jù)源哆料,然后根據(jù)條件 dispatch 需要的 action缸剪。數(shù)據(jù)源可以是當(dāng)前的時(shí)間、服務(wù)器的 websocket 連接东亦、keyboard 輸入杏节、geolocation 變化、history 路由變化等等典阵。
subscriptions: {
setup({ dispatch, history }) {
history.listen(async ({ pathname }, action) => {
const re =
pathToRegexp('/group-member/list/:groupId').exec(pathname)
||
pathToRegexp('/group-member/del/:groupId').exec(pathname)
if (action !== 'POP' && re && re[1]) {
const groupId = +re[1]
dispatch({ type: 'initList' })
dispatch({ type: 'fetchGroupMemberList', groupId })
}
})
},
},
Effect
Effect 被稱為副作用奋渔,在我們的應(yīng)用中,最常見的就是異步操作壮啊。它來(lái)自于函數(shù)編程的概念嫉鲸,之所以叫副作用是因?yàn)樗沟梦覀兊暮瘮?shù)變得不純,同樣的輸入不一定獲得同樣的輸出歹啼。
dva 為了控制副作用的操作玄渗,底層引入了redux-sagas做異步流程控制,由于采用了generator的相關(guān)概念狸眼,所以將異步轉(zhuǎn)成同步寫法捻爷,從而將effects轉(zhuǎn)為純函數(shù)。
effects: {
* fetchGroupMemberList({ groupId }, { call, put }) {
try {
const { succeed, data: { role, member: { list: briefs } } } =
yield call(fetch.get, `${GROUP_MEMBER_URL}/${groupId}/1/${GROUP_MEMBER_PAGE_SIZE}`)
if (succeed) {
yield put({ type: 'nextList', briefs, page: 1 })
yield put({ type: 'setIdAndRole', role })
}
} catch (err) {
console.log('Error when fetch group member list', err.stack)
yield put({ type: 'app/showToast', title: '獲取群組成員列表錯(cuò)誤' })
}
},
...
},
Reducer
在 dva 中份企,reducers 聚合積累的結(jié)果是當(dāng)前 model 的 state 對(duì)象也榄。通過(guò) actions 中傳入的值,與當(dāng)前 reducers 中的值進(jìn)行運(yùn)算獲得新的值(也就是新的 state)司志。需要注意的是 Reducer 必須是純函數(shù)甜紫,所以同樣的輸入必然得到同樣的輸出,它們不應(yīng)該產(chǎn)生任何副作用骂远。并且囚霸,每一次的計(jì)算都應(yīng)該使用immutable data,這種特性簡(jiǎn)單理解就是每次操作都是返回一個(gè)全新的數(shù)據(jù)(獨(dú)立激才,純凈)拓型,所以熱重載和時(shí)間旅行這些功能才能夠使用额嘿。
reducers: {
initList(state) {
console.log('initLists')
return {
...state,
list: [],
}
},
...
},
State
State 表示 Model 的狀態(tài)數(shù)據(jù),通常表現(xiàn)為一個(gè) javascript 對(duì)象(當(dāng)然它可以是任何值)劣挫;操作的時(shí)候每次都要當(dāng)作不可變數(shù)據(jù)(immutable data)來(lái)對(duì)待册养,保證每次都是全新對(duì)象,沒(méi)有引用關(guān)系压固,這樣才能保證 State 的獨(dú)立性球拦,便于測(cè)試和追蹤變化
state: {
id: 0,
title: '全部成員',
list: [],
briefs: {},
itemCount: 1,
isManager: false,
},
Action
Action 是一個(gè)普通 javascript 對(duì)象,它是改變 State 的唯一途徑帐我。無(wú)論是從 UI 事件坎炼、網(wǎng)絡(luò)回調(diào),還是 WebSocket 等數(shù)據(jù)源所獲得的數(shù)據(jù)拦键,最終都會(huì)通過(guò) dispatch 函數(shù)調(diào)用一個(gè) action谣光,從而改變對(duì)應(yīng)的數(shù)據(jù)。action 必須帶有 type 屬性指明具體的行為芬为,其它字段可以自定義萄金,如果要發(fā)起一個(gè) action 需要使用 dispatch 函數(shù);需要注意的是 dispatch 是在組件 connect Models以后碳柱,通過(guò) props 傳入的
dispatch({ type: 'initList' })
dispatching function 是一個(gè)用于觸發(fā) action 的函數(shù),action 是改變 State 的唯一途徑熬芜,但是它只描述了一個(gè)行為莲镣,而 dipatch 可以看作是觸發(fā)這個(gè)行為的方式,而 Reducer 則是描述如何改變數(shù)據(jù)的涎拉。
在 dva 中瑞侮,connect Model 的組件通過(guò) props 可以訪問(wèn)到 dispatch,可以調(diào)用 Model 中的 Reducer 或者 Effects
Route Components
在 dva 中我們通常將其約束為 Route Components鼓拧,因?yàn)樵?dva 中我們通常以頁(yè)面維度來(lái)設(shè)計(jì) Container Components半火。
所以在 dva 中,通常需要 connect Model的組件都是 Route Components季俩,組織在/routes/目錄下钮糖,而/components/目錄下則是純組件(Presentational Components)
class GroupMember extends PureComponent {
static propTypes = {
groupId: PropTypes.number.isRequired,
title: PropTypes.string,
list: PropTypes.arrayOf(PropTypes.number),
fetchNext: PropTypes.func,
briefs: PropTypes.instanceOf(Object),
removeMember: PropTypes.func,
showConfirm: PropTypes.func,
url: PropTypes.string.isRequired,
}
static defaultProps = {
title: '',
list: [],
fetchNext: () => {},
briefs: {},
isManager: false,
removeMember: () => {},
showConfirm: () => {},
}
submitRemoveMember = id => () => {
const { removeMember, groupId } = this.props
removeMember(groupId, id)
}
...
render() {
const { title, list, fetchNext, groupId } = this.props
return (
<Container>
<Navigator
title={title}
/>
<MemberListView
ref={(listView) => { this.listView = listView }}
dataSource={ds.cloneWithRows(list)}
renderRow={this.renderMember}
enableEmptySections
onEndReached={() => fetchNext(groupId)}
onEndReachedThreshold={100}
/>
</Container>
)
}
}
connect
通過(guò)connect將modul中的元素作為props的方式傳遞給component
export default connect(({ groupMember, app }, { location }) => {
const url = location.pathname
const groupId = +location.params.groupId
const userId = app.userInfo.userId
let list = [...groupMember.list]
let briefs = { ...groupMember.briefs }
if (url.indexOf('del') !== -1) {
list = list.filter(each => (each !== userId))
briefs = R.dissoc(userId, briefs)
}
return { ...groupMember, groupId, url, list, briefs }
}, { ...actions, ...appActions })(GroupMember)
參考文檔: