Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。 (若是你須要一個 WordPress 框架,請查看 Redux Framework。)react
Redux 除了和 React 一塊兒用外,還支持其它界面庫。 它體小精悍(只有 2kB,包括依賴)。ios
安裝:git
npm install --save redux
yarn add redux
Redux的設計理念:Web 應用是一個狀態機,視圖與狀態是一一對應的。全部的狀態,保存在一個對象裏面。只能按照Redux提供的約定的方式對狀態進行編輯。github
Store 就是保存數據的地方,你能夠把它當作一個容器。整個應用只能有一個 Store。另外Store是整個Redux的統一操做的入口。web
Redux 提供createStore這個函數,用來生成 Store。chrome
import { createStore } from 'redux';
const store = createStore(fn);
上面代碼中,createStore函數接受另外一個函數做爲參數,返回新生成的 Store 對象。npm
Store對象包含全部數據。若是想獲得某個時點的數據,就要對 Store 生成快照。這種時點的數據集合,就叫作 State。json
當前時刻的 State,能夠經過store.getState()拿到。redux
import { createStore } from 'redux'; const store = createStore(fn); const state = store.getState();
Redux 規定, 一個 State 對應一個 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什麼樣,反之亦然。axios
State 的變化,會致使 View 的變化。可是,用戶接觸不到 State,只能接觸到 View。因此,State 的變化必須是 View 致使的。Action 就是 View 發出的通知,表示 State 應該要發生變化了。
Action 是一個對象。其中的type屬性是必須的,表示 Action 的名稱。其餘屬性能夠自由設置,社區有一個規範能夠參考。
const action = { type: 'ADD_TODO', payload: 'Learn Redux' };
上面代碼中,Action 的名稱是ADD_TODO,它攜帶的信息是字符串Learn Redux。
能夠這樣理解,Action 描述當前發生的事情。改變 State 的惟一辦法,就是使用 Action。它會運送數據到 Store。
View 要發送多少種消息,就會有多少種 Action。若是都手寫,會很麻煩。能夠定義一個函數來生成 Action,這個函數就叫 Action Creator。
const ADD_TODO = '添加 TODO'; function addTodo(text) { return { type: ADD_TODO, text } } const action = addTodo('Learn Redux');
上面代碼中,addTodo函數就是一個 Action Creator。
Store 收到 Action 之後,必須給出一個新的 State,這樣 View 纔會發生變化。這種 State 的計算過程就叫作 Reducer。
Reducer 是一個函數,它接受 Action 和當前 State 做爲參數,返回一個新的 State
const reducer = function (state, action) { // ... return new_state; };
store.dispatch()
是 View 發出 Action 的惟一方法。
import { createStore } from 'redux'; const store = createStore(fn); store.dispatch({ type: 'ADD_TODO', payload: 'Learn Redux' });
上面代碼中,store.dispatch接受一個 Action 對象做爲參數,將它發送出去。
結合 Action Creator,這段代碼能夠改寫以下。
store.dispatch(addTodo('Learn Redux'));
用一個圖來完整的展示他們之間的關係:
Store 容許使用store.subscribe方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數。
import { createStore } from 'redux'; const store = createStore(reducer); store.subscribe(listener); 顯然,只要把 View 的更新函數(對於 React 項目,就是組件的render方法或setState方法)放入listen,就會實現 View 的自動渲染。 store.subscribe方法返回一個函數,調用這個函數就能夠解除監聽。
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
);
unsubscribe();
store是整個Redux的操做的入口。構建Store的時候還能夠指定中間件和Reducer及默認的state。
import { createStore } from 'redux';
let store = createStore(reducer);
import { createStore } from 'redux';
let store = createStore(reducer, initialState);
applyMiddleware
import { applyMiddleware, createStore } from 'redux'; import createLogger from 'redux-logger'; // 日誌中間件 const store = createStore( reducer, initial_state, applyMiddleware(logger) );
固然能夠構建帶多箇中間件的store
const store = createStore(
reducer,
applyMiddleware(thunk, promise, logger)
);
store.getState()
獲取整個狀態數據對象。store.dispatch()
分發Actionstore.subscribe()
訂閱狀態數據的變化import { createStore } from 'redux';
let { subscribe, dispatch, getState } = createStore(reducer);
import React, { Component } from 'react' import {createStore, combineReducers} from 'redux'; const ActionTypes = { ADD_NUM: 'ADD_NUM', MINUSE_NUM: 'MINUSE_NUM' }; const ActionCreators = { AddNum(num) { return { type: ActionTypes.ADD_NUM, payload: num } }, MinusNum(num) { return { type: ActionTypes.MINUSE_NUM, payload: num } } } // 狀態樹中就只有一個值 Num的值。 const numReducer = (state=0, action) => { switch(action.type) { case ActionTypes.ADD_NUM : return state + action.payload; case ActionTypes.MINUSE_NUM : return state - action.payload default: return state; } }; const store = createStore(numReducer); class Count extends Component { constructor (props, context) { super(props, context) this.state ={ Num: 0 } } componentDidMount() { // 訂閱store的變化。 store.subscribe(() => { this.setState({ Num: store.getState() // 獲取最新的state的狀態 }) }); } render () { return ( <div> <p>{ store.getState() }</p> <p>{ this.state.Num }</p> <button onClick={ () => { store.dispatch(ActionCreators.AddNum(1)) }} > +1 </button> <button onClick={ () => { store.dispatch(ActionCreators.MinusNum(1)) }} > -1 </button> </div> ) } } export default Count
Reducer 函數負責生成 State。因爲整個應用只有一個 State 對象,包含全部數據,對於大型應用來講,這個 State 必然十分龐大,致使 Reducer 函數也十分龐大。
redux提供了combineReducers
方法協助咱們把狀態對應的Reducer進行拆分。單獨狀態對應的Reducer進行單獨編寫。combineReducers
能夠將各個子 Reducer 函數合成一個大的 Reducer。
import { combineReducers } from 'redux'; const chatReducer = combineReducers({ chatLog, statusMessage, userName }) export default todoApp;
合併的Reducer中的key就是咱們的狀態樹中的屬性名。
例如:
//首頁得文字 function titleReducer(state = 'aicoder全棧實習', action) { if (action.type === 'EDIT_APP_TITLE') { return action.payload; } else { return state; } } function LoginUserReducer(state = null, action) { if (action.type === 'LOGIN') { return action.payload; } else { return state; } } import { combineReducers } from 'redux'; const chatReducer = combineReducers({ Title: titleReducer, LoginUser: LoginUserReducer })
....
redux-thunk
中間件改造了redux的dispatch方法容許咱們用store.dispatch(fn)
, fn
能夠是一個函數。並且此函數能夠接受兩個參數:dispatch
、getState
作爲參數。
安裝
npm install redux-thunk
配置中間件
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import reducer from './reducers'; // Note: this API requires redux@>=3.1.0 const store = createStore( reducer, applyMiddleware(thunk) ); const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; function increment() { return { type: INCREMENT_COUNTER }; } function incrementAsync() { return dispatch => { setTimeout(() => { // Yay! Can invoke sync or async actions with `dispatch` dispatch(increment()); }, 1000); }; } store.dispatch(incrementAsync());
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import reducer from './reducers'; // Note: this API requires redux@>=3.1.0 const store = createStore( reducer, applyMiddleware(thunk) ); // 異步刪除的方法 function asyncDelStu(stuId) { return (dispatch, getState) => { return axios .post('http://yapi.demo.qunar.com/mock/7378/api/delstu') .then(res => { // getState dispatch(delStu(stuId)); }) .catch((res) => { console.log(res); }); } } store .dispatch(asyncDelStu(33)) // 執行完成dispatch後,若是內部有返回值,此處還能夠拿到返回值的結果。 .then(res => { console.log(res.data) }) .catch(e => {})
import React, {Component} from 'react' import {createStore, applyMiddleware} from 'redux'; import thunk from 'redux-thunk' const ActionTypes = { ADD_NUM: 'ADD_NUM', MINUSE_NUM: 'MINUSE_NUM', INIT_NUM: 'INIT_NUM' }; const ActionCreators = { AddNum(num) { return {type: ActionTypes.ADD_NUM, payload: num} }, MinusNum(num) { return {type: ActionTypes.MINUSE_NUM, payload: num} }, AddNumAsync(num) { // ** 核心: 添加異步增長的方法 return (dispatch, getState) => { return new Promise((resolve, reject) => { setTimeout(() => { dispatch(ActionCreators.AddNum(num)); resolve(num); }, 1000); }); } } } const numReducer = (state = 0, action) => { switch (action.type) { case ActionTypes.ADD_NUM: return state + action.payload; case ActionTypes.MINUSE_NUM: return state - action.payload default: return state; } }; const store = createStore(numReducer,applyMiddleware(thunk)); class Count extends Component { constructor(props, context) { super(props, context) this.state = { Num: 0 } } componentDidMount() { store.subscribe(() => { this.setState({ Num: store.getState() }) }); } render() { return ( <div> <p>{store.getState()}</p> <p>{this.state.Num}</p> <button onClick={() => { store.dispatch(ActionCreators.AddNum(1)) }}> +1 </button> <button onClick={() => { store .dispatch(ActionCreators.AddNumAsync(2)) // dispatch一個函數 .then(res => console.log(res)) .catch(e => console.log(e)) }}> async+1 </button> <button onClick={() => { store.dispatch(ActionCreators.MinusNum(1)) }}> -1 </button> </div> ) } } export default Count
redux-promise 中間件 既然 Action Creator 能夠返回函數,固然也能夠返回其餘值。另外一種異步操做的解決方案,就是讓 Action Creator 返回一個 Promise 對象。
這就須要使用redux-promise中間件。
import { createStore, applyMiddleware } from 'redux'; import promiseMiddleware from 'redux-promise'; import reducer from './reducers'; const store = createStore( reducer, applyMiddleware(promiseMiddleware) );
這個中間件使得store.dispatch方法能夠接受 Promise 對象做爲參數。這時,Action Creator 有兩種寫法。寫法一,返回值是一個 Promise 對象。
const fetchPosts = (dispatch, postTitle) => new Promise(function (resolve, reject) { dispatch(requestPosts(postTitle)); return fetch(`/some/API/${postTitle}.json`) .then(response => { type: 'FETCH_POSTS', payload: response.json() }); });
import React, {Component} from 'react' import {createStore, combineReducers, applyMiddleware} from 'redux'; import thunk from 'redux-thunk' import redudxPromise from 'redux-promise'; import {composeWithDevTools} from 'redux-devtools-extension'; const ActionTypes = { ADD_NUM: 'ADD_NUM', MINUSE_NUM: 'MINUSE_NUM', INIT_NUM: 'INIT_NUM' }; const ActionCreators = { AddNum(num) { return {type: ActionTypes.ADD_NUM, payload: num} }, MinusNum(num) { return {type: ActionTypes.MINUSE_NUM, payload: num} }, AddNumAsync(num) { return (dispatch, getState) => { return new Promise((resolve, reject) => { setTimeout(() => { dispatch(ActionCreators.AddNum(num)); resolve(num); }, 1000); }); } }, MinusNumAsyncPromise(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(ActionCreators.MinusNum(num)); }, 1000); }) } } const numReducer = (state = 0, action) => { switch (action.type) { case ActionTypes.ADD_NUM: return state + action.payload; case ActionTypes.MINUSE_NUM: return state - action.payload default: return state; } }; // 此處因爲是開發階段用了redex的開發工具因此不用考慮如下的變化。 const store = createStore(numReducer, composeWithDevTools(applyMiddleware(thunk, redudxPromise), // other store enhancers if any )); class Count extends Component { constructor(props, context) { super(props, context) this.state = { Num: 0 } } componentDidMount() { store.subscribe(() => { this.setState({ Num: store.getState() }) }); } render() { return ( <div> <p>{store.getState()}</p> <p>{this.state.Num}</p> <button onClick={() => { store.dispatch(ActionCreators.AddNum(1)) }}> +1 </button> <button onClick={() => { store .dispatch(ActionCreators.AddNumAsync(2)) .then(res => console.log(res)) .catch(e => console.log(e)) }}> async+1 </button> <button onClick={() => { store.dispatch(ActionCreators.MinusNum(1)) }}> -1 </button> <button onClick={() => { store.dispatch(ActionCreators.MinusNumAsyncPromise(1)) .then(res => console.log(res)) }}> async Promise -1 </button> </div> ) } } export default Count
這個庫是能夠選用的。實際項目中,你應該權衡一下,是直接使用 Redux,仍是使用 React-Redux。後者雖然提供了便利,可是須要掌握額外的 API,而且要遵照它的組件拆分規範。 React-Redux 將全部組件分紅兩大類:UI 組件(presentational component)和容器組件(container component)。
npm install --save react-redux
UI 組件有如下幾個特徵。
const Title = value => <h1>{value}</h1>;
由於不含有狀態,UI 組件又稱爲"純組件",即它純函數同樣,純粹由參數決定它的值。
React-Redux 規定,全部的 UI 組件都由用戶提供,容器組件則是由 React-Redux 自動生成。也就是說,用戶負責視覺層,狀態管理則是所有交給它。
React-Redux 提供connect方法,用於從 UI 組件生成容器組件。connect的意思,就是將這兩種組件連起來。
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
上面代碼中,TodoList是 UI 組件,VisibleTodoList就是由 React-Redux 經過connect方法自動生成的容器組件。
可是,由於沒有定義業務邏輯,上面這個容器組件毫無心義,只是 UI 組件的一個單純的包裝層。爲了定義業務邏輯,須要給出下面兩方面的信息。
(1)輸入邏輯:外部的數據(即state對象)如何轉換爲 UI 組件的參數
(2)輸出邏輯:用戶發出的動做如何變爲 Action 對象,從 UI 組件傳出去。
所以,connect方法的完整 API 以下。
import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList)
上面代碼中,connect方法接受兩個參數:mapStateToProps和mapDispatchToProps。它們定義了 UI 組件的業務邏輯。前者負責輸入邏輯,即將state映射到 UI 組件的參數(props),後者負責輸出邏輯,即將用戶對 UI 組件的操做映射成 Action。
mapStateToProps是一個函數。它的做用就是像它的名字那樣,創建一個從(外部的)state對象到(UI 組件的)props對象的映射關係。
做爲函數,mapStateToProps執行後應該返回一個對象,裏面的每個鍵值對就是一個映射。請看下面的例子。
const mapStateToProps = (state) => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } }
上面代碼中,mapStateToProps是一個函數,它接受state做爲參數,返回一個對象。這個對象有一個todos屬性,表明 UI 組件的同名參數,後面的getVisibleTodos也是一個函數,能夠從state算出 todos 的值。
下面就是getVisibleTodos的一個例子,用來算出todos。
const getVisibleTodos = (todos, filter) => { switch (filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => !t.completed) default: throw new Error('Unknown filter: ' + filter) } }
mapStateToProps會訂閱 Store,每當state更新的時候,就會自動執行,從新計算 UI 組件的參數,從而觸發 UI 組件的從新渲染。
mapStateToProps的第一個參數老是state對象,還可使用第二個參數,表明容器組件的props對象。
// 容器組件的代碼 // <FilterLink filter="SHOW_ALL"> // All // </FilterLink> const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.visibilityFilter } }
使用ownProps做爲參數後,若是容器組件的參數發生變化,也會引起 UI 組件從新渲染。
connect方法能夠省略mapStateToProps參數,那樣的話,UI 組件就不會訂閱Store,就是說 Store 的更新不會引發 UI 組件的更新。
mapDispatchToProps是connect函數的第二個參數,用來創建 UI 組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操做應該看成 Action,傳給 Store。它能夠是一個函數,也能夠是一個對象。
若是mapDispatchToProps是一個函數,會獲得dispatch和ownProps(容器組件的props對象)兩個參數。
const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); } }; }
從上面代碼能夠看到,mapDispatchToProps做爲函數,應該返回一個對象,該對象的每一個鍵值對都是一個映射,定義了 UI 組件的參數怎樣發出 Action。
若是mapDispatchToProps是一個對象,它的每一個鍵名也是對應 UI 組件的同名參數,鍵值應該是一個函數,會被看成 Action creator ,返回的 Action 會由 Redux 自動發出。舉例來講,上面的mapDispatchToProps寫成對象就是下面這樣。
const mapDispatchToProps = { onClick: (filter) => { type: 'SET_VISIBILITY_FILTER', filter: filter }; }
<Provider>
組件connect方法生成容器組件之後,須要讓容器組件拿到state對象,才能生成 UI 組件的參數。
一種解決方法是將state對象做爲參數,傳入容器組件。可是,這樣作比較麻煩,尤爲是容器組件可能在很深的層級,一級級將state傳下去就很麻煩。
React-Redux 提供Provider組件,可讓容器組件拿到state。
import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App' let store = createStore(todoApp); render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
上面代碼中,Provider在根組件外面包了一層,這樣一來,App的全部子組件就默認均可以拿到state了。
store放在了上下文對象context上面。而後,子組件就能夠從context拿到store,代碼大體以下。
class VisibleTodoList extends Component { componentDidMount() { const { store } = this.context; this.unsubscribe = store.subscribe(() => this.forceUpdate() ); } render() { const props = this.props; const { store } = this.context; const state = store.getState(); // ... } } VisibleTodoList.contextTypes = { store: React.PropTypes.object }
React-Redux自動生成的容器組件的代碼,就相似上面這樣,從而拿到store。
App.js代碼
import React, { Component } from 'react'; import RRDemo from './components/RRDemo'; class App extends Component { render() { return ( <div> <RRDemo></RRDemo> </div> ); } } export default App;
index.js代碼
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import {Provider} from 'react-redux'; import store from './store'; ReactDOM.render( <Provider store={store}> <App></App> </Provider> , document.getElementById('root'));
store.js代碼
import {createStore, applyMiddleware} from 'redux'; import thunk from 'redux-thunk' import redudxPromise from 'redux-promise'; import {composeWithDevTools} from 'redux-devtools-extension'; export const ActionTypes = { ADD_NUM: 'ADD_NUM', MINUSE_NUM: 'MINUSE_NUM', INIT_NUM: 'INIT_NUM' }; export const ActionCreators = { AddNum(num) { return {type: ActionTypes.ADD_NUM, payload: num} }, MinusNum(num) { return {type: ActionTypes.MINUSE_NUM, payload: num} }, AddNumAsync(num) { return (dispatch, getState) => { return new Promise((resolve, reject) => { setTimeout(() => { dispatch(ActionCreators.AddNum(num)); resolve(num); }, 1000); }); } }, MinusNumAsyncPromise(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(ActionCreators.MinusNum(num)); }, 1000); }) } } const numReducer = (state = 0, action) => { switch (action.type) { case ActionTypes.ADD_NUM: return state + action.payload; case ActionTypes.MINUSE_NUM: return state - action.payload default: return state; } }; // const store = createStore(numReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && // window.__REDUX_DEVTOOLS_EXTENSION__()); const store = createStore(numReducer, composeWithDevTools(applyMiddleware(thunk, redudxPromise), // other store enhancers if any )); export default store;
組件代碼:
import React, { Component } from 'react'; import { connect } from 'react-redux'; import { ActionCreators } from '../store'; function mapStateToProps(state) { return { Num: state }; } function mapDispatchToProps(dispatch) { return { AddNum: (num) => dispatch(ActionCreators.AddNum(num)), MinusNum: (num) => dispatch(ActionCreators.MinusNum(num)), AddNumAsync: (num) => dispatch(ActionCreators.AddNumAsync(num)), MinusNumAsyn: (num) => dispatch(ActionCreators.MinusNumAsyncPromise(num)) }; } class RRDemo extends Component { render() { return ( <div> { this.props.Num } <hr/> <button onClick={ () => this.props.AddNum(1)}> +1 </button> <button onClick={() => { this.props.AddNumAsync(2) .then(res => console.log(res)) .catch(e => console.log(e)) }}> async+1 </button> <button onClick={() => { this.props.MinusNum(1) }}> -1 </button> <button onClick={() => { this.props.MinusNumAsyn(1) .then(res => console.log(res)) }}> async Promise -1 </button> </div> ); } } export default connect( mapStateToProps, mapDispatchToProps )(RRDemo);
地址: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
地址: https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/
建立store的時候,添加以下代碼
const store = createStore( reducer, /* preloadedState, */ + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );
安裝:
npm install --save-dev redux-devtools-extension
import { createStore, applyMiddleware } from 'redux'; import { composeWithDevTools } from 'redux-devtools-extension'; const store = createStore(reducer, composeWithDevTools( applyMiddleware(...middleware), // other store enhancers if any ));