摘要:還有這個解決異步問題,極度不優雅。是狀態管理的庫,是唯一控制頁面跳轉的庫。如果您計時旅行,它還會將新狀態傳遞給以再次更新組件樹。這使得從容器組件訪問它們變得容易。
Part01 What"s the problem
這段代碼意圖是把router傳遞props的路由信息再傳遞給redux。有這么幾個問題:
如果靠組件生命周期轉發 每個路由下面的頂級組件都要調這樣一個action
并且,如果路由有參數改變(很多時候頁面狀態的參數會在路由中體現),這段代碼是無法檢測的,還需要在componentWillReceiveProps里去處理邏輯。
還有這個setTimeout解決異步問題,極度不優雅。
Can"t cooperateredux 是狀態管理的庫,router 是(唯一)控制頁面跳轉的庫。兩者都很美好,但是不美好的是兩者無法協同工作。換句話說,當路由變化以后,store 無法感知到。
redux是想把絕大多數應用程序的狀態都保存在單一的store里,而當前的路由狀態明顯是應用程序狀態很重要的一部分,應當是要保存在store中的。
目前是,如果直接使用react router,就意味著所有路由相關的信息脫離了Redux store的控制,假借組件接受router信息轉發dispatch的方法屬于反模式,違背了redux的設計思想,也給我們應用程序帶來了更多的不確定性。
Part02 What do we need我們需要一個這樣的路由系統,他技能利用React Router的聲明式特性,又能將路由信息整合進Redux Store中。
react-router-reduxreact-router-redux 是 redux 的一個中間件(中間件:JavaScript 代理模式的另一種實踐 針對 dispatch 實現了方法的代理,在 dispatch action 的時候增加或者修改) ,主要作用是:
加強了React Router庫中history這個實例,以允許將history中接受到的變化反應到state中去。
import React from "react" import ReactDOM from "react-dom" import { createStore, combineReducers } from "redux" import { Provider } from "react-redux" import { Router, Route, browserHistory } from "react-router" import { syncHistoryWithStore, routerReducer } from "react-router-redux" import reducers from "/reducers" const store = createStore( combineReducers({ ...reducers, routing: routerReducer }) ) const history = syncHistoryWithStore(browserHistory, store) ReactDOM.render( , document.getElementById(‘app") )
使用簡單直白的api syncHistoryWithStore來完成redux的綁定工作,我們只需要傳入react router中的history(前面提到的)以及redux中的store,就可以獲得一個增強后的history對象。
將這個history對象傳給react router中的Router組件作為props,就給應用提供了觀察路由變化并改變store的能力。
現在,只要您按下瀏覽器按鈕或在應用程序代碼中導航,導航就會首先通過Redux存儲區傳遞新位置,然后再傳遞到React Router以更新組件樹。如果您計時旅行,它還會將新狀態傳遞給React Router以再次更新組件樹。
React Router?通過路徑組件的props提供路由信息。這使得從容器組件訪問它們變得容易。當使用react-redux對connect()你的組件進行陳述時,你可以從第二個參數mapStateToProps訪問路由器的道具:
Part04 Code principle https://github.com/reactjs/react-router-redux// index.js /** * 作為外部 syncHistoryWithStore * 綁定store.dispatch方法引起的state中路由狀態變更到影響瀏覽器location變更 * 綁定瀏覽器location變更觸發store.dispatch,更新state中路由狀態 * 返回當前的histroy、綁定方法listen(dispatch方法觸發時執行,以綁定前的路由狀態為參數)、解綁函數unsubscribe */ export syncHistoryWithStore from "./sync" /** * routerReducer監聽路由變更子reducer,通過redux的combineReducers復合多個reducer后使用 */ export { LOCATION_CHANGE, routerReducer } from "./reducer" /** * 構建actionCreater,作為外部push、replace、go、goBack、goForward方法的接口,通常不直接使用 */ export { CALL_HISTORY_METHOD, push, replace, go, goBack, goForward, routerActions } from "./actions" /** * 構建route中間件,用于分發action,觸發路徑跳轉等事件 */ export routerMiddleware from "./middleware"
// sync.js import { LOCATION_CHANGE } from "./reducer" // 默認用state.routing存取route變更狀態數據 const defaultSelectLocationState = state => state.routing /** * 作為外部syncHistoryWithStore接口方法 * 綁定store.dispatch方法引起的state中路由狀態變更到影響瀏覽器location變更 * 綁定瀏覽器location變更觸發store.dispatch,更新state中路由狀態 * 返回當前的histroy、綁定方法listen(dispatch方法觸發時執行,以綁定前的路由狀態為參數)、解綁函數unsubscribe */ export default function syncHistoryWithStore(history, store, { // 約定redux.store.state中哪個屬性用于存取route變更狀態數據 selectLocationState = defaultSelectLocationState, // store中路由狀態變更是否引起瀏覽器location改變 adjustUrlOnReplay = true } = {}) { // Ensure that the reducer is mounted on the store and functioning properly. // 確保redux.store.state中某個屬性綁定了route變更狀態 if (typeof selectLocationState(store.getState()) === "undefined") { throw new Error( "Expected the routing state to be available either as `state.routing` " + "or as the custom expression you can specify as `selectLocationState` " + "in the `syncHistoryWithStore()` options. " + "Ensure you have added the `routerReducer` to your store"s " + "reducers via `combineReducers` or whatever method you use to isolate " + "your reducers." ) } let initialLocation // 初始化route狀態數據 let isTimeTraveling // 瀏覽器頁面location.url改變過程中標識,區別頁面鏈接及react-router-redux變更location兩種情況 let unsubscribeFromStore // 移除store.listeners中,因路由狀態引起瀏覽器location變更函數 let unsubscribeFromHistory // 移除location變更引起路由狀態更新函數 let currentLocation // 記錄上一個當前路由狀態數據 // 獲取路由事件觸發后路由狀態,或者useInitialIfEmpty為真值獲取初始化route狀態,或者undefined const getLocationInStore = (useInitialIfEmpty) => { const locationState = selectLocationState(store.getState()) return locationState.locationBeforeTransitions || (useInitialIfEmpty ? initialLocation : undefined) } // 初始化route狀態數據 initialLocation = getLocationInStore() // If the store is replayed, update the URL in the browser to match. // adjustUrlOnReplay為真值時,store數據改變事件dispatch發生后,瀏覽器頁面更新location if (adjustUrlOnReplay) { // 由store中路由狀態改變情況,更新瀏覽器location const handleStoreChange = () => { // 獲取路由事件觸發后路由狀態,或者初始路由狀態 const locationInStore = getLocationInStore(true) if (currentLocation === locationInStore || initialLocation === locationInStore) { return } // 瀏覽器頁面location.url改變過程中標識,區別頁面鏈接及react-router-redux變更location兩種情況 isTimeTraveling = true // 記錄上一個當前路由狀態數據 currentLocation = locationInStore // store數據改變后,瀏覽器頁面更新location history.transitionTo({ ...locationInStore, action: "PUSH" }) isTimeTraveling = false } // 綁定事件,完成功能為,dispatch方法觸發store中路由狀態改變時,更新瀏覽器location unsubscribeFromStore = store.subscribe(handleStoreChange) // 初始化設置路由狀態時引起頁面location改變 handleStoreChange() } // 頁面鏈接變更瀏覽器location,觸發store.dispatch變更store中路由狀態 const handleLocationChange = (location) => { // react-router-redux引起瀏覽器location變更過程中,無效;頁面鏈接變更,有效 if (isTimeTraveling) { return } // Remember where we are currentLocation = location // Are we being called for the first time? if (!initialLocation) { // Remember as a fallback in case state is reset initialLocation = location // Respect persisted location, if any if (getLocationInStore()) { return } } // Tell the store to update by dispatching an action store.dispatch({ type: LOCATION_CHANGE, payload: location }) } // hashHistory、boswerHistory監聽瀏覽器location變更,觸發store.dispatch變更store中路由狀態 unsubscribeFromHistory = history.listen(handleLocationChange) // History 3.x doesn"t call listen synchronously, so fire the initial location change ourselves // 初始化更新store中路由狀態 if (history.getCurrentLocation) { handleLocationChange(history.getCurrentLocation()) } // The enhanced history uses store as source of truth return { ...history, // store中dispatch方法觸發時,綁定執行函數listener,以綁定前的路由狀態為參數 listen(listener) { // Copy of last location. // 綁定前的路由狀態 let lastPublishedLocation = getLocationInStore(true) // Keep track of whether we unsubscribed, as Redux store // only applies changes in subscriptions on next dispatch let unsubscribed = false // 確保listener在解綁后不執行 const unsubscribeFromStore = store.subscribe(() => { const currentLocation = getLocationInStore(true) if (currentLocation === lastPublishedLocation) { return } lastPublishedLocation = currentLocation if (!unsubscribed) { listener(lastPublishedLocation) } }) // History 2.x listeners expect a synchronous call. Make the first call to the // listener after subscribing to the store, in case the listener causes a // location change (e.g. when it redirects) if (!history.getCurrentLocation) { listener(lastPublishedLocation) } // Let user unsubscribe later return () => { unsubscribed = true unsubscribeFromStore() } }, // 解綁函數,包括location到store的handleLocationChange、store到location的handleStoreChange unsubscribe() { if (adjustUrlOnReplay) { unsubscribeFromStore() } unsubscribeFromHistory() } } }
// reducer.js /** * This action type will be dispatched when your history * receives a location change. */ export const LOCATION_CHANGE = "@@router/LOCATION_CHANGE" const initialState = { locationBeforeTransitions: null } /** * 監聽路由變更子reducer,通過redux的combineReducers復合多個reducer后使用,作為外部routerReducer接口 * 提示redux使用過程中,可通過子組件模塊中注入reducer,再使用combineReducers復合多個reducer * 最后使用replaceReducer方法更新當前store的reducer,意義是構建reducer拆解到各個子模塊中 * */ export function routerReducer(state = initialState, { type, payload } = {}) { if (type === LOCATION_CHANGE) { return { ...state, locationBeforeTransitions: payload } } return state }
// actions.js export const CALL_HISTORY_METHOD = "@@router/CALL_HISTORY_METHOD" function updateLocation(method) { return (...args) => ({ type: CALL_HISTORY_METHOD, // route事件標識,避免和用于定義的action沖突 payload: { method, args } // method系hashHistroy、boswerHistroy對外接口方法名,args為參數 }) } /** * 返回actionCreater,作為外部push、replace、go、goBack、goForward方法的接口,通常不直接使用 */ export const push = updateLocation("push") export const replace = updateLocation("replace") export const go = updateLocation("go") export const goBack = updateLocation("goBack") export const goForward = updateLocation("goForward") export const routerActions = { push, replace, go, goBack, goForward }完結
(此文由PPT摘抄完成)PPT鏈接
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/99797.html
摘要:服務端渲染的手腳架這個啟動包的設計是為了讓你使用一整套最新最酷的前端技術,所有都是可配置,富特性,基于已經提供代碼熱加載,使用預處理,單元測試,代碼覆蓋率報告,代碼分割等等更多。 Universal React Starter Kit 服務端渲染的React手腳架 這個啟動包的設計是為了讓你使用一整套最新最酷的前端技術,所有都是可配置,富特性,基于webpack已經提供代碼熱加載,使用...
摘要:服務端渲染的手腳架這個啟動包的設計是為了讓你使用一整套最新最酷的前端技術,所有都是可配置,富特性,基于已經提供代碼熱加載,使用預處理,單元測試,代碼覆蓋率報告,代碼分割等等更多。 Universal React Starter Kit 服務端渲染的React手腳架 這個啟動包的設計是為了讓你使用一整套最新最酷的前端技術,所有都是可配置,富特性,基于webpack已經提供代碼熱加載,使用...
摘要:服務端渲染的手腳架這個啟動包的設計是為了讓你使用一整套最新最酷的前端技術,所有都是可配置,富特性,基于已經提供代碼熱加載,使用預處理,單元測試,代碼覆蓋率報告,代碼分割等等更多。 Universal React Starter Kit 服務端渲染的React手腳架 這個啟動包的設計是為了讓你使用一整套最新最酷的前端技術,所有都是可配置,富特性,基于webpack已經提供代碼熱加載,使用...
摘要:下面會從淺到深,淡淡在閱讀源碼過程中自己的理解。分拆子頁面后,每一個子頁面對應一個文件。總結上面就是最早版本的源碼,很簡潔的使用了等其目的也很簡單簡化相關生態的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯的,大大提升了開發效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態管理,以及Redux-saga中...
閱讀 1668·2021-11-23 10:07
閱讀 2664·2019-08-30 11:10
閱讀 2844·2019-08-29 17:08
閱讀 1787·2019-08-29 15:42
閱讀 3175·2019-08-29 12:57
閱讀 2402·2019-08-28 18:06
閱讀 3553·2019-08-27 10:56
閱讀 394·2019-08-26 11:33