【教程】Pastate.js 响应式 react 框架(七)规模化

这是 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 模块, 只需两步:

  1. 使用 Pastate 模块的 store 中提供的 getReduxReducer 获取该模块的 redux reducer,并把它挂载到 redux 的 reducerTree 中;
  2. 把 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

使用 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 包结构

下一章,我们来详细介绍 pastate 的原理。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容