这一章, 我们将介绍 pastate 如何构建大规模应用, 以及如何在原生 redux 项目中使用等.
路由
当我们的应用日益复杂时, 需要用前端路由的模式来管理多个界面.
无参数路由
Pastate 应用可以与通用的 https://reacttraining.com/react-router/ 路由框架配合使用. 我们还是以 班级信息管理系统 为例来介绍如何在 pastate 中使用路由, 我们接下来把学生板块和课程板块分别放在 /student 和 /class 路由目录下.
首先安装 react-router 的网页版 react-router-dom:
$ npm install react-router-dom --save
或
$ yarn add react-router-dom
使用 Router 作为根容器
接着我们用路由 Router 组件作为新的根容器来包装我们的原来的根容器 Navigator, 代码如下:
- src/index.js
- ...
- import {BrowserRouter as Router} from 'react-router-dom';
- ReactDOM.render(
- makeApp(
- <Router>
- <Navigator.view />
- </Router>,
- storeTree
- ),
- document.getElementById('root')
- );
使用 Route 组件来定义路由
接下来, 我们来看看导航模块 Navigator 的视图如何修改:
Navigator.view.js
import { Route, Redirect, Switch, withRouter, NavLink } from 'react-router-dom'
- class Navigator extends React.PureComponent{
- render(){
- /** @type {initState} */
- const state = this.props.state;
- return (
- <div>
- <div className="nav">
- <div className="nav-title">班级信息管理系统</div>
- <div className="nav-bar">
- <NavLink
- className="nav-item"
- activeClassName="nav-item-active"
- to="/student"
- >
学生({this.props.count.student})
- </NavLink>
- <NavLink
- className="nav-item"
- activeClassName="nav-item-active"
- to="/class"
- >
课程({this.props.count.class})
- </NavLink>
- </div>
- </div>
- <div className="main-panel">
- <Switch>
- <Redirect exact from='/' to='/student'/>
- <Route path="/student" component={StudentPanel.view}/>
- <Route path="/class" component={ClassPanel.view}/>
- </Switch>
- </div>
- </div>
- )
- }
- }
- export default withRouter(makeContainer(Navigator, state => ({...})))
我们对该文件进行了 3 处修改
使用路由组件 Switch + Route 来代替之前手动判断渲染子模块的代码, 让路由自动根据当前 url 选择对应的子组件来显示;
使用路由组件 NavLink 来代替之前的导航栏按钮, 把每个 tab 绑定到一个特定的 url;
使用路由的 withRouter 函数包装我们原来生成的 container, 这使得当路由变化时, container 会收到通知并重新渲染
完成! 这时我们就可以看到, 当我们切换导航标签时, url 和显示的子组件同时改变:
路由生效
当我们在 /class 路径下刷新或进入时, 应用可以显示为课程板块, 这是路由组件为我们提供的一个在原来的无路由模式中没有的功能.
我们这里使用的是 BrowserRouter 对应的 browserHistory , 因此在 url 中是不用 HashRouter 的 hashHistory 模式下的 # 分割符来分割前端和后端路由, 所以在服务器端需要做一些相关配置 https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#serving-apps-with-client-side-routing , 把这些路径都路由到相同的一个 html 应用. 如果后端难以配合修改, 你可以使用 HashRouter 代替我们上面的 BrowserRouter:
- // index.js
- import { HashRouter as Router } from 'react-router-dom';
这是就会自动启用 # (hash 路由)来分割前端和后端路由:
启用 `#` 来分割前端和后端路由
有参数路由
我们在上面的路由不涉及参数, 如果我们需要使用类似 \student\1 ,\student\2 的方式来表示目前显示哪一个 index 或 id 的学生, 我们可以在定义路由时使用参数:
// Navigator.view.js
<Route path="/student/:id" component={StudentPanel.view}/>
并在被路由的组件的 view 中这样获取参数:
- // StudentPanel.view.js
- let selected = this.props.match.params.id;
在 actions 中使用路由
如果你要在 actions 中简单地获取当前网址的路径信息, 你可以直接使用 window.location 获取:
- // StudentPanel.model.js
- const actions = {
- handleClick(){
- console.log(window.location)
- console.log(window.location.pathname) // 当使用 BrowserRouter 时
- console.log(window.location.hash) // 当使用 HashRouter 时
- }
- }
如果你需要在 actions 获取和更改路由信息, 如跳转页面等, 你可以在 view 视图中把 this.props.history 传入 action, 并通过 https://reacttraining.com/react-router/web/api/history 的 API 获取并修改路由信息:
- // StudentPanel.model.js
- const actions = {
- selectStudent(history, index){
- console.log(history)
- history.push(index+'')
- // history.push('/student/' + index)
- // history.goBack() // or .go(-1)
- }
- }
经过基础的测试, pastate 兼容 react-router 的路由参数, history 等功能, 如果你发现问题, 请提交 issue 告诉我们.
如果你对开发调试体验的要求非常高, 要实现 "Keep your state in sync with your router", 以支持用开发工具来实现高级调试, 可以参考 https://github.com/reacttraining/react-router/tree/master/packages/react-router-redux 把路由功能封装为一个只有 store 而没有 veiw 的 pastate 服务模块, 并挂载到 storeTree. 如果你做了, 请提交 issue 告诉我们.
嵌入 redux 应用
Pastate 内部使用 redux 作为默认的多模块引擎, 这意味着, 你可以在原生的 redux 项目中使用 pastate 模块, 只需两步:
使用 Pastate 模块的 store 中提供的 getReduxReducer 获取该模块的 redux reducer, 并把它挂载到 redux 的 reducerTree 中;
把 redux 的 store 实例的 dispatch 注入 astate 模块的 store 的 dispatch 属性 .
- import { createStore, combineReducers } from 'redux';
- import { makeApp, combineStores } from 'pastate';
- ...
- import * as StudentPanel from './StudentPanel';
- const reducerTree = combineReducers({
- panel1: oldReducer1,
- panel2: oldReducer2,
- panel3: StudentPanel.store.getReduxReducer() // 1. 获取 Pastate 模块的 reducer 并挂载到你想要的位置
- })
- let reduxStore = createStore(reducerTree)
- StudentPanel.store.dispatch = reduxStore.dispatch // 2. 把 redux 的 dispatch 注入 Pastate 模块中
完成! 这时你就可以在 redux 应用中渐进式地使用 pastate 啦!
如果你在使用 dva.js https://github.com/dvajs/dva 等基于 redux 开发的框架, 同样可以用这种方式嵌入 pastate 模块.
开发调试工具
由于 pastate 内部使用 redux 作为多模块引擎, 所以你可以直接使用 redux devtools https://github.com/gaearon/redux-devtools 作为 pastate 应用的调试工具. Pastate 对其做了友好支持, 你无需任何配置, 就可以直接打开 redux devtools 来调试你的应用:
redux devtools
使用 redux devtools 不要求 你把 pastate 嵌入到原生 redux 应用中, 你不需要懂得什么是 redux, 在纯 pastate 项目中就可以使用 redux devtools!
中间件
内置中间件
Pastate 目前实现了基于 actions 的中间件系统, 可用于对 actions 的调用进行前置或后置处理. Pastate 内置了几个实用的中间件生成器:
logActions(time: boolean = true, spend: boolean = true, args: boolean = true): 把 actions 的调用情况 log 到控制台
syncActions(onlyMutations: boolean = false): 把每个 action 都转化为同步 action, 方便互相调用时的调试观察每个 action 对数据的改变情况
dispalyActionNamesInReduxTool(onlyMutations: boolean = false): 把 actions 名称显示在 redux devtools 中, 方便调试
自定义中间件
你也可以很轻松的定义中间件, pastate 中间件定义方式和 https://github.com/koajs/koa 中间件类似, 例如我们定义一个简单的 log 中间件:
- const myLogMiddleware = function (ctx, next) {
- let before = Date.now();
- next();
- console.log(ctx.name + ':'+ (Date.now() - before) + 'ms');
- }
ctx 参数是上下文 (context) 对象, 包括如下属性:
- type MiddlewareContext = {
- name: string, // action 的名称
- agrs?: IArguments, // action 的调用参数
- return: any, // action 的返回值
- store: XStore // action 绑定的 store
- }
next 参数是下一个中间件或已做参数绑定的 action 实体, 你可以实现自己的中间件逻辑, 决定要不要调用或在什么时候调用 next.
编译与部署
Pastate 推荐使用 https://github.com/facebook/create-react-app 来作为应用的初始化和开发工具, create-react-app 不只是一个简单的 react 应用模板, 它还为我们提供了非常完善的 react 开发支持, 详见其文档.
在 pastate 应用中, 你可以简单的使用默认的 build 指令来编译应用:
$ npm run build
或
$ yarn build
其他编译和部署方式请参考这里 https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#deployment .
Pastate 在编译之后仅为 ~28kb, gzip 之后仅为 ~9kb, 非常轻便. Pastate 包的整体结构如下(图中显示的是未 gzip 的大小):
pastate 包结构
下一章, 我们来详细介绍 pastate 的原理.
来源: http://www.jianshu.com/p/cc3668fbe52f