寫在前面
在react中使用redux一直是一大痛點,redux 過於復雜 所以后來出現了mobx,當然今天不說mobx,今天說下redux(自以為很復雜的redux)。
redux的基本用法就是:用戶發出 Action,Reducer 函數算出新的 State,View 重新渲染。但是現在有一個問題就是,異步操作怎么辦?Action 發出以后,Reducer 立即算出 State,這叫做同步;Action 發出以后,過一段時間再執行 Reducer,這就是異步。
怎么才能 Reducer 在異步操作結束后自動執行呢?這就要用到新的工具:中間件(middleware)。本次用到的中間件有redux-thunk,redux-logger
-
安裝四個npm包
npm install --save redux react-redux redux-thunk npm install --save-dev redux-logger
redux 和 react-redux 是必須的 redux-thunk可以幫你實現異步action。redux-logger可以實現監測redux的變化
-
文件結構
然后我們在詳細介紹一下每個文件所做的事情
-
actions.js
export const SET_PAGE_TITLE = "SET_PAGE_TITLE"; export const SET_INFO_LIST = "SET_INFO_LIST"; export function setPageTitle(data){ return {type: SET_PAGE_TITLE, data: data} } export function setInfoList (data) { return (dispatch, getState) => { // 使用fetch實現異步請求 // 請求發起前可以先dispatch一個加載開始狀態 // 例如 dispatch({ type: SET_LOADING_TYPE, data: "loading" }) window.fetch('/api/getInfoList', { method: 'GET', headers: { 'Content-Type': 'application/json' } }).then(res => { return res.json() }).then(data => { let { code, data } = data if (code === 0) { dispatch({ type: SET_INFO_LIST, data: data }) } // 請求結束可以dispatch一個加載結束狀態 // 例如 dispatch({ type: SET_LOADING_TYPE, data: "loaded" }) // 還可以區分一下成功或者失敗 }) } }
可以看出第一個action 是直接返回了一個action:{type: SET_PAGE_TITLE, data: data} ,而第二個則返回了一個函數,這個函數接受兩個參數 dispatch 與 getState , dispatch可以在異步加載完成以后發出一個action。在不使用redux-thunk這個中間件的時候dispatch接受的只能是一個action , 使用這個中間件就可以接受上文所說的參數,也就實現了Reducer 在異步操作結束后自動執行。
-
reducers.js
// reducers.js // combineReducers 工具函數,用於組織多個reducer,並返回reducer集合 import {combineReducers} from 'redux' import defaultState from './state' import {SET_PAGE_TITLE,SET_INFO_LIST} from './actions' //一個reducer 就是一個函數 function pageTitle ( state = defaultState.pageTitle, action){ // 不同的action 有不同的處理邏輯 switch (action.type) { case SET_PAGE_TITLE: return action.data default: return state } } function infoList (state = defaultState.infoList, action){ switch (action.type) { case SET_INFO_LIST: return action.data default: return state } } export default combineReducers({ pageTitle, infoList })
-
state.js
/** 此文件存放state默認值 同步數據:pageTitle 異步數據:infoList(將來用異步接口獲取) */ export default { pageTitle:"首頁", infoList:[] }
-
index.js
// index.js // applyMiddleware: redux通過該函數來使用中間件 // createStore: 用於創建store實例 import { applyMiddleware, createStore } from 'redux' // 中間件,作用:如果不使用該中間件,當我們dispatch一個action時,需要給dispatch函數傳入action對象;但如果我們使用了這個中間件,那么就可以傳入一個函數,這個函數接收兩個參數:dispatch和getState。這個dispatch可以在將來的異步請求完成后使用,對於異步action很有用 import thunk from 'redux-thunk' import logger from 'redux-logger' // 引入reducer import reducers from './reducers.js' // 創建store實例 第一個參數是reducer集合(reducers),第二個參數初始化的store(initialState),第三個參數是applyMiddleware函數,來使用中間件 ,因為只在reducer里面初始化了值,所以這里並沒有傳入初始化store的值 let store = createStore( reducers, applyMiddleware(thunk,logger) ) export default store
-
入口文件
import React from 'react'; import ReactDOM from 'react-dom'; import {BrowserRouter as Router,Route} from "react-router-dom"; import {Provider} from "react-redux"; import store from './store' import App from './App' import * as serviceWorker from './serviceWorker'; ReactDOM.render( <Provider store={store}> <Router > <Route component={App} /> </Router> </Provider> , document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: http://bit.ly/CRA-PWA serviceWorker.unregister();
-
test.js
// Test.jsx import React, { Component } from 'react' import {Route,Link} from 'react-router-dom' // connect方法的作用:將額外的props傳遞給組件,並返回新的組件,組件在該過程中不會受到影響 import { connect } from 'react-redux' // 引入action import { setPageTitle, setInfoList } from '../store/actions.js' class Test extends Component { constructor(props) { super(props) this.state = {} } componentDidMount () { let { setPageTitle, setInfoList } = this.props // 觸發setPageTitle action setPageTitle('新的標題') // 觸發setInfoList action setInfoList([{data:1}]) } render () { // 從props中解構store let { pageTitle, infoList } = this.props // 使用store return ( <div> <div> <Link to="/courses/ii">Courses</Link> </div> <h1>{pageTitle}</h1> { infoList.length > 0 ? ( <ul> { infoList.map((item, index) => { return <li key={index}>{item.data}</li> }) } </ul> ):null } </div> ) } } // mapStateToProps:將state映射到組件的props中 const mapStateToProps = (state) => { return { pageTitle: state.pageTitle, infoList: state.infoList } } // mapDispatchToProps:將dispatch映射到組件的props中 const mapDispatchToProps = (dispatch, ownProps) => { return { setPageTitle (data) { // 如果不懂這里的邏輯可查看前面對redux-thunk的介紹 dispatch(setPageTitle(data)) // 執行setPageTitle會返回一個函數 // 這正是redux-thunk的所用之處:異步action // 上行代碼相當於 /*dispatch((dispatch, getState) => { dispatch({ type: 'SET_PAGE_TITLE', data: data }) )*/ }, setInfoList (data) { dispatch(setInfoList(data)) } } } export default connect(mapStateToProps, mapDispatchToProps)(Test)
代碼的重點在connect函數。這個函數也是由react-redux提供的。使用它可以包裝普通的展示組件(這里的Test——只負責展示數據),然后返回一個容器組件。connect函數通過第一個參數讓展示組件訂閱了來自store的數據;通過第二個參數讓展示組件默認可以dispatch各種action。
applyMiddlewares()
applyMiddlewares是redux的原生方法,作用是將所有中間件組成一個數組,依次執行。
要注意的是中間件為順序有要求,具體可以查看你下載的npm包介紹。
本次使用的redux-logger 必須放最后,不然可能監測不到正確的store