在 react
、redux
、router
的基础上实现新一代 MVC 的一套解决方案,可以摆脱大量繁杂的配置过程。
相关依赖: react-router-controller 、react-router-redux-saga-model、redux-saga-model
import React from 'react';
import { render } from 'react-dom';
import { BrowserMVC as MVC, Controller } from 'mvc-react';
function modelRegister(register) {
//register model or else
}
function renderApp() {
render(<MVC modelRegister={modelRegister} />, document.getElementById('root'));
}
// start
renderApp();
$ npm i mvc-react
or
$ yarn add mvc-react
首先我们将传统的 MVC 模式与我们的 MVC 模式进行对比。
传统的 MVC 模式:
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
mvc-react 与传统的 MVC 模式相比较有几点不同:
- 传统的 view 不负责交互逻辑,由 controller 处理交互逻辑并更新 model 数据。 而我们使用了 react 作为我们的 view 层,完全可以在 react 组件中写与用户的交互逻辑。
- 传统的 model 与 view 一样不负责交互逻辑,只负责数据的维护,这里我们使用了 redux-saga-model 的**,将其作为 model 层。由于 model 中的 saga 拥有极强的流程控制能力( 底层使用的是 redux-saga ),所以我们完全可以把比较复杂的交互逻辑抽取放在 model 层,此时我们的 model 即具有数据维护的能力也具备实现复杂交互逻辑的能力,再进一步的细分我们还可以将 model 细分成两部分( dataModel、viewModel)。
- 传统的 controller 除了实现交互逻辑还负责路由的控制,mvc-react 的 Controller 只实现路由功能不参与其他逻辑 。
mvc-react 与传统的 MVC 模式比较的相同点:
- 当 model 的数据发生了变化时通知 view 进行及时更新。
- 一样是 model 层对数据负责,不同的 model 负责不同纬度亦或不同业务逻辑相关的数据。
在开发中我们最常做的就是区分模块,Controller 也一样,你可以根据你的需要来划分 controller 的纬度。
在 mvc-react 中,controller 实际上是对路由的高阶抽象,在 controller 中,你可以选择对外开放什么页面以及开放哪些页面。
mvc-react 底层封装了 react-router-controller ,该组件实现了 URL 与 controller 以及对应的 View 的动态映射规则。
使用该功能只需要提前提供一个参数对象给 Controller 的静态 set 方法即可:
Controller.set({
readViewFile(viewId) {
//view可以异步载入
return import(`./view/${viewId}/index.jsx`).then(component => {
return component.default;
});
},
readControllerFile(controllerId) {
//webpackMode: eager是使import变为不异步,跟require一样,
return import(/* webpackMode: "eager" */
`./controller/${controllerId}.js`)
.then(controller => {
return controller.default;
})
},
//设置首页path(跳转路径,即react-router path='/'时,会跳转到indexPath)
indexPath: '/main/index',
});
这样当每次 URL 发生变更时,只要符合映射规则就会执行上述通用流程,并获取对应的 controller ,以及 View 然后展示给用户。
controller 的配置规则如下:
import Controller from 'react-router-controller';
export default class MainController extends Controller {
indexView(params) {
return this.render(
{
title: '主页',
breadcrumbs: [],
},
params
);
}
}
很明显这里采用了类的写法,首先引入基类 Controller
然后写下对外开放的 viewId 对应的方法名,譬如这里对外开放的 viewId 为 index,则对应的方法名为 indexView。更多细节查看该框架的使用入门
mvc-react 底层封装了 redux-saga-model ,每个 model 内都有 reducers
和 sagas
,reducers 中的 reducer 维护 redux 的 model namespace 下的数据块,每个 reducer 都输纯函数,不包括副作用,而 sagas 中的每个 saga 都可以写复杂的副作用。
{
namespace:'index',
state:{
name:'Tim'
},
reducers:{
update:function(state,{payload}){
return{ ...state,name:payload.name };
}
},
sagas:{
*updateName({payload},effects){
yield effects.put({
type:'update',
payload,
});
}
}
}
篇幅有限,这里不对 model 进行细入的讲解,重点讲如何在 mvc-react 中使用上述的这些 model。
我们回看初栏-第一印象中的代码:
import React from 'react';
import { render } from 'react-dom';
import { BrowserMVC as MVC, Controller } from 'mvc-react';
import model from 'model/index/db.js'
function modelRegister(register) {
//register model or else
register(model);
}
function renderApp() {
render(<MVC modelRegister={modelRegister} />, document.getElementById('root'));
}
// start
renderApp();
我们只需要在 mvc 组件中注入一个 modelRegister 回调,即可拿到入参,注册 model 的方法 register
对 model 进行注册。
配合 Controller 中的介绍,我们可以写出动态加载 controller 以及 view 组件的同时动态注册 model 的代码。
import React from 'react';
import { render } from 'react-dom';
import { BrowserMVC as MVC, Controller } from 'mvc-react';
function modelRegister(register) {
// Controller.set 设置如何读取指定模块的过程
// 譬如如何根据一个 controllerID 加载一个 controller 组件,如何根据一个 viewID 加载一个 view 组件
Controller.set({
readViewFile(viewId, firstLoad) {
if (firstLoad) {
import(/* webpackMode: "eager" */
`./model/${viewId}.js`)
.then(model => {
//注册sagaModel
register(model.default);
});
}
//view 可以异步载入
return import(`./view/${viewId}/index.jsx`).then(component => {
return component.default;
});
},
readControllerFile(controllerId) {
//webpackMode: eager是使import变为不异步,跟require一样,
return import(/* webpackMode: "eager" */
`./controller/${controllerId}.js`)
.then(controller => {
return controller.default;
});
},
//设置首页path(跳转路径,即 react-router path='/'时,会跳转到indexPath)
indexPath: '/main/index',
});
}
function renderApp() {
render(<MVC modelRegister={modelRegister} />, document.getElementById('root'));
}
// start
renderApp();
注意:配置 controller 规则的 Controller.set 方法必须在项目启动时同步执行,不支持异步配置。 modeRegister 是同步执行的,故可以在 modeRegister 回调中放入 Controller.set。
有了上面强大的 Controller 和 Model ,对于 View 而言,我么可以写出非常轻量级的组件,在 react 组件中,当需要触发对应的后续处理时,只需要分发对应的 action 即可,对应 namespace 的 model 会对其进行捕获,并更新数据,重新触发 view 的渲染更新。
import React from 'react';
import { connect } from 'react-redux';
@connect(state => {
return {
display: state.index,
};
})
export default class AboutView extends React.Component {
showToggleEvent = e => {
const { dispatch, display } = this.props;
if (display) {
dispatch({
type: 'index/toggleShow',
payload: false,
});
} else {
dispatch({
type: 'index/toggleShow',
payload: true,
});
}
};
render() {
const { display } = this.props;
return (
<div>
当前位置主页页面:
<button onClick={this.showToggleEvent}>
{display ? '隐藏' : '显示'}
</button>
{display && <div>我被显示了</div>}
</div>
);
}
}
-
项目打包工具: webpack
-
项目启动工具:create-react-boilerplate-app