react学习了也很有一阵子了,从之前最简单的react应用,到使用redux管理状态,然后使用中间件和storeEnhancer,接着学习了react-router,从v3版本到v4版本的变化,以及使用react-loadable来进行异步加载。到这里,code-splitting就基本完成。
但是,接下来的又要考虑一个非常重要的问题,每个页面的使用的reducer和state都是不同的,比如我刚开始有一个页面A,他有自己的reducer和state,我也有另外一个页面B,他也有自己的reducer和state。接下来要处理的问题就是,当我访问页面A,需要对store进行replceReducer的操作,然后初始化页面A的state。当访问页面B的时候,也要对store进行replaceReducer的操作,然后重新初始化页面B的state。
还有一个问题,就是如果我们有一些公共的reducer,比如路由router,这个是connected-react-router提供的,在页面A和页面B都需要,应该如何处理呢?
我们最终想要实现的效果应该是这个样子的: 有一个类似于导航栏的链接,上面有几个链接指向不同的页面,每个页面都有自己的内容,并且有些页面还有自己的state和reducer,当然,这些都是需要同redux结合起来的。
最终效果图如下:
其中,Couter和Weather的页面有自己的state和reducer,并且Weather页面使用了api去中国天气网获取一些城市的天气信息。关于这两个页面,其实之前的例子都有,这里不会着重讲解,我们要讲解的关键是关于code-splitting
这里使用到了connected-react-router和history并结合redux-devtools进行时光旅行
vim src/Store.js
import { createStore, combineReducers, compose, applyMiddleware } from 'redux'
import createBrowserHistory from 'history/createBrowserHistory'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import thunk from 'redux-thunk'
import resetEnhancer from './enhancer/reset'
const history = createBrowserHistory()
const reducer = combineReducers({
})
const middlewares = [routerMiddleware(history), thunk]
const win = window
const storeEnhancers = compose(
resetEnhancer,
applyMiddleware(...middlewares),
(win && win.devToolsExtension) ? win.devToolsExtension() : (f) => f,
)
const initialState = {}
// console.log('in init store.js')
const store = createStore(connectRouter(history)(reducer), initialState, storeEnhancers)
export default store
export { history }
我们使用了一个resetEnhancer的store增强器,这个增强器的作用就是用来重置state和replaceReducer的
vim src/enhancer/reset.js
import { history } from '../Store'
import { connectRouter } from 'connected-react-router'
const RESET_ACTION_TYPE = '@@RESET'
const resetReducerCreator = (reducer, resetState) => (state, action) => {
if (action.type === RESET_ACTION_TYPE) {
return resetState
}
return reducer(state, action)
}
const reset = (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
const reset = (resetReducer, resetState) => {
const newReducer = resetReducerCreator(resetReducer, resetState)
store.replaceReducer(connectRouter(history)(newReducer))
store.dispatch({type: RESET_ACTION_TYPE, state: resetState})
}
return {
...store,
reset
}
}
export default reset
在replaceReducer的函数中,我们使用到了connected-react-router提供的API,这里是一个需要注意的地方
vim src/components/Counter/index.js
import React from 'react'
import reducer from './reducer.js'
import Counter, { stateKey } from './page.js'
import store from '../../Store.js'
import { combineReducers } from 'redux'
const initialState = 20
const routerCounter = () => {
const state = store.getState()
store.reset(combineReducers({
counter: reducer
}), {
...state,
[stateKey]: initialState
})
return (
<div>
<div>Counter</div>
<Counter />
</div>
)
}
export default routerCounter
在返回Counter组件之前,我们使用store.reset来重置,这里是很关键的地方
vim src/components/Weather/index.js
import React from 'react'
import weatherReducers from './weather/reducers'
import Weather from './weather/views'
import CitySelector from './city_selector'
import store from '../../Store.js'
import { combineReducers } from 'redux'
const initState = {}
const stateKey = 'weather'
const routeWeather = () => {
const state = store.getState()
store.reset(combineReducers({
[stateKey]: weatherReducers
}), {
...state,
[stateKey]: initState
})
return (
<div>
<CitySelector />
<Weather />
</div>
)
}
export default routeWeather
使用react-router v4来做路由控制
vim src/App.js
import React from 'react'
import Loadable from 'react-loadable'
import { Link, Route } from 'react-router-dom'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import { history } from './Store'
// import createBrowserHistory from 'history/createBrowserHistory'
import Dashboard from './components/Dashboard'
import store from './Store'
// const history = createBrowserHistory()
const liStyle = {
display: 'inline-block',
margin: '10px 20px'
}
function Loading({ error }) {
if (error) {
console.log(error)
return 'Oh nooess!';
} else {
return <h3>Loading...</h3>;
}
}
const Settings = Loadable({
loader: () => import('./components/Settings'),
loading: Loading
});
const AddUser = Loadable({
loader: () => import('./components/AddUser'),
loading: Loading
});
const Counter = Loadable({
loader: () => import('./components/Counter'),
loading: Loading
})
const Weather = Loadable({
loader: () => import('./components/Weather'),
loading: Loading
})
const App = () => (
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<ul>
<li style={liStyle}><Link to="/">Dashboard</Link></li>
<li style={liStyle}><Link to="/settings">Settings</Link></li>
<li style={liStyle}><Link to="/add-user">Add User</Link></li>
<li style={liStyle}><Link to="/counter">Counter</Link></li>
<li style={liStyle}><Link to="/weather">Weather</Link></li>
</ul>
<Route exact path="/" component={Dashboard} />
<Route path="/settings" component={Settings} />
<Route path="/add-user" component={AddUser} />
<Route path="/counter" component={Counter} />
<Route path="/weather" component={Weather} />
</div>
</ConnectedRouter>
</Provider>
)
export default App


