【教程】Pastate.js 響應(yīng)式 react 框架(七)規(guī)?;?/h2>

這是 Pastate.js 響應(yīng)式 react state 管理框架系列教程,歡迎關(guān)注,持續(xù)更新。

這一章,我們將介紹 pastate 如何構(gòu)建大規(guī)模應(yīng)用,以及如何在原生 redux 項(xiàng)目中使用等。

路由

當(dāng)我們的應(yīng)用日益復(fù)雜時(shí),需要用前端路由的模式來管理多個(gè)界面。

無參數(shù)路由

Pastate 應(yīng)用可以與通用的 react-router 路由框架配合使用。我們還是以 班級(jí)信息管理系統(tǒng) 為例來介紹如何在 pastate 中使用路由,我們接下來把學(xué)生板塊和課程板塊分別放在 /student 和 /class 路由目錄下。

首先安裝 react-router 的網(wǎng)頁版 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 組件來定義路由

接下來,我們來看看導(dǎo)航模塊 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">班級(jí)信息管理系統(tǒng)</div>
                    <div className="nav-bar">
                        <NavLink 
                            className="nav-item"
                            activeClassName="nav-item-active"
                            to="/student"
                        >
                            學(xué)生({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 => ({...})))

我們對(duì)該文件進(jìn)行了 3 處修改

  • 使用路由組件 Switch + Route 來代替之前手動(dòng)判斷渲染子模塊的代碼,讓路由自動(dòng)根據(jù)當(dāng)前 url 選擇對(duì)應(yīng)的子組件來顯示;
  • 使用路由組件 NavLink 來代替之前的導(dǎo)航欄按鈕,把每個(gè) tab 綁定到一個(gè)特定的 url;
  • 使用路由的 withRouter 函數(shù)包裝我們?cè)瓉砩傻?container,這使得當(dāng)路由變化時(shí),container 會(huì)收到通知并重新渲染

完成!這時(shí)我們就可以看到,當(dāng)我們切換導(dǎo)航標(biāo)簽時(shí),url 和顯示的子組件同時(shí)改變:

路由生效

當(dāng)我們?cè)?/class 路徑下刷新或進(jìn)入時(shí),應(yīng)用可以顯示為課程板塊,這是路由組件為我們提供的一個(gè)在原來的無路由模式中沒有的功能。

我們這里使用的是 BrowserRouter 對(duì)應(yīng)的 browserHistory ,因此在 url 中是不用 HashRouter 的 hashHistory 模式下的 # 分割符來分割前端和后端路由,所以在服務(wù)器端需要做一些相關(guān)配置,把這些路徑都路由到相同的一個(gè)HTML應(yīng)用。如果后端難以配合修改,你可以使用 HashRouter 代替我們上面的 BrowserRouter:

// index.js
import {  HashRouter as Router } from 'react-router-dom';

這是就會(huì)自動(dòng)啟用 # (hash路由)來分割前端和后端路由:

啟用 `#` 來分割前端和后端路由

有參數(shù)路由

我們?cè)谏厦娴穆酚刹簧婕皡?shù),如果我們需要使用類似 \student\1 、\student\2 的方式來表示目前顯示哪一個(gè) index 或 id 的學(xué)生,我們可以在定義路由時(shí)使用參數(shù):

// Navigator.view.js
<Route path="/student/:id" component={StudentPanel.view}/>

并在被路由的組件的 view 中這樣獲取參數(shù):

// StudentPanel.view.js
let selected = this.props.match.params.id;

在 actions 中使用路由

如果你要在 actions 中簡(jiǎn)單地獲取當(dāng)前網(wǎng)址的路徑信息,你可以直接使用 window.location 獲取:

// StudentPanel.model.js
const actions = {
    handleClick(){
        console.log(window.location)
        console.log(window.location.pathname) // 當(dāng)使用 BrowserRouter 時(shí)
        console.log(window.location.hash) // 當(dāng)使用 HashRouter 時(shí)
    }
}

如果你需要在 actions 獲取和更改路由信息,如跳轉(zhuǎn)頁面等,你可以在 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)
    }
}

經(jīng)過基礎(chǔ)的測(cè)試,pastate 兼容 react-router 的路由參數(shù)、history 等功能,如果你發(fā)現(xiàn)問題,請(qǐng)?zhí)峤?issue 告訴我們。

