標籤:
上節課,我們介紹了一些es6的新文法:react+redux教程(三)reduce()、filter()、map()、some()、every()、...展開屬性
今天我們通過解讀redux-undo的官方範例程式碼來學習,在redux中使用撤銷功能、devtools功能、以及router。
例子
這個例子是個計數器程式,包含計數器、右邊的redux開發工具、還有一個路由(不過只有“/”這一個地址)。
原始碼:
https://github.com/lewis617/myReact/tree/master/redux-undo-boilerplate
撤銷
實現撤銷功能非常簡單,你只需要使你的reducers可以撤銷就可以了。這是什麼意思呢?看代碼
reducers/index.js
import { combineReducers } from ‘redux‘import counter from ‘./counter‘import { INCREMENT_COUNTER, DECREMENT_COUNTER, UNDO_COUNTER, REDO_COUNTER} from ‘../actions/counter‘import undoable, { includeAction } from ‘redux-undo‘const rootReducer = combineReducers({ counter: undoable(counter, { filter: includeAction([INCREMENT_COUNTER, DECREMENT_COUNTER]), limit: 10, debug: true, undoType: UNDO_COUNTER, redoType: REDO_COUNTER })})export default rootReducer
我們使用redux-undo這個包給我們提供的undoable和includeAction,就可以可以給指定reducer(counter)添加撤銷功能。filter是選擇過濾的action有哪些,這裡我們只撤銷重做加減action,也就是INCREMENT_COUNTER, DECREMENT_COUNTER,
limit是次數限制,debug是是否調試碼,undotype和redotype是撤銷重做的action。
如此以來,我只需要觸發撤銷重做的action便可以實現撤銷重做功能,就是這麼簡單!
devtools
接下來,我們開始學習使用devtools這個功能,devtools是什嗎?devtools的實質其實也是組件。devtools能幹什嗎?devtools可以協助我們看到整個程式的狀態和整個程式的觸發的action的日誌記錄。我們如何安裝devtools呢?首先,我們知道devtools是個組件,那麼我們直接把devtools放在容器中渲染出來不就可以了嗎?
containers/DevTools.js
/*eslint-disable*/import React from ‘react‘import { createDevTools } from ‘redux-devtools‘import LogMonitor from ‘redux-devtools-log-monitor‘import DockMonitor from ‘redux-devtools-dock-monitor‘/*eslint-enable*/export default createDevTools( <DockMonitor toggleVisibilityKey="H" changePositionKey="Q"> <LogMonitor /> </DockMonitor>)
這是一個可以複用的容器代碼,也就意味著,你可以直接把這個js檔案,複製粘貼到你的項目中。這段代碼我們輸出了一個devtools組件。
containers/Root.js
/* global __DEVTOOLS__ *//*eslint-disable*/import React, { Component, PropTypes } from ‘react‘import { Provider } from ‘react-redux‘import { Router } from ‘react-router‘import configureStore from ‘../store/configureStore‘import routes from ‘../routes‘/*eslint-enable*/const store = configureStore()function createElements (history) { const elements = [ <Router key="router" history={history} children={routes} /> ] if (typeof __DEVTOOLS__ !== ‘undefined‘ && __DEVTOOLS__) { /*eslint-disable*/ const DevTools = require(‘./DevTools‘) /*eslint-enable*/ elements.push(<DevTools key="devtools" />) } return elements}export default class Root extends Component { static propTypes = { history: PropTypes.object.isRequired } render () { return ( <Provider store={store} key="provider"> <div> {createElements(this.props.history)} </div> </Provider> ) }}
這段代碼,我們將我們匯出的devtools組件放在了router這個組件的下面,不過我們加了一個typeof __DEVTOOLS__ !== ‘undefined‘ && __DEVTOOLS__的判斷,如果條件成立,我們將渲染devtools,否則不渲染。這樣做,意味著我們可以通過參數控制devtools在開發環境中顯示,在生產環境中不顯示。
是不是渲染出來,就可以了?當然不是!我們還需要在store裡面註冊!
store/configureStore.js
/* global __DEVTOOLS__ */import { createStore, applyMiddleware, compose } from ‘redux‘// reducerimport rootReducer from ‘../reducers‘// middlewareimport thunkMiddleware from ‘redux-thunk‘import promiseMiddleware from ‘redux-promise‘import createLogger from ‘redux-logger‘const loggerMiddleware = createLogger({ level: ‘info‘, collapsed: true})const enforceImmutableMiddleware = require(‘redux-immutable-state-invariant‘)()let createStoreWithMiddlewareif (typeof __DEVTOOLS__ !== ‘undefined‘ && __DEVTOOLS__) { const { persistState } = require(‘redux-devtools‘) const DevTools = require(‘../containers/DevTools‘) createStoreWithMiddleware = compose( applyMiddleware( enforceImmutableMiddleware, thunkMiddleware, promiseMiddleware, loggerMiddleware ), DevTools.instrument(), persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)) )(createStore)} else { createStoreWithMiddleware = compose( applyMiddleware(thunkMiddleware, promiseMiddleware) )(createStore)}/** * Creates a preconfigured store. */export default function configureStore (initialState) { const store = createStoreWithMiddleware(rootReducer, initialState) if (module.hot) { // Enable Webpack hot module replacement for reducers module.hot.accept(‘../reducers‘, () => { const nextRootReducer = require(‘../reducers/index‘) store.replaceReducer(nextRootReducer) }) } return store}
到此為止,devtools我們就安裝好了,就是這麼簡單!把它渲染出來就可以了,可以放在整個程式的下面就可以了!
store enhancer
DevTools.instrument() 這行代碼使得devtools可以使用了!有的同學會問,這個instrument()是什麼鬼?官方稱之為store enhancer,翻譯過來就是store加強器,跟applymiddleware是一類,都是store加強器。那麼store加強器,能幹什嗎?store加強器可以重新構建一個更牛逼的store,來替換之前的基礎版的store,讓你的程式可以增加很多別的功能,比如appllymiddleware可以給你的redux增加中介軟體,使之可以擁有非同步功能,日誌功能等!
enforceImmutableMiddleware,thunkMiddleware, promiseMiddleware, loggerMiddleware
有的同學會問,enforceImmutableMiddleware是什嗎?幹嘛用的?這個使用禁止你改變state的。什嗎?不改變state,我們如何更新狀態,redux不允許你改變state,在reducer中我們必須要返回一個新的state,而不是修改原來的state!
那個thunk是什嗎?thunk我們已經在react+redux教程(一)connect、applyMiddleware、thunk、webpackHotMiddleware裡面講過了。
那麼什麼是promiseMiddleware?這也是個中介軟體,和thunk一樣,使得你的action可以具備非同步功能。不過,我們可以發現,本例中我們並沒有用到thunk和promiseMiddleware這兩個中介軟體,本例子是個種子檔案,可以在這個基礎上拓展,所以作者提前寫好了兩個常用中介軟體,便於我們日後使用!
那麼loggerMiddleware是用來幹嘛的?顧名思義,就是用來記錄日誌的,當你添加這個中介軟體,你可以在命令列中看到相關的列印日誌!當然你可以在運行程式的時候,去掉這個中介軟體,來對比觀察它的作用!
instrument()與compose()寫法
instrument()不同於applymiddleware,它只能用於開發環境,只能enable你的devtools組件!那麼我們把applymiddleware和instrument用逗號隔開,為什嗎?這是compose寫法,用來代替以前的函數嵌套!
router
簡單來說,router也是個組件,一個多重視圖的組件,這個組件可以通過切換url來切換視圖,總之它還是個組件。既然是組件,我們只要把它渲染出來就可以了!
最頂層我們要渲染一個Router ,代碼在containers/Root.js中,我們就不重複列出代碼清單了。
然後我們開始渲染各個視圖,這裡我們只有一個視圖,也就是目錄是“/”的視圖,我們把它渲染出來!
routes.js
/*eslint-disable*/import React from ‘react‘import { Route } from ‘react-router‘import App from ‘./containers/App‘import * as containers from ‘./containers‘/*eslint-enable*/const { CounterPage} = containersexport default ( <Route component={App}> <Route path="/" component={CounterPage} /> </Route>)
我們匯出了一個視圖,這個視圖的組件是CounterPage。就是這麼簡單!
然後,我們在containers/Root.js,將它渲染到Router組件裡面就可以了!
<Router key="router" history={history} children={routes} />
我們可以發現,我們並沒有把devtools這個組件放在路由群組件裡面。這意味著,無論你如何切換視圖,devtools都一直會渲染出來!
當然react-router的api還有很多,我們只是用了很少一部分,我不建議專門閱讀api文檔,應該在項目中,遇到不會的查詢api文檔,這樣對api的用法的理解會更加的深刻!
react+redux教程(四)undo、devtools、router