開80服務(wù)器怎么做網(wǎng)站網(wǎng)站設(shè)計的基本原則
4、listener可以通過 store.getState() 得到當(dāng)前狀態(tài)。如果使用的是 React,這時可以觸發(fā)重新渲染 View。
function listerner() {
let newState = store.getState();
component.setState(newState);
}
對比 Flux
和 Flux 比較一下:Flux 中 Store 是各自為戰(zhàn)的,每個 Store 只對對應(yīng)的 View 負(fù)責(zé),每次更新都只通知對應(yīng)的View:
Redux 中各子 Reducer 都是由根 Reducer 統(tǒng)一管理的,每個子 Reducer 的變化都要經(jīng)過根 Reducer 的整合:
簡單來說,Redux有三大原則:單一數(shù)據(jù)源:Flux 的數(shù)據(jù)源可以是多個。State 是只讀的:Flux 的 State 可以隨便改。* 使用純函數(shù)來執(zhí)行修改:Flux 執(zhí)行修改的不一定是純函數(shù)。
Redux 和 Flux 一樣都是單向數(shù)據(jù)流。
中間件
剛才說到的都是比較理想的同步狀態(tài)。在實際項目中,一般都會有同步和異步操作,所以 Flux、Redux 之類的思想,最終都要落地到同步異步的處理中來。
在 Redux 中,同步的表現(xiàn)就是:Action 發(fā)出以后,Reducer 立即算出 State。那么異步的表現(xiàn)就是:Action 發(fā)出以后,過一段時間再執(zhí)行 Reducer。
那怎么才能 Reducer 在異步操作結(jié)束后自動執(zhí)行呢?Redux 引入了中間件 Middleware 的概念。
其實我們重新回顧一下剛才的流程,可以發(fā)現(xiàn)每一個步驟都很純粹,都不太適合加入異步的操作,比如 Reducer,純函數(shù),肯定不能承擔(dān)異步操作,那樣會被外部IO干擾。Action呢,就是一個純對象,放不了操作。那想來想去,只能在 View 里發(fā)送 Action 的時候,加上一些異步操作了。比如下面的代碼,給原來的 dispatch 方法包裹了一層,加上了一些日志打印的功能:
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
console.log(‘dispatching’, action);
next(action);
console.log(‘next state’, store.getState());}
既然能加日志打印,當(dāng)然也能加入異步操作。所以中間件簡單來說,就是對 store.dispatch 方法進(jìn)行一些改造的函數(shù)。不展開說了,所以如果想詳細(xì)了解中間件,可以點這里。
Redux 提供了一個 applyMiddleware 方法來應(yīng)用中間件:
const store = createStore(
reducer,
applyMiddleware(thunk, promise, logger));
這個方法主要就是把所有的中間件組成一個數(shù)組,依次執(zhí)行。也就是說,任何被發(fā)送到 store 的 action 現(xiàn)在都會經(jīng)過thunk,promise,logger 這幾個中間件了。
處理異步
對于異步操作來說,有兩個非常關(guān)鍵的時刻:發(fā)起請求的時刻,和接收到響應(yīng)的時刻(可能成功,也可能失敗或者超時),這兩個時刻都可能會更改應(yīng)用的 state。一般是這樣一個過程:
請求開始時,dispatch 一個請求開始 Action,觸發(fā) State 更新為“正在請求”狀態(tài),View 重新渲染,比如展現(xiàn)個Loading啥的。
請求結(jié)束后,如果成功,dispatch 一個請求成功 Action,隱藏掉 Loading,把新的數(shù)據(jù)更新到 State;如果失敗,dispatch 一個請求失敗 Action,隱藏掉 Loading,給個失敗提示。
顯然,用 Redux 處理異步,可以自己寫中間件來處理,當(dāng)然大多數(shù)人會選擇一些現(xiàn)成的支持異步處理的中間件。比如 redux-thunk 或 redux-promise 。
Redux-thunk
thunk 比較簡單,沒有做太多的封裝,把大部分自主權(quán)交給了用戶:
const createFetchDataAction = function(id) {
return function(dispatch, getState) {
// 開始請求,dispatch 一個 FETCH_DATA_START action
dispatch({
type: FETCH_DATA_START,
payload: id
})
api.fetchData(id)
.then(response => {
// 請求成功,dispatch 一個 FETCH_DATA_SUCCESS action
dispatch({
type: FETCH_DATA_SUCCESS,
payload: response
})
})
.catch(error => {
// 請求失敗,dispatch 一個 FETCH_DATA_FAILED action
dispatch({
type: FETCH_DATA_FAILED,
payload: error
})
})
}}//reducerconst reducer = function(oldState, action) {
switch(action.type) {
case FETCH_DATA_START :
// 處理 loading 等
case FETCH_DATA_SUCCESS :
// 更新 store 等
case FETCH_DATA_FAILED :
// 提示異常
}}
缺點就是用戶要寫的代碼有點多,可以看到上面的代碼比較啰嗦,一個請求就要搞這么一套東西。
Redux-promise
redus-promise 和 redux-thunk 的思想類似,只不過做了一些簡化,成功失敗手動 dispatch 被封裝成自動了:
const FETCH_DATA = ‘FETCH_DATA’//action creatorconst getData = function(id) {
return {
type: FETCH_DATA,
payload: api.fetchData(id) // 直接將 promise 作為 payload
}}//reducerconst reducer = function(oldState, action) {
switch(action.type) {
case FETCH_DATA:
if (action.status === ‘success’) {
// 更新 store 等處理
} else {
// 提示異常
}
}}
剛才的什么 then、catch 之類的被中間件自行處理了,代碼簡單不少,不過要處理 Loading 啥的,還需要寫額外的代碼。
其實任何時候都是這樣:封裝少,自由度高,但是代碼就會變復(fù)雜;封裝多,代碼變簡單了,但是自由度就會變差。redux-thunk 和 redux-promise 剛好就是代表這兩個面。
redux-thunk 和 redux-promise 的具體使用就不介紹了,這里只聊一下大概的思路。大部分簡單的異步業(yè)務(wù)場景,redux-thunk 或者 redux-promise 都可以滿足了。
上面說的 Flux 和 Redux,和具體的前端框架沒有什么關(guān)系,只是思想和約定層面。下面就要和我們常用的 Vue 或 React 結(jié)合起來了:
Vuex
Vuex 主要用于 Vue,和 Flux,Redux 的思想很類似。
Store
每一個 Vuex 里面有一個全局的 Store,包含著應(yīng)用中的狀態(tài) State,這個 State 只是需要在組件中共享的數(shù)據(jù),不用放所有的 State,沒必要。這個 State 是單一的,和 Redux 類似,所以,一個應(yīng)用僅會包含一個 Store 實例。單一狀態(tài)樹的好處是能夠直接地定位任一特定的狀態(tài)片段,在調(diào)試的過程中也能輕易地取得整個當(dāng)前應(yīng)用狀態(tài)的快照。
Vuex通過 store 選項,把 state 注入到了整個應(yīng)用中,這樣子組件能通過 this.\$store 訪問到 state 了。
const app = new Vue({
el: ‘#app’,
// 把 store 對象提供給 “store” 選項,這可以把 store 的實例注入所有的子組件
store,
components: { Counter },
template: `
`})const Counter = {
template: <div>{{ count }}</div>
,
computed: {
count () {
return this.$store.state.count
}
}}
State 改變,View 就會跟著改變,這個改變利用的是 Vue 的響應(yīng)式機(jī)制。
Mutation
顯而易見,State 不能直接改,需要通過一個約定的方式,這個方式在 Vuex 里面叫做 mutation,更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似于事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調(diào)函數(shù) (handler)。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 變更狀態(tài)
state.count++
}
}})
觸發(fā) mutation 事件的方式不是直接調(diào)用,比如 increment(state) 是不行的,而要通過 store.commit 方法:
store.commit(‘increment’)
注意:mutation 都是同步事務(wù)。
mutation 有些類似 Redux 的 Reducer,但是 Vuex 不要求每次都搞一個新的 State,可以直接修改 State,這塊兒又和 Flux 有些類似。具尤大的說法,Redux 強制的 immutability,在保證了每一次狀態(tài)變化都能追蹤的情況下強制的 immutability 帶來的收益很有限,為了同構(gòu)而設(shè)計的 API 很繁瑣,必須依賴第三方庫才能相對高效率地獲得狀態(tài)樹的局部狀態(tài),這些都是 Redux 不足的地方,所以也被 Vuex 舍掉了。
到這里,其實可以感覺到 Flux、Redux、Vuex 三個的思想都差不多,在具體細(xì)節(jié)上有一些差異,總的來說都是讓 View 通過某種方式觸發(fā) Store 的事件或方法,Store 的事件或方法對 State 進(jìn)行修改或返回一個新的 State,State 改變之后,View 發(fā)生響應(yīng)式改變。
Action
到這里又該處理異步這塊兒了。mutation 是必須同步的,這個很好理解,和之前的 reducer 類似,不同步修改的話,會很難調(diào)試,不知道改變什么時候發(fā)生,也很難確定先后順序,A、B兩個 mutation,調(diào)用順序可能是 A -> B,但是最終改變 State 的結(jié)果可能是 B -> A。
對比Redux的中間件,Vuex 加入了 Action 這個東西來處理異步,Vuex的想法是把同步和異步拆分開,異步操作想咋搞咋搞,但是不要干擾了同步操作。View 通過 store.dispatch(‘increment’) 來觸發(fā)某個 Action,Action 里面不管執(zhí)行多少異步操作,完事之后都通過 store.commit(‘increment’) 來觸發(fā) mutation,一個 Action 里面可以觸發(fā)多個 mutation。所以 Vuex 的Action 類似于一個靈活好用的中間件。
Vuex 把同步和異步操作通過 mutation 和 Action 來分開處理,是一種方式。但不代表是唯一的方式,還有很多方式,比如就不用 Action,而是在應(yīng)用內(nèi)部調(diào)用異步請求,請求完畢直接 commit mutation,當(dāng)然也可以。
Vuex 還引入了 Getter,這個可有可無,只不過是方便計算屬性的復(fù)用。
Vuex 單一狀態(tài)樹并不影響模塊化,把 State 拆了,最后組合在一起就行。Vuex 引入了 Module 的概念,每個 Module 有自己的 state、mutation、action、getter,其實就是把一個大的 Store 拆開。
總的來看,Vuex 的方式比較清晰,適合 Vue 的思想,在實際開發(fā)中也比較方便。
對比Redux
Redux:view——>actions——>reducer——>state變化——>view變化(同步異步一樣)
Vuex:view——>commit——>mutations——>state變化——>view變化(同步操作) view——>dispatch——>actions——>mutations——>state變化——>view變化(異步操作)
React-redux
Redux 和 Flux 類似,只是一種思想或者規(guī)范,它和 React 之間沒有關(guān)系。Redux 支持 React、Angular、Ember、jQuery 甚至純 JavaScript。
但是因為 React 包含函數(shù)式的思想,也是單向數(shù)據(jù)流,和 Redux 很搭,所以一般都用 Redux 來進(jìn)行狀態(tài)管理。為了簡單處理 Redux 和 React UI 的綁定,一般通過一個叫 react-redux 的庫和 React 配合使用,這個是 react 官方出的(如果不用 react-redux,那么手動處理 Redux 和 UI 的綁定,需要寫很多重復(fù)的代碼,很容易出錯,而且有很多 UI 渲染邏輯的優(yōu)化不一定能處理好)。
Redux將React組件分為容器型組件和展示型組件,容器型組件一般通過connect函數(shù)生成,它訂閱了全局狀態(tài)的變化,通過mapStateToProps函數(shù),可以對全局狀態(tài)進(jìn)行過濾,而展示型組件不直接從global state獲取數(shù)據(jù),其數(shù)據(jù)來源于父組件。
如果一個組件既需要UI呈現(xiàn),又需要業(yè)務(wù)邏輯處理,那就得拆,拆成一個容器組件包著一個展示組件。
因為 react-redux 只是 redux 和 react 結(jié)合的一種實現(xiàn),除了剛才說的組件拆分,并沒有什么新奇的東西,所以只拿一個簡單TODO項目的部分代碼來舉例:
入口文件 index.js,把 redux 的相關(guān) store、reducer 通過 Provider 注冊到 App 里面,這樣子組件就可以拿到 store 了。
import React from 'react’import { render } from 'react-dom’import { Provider } from 'react-redux’import { createStore } from 'redux’import rootReducer from './reducers’import App from './components/App’const store = createStore(rootReducer)
render(
,
document.getElementById(‘root’))
actions/index.js,創(chuàng)建 Action:
let nextTodoId = 0export const addTodo = text => ({
type: ‘ADD_TODO’,
id: nextTodoId++,
text})export const setVisibilityFilter = filter => ({
type: ‘SET_VISIBILITY_FILTER’,
filter})export const toggleTodo = id => ({
type: ‘TOGGLE_TODO’,
id})export const VisibilityFilters = {
SHOW_ALL: ‘SHOW_ALL’,
SHOW_COMPLETED: ‘SHOW_COMPLETED’,
SHOW_ACTIVE: ‘SHOW_ACTIVE’}
reducers/todos.js,創(chuàng)建 Reducers:
const todos = (state = [], action) => {
switch (action.type) {
case ‘ADD_TODO’:
return [
…state,
{
id: action.id,
text: action.text,
completed: false
}
]
case ‘TOGGLE_TODO’:
return state.map(todo =>
todo.id === action.id ? { …todo, completed: !todo.completed } : todo
)
default:
return state
}}export default todos
reducers/index.js,把所有的 Reducers 綁定到一起:
import { combineReducers } from 'redux’import todos from './todos’import visibilityFilter from './visibilityFilter’export default combineReducers({
todos,
visibilityFilter,
…})
containers/VisibleTodoList.js,容器組件,connect 負(fù)責(zé)連接React組件和Redux Store:
import { connect } from 'react-redux’import { toggleTodo } from '…/actions’import TodoList from '…/components/TodoList’const getVisibleTodos = (todos, filter) => {
switch (filter) {
case ‘SHOW_COMPLETED’:
return todos.filter(t => t.completed)
case ‘SHOW_ACTIVE’:
return todos.filter(t => !t.completed)
case ‘SHOW_ALL’:
default:
return todos
}}// mapStateToProps 函數(shù)指定如何把當(dāng)前 Redux store state 映射到展示組件的 props 中const mapStateToProps = state => ({
todos: getVisibleTodos(state.todos, state.visibilityFilter)})// mapDispatchToProps 方法接收 dispatch() 方法并返回期望注入到展示組件的 props 中的回調(diào)方法。const mapDispatchToProps = dispatch => ({
toggleTodo: id => dispatch(toggleTodo(id))})export default connect(
mapStateToProps,
mapDispatchToProps)(TodoList)
簡單來說,react-redux 就是多了個 connect 方法連接容器組件和UI組件,這里的“連接”就是一種映射:mapStateToProps 把容器組件的 state 映射到UI組件的 props mapDispatchToProps 把UI組件的事件映射到 dispatch 方法
Redux-saga
剛才介紹了兩個Redux 處理異步的中間件 redux-thunk 和 redux-promise,當(dāng)然 redux 的異步中間件還有很多,他們可以處理大部分場景,這些中間件的思想基本上都是把異步請求部分放在了 action creator 中,理解起來比較簡單。
redux-saga 采用了另外一種思路,它沒有把異步操作放在 action creator 中,也沒有去處理 reductor,而是把所有的異步操作看成“線程”,可以通過普通的action去觸發(fā)它,當(dāng)操作完成時也會觸發(fā)action作為輸出。saga 的意思本來就是一連串的事件。
redux-saga 把異步獲取數(shù)據(jù)這類的操作都叫做副作用(Side Effect),它的目標(biāo)就是把這些副作用管理好,讓他們執(zhí)行更高效,測試更簡單,在處理故障時更容易。
在聊 redux-saga 之前,需要熟悉一些預(yù)備知識,那就是 ES6 的 Generator。
如果從沒接觸過 Generator 的話,看著下面的代碼,給你個1分鐘傻瓜式速成,函數(shù)加個星號就是 Generator 函數(shù)了,Generator 就是個罵街生成器,Generator 函數(shù)里可以寫一堆 yield 關(guān)鍵字,可以記成“丫的”,Generator 函數(shù)執(zhí)行的時候,啥都不干,就等著調(diào)用 next 方法,按照順序把標(biāo)記為“丫的”的地方一個一個拎出來罵(遍歷執(zhí)行),罵到最后沒有“丫的”標(biāo)記了,就返回最后的return值,然后標(biāo)記為 done: true,也就是罵完了(上面只是幫助初學(xué)者記憶,別噴~)。
function* helloWorldGenerator() {
yield ‘hello’;
yield ‘world’;
return ‘ending’;}var hw = helloWorldGenerator();
hw.next() // 先把 ‘hello’ 拎出來,done: false 代表還沒罵完// { value: ‘hello’, done: false } next() 方法有固定的格式,value 是返回值,done 代表是否遍歷結(jié)束
hw.next() // 再把 ‘world’ 拎出來,done: false 代表還沒罵完// { value: ‘world’, done: false }
hw.next() // 沒有 yield 了,就把最后的 return ‘ending’ 拎出來,done: true 代表罵完了// { value: ‘ending’, done: true }
hw.next() // 沒有 yield,也沒有 return 了,真的罵完了,只能擠出來一個 undefined 了,done: true 代表罵完了// { value: undefined, done: true }
這樣搞有啥好處呢?我們發(fā)現(xiàn) Generator 函數(shù)的很多代碼可以被延緩執(zhí)行,也就是具備了暫停和記憶的功能:遇到y(tǒng)ield表達(dá)式,就暫停執(zhí)行后面的操作,并將緊跟在yield后面的那個表達(dá)式的值,作為返回的對象的value屬性值,等著下一次調(diào)用next方法時,再繼續(xù)往下執(zhí)行。用 Generator 來寫異步代碼,大概長這樣:
function* gen(){
var url = ‘https://api.github.com/users/github’;
var jsonData = yield fetch(url);
console.log(jsonData);}var g = gen();var result = g.next();
// 這里的result是 { value: fetch(‘https://api.github.com/users/github’), done: true }// fetch(url) 是一個 Promise,所以需要 then 來執(zhí)行下一步
result.value.then(function(data){
return data.json();}).then(function(data){
// 獲取到 json data,然后作為參數(shù)調(diào)用 next,相當(dāng)于把 data 傳給了 jsonData,然后執(zhí)行 console.log(jsonData);
g.next(data);});
再回到 redux-saga 來,可以把 saga 想象成開了一個以最快速度不斷地調(diào)用 next 方法并嘗試獲取所有 yield 表達(dá)式值的線程。舉個例子:
// saga.jsimport { take, put } from 'redux-saga/effects’function* mySaga(){
// 阻塞: take方法就是等待 USER_INTERACTED_WITH_UI_ACTION 這個 action 執(zhí)行
yield take(USER_INTERACTED_WITH_UI_ACTION);
// 阻塞: put方法將同步發(fā)起一個 action
yield put(SHOW_LOADING_ACTION, {isLoading: true});
// 阻塞: 將等待 FetchFn 結(jié)束,等待返回的 Promise
const data = yield call(FetchFn, ‘https://my.server.com/getdata’);
// 阻塞: 將同步發(fā)起 action (使用剛才返回的 Promise.then)
yield put(SHOW_DATA_ACTION, {data: data});}
這里用了好幾個yield,簡單理解,也就是每個 yield 都發(fā)起了阻塞,saga 會等待執(zhí)行結(jié)果返回,再執(zhí)行下一指令。也就是相當(dāng)于take、put、call、put 這幾個方法的調(diào)用變成了同步的,上面的全部完成返回了,才會執(zhí)行下面的,類似于 await。
用了 saga,我們就可以很細(xì)粒度的控制各個副作用每一部的操作,可以把異步操作和同步發(fā)起 action 一起,隨便的排列組合。saga 還提供 takeEvery、takeLatest 之類的輔助函數(shù),來控制是否允許多個異步請求同時執(zhí)行,尤其是 takeLatest,方便處理由于網(wǎng)絡(luò)延遲造成的多次請求數(shù)據(jù)沖突或混亂的問題。
saga 看起來很復(fù)雜,主要原因可能是因為大家不熟悉 Generator 的語法,還有需要學(xué)習(xí)一堆新增的 API 。如果拋開這些記憶的東西,改造一下,再來看一下代碼:
function mySaga(){
if (action.type === ‘USER_INTERACTED_WITH_UI_ACTION’) {
store.dispatch({ type: ‘SHOW_LOADING_ACTION’, isLoading: true});
const data = await Fetch(‘https://my.server.com/getdata’);
store.dispatch({ type: ‘SHOW_DATA_ACTION’, data: data});
}}
上面的代碼就很清晰了吧,全部都是同步的寫法,無比順暢,當(dāng)然直接這樣寫是不支持的,所以那些 Generator 語法和API,無非就是做一些適配而已。
saga 還能很方便的并行執(zhí)行異步任務(wù),或者讓兩個異步任務(wù)競爭:
// 并行執(zhí)行,并等待所有的結(jié)果,類似 Promise.all 的行為const [users, repos] = yield [
call(fetch, ‘/users’),
call(fetch, ‘/repos’)]// 并行執(zhí)行,哪個先完成返回哪個,剩下的就取消掉了const {posts, timeout} = yield race({
posts: call(fetchApi, ‘/posts’),
timeout: call(delay, 1000)})
saga 的每一步都可以做一些斷言(assert)之類的,所以非常方便測試。而且很容易測試到不同的分支。
這里不討論更多 saga 的細(xì)節(jié),大家了解 saga 的思想就行,細(xì)節(jié)請看文檔。
對比 Redux-thunk
比較一下 redux-thunk 和 redux-saga 的代碼:
和 redux-thunk 等其他異步中間件對比來說,redux-saga 主要有下面幾個特點:異步數(shù)據(jù)獲取的相關(guān)業(yè)務(wù)邏輯放在了單獨的 saga.js 中,不再是摻雜在 action.js 或 component.js 中。dispatch 的參數(shù)是標(biāo)準(zhǔn)的 action,沒有魔法。saga 代碼采用類似同步的方式書寫,代碼變得更易讀。代碼異常/請求失敗 都可以直接通過 try/catch 語法直接捕獲處理。* 很容易測試,如果是 thunk 的 Promise,測試的話就需要不停的 mock 不同的數(shù)據(jù)。
其實 redux-saga 是用一些學(xué)習(xí)的復(fù)雜度,換來了代碼的高可維護(hù)性,還是很值得在項目中使用的。
Dva
Dva是什么呢?官方的定義是:dva 首先是一個基于 redux 和 redux-saga 的數(shù)據(jù)流方案,然后為了簡化開發(fā)體驗,dva 還額外內(nèi)置了 react-router 和 fetch,所以也可以理解為一個輕量級的應(yīng)用框架。
簡單理解,就是讓使用 react-redux 和 redux-saga 編寫的代碼組織起來更合理,維護(hù)起來更方便。
之前我們聊了 redux、react-redux、redux-saga 之類的概念,大家肯定覺得頭昏腦漲的,什么 action、reducer、saga 之類的,寫一個功能要在這些js文件里面不停的切換。
dva 做的事情很簡單,就是讓這些東西可以寫到一起,不用分開來寫了。比如:
app.model({
// namespace - 對應(yīng) reducer 在 combine 到 rootReducer 時的 key 值
namespace: ‘products’,
// state - 對應(yīng) reducer 的 initialState
state: {
list: [],
loading: false,
},
// subscription - 在 dom ready 后執(zhí)行
subscriptions: [
function(dispatch) {
dispatch({type: ‘products/query’});
},
],
// effects - 對應(yīng) saga,并簡化了使用
effects: {
[‘products/query’]: function*() {
yield call(delay(800));
yield put({
type: ‘products/query/success’,
payload: [‘a(chǎn)nt-tool’, ‘roof’],
});
},
},
// reducers - 就是傳統(tǒng)的 reducers
reducers: {
‘products/query’ {
return { …state, loading: true, };
},
[‘products/query/success’](state, { payload }) {
return { …state, loading: false, list: payload };
},
},});
以前書寫的方式是創(chuàng)建 sagas/products.js, reducers/products.js 和 actions/products.js,然后把 saga、action、reducer 啥的分開來寫,來回切換,現(xiàn)在寫在一起就方便多了。
比如傳統(tǒng)的 TODO 應(yīng)用,用 redux + redux-saga 來表示結(jié)構(gòu),就是這樣:
saga 攔截 add 這個 action, 發(fā)起 http 請求, 如果請求成功, 則繼續(xù)向 reducer 發(fā)一個 addTodoSuccess 的 action, 提示創(chuàng)建成功, 反之則發(fā)送 addTodoFail 的 action 即可。
如果使用 Dva,那么結(jié)構(gòu)圖如下:
整個結(jié)構(gòu)變化不大,最主要的就是把 store 及 saga 統(tǒng)一為一個 model 的概念(有點類似 Vuex 的 Module),寫在了一個 js 文件里。增加了一個 Subscriptions, 用于收集其他來源的 action,比如快捷鍵操作。
app.model({
namespace: ‘count’,
state: {
record: 0,
current: 0,
},
reducers: {
add(state) {
const newCurrent = state.current + 1;
return { …state,
record: newCurrent > state.record ? newCurrent : state.record,
current: newCurrent,
};
},
minus(state) {
return { …state, current: state.current - 1};
},
},
effects: {
*add(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: ‘minus’ });
},
},
subscriptions: {
keyboardWatcher({ dispatch }) {
key(‘?+up, ctrl+up’, () => { dispatch({type:‘a(chǎn)dd’}) });
},
},});
之前我們說過約定優(yōu)于配置的思想,Dva正式借鑒了這個思想。
MobX
前面扯了這么多,其實還都是 Flux 體系的,都是單向數(shù)據(jù)流方案。接下來要說的 MobX,就和他們不太一樣了。
我們先清空一下大腦,回到初心,什么是初心?就是我們最初要解決的問題是什么?最初我們其實為了解決應(yīng)用狀態(tài)管理的問題,不管是 Redux 還是 MobX,把狀態(tài)管理好是前提。什么叫把狀態(tài)管理好,簡單來說就是:統(tǒng)一維護(hù)公共的應(yīng)用狀態(tài),以統(tǒng)一并且可控的方式更新狀態(tài),狀態(tài)更新后,View跟著更新。不管是什么思想,達(dá)成這個目標(biāo)就ok。
Flux 體系的狀態(tài)管理方式,只是一個選項,但并不代表是唯一的選項。MobX 就是另一個選項。
MobX背后的哲學(xué)很簡單:任何源自應(yīng)用狀態(tài)的東西都應(yīng)該自動地獲得。譯成人話就是狀態(tài)只要一變,其他用到狀態(tài)的地方就都跟著自動變。
看這篇文章的人,大概率會對面向?qū)ο蟮乃枷氡容^熟悉,而對函數(shù)式編程的思想略陌生。Flux 或者說 Redux 的思想主要就是函數(shù)式編程(FP)的思想,所以學(xué)習(xí)起來會覺得累一些。而 MobX 更接近于面向?qū)ο缶幊?#xff0c;它把 state 包裝成可觀察的對象,這個對象會驅(qū)動各種改變。什么是可觀察?就是 MobX 老大哥在看著 state 呢。state 只要一改變,所有用到它的地方就都跟著改變了。這樣整個 View 可以被 state 來驅(qū)動。
const obj = observable({
a: 1,
b: 2})
autoRun(() => {
console.log(obj.a)})
obj.b = 3 // 什么都沒有發(fā)生
obj.a = 2 // observe 函數(shù)的回調(diào)觸發(fā)了,控制臺輸出:2
上面的obj,他的 obj.a 屬性被使用了,那么只要 obj.a 屬性一變,所有使用的地方都會被調(diào)用。autoRun 就是這個老大哥,他看著所有依賴 obj.a 的地方,也就是收集所有對 obj.a 的依賴。當(dāng) obj.a 改變時,老大哥就會觸發(fā)所有依賴去更新。
MobX 允許有多個 store,而且這些 store 里的 state 可以直接修改,不用像 Redux 那樣每次還返回個新的。這個有點像 Vuex,自由度更高,寫的代碼更少。不過它也會讓代碼不好維護(hù)。
MobX 和 Flux、Redux 一樣,都是和具體的前端框架無關(guān)的,也就是說可以用于 React(mobx-react) 或者 Vue(mobx-vue)。一般來說,用到 React 比較常見,很少用于 Vue,因為 Vuex 本身就類似 MobX,很靈活。如果我們把 MobX 用于 React 或者 Vue,可以看到很多 setState() 和 http://this.state.xxx = 這樣的處理都可以省了。
還是和上面一樣,只介紹思想。具體 MobX 的使用,可以看這里。
對比 Redux
我們直觀地上兩坨實現(xiàn)計數(shù)器代碼:
基礎(chǔ)學(xué)習(xí):
前端最基礎(chǔ)的就是 HTML , CSS 和 JavaScript 。
網(wǎng)頁設(shè)計:HTML和CSS基礎(chǔ)知識的學(xué)習(xí)
HTML是網(wǎng)頁內(nèi)容的載體。內(nèi)容就是網(wǎng)頁制作者放在頁面上想要讓用戶瀏覽的信息,可以包含文字、圖片、視頻等。
CSS樣式是表現(xiàn)。就像網(wǎng)頁的外衣。比如,標(biāo)題字體、顏色變化,或為標(biāo)題加入背景圖片、邊框等。所有這些用來改變內(nèi)容外觀的東西稱之為表現(xiàn)。
動態(tài)交互:JavaScript基礎(chǔ)的學(xué)習(xí)
JavaScript是用來實現(xiàn)網(wǎng)頁上的特效效果。如:鼠標(biāo)滑過彈出下拉菜單。或鼠標(biāo)滑過表格的背景顏色改變。還有焦點新聞(新聞圖片)的輪換??梢赃@么理解,有動畫的,有交互的一般都是用JavaScript來實現(xiàn)的。
e.log(obj.a)})
obj.b = 3 // 什么都沒有發(fā)生
obj.a = 2 // observe 函數(shù)的回調(diào)觸發(fā)了,控制臺輸出:2
上面的obj,他的 obj.a 屬性被使用了,那么只要 obj.a 屬性一變,所有使用的地方都會被調(diào)用。autoRun 就是這個老大哥,他看著所有依賴 obj.a 的地方,也就是收集所有對 obj.a 的依賴。當(dāng) obj.a 改變時,老大哥就會觸發(fā)所有依賴去更新。
MobX 允許有多個 store,而且這些 store 里的 state 可以直接修改,不用像 Redux 那樣每次還返回個新的。這個有點像 Vuex,自由度更高,寫的代碼更少。不過它也會讓代碼不好維護(hù)。
MobX 和 Flux、Redux 一樣,都是和具體的前端框架無關(guān)的,也就是說可以用于 React(mobx-react) 或者 Vue(mobx-vue)。一般來說,用到 React 比較常見,很少用于 Vue,因為 Vuex 本身就類似 MobX,很靈活。如果我們把 MobX 用于 React 或者 Vue,可以看到很多 setState() 和 http://this.state.xxx = 這樣的處理都可以省了。
還是和上面一樣,只介紹思想。具體 MobX 的使用,可以看這里。
對比 Redux
我們直觀地上兩坨實現(xiàn)計數(shù)器代碼:
基礎(chǔ)學(xué)習(xí):
前端最基礎(chǔ)的就是 HTML , CSS 和 JavaScript 。
網(wǎng)頁設(shè)計:HTML和CSS基礎(chǔ)知識的學(xué)習(xí)
HTML是網(wǎng)頁內(nèi)容的載體。內(nèi)容就是網(wǎng)頁制作者放在頁面上想要讓用戶瀏覽的信息,可以包含文字、圖片、視頻等。
[外鏈圖片轉(zhuǎn)存中…(img-KpPet6p5-1719238503886)]
CSS樣式是表現(xiàn)。就像網(wǎng)頁的外衣。比如,標(biāo)題字體、顏色變化,或為標(biāo)題加入背景圖片、邊框等。所有這些用來改變內(nèi)容外觀的東西稱之為表現(xiàn)。
[外鏈圖片轉(zhuǎn)存中…(img-JdbdPxoQ-1719238503886)]
動態(tài)交互:JavaScript基礎(chǔ)的學(xué)習(xí)
JavaScript是用來實現(xiàn)網(wǎng)頁上的特效效果。如:鼠標(biāo)滑過彈出下拉菜單。或鼠標(biāo)滑過表格的背景顏色改變。還有焦點新聞(新聞圖片)的輪換??梢赃@么理解,有動畫的,有交互的一般都是用JavaScript來實現(xiàn)的。