如果你對(duì)開發(fā)調(diào)試體驗(yàn)的要求非常高,要實(shí)現(xiàn) “Keep your state in sync with your router”, 以支持用開發(fā)工具來實(shí)現(xiàn)高級(jí)調(diào)試,可以參考 react-router-redux 把路由功能封裝為一個(gè)只有 store 而沒有 veiw 的 pastate 服務(wù)模塊, 并掛載到 storeTree。如果你做了,請(qǐng)?zhí)峤?issue 告訴我們。

嵌入 redux 應(yīng)用

Pastate 內(nèi)部使用 redux 作為默認(rèn)的多模塊引擎,這意味著,你可以在原生的 redux 項(xiàng)目中使用 pastate 模塊, 只需兩步:

  1. 使用 Pastate 模塊的 store 中提供的 getReduxReducer 獲取該模塊的 redux reducer,并把它掛載到 redux 的 reducerTree 中;
  2. 把 redux 的 store 實(shí)例的 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 模塊中

完成!這時(shí)你就可以在 redux 應(yīng)用中漸進(jìn)式地使用 pastate 啦!
如果你在使用 dva.js 等基于 redux 開發(fā)的框架,同樣可以用這種方式嵌入 pastate 模塊。

開發(fā)調(diào)試工具

由于 pastate 內(nèi)部使用 redux 作為多模塊引擎,所以你可以直接使用 redux devtools 作為 pastate 應(yīng)用的調(diào)試工具。Pastate 對(duì)其做了友好支持,你無需任何配置,就可以直接打開 redux devtools 來調(diào)試你的應(yīng)用:

redux devtools

使用 redux devtools 不要求 你把 pastate 嵌入到原生 redux 應(yīng)用中,你不需要懂得什么是 redux,在純 pastate 項(xiàng)目中就可以使用 redux devtools!

中間件

內(nèi)置中間件

Pastate 目前實(shí)現(xiàn)了基于 actions 的中間件系統(tǒng),可用于對(duì) actions 的調(diào)用進(jìn)行前置或后置處理。Pastate 內(nèi)置了幾個(gè)實(shí)用的中間件生成器

  • logActions(time: boolean = true, spend: boolean = true, args: boolean = true): 把 actions 的調(diào)用情況 log 到控制臺(tái)
  • syncActions(onlyMutations: boolean = false): 把每個(gè) action 都轉(zhuǎn)化為同步 action, 方便互相調(diào)用時(shí)的調(diào)試觀察每個(gè) action 對(duì)數(shù)據(jù)的改變情況
  • dispalyActionNamesInReduxTool(onlyMutations: boolean = false): 把 actions 名稱顯示在 redux devtools 中,方便調(diào)試

自定義中間件

你也可以很輕松的定義中間件,pastate 中間件定義方式和 koa 中間件類似,例如我們定義一個(gè)簡(jiǎn)單的 log 中間件:

const myLogMiddleware = function (ctx, next) {
    let before = Date.now();
    next();
    console.log(ctx.name + ': '+ (Date.now() - before) + 'ms');
}
  • ctx 參數(shù)是上下文(context)對(duì)象,包括如下屬性:
type MiddlewareContext = {
    name: string, // action 的名稱
    agrs?: IArguments, // action 的調(diào)用參數(shù)
    return: any, // action 的返回值
    store: XStore // action 綁定的 store
}
  • next 參數(shù)是下一個(gè)中間件或已做參數(shù)綁定的 action 實(shí)體, 你可以實(shí)現(xiàn)自己的中間件邏輯,決定要不要調(diào)用或在什么時(shí)候調(diào)用 next。

編譯與部署

Pastate 推薦使用 create-react-app 來作為應(yīng)用的初始化和開發(fā)工具,create-react-app 不只是一個(gè)簡(jiǎn)單的 react 應(yīng)用模板,它還為我們提供了非常完善的 react 開發(fā)支持,詳見其文檔。

在 pastate 應(yīng)用中,你可以簡(jiǎn)單的使用默認(rèn)的 build 指令來編譯應(yīng)用:

$ npm run build
或
$ yarn build

其他編譯和部署方式請(qǐng)參考這里。

Pastate 在編譯之后僅為 ~28kb, gzip 之后僅為 ~9kb,非常輕便。Pastate 包的整體結(jié)構(gòu)如下(圖中顯示的是未 gzip 的大小):


pastate 包結(jié)構(gòu)

下一章,我們來詳細(xì)介紹 pastate 的原理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容