1,Redux javascript
單一對象,單一store狀態樹的形式存儲數據。css
多個reducer來編輯action 經過action對象修改 store,共同維護一個根store。html
redux就是純函數,純函數,純函數,重要的事情說三遍。java
純函數做爲reducer也就是action返回新的state,更新state,這個是約定。node
中間件的嵌套要保證最後一箇中間件是返回的state對象,而後用這些函數處理並返回對象。react
2,Redux適應的場景git
單頁應用過於複雜,須要管理state,es6
包括服務區響應、本地數據、待提交數據,UI狀態,路由狀態,標籤狀態等。github
若是單純用state開發複雜度會增大。。npm
在加上 更新調優、服務端渲染、路由跳轉前調用接口等等。。複雜。。
複雜主要的兩個緣由是:變化和異步,須要redux優化一下。
redux的三大原則試圖讓state的變化可預測!
redux實踐舉例
import { createStore } from 'redux'; /* * 這是一個 reducer,形式爲 (state, action) => state 的純函數。 * 描述了 action 如何把 state 轉變成下一個 state。 */ function counter (state=0, action) { switch (action.type) { case 'INCREMENT': return state + 1; default: return state; } } // 建立store數據存儲對象,提供3個api: getState subScribe dispatch let store = createStore(counter); // 手動監聽 訂閱更新,綁定在視圖層 store.subscribe(() => { console.log(store.getState()); }); // 改變內部state 的惟一方法是觸發dispatch一個action // action 能夠被序列化,或者存儲下來方便追溯 store.dispatch({ type: 'INCREMENT' }); // 要作的修改變成了一個對象,這個對象就是action // 寫函數編輯這個action,獲得想要的store,這個函數就是reducer ! // 只有 一個store 能夠有多個reducer,想react有一個root根組件和其餘子組件 export default store;
3,核心概念
舉例,可能的state對象是這樣,一個對象,包含一個對象數組的數據。
{ todos: [{ text: 'Eat food', completed: true }, { text: 'Exercise', completed: false }], visibilityFilter: 'SHOW_COMPLETED' }
規定:發起action來修改state值,有了規定就。
action就是普通的js對象,舉例說明:
{ type: 'ADD_TODO', text: 'Go to swimming pool' } { type: 'TOGGLE_TODO', index: 1 } { type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
必定有一個type,而後還有對應的值
用 reducer函數聯繫 action 和 state,並返回新是state。
這樣的 reducer 通常可能有多個。
// 過濾,傳參是 state action function visibilityFilter(state = 'SHOW_ALL', action) { if (action.type === 'SET_VISIBILITY_FILTER') { return action.filter; } else { return state; } } // 能夠修改state並返回 // action上獲取條件,改 state並返回 function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return state.concat([{ text: action.text, completed: false }]); case 'TOGGLE_TODO': return state.map((todo, index) => // 用action中的條件,改state action.index === index ? { text: todo.text, completed: !todo.completed } : todo ) default: return state; } }
上面這兩個就是 reducer。
再開發一個reducer,像下面這樣:
function todoApp(state = {}, action) { return { todos: todos(state.todos, action), visibilityFilter: visibilityFilter(state.visibilityFilter, action) }; }
實際上是一個函數,返回的是對象,包含了以前聲明的reducer。
4,3大原則 (繼續重複這3大原則)
4.1 單一的數據源
整個應用的state保持在一個 object tree 中,而且整個 object tree 只存在於一個 store 中。(這樣同構應用開發會容易?爲啥?)。
來自客戶端的數據能夠很容易的序列化到客戶端。加快開發速度。
單一的state tree ,像撤銷和重作的操做更容易
4.2 State是只讀的
只能經過觸發 action 的方式改變state值。
這樣視圖和網絡都不能直接修改state,全部修改都被集中處理,且按照one by one 的順序執行。
所以不會出現 race condition競爭條件。
action是普通對象,全部能夠被打印、序列化、存儲、後期調試或測試時放出來。
store.dispatch({ type: 'COMPLETE_TODO', index: 1 }) store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_COMPLETED' })
4.3 使用純函數進行修改
reducer 是一些純函數,它接收以前的state和action,隨着應用變大會有不少 reducer。能夠這樣寫。
import { combineReducers, createStore } from 'redux' let reducer = combineReducers({ visibilityFilter, todos }) let store = createStore(reducer)
5,先前技術
先不急猥瑣發育。對比其餘框架。
看下redux以前的數據管理技術方案。
Flux
redux的靈感來源Flux的幾個特性。redux規定將模型的更新集中於一個特定的層。
redux和flux都不容許直接修改數據。
(state, action) => state ,這點兩個框架同樣。
支持用純函數,若是是不純的函數,時間旅行、記錄/回放或熱加載不可實現。
Elm
是一種編程語言,受到Haskell語言啓發,遵循 (state, action) => state, 是 modal-view-update 的架構。
Elm 語言具備更好的 執行純度、靜態類型、不可變性、action、匹配模式等方面更具優點。
Immutable
這是一個不可變數據結構的庫文件。看過react不可變數據的都知道。
和 redux對接的也很好
和backbone不一樣,backbone是可變數據
Baobab
Baobab是另外一個優秀的庫,實現了數據的不可變特性的api,用於更新純js對象但方式和redux不一樣
Rx
Reactive Extensions 一種重寫的現代化方案,管理複雜異步應用。
Rx 提供構建將人機交互轉變爲 相互依賴可觀測變量的庫。
專家建議能夠同時使用它和redux庫。
組合獲得一個新的action,在提交給 store.dispatch ()
擴展閱覽
redux開發工具-> https://github.com/zalmoxisus/redux-devtools-extension/releases
redux項目引入開發工具 -> https://github.com/zalmoxisus/redux-devtools-extension#usage
redux 源碼閱讀 -> https://cdn.bootcss.com/redux/4.0.4/redux.js
6,生態系統
redux 生態系統體小精湛,還有很與之相匹配的庫,https://github.com/xgrommx/awesome-redux
包括:中間件、工具庫、樣板示例、代碼魔板等。https://www.redux.org.cn/docs/introduction/Ecosystem.html
而後不少示例,暫時不看。
7,基礎
7.1 Action
const action = { type: ADD_TODO, text: 'Build my first Redux app', index: 1, filter: SHOW_COMPLETED } import { ADD_TODO, REMOVE_TODO, SHOW_COMPLETED } from '../actionTypes' // 不是超大文件能夠不用這樣寫。
7.2 Reducer
指定了狀態如何發生變化和響應的store的,action表示有什麼事發生,reducer是具體操做。
處理reducer關係時,注意應該儘可能state範式化,不存在嵌套,放在一個對象,不一樣實體或列表用id引用。
(previousState, action) => newState
---------------------------
永遠不要在reducer函數中作這些事:
a, 修改傳參。
b, 執行反作用操做。
c, 調用非純函數,random() now() 等。
--------------------------------
特別純:只要傳入參數相同,返回計算獲得的下一個 state 就必定相同。沒有特殊狀況、沒有反作用,沒有 API 請求、沒有變量修改,單純執行計算。
const todoApp = combineReducers({ visibilityFilter, todos }); const reducer = combineReducers({ a: doSomethingWithA, b: processB, c: c }); function reducer(state = {}, action) { return { a: doSomethingWithA(state.a, action), b: processB(state.b, action), c: c(state.c, action) } };
es6簡介寫法。
import { combineReducers } from 'redux' import * as reducers from './reducers' const todoApp = combineReducers(reducers)
8,Store
提供:
unsubscribe() //中止監聽更新
9,實現容器組件
把展現組件和redux 關聯起來,原理上就是 store.subscribe 從 redux.state 中讀取數據做爲props。
而後渲染展現組件。
connect方法作了性能優化,相似componentShouldUpdate。
須要先定義 mapStateToProps 這個函數來指定如何把state值映射到組件的props。
這個函數返回props須要的對象。
const mapStateToProps = state => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } }
還能夠分發action,定義 mapDispatchToProps方法返回 dispatch 傳給展現組件props的回調方法。
傳入一個dispatch 並返回一個有事件的對象。
const mapDispatchToProps = dispatch => { return { onTodoClick: id => { dispatch(toggleTodo(id)) } } }
如今就有了展現組件須要的 props和執行函數,還須要一步把這些集成到組件上。
import { connect } from 'react-redux'; import { TodoList } from './TodoList.jsx'; const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList); export default VisibleTodoList;
這樣就能夠和react結合使用。將視圖和邏輯混合使用。
import React from 'react' import { connect } from 'react-redux' import { addTodo } from '../actions' let AddTodo = ({ dispatch }) => { let input return ( <div> <form onSubmit={e => { e.preventDefault() if (!input.value.trim()) { return } // 提交的是 dispatch,去更新state模擬提交數據。 dispatch(addTodo(input.value)) input.value = '' }} > <input ref={node => { input = node }} /> <button type="submit"> Add Todo </button> </form> </div> ) } // connect() 返回一個函數,函數的傳參綁定了store 的api。 AddTodo = connect()(AddTodo) export default AddTodo
用react-redux 的 Provider 對根組件封裝,傳入store
import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App' // 建立一個store let store = createStore(todoApp) render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
----------------------------------------------
高級部分
1,異步action
當調用異步action時,須要注意兩個注意的時間點:發起和響應。通常要dispatch三種action
這個過程可使用這種囉嗦的方式定義:
不一樣的 status,可使用https://github.com/redux-utilities/redux-actions 輔助庫返回reducer。
{ type: 'FETCH_POSTS' } { type: 'FETCH_POSTS', status: 'error', error: 'Oops' } { type: 'FETCH_POSTS', status: 'success', response: {
異步action就是在異步完成或結束以後執行dispatch。
redux結合使用的異步請求庫通常用 cross_fetch。一點都很差用,還要引入補丁 babel-polyfill。
2,中間件
引入中間件的store能夠支持異步流,好比redux-promise,redux-thunk能夠異步處理action
須要注意的是,applyMiddleware 加強createStore中,攔截promise,最後一個middleware必須是返回對象。
在action發起後,到達reducer以前的擴展點。
中間件能夠記錄日誌,建立報告,異步接口或路由等。
import thunkMiddleware from 'redux-thunk' import { createLogger } from 'redux-logger' import { createStore, applyMiddleware } from 'redux' import { selectSubreddit, fetchPosts } from './actions' import rootReducer from './reducers' const loggerMiddleware = createLogger() const store = createStore( rootReducer, applyMiddleware( thunkMiddleware, // 容許咱們 dispatch() 函數 loggerMiddleware // 一個很便捷的 middleware,用來打印 action 日誌 ) )
3,語法儘可能符合es6規範
4,服務端渲染
兩個部分:__INITIAL_STATE__ 和 renderToString。
// 引入服務端渲染接口 import { renderToString } from 'react-dom/server' function handleRender(req, res) { // 建立Redux store 實例 const store = createStore(counterApp); // 把組件渲染 HTML 字符串 const html = renderToString( <Provider store={store}> <App /> </Provider> ) // 從 store 中得到初始 state const preloadedState = store.getState(); // 把渲染後的頁面內容發送給客戶端 res.send(renderFullPage(html, preloadedState)); } 客戶端能夠經過 window.__INITIAL_STATE_ 獲取state值。 function renderFullPage(html, preloadedState) { return ` <!doctype html> <html> <head> <title>Redux Universal Example</title> </head> <body> <div id="root">${html}</div> <script> window.__INITIAL_STATE__ = ${JSON.stringify(preloadedState)} </script> <script src="/static/bundle.js"></script> </body> </html> ` }
5,jest 編寫測試
由於redux代碼大部分都是純函數,純函數比較好測。
和其餘jest mocha 測試同樣,須要編寫測試用例,斷言查看結果。
import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import * as actions from '../../actions/TodoActions' import * as types from '../../constants/ActionTypes' import nock from 'nock' import mocha from 'mocha' // 你可使用任何測試庫 const middlewares = [ thunk ] const mockStore = configureMockStore(middlewares) describe('async actions', () => { afterEach(() => { nock.cleanAll() }) it('建立對 action: FETCH_TODOS_SUCCESS 是否會完成 fetch 的判斷', () => { nock('http://example.com/') .get('/todos') .reply(200, { body: { todos: ['do something'] }}) const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) return store.dispatch(actions.fetchTodos()) .then(() => { // 異步 actions 的返回 expect(store.getActions()).toEqual(expectedActions) }) }) })
6,實現數據撤銷和可追溯
指望的數據結構
{ past: [0,1,2,3], preset: 4, feature: [5,6] }
引入插件 redux-undo 保留數據支持撤銷
npm install --save redux-undo
使用undoable()
import undoable, { distinctState } from 'redux-undo' const todos = (state = [], action) => { } const undoableTodos = undoable(todos, { filter: distinctState() }); export default undoableTodos;