这是 Pastate.js 响应式 react state 管理框架系列教程,欢迎关注,持续更新。
这一章,我们将介绍 pastate 如何构建大规模应用,以及如何在原生 redux 项目中使用等。
路由
当我们的应用日益复杂时,需要用前端路由的模式来管理多个界面。
无参数路由
Pastate 应用可以与通用的 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
模式下的 #
分割符来分割前端和后端路由,所以在服务器端需要做一些相关配置,把这些路径都路由到相同的一个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, 并通过 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”, 以支持用开发工具来实现高级调试,可以参考 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 等基于 redux 开发的框架,同样可以用这种方式嵌入 pastate 模块。
开发调试工具
由于 pastate 内部使用 redux 作为多模块引擎,所以你可以直接使用 redux devtools 作为 pastate 应用的调试工具。Pastate 对其做了友好支持,你无需任何配置,就可以直接打开 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 中间件定义方式和 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 推荐使用 create-react-app 来作为应用的初始化和开发工具,create-react-app 不只是一个简单的 react 应用模板,它还为我们提供了非常完善的 react 开发支持,详见其文档。
在 pastate 应用中,你可以简单的使用默认的 build 指令来编译应用:
$ npm run build
或
$ yarn build
其他编译和部署方式请参考这里。
Pastate 在编译之后仅为 ~28kb, gzip 之后仅为 ~9kb,非常轻便。Pastate 包的整体结构如下(图中显示的是未 gzip 的大小):
下一章,我们来详细介绍 pastate 的原理。