在react中使用redux

寫在前面

  在react中使用redux一直是一大痛點,redux 過於複雜 所以後來出現了mobx,當然今天不說mobx,今天說下redux(自以爲很複雜的redux)。

         redux的基本用法就是:用戶發出 Action,Reducer 函數算出新的 State,View 重新渲染。但是現在有一個問題就是,異步操作怎麼辦?Action 發出以後,Reducer 立即算出 State,這叫做同步;Action 發出以後,過一段時間再執行 Reducer,這就是異步。

         怎麼才能 Reducer 在異步操作結束後自動執行呢?這就要用到新的工具:中間件(middleware)。本次用到的中間件有redux-thunk,redux-logger

         

  1. 安裝四個npm包

    npm install --save redux react-redux redux-thunk
    npm install --save-dev redux-logger

    redux 和 react-redux 是必須的 redux-thunk可以幫你實現異步action。redux-logger可以實現監測redux的變化

  2. 文件結構

    然後我們在詳細介紹一下每個文件所做的事情

  3. 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 在異步操作結束後自動執行。

  4. 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
    })
  5. state.js

    /**
        此文件存放state默認值
        同步數據:pageTitle
        異步數據:infoList(將來用異步接口獲取)
     */
    
    export default {
        pageTitle:"首頁",
        infoList:[]
    }
    

      

  6. 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
    

      

  7. 入口文件

    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();
    

      

  8. 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

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章