redux是一個狀態管理器。在react中使用redux能夠幫咱們實現全局的數據管理。這篇文章選擇了最簡單的計數器爲例子,經過純redux的使用, react跟redux配合使用、以及異步redux從漸到深介紹redux。
首先,咱們要先理清楚基礎的概念以及工做流程。咱們須要理解的地方就是:
store.dispatch(action) => excute reducer => change state
react
store在我理解,它是一個管理者的概念。json
1.建立store: 咱們能夠用combineReducers把全部的reducer傳進去做爲參數,固然也能夠只傳一個reducerredux
const store = createStore( combineReducers({ todos, counter }) );
2.dispatch: 經過store.dispatch(action)能夠觸發對應的reducer(這裏的對應的,主要依賴於reducer裏面對action的type進行判斷)
3.subscribe: 經過store.subscribe能夠監聽state的變化。app
store.subscribe(() => console.log(store.getState()));
reducer,它是一個修改state的函數。這裏的修改,不是修改某個屬性值,而是直接返回一個新的state。能夠用咱們的...,也能夠用Object.assign。它主要負責根據action.type,修改不一樣的state屬性值。異步
const initialState = { count: 0 }; const counter = (state = initialState, action) => { switch (action.type) { case "CUSTOM_COUNT": return { count: state.count + action.count }; default: break; } };
actions就是一個純函數。在同步的action裏面,它負責返回一個對象,給dispatch執行。ide
function addOne() { return {type: 'ADD', count: 1} }
咱們寫代碼的時候,
首先肯定有多少種actions,這裏建議定義一個actionsType.js的文件。
而後寫reducer,在reducer引入actionsType.js, 裏面進行switch的時候,寫成actionsType.XXX以免拼寫錯誤問題。
而後combine reducer,建立store
最後,寫actions,引入actionsType.js,這裏面的函數就只是返回一個對象的純函數,這個對象有個type,就用actionsType.XXX,你願意返回什麼值就返回什麼值,跟reducer對的上就好啦。
僞代碼:
actionsType.js函數
export default () => ( { ADD_ONE: 'ADD_ONE' } )
reducerpost
import actionsTypes from "./actionsType.js" const reducer1 = (state = {}, action) => { switch (action.tyep) { case actionsTypes.ADD_ONE: return {...state, count: action.count} default: return state; } }
actionsfetch
import actionsTypes from "./actionsType.js" function addOne() { return {actionsTypes.ADD_ONE, count: 1} // 上面的reducer取的就是action.count,就是由於咱們這裏給它的是這樣的。 }
storethis
const store = createStore( combineReducers({ reducer1 }) ); store.dispatch(addOne())// 注意dispatch的是一個對象,因此是action執行後的結果 store.suscibe(() => console.log(store.reducer1.getState()))// 若是建立的時候不是combineReducers就能夠直接store.getState()
下面的例子爲了便於理解,就沒有分那麼多個文件了。
直接上代碼,由於具體步驟解釋上面的僞代碼應該已經寫清楚了。
import { createStore, combineReducers, bindActionCreators } from "redux"; import React from "react" function run () { const initialState = { count: 0 }; // reducer function counter (state = initialState, action) { switch (action.type) { case 'ADD': return { count: state.count + action.count} // create a new state, rather than just change the state.count case 'REDUCE': return { count: state.count - action.count} default: return state } } const store = createStore(combineReducers({counter})) store.subscribe(() => { console.log(store.getState()) }) //action function addOne() { return {type: 'ADD', count: 1} } function reduceOne() { return {type: 'REDUCE', count: 1} } // trigger store.dispatch(addOne()) // dispatch the excute result of action // in another way to trigger const dispatchReduceOne = bindActionCreators(reduceOne, store.dispatch) dispatchReduceOne() } export default () => ( <div> <button onClick={run}>Run</button> <p>* 請打開控制檯查看運行結果</p> </div> );
在react中使用redux,咱們不會再用store.subscribe去監聽值的變化,而是用react-reudx裏面的connect方法,把咱們要監聽的值和要調用的action方法做爲屬性值傳給組件。
connect方法,它是一個返回高階組件的高階組件。先看第一層,connect(mapStateToProps, mapDispatchToProps)返回咱們能夠在組件裏面使用到的state值和dispatch方法。第二層就是把咱們的組件傳遞進去,讓它具有這些值跟方法。
這裏有個須要講解的地方就是bindActionCreators,它負責把actions跟dispatch綁定在一塊兒,返回一個新的函數。這個新的函數,直接執行就至關於store.dispatch。
看源碼:
function bindActionCreator()actionCreator, dispatch) { return function () { return dispatch(actionCreator.apply(this, arguments)) } }
import { createStore, combineReducers, bindActionCreators } from "redux"; import React from "react" import { connect, Provider } from "react-redux"; const initialState = { count: 0 }; function counter (state = initialState, action) { // reducer switch (action.type) { case 'ADD': return { count: state.count + action.count} // create a new state, rather than just change the state.count case 'REDUCE': return { count: state.count - action.count} default: return state } return state } const store = createStore(combineReducers({counter})) //action function addCount(count) { return {type: 'ADD', count} } function reduceCount(count) { return {type: 'REDUCE', count} } class counterElement extends React.Component { render () { const { count, addCount, reduceCount } = this.props return <Provider store={store}> <div> <p>the count is: {count}</p> <input type="button" value="add" onClick={() => addCount(5)}/> <input type="button" value="reduce" onClick={() => reduceCount(5)}/> </div> </Provider> } } function mapStateToProps (state) { console.log(state) return { count: state.counter.count } } function mapDispatchToProps (dispatch) { return bindActionCreators({addCount, reduceCount}, dispatch) } const WrappedCounterElement = connect(mapStateToProps, mapDispatchToProps)(counterElement) class counterContainer extends React.Component { render () { return <Provider store={store}> <WrappedCounterElement/> </Provider> } } export default counterContainer
若是在action裏面調用的方法是異步的,那麼是須要經過redux中間件,先把這個action攔截了,等到它完成了,再dispatch出去。
這裏主要是咱們在建立store的時候,要使用thunk中間件。
const store = createStore( rootReducer, applyMiddleware( thunkMiddleware, // 容許咱們 dispatch() 函數 loggerMiddleware // 一個很便捷的 middleware,用來打印 action 日誌 ) )
import React from "react" import { createStore, combineReducers, applyMiddleware } from 'redux' import thunkMiddleware from 'redux-thunk' import { createLogger } from 'redux-logger' function run () { const loggerMiddleware = createLogger() const actionsType = { REQUEST_POSTS: 'REQUEST_POSTS', RECEIVE_POSTS: 'RECEIVE_POSTS', INVALIDATE_SUBREDDIT: 'INVALIDATE_SUBREDDIT' } //reducer const initialState = { isFetching: false, didInvalidate: false, items: [] } function posts( state = initialState, action ) { switch (action.type) { case actionsType.INVALIDATE_SUBREDDIT: return Object.assign({}, state, { didInvalidate: true }) case actionsType.REQUEST_POSTS: return Object.assign({}, state, { isFetching: true, didInvalidate: false }) case actionsType.RECEIVE_POSTS: return Object.assign({}, state, { isFetching: false, didInvalidate: false, items: action.posts, lastUpdated: action.receivedAt }) default: return state } } const rootReducer = combineReducers({ posts }) // store const store = createStore( rootReducer, applyMiddleware( thunkMiddleware, // 容許咱們 dispatch() 函數 loggerMiddleware // 一個很便捷的 middleware,用來打印 action 日誌 ) ) // action function requestPosts(subreddit) { return { type: actionsType.REQUEST_POSTS, subreddit } } function receivePosts(subreddit, json) { return { type: actionsType.RECEIVE_POSTS, subreddit, posts: json.data.children.map(child => child.data), receivedAt: Date.now() } } function fetchPosts(subreddit) { return function (dispatch) { dispatch(requestPosts(subreddit)) // display the loading status return fetch(`http://www.subreddit.com/r/${subreddit}.json`) .then( response => response.json(), error => console.log('An error occurred.', error) ) .then(json => dispatch(receivePosts(subreddit, json)) ) } } // excute store.dispatch(fetchPosts('reactjs')).then(() => console.log(store.getState())) } export default () => ( <div> <button onClick={run}>Run</button> <p>* 請打開控制檯查看運行結果</p> </div> );