摘要:系列文章入門進階本文番外篇在之前的文章中,我們已經了解了到底是什么,用來處理什么樣的問題,并創建了一個簡單的。啟動應用之后,就能在控制臺中看到一下的輸出。現在,如果你刷新界面就應該能看到控制臺中已經輸出了為和的。
系列文章:
Redux 入門
Redux 進階(本文)
番外篇: Vuex — The core of Vue application
在之前的文章中,我們已經了解了 Redux 到底是什么,用來處理什么樣的問題,并創建了一個簡單的 TodoMVC Demo。但是,我們同樣遺留了一些問題沒有處理,比如:異步處理、中間件、模板綁定等,這些問題我們將在這篇文章中通過一個簡單的天氣預報 Demo 來一一梳理(查看源碼點這里)。
在開始新的內容之前,先快速回顧一下上一篇的內容。
Action, Reducer & Store創建一個基于 Redux 狀態管理的應用時,我們還是從創建 Redux 的核心開始。
首先,建立 Action。假設,發出請求和收到請求之間有一個 loading 的狀態,那么,我們將查詢天氣這個行為劃分為 2 個 action,并為此創建 2 個工廠函數。
export const QUERY_WEATHER_TODAY = "QUERY_WEATHER_TODAY" export const RECEIVE_WEATHER_TODAY = "RECEIVE_WEATHER_TODAY" export function queryWeatherToday(city) { return { type: QUERY_WEATHER_TODAY, city } } export function receiveWeatherToday(weatherToday) { return { type: RECEIVE_WEATHER_TODAY, weatherToday } }
然后,為 Action 創建相應的 Reducer,不要忘了 Reducer 必須是一個純函數。
export default function WeatherTodayReducer(state = {}, action) { switch (action.type) { case QUERY_WEATHER_TODAY: return { load: true, city: action.city } case RECEIVE_WEATHER_TODAY: return { ...state, load: false, detail: action.weatherToday} default: return state } }
最后是 Sotre。
import { createStore } from "redux" import WeatherForecastReducer from "../reducers" import actions from "../actions" let store = createStore(WeatherForecastReducer) // Log the initial state console.log("init store", store.getState()) store.dispatch(actions.queryWeatherToday("shanghai")) console.log(store.getState()) store.dispatch(actions.receiveWeatherToday({})) console.log(store.getState()) export default store
啟動應用之后,就能在控制臺中看到一下的輸出。
回顧了之前的內容以后,那我們就進入正題,來看一些新概念。
中間件相信大家對中間件這個詞并不陌生,Redux 中的中間件和其他的中間件略微有些不同。它并不是對整個 Redux 進行包裝,而是對 store.dispatch 方法進行的封裝,是 action 與 reducer 之間的擴展。
Redux 官網一步一步詳細地演示了中間件產生的原因及其演變過程,在此我就不再多做贅述了。
中間件在真正應用中是必不可少的一環,或許你不需要寫一個中間件,但理解它會對你運用 Redux 編寫代碼會有很大的幫助。
異步請求在上一篇文章中有提到,為了保證 reducer 的純凈,Redux 中的異步請求都是由 action 處理。
但是,reducer 需要接收一個普通的 JS 對象,action 工廠返回一個描述事件的簡單對象,那我們的異步方法該怎么處理哪?這就需要我們剛才提到的中間件來幫忙了,添加 redux-thunk 這個中間件,使我們的 action 得到增強,使得 action 不單能返回對象,還能返回函數,在這個函數中還可以發起其他的 action。
其實,redux-thunk 這個中間件也沒有什么特別之處,在 Redux 官網的案例最后已經簡單地實現了它。
/** * 雖然,中間件是對 store.dispatch 的封裝,但它是添加在整個 store 上 * 所以,函數能傳遞 `dispatch` 和 `getState` 作為參數 * * redux-thunk 的邏輯就是判斷當前的 action 是不是一個函數,是就執行函數,不是就繼續傳遞 action 給下一個中間件 */ const thunk = store => next => action => typeof action === "function" ? action(store.dispatch, store.getState) : next(action)
于是,我們就修改一下之前的 action,給它添加一個異步請求。
export const QUERY_WEATHER_TODAY = "QUERY_WEATHER_TODAY" export const RECEIVE_WEATHER_TODAY = "RECEIVE_WEATHER_TODAY" const queryWeatherToday = city => ({ type: QUERY_WEATHER_TODAY, city }) const receiveWeatherToday = weatherToday => ({ type: RECEIVE_WEATHER_TODAY, weatherToday }) export function fetchWeatherToday(city) { return dispatch => { dispatch(queryWeatherToday(city)) return fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&APPID=${CONFIG.APPID}`) .then(response => response.json()) .then(data => dispatch(receiveWeatherToday(data))) } }
既然,我們用了中間件,那就要在 createStore 的時候裝載中間件。
import { createStore, applyMiddleware } from "redux" import thunkMiddleware from "redux-thunk" import createLogger from "redux-logger" import WeatherForecastReducer from "../reducers" import actions from "../actions" const loggerMiddleware = createLogger() const store = createStore( WeatherForecastReducer, applyMiddleware( thunkMiddleware, loggerMiddleware ) ) store.dispatch(actions.fetchWeatherToday("shanghai")) export default store
這時,再看看應用的控制臺。
OK,Redux 核心的功能我們基本完成,我們繼續看看如何將它同界面綁定在一起。
模板綁定官網的例子都是 Redux 搭配 React,用的是 react-redux;然而,本文一直是以 Angular 來寫的例子,所以,這里就用到另一個 redux 生態圈中的項目 angular-redux。它其中包含了 2 個不同的庫,ng-redux 和 ng2-redux,分別對應 Angular 1.x 和 Angular 2 兩個版本。
當然,我們這里使用 ng-redux。之前那些章節和官網講述的可能相差不大,但這部分就有所區分了。
react-redux 提供一個特殊的 React 組件 Provider,它通過 React Context 特性使每個組件不用顯示地傳遞 store 就能使用它。
ng-redux 當然不能使用這種方式,但它可以使用 angular 自己的方式——依賴注入。
ng-redux 是一個 provider,它包含了所有 Redux store 所有的 API,額外只有 2 個 API,分別是 createStoreWith 和 connect。
其中,createStoreWith 顯而易見是用來創建一個 store,參數同 Redux 的 createStore 方法差不多,原有創建 store 的方法就用不到了,之前的 store.js 也就被合并到了應用啟動的 index.js 里。
import angular from "angular" import ngRedux from "ng-redux" import thunkMiddleware from "redux-thunk" import createLogger from "redux-logger" import "./assets/main.css" import WeatherForecastReducer from "./reducers" import Components from "./components" const loggerMiddleware = createLogger() angular.module("WeatherForecastApp", [ngRedux, Components]) .config($ngReduxProvider => { $ngReduxProvider.createStoreWith( WeatherForecastReducer, [thunkMiddleware, loggerMiddleware] ) })
這樣應用的 store 就建立好了。
另一個 API connect 的用法同 react-redux 的 connect 方法差不多,用于將 props 和 actions 綁定到 template 上。
API 簽名是 connect(mapStateToTarget, [mapDispatchToTarget])(target)。
其中,mapStateToTarget 是一個 function,function 的參數是 state,返回 state 的一部分,即 select;mapDispatchToTarget 可以是對象或函數,如果是對象,那么它的每個屬性都必須是 actions 工廠方法,這些方法會自動地綁定到 target 對象上,也就是說,如果用之前定義好的 action,這邊就不需要做任何的修改;如果是函數,那么這個函數會被傳遞 dispatch 作為參數,而且這個函數需要返回一個對象,如何 dispatch action 就由你自己設定,同時這個對象的屬性也會綁定到 target 對象上。
最后的 target 就是目標對象了,也可以是函數,如果是函數的話,前面所傳的 2 個參數會作為 target 函數的參數。
好了,扯了這么多概念,估計你也暈了。
Talk is sxxt,show me the code!
// query-city/controller.js import actions from "../../actions" export default class QueryCity { constructor($ngRedux, $scope) { const unsubscribe = $ngRedux.connect(null, actions)(this) $scope.$on("$destroy", unsubscribe) } } // today-weather-board/controller.js export default class TodayWeatherBoardCtrl { constructor($ngRedux, $scope) { const unsubscribe = $ngRedux.connect(this.mapStateToThis)(this); $scope.$on("$destroy", unsubscribe); } mapStateToThis(state) { return { weatherToday: state.weatherToday }; } }
這樣,controller 是不是變得很簡潔?
Weather Forecast 部分基本和之前的部分相同,唯一的一處小修改就是把 QueryCity 控制器里添加一個方法,在方法里調用 2 個不同的 action 來替換之前按鈕上直接綁定的 action。
于是,我們的天氣預報應用就成了這樣。
路由切換一個真實的項目肯定會用到路由切換,路由狀態也是應用狀態的一部分,那么它也應當由 Redux 來統一管理。
談到 Angular 的路由,那必須提到 ui-router。那 ui-router 怎么整合到由 Redux 管理的項目中哪?答案是:redux-ui-router。
使用 redux-ui-router 同樣也有 3 點要注意:
使用 store 來管理應用的路由狀態
使用 action 代替 $state 來觸發路由的變更
使用 state 代替 $stateParams 來作為路由參數
記住這些就可以動手開工了。首先,安裝依賴:
npm install angular-ui-router redux-ui-router --save
這里有一點要注意,redux-ui-router 雖然依賴 angular-ui-router,但它不會幫你自動安裝,需要你自己額外手動安裝,雖然你項目里不需要引入 angular-ui-router 模塊。
安裝完依賴之后,就把它引入到我們項目中,項目的 index.js 就變為了
import angular from "angular" import ngRedux from "ng-redux" import ngReduxUiRouter from "redux-ui-router" import thunkMiddleware from "redux-thunk" import createLogger from "redux-logger" import "./assets/main.css" import { current, forecast } from "./Router" import App from "./app/app" import WeatherForecastReducer from "./reducers" import Components from "./components" const loggerMiddleware = createLogger() angular.module("WeatherForecastApp", [ngReduxUiRouter, ngRedux, App, Components]) .config(($urlRouterProvider, $stateProvider) => { $urlRouterProvider .otherwise("/current") $stateProvider .state("current", current) .state("forecast", forecast) }) .config($ngReduxProvider => { $ngReduxProvider.createStoreWith( WeatherForecastReducer, [thunkMiddleware, loggerMiddleware, "ngUiRouterMiddleware"] ) })
項目中只需引入 ngReduxUiRouter 模塊,而不用再引入 ui-router 模塊到應用中。ui-router 的路由聲明就不在這里贅述了,網上的資料也是大把大把的。
接著,將 "ngUiRouterMiddleware" 添加到中間件中,這樣距離完工就只剩最后一步了。
那就是修改主 Reducer 文件,將路由的 Reducer 合并到主 Reducer中,
import { combineReducers } from "redux" import { router } from "redux-ui-router" import weatherToday from "./WeatherToday" import weatherForecast from "./WeatherForecast" export default combineReducers({ weatherToday, weatherForecast, router })
OK,大工告成。現在,如果你刷新界面就應該能看到控制臺中已經輸出了 type 為 @@reduxUiRouter/$stateChangeStart 和 @@reduxUiRouter/$stateChangeSuccess 的 action log。此時,如果頁面上使用 ui-sref 來切換應用路由狀態的話,同樣也能看到 redux-logger 輸出的日志。
在這個 Demo 里,我就不直接使用 ui-sref,而是用例子來說明剛剛提到的 3 點中的第二點:使用 action 代替 $state 來觸發路由的變更。
import { stateGo } from "redux-ui-router" export default class NavBarCtrl { constructor($ngRedux, $scope) { const routerAction = { stateGo } const unsubscribe = $ngRedux.connect(this.mapStateToThis, routerAction)(this) $scope.$on("$destroy", unsubscribe) } mapStateToThis(state) { return { router: state.router } } }
從代碼中可以看到,先從 redux-ui-router 里引入了 stateGo 方法,然后通過上一節所說的模板綁定,將這個方法綁定到當前的模板上,于是在模板中就可以使用 $ctrl.stateGo() 方法來跳轉路由。
那為什么說這就滿足了剛剛的第二點哪?查看源碼就可以發現,redux-ui-router 提供的 stateGo(to, params, options)等 API 也只是個再普通不過的 action 工廠方法,返回一個特定 type 的 action。
路由的切換是在之前添加的中間件中,做了一個類似 reducer 的處理,根據不同的 action type 觸發不同的路由事件。
舉一反三,通過模板綁定我們可以獲得當前應用的 state。那么,我們同樣可以用過調用 $ctrl.stateGo() 等方法給路由切換添加參數來做到使用 state 代替 $stateParams 來作為路由參數。
順便說一句,redux-ui-router 似乎還沒有支持 angular-ui-router 中的 View Load Events,如果你看懂了我剛剛所說的,那么 pr 走起。
寫在最后一不小心寫了那么長,文筆又不是很好,不知有多少人看完了,希望大家都有所收獲。
其中,也有不少細節也沒有細說,有疑問的就留言吧。
在學習的過程中發現還有不少相關的知識可以擴展,應該還會有下一篇。
最后,最重要的當然是附上源碼。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86411.html
摘要:進階教程原文保持更新寫在前面相信您已經看過簡明教程,本教程是簡明教程的實戰化版本,伴隨源碼分析用的是編寫,看到有疑惑的地方的,可以復制粘貼到這里在線編譯總覽在的源碼目錄,我們可以看到如下文件結構打醬油的,負責在控制臺顯示警告信息入口文件除去 Redux 進階教程 原文(保持更新):https://github.com/kenberkele... 寫在前面 相信您已經看過 Redux ...
簡介:簡單實現react-redux基礎api react-redux api回顧 把store放在context里,所有子組件可以直接拿到store數據 使組件層級中的 connect() 方法都能夠獲得 Redux store 根組件應該嵌套在 中 ReactDOM.render( , rootEl ) ReactDOM.render( ...
摘要:前言是一個非常實用的狀態管理庫,對于大多數使用庫的開發者來說,都是會接觸到的。在使用享受其帶來的便利的同時,我們也深受其問題的困擾。只支持同步,讓狀態可預測,方便測試。粗暴地級聯式刷新視圖使用優化。 前言 Redux是一個非常實用的狀態管理庫,對于大多數使用React庫的開發者來說,Redux都是會接觸到的。在使用Redux享受其帶來的便利的同時, 我們也深受其問題的困擾。 redux...
摘要:前言前段時間學習完了的基礎自己網上找了一些實戰項目做了幾個感覺項目不是很全面就想做一個完整的項目來提升自己的水平以前學習的時候就看過大神的項目所以自己打算用重寫它后端數據還是用實在沒有精力擼后端感謝大神該項目是餓了么目前開發了登錄注冊購 前言 前段時間學習完了React的基礎,自己網上找了一些實戰項目,做了幾個感覺項目不是很全面,就想做一個完整的項目來提升自己的React水平.以前學習...
閱讀 3420·2021-11-15 11:39
閱讀 1552·2021-09-22 10:02
閱讀 1309·2021-08-27 16:24
閱讀 3596·2019-08-30 15:52
閱讀 3412·2019-08-29 16:20
閱讀 824·2019-08-28 18:12
閱讀 550·2019-08-26 18:27
閱讀 715·2019-08-26 13:32