decorator的mvc开发模式

修改

koa-cola可以使用es7的decorator装饰器开发模式来写mvc,controller是必须用提供的decorator来开发(因为涉及到router相关的定义),model和view层则没有强制需要demo所演示的decorator来开发。

Controller

使用decorator装饰器来注入相关依赖,路由层的decorators包括router、中间件、response、view,响应阶段的decorators包括koa.Context、param、response、request等,比如以下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const { 
Controller, Get, Use, Param, Body, Delete, Put, Post, QueryParam, View, Ctx, Response
} = require('koa-cola/client');
import Ok from '../responses/ok';

@Controller('')
class FooController {
@Get('/some_api') // 定义 router 以及 method
@Response(Ok) // 定义 API 返回的数据结构
some_api (@Ctx() ctx, @QueryParam() param : any) { // 注入 ctx 和 param
// 初始化数据,数据将会以 “Ok” 定义的格式返回
return {
foo : 'bar'
}
}
}

Ok 模块:

1
2
3
4
5
6
7
8
9
10
import * as Koa from 'koa';
export default function Ok(ctx : Koa.Context, data){
ctx.status = 200;
if(data){
ctx.body = {
code : 200,
result : data
};
}
}

因为使用decorator定义router,所以在koa-cola里面不需要单独定义router。

View

page的view组件可以使用不同类型的react组件:

React.Component组件

1
2
3
4
5
6
7
8
9
10
11
12
class Index extends React.Component<Props, States>   {
constructor(props: Props) {
super(props);
}
static defaultProps = {

};
render() {
return <h1>Wow koa-cola!</h1>
}
};
export default Index

stateless组件

1
2
3
export default function({some_props}) {
return <h1>Wow koa-cola!</h1>
}

react-redux组件

1
2
3
4
5
6
7
8
import { connect } from 'react-redux'
const Index = function({some_props}) {
return <h1>Wow koa-cola!</h1>
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Index)

Cola 装饰器组件

使用Cola装饰器来封装基于react-redux的组件

如果有子组件也是使用Cola 装饰器封装,则需要使用装饰器include

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import AddTodo from '../official-demo/containers/AddTodo';
import FilterLink from '../official-demo/containers/FilterLink';
import VisibleTodoList from '../official-demo/containers/VisibleTodoList';
const {
Cola
include
} = require('koa-cola/client');

@Cola({
initData : {
todosData : async ({ params, helpers, store: { dispatch } }) => {
const api = new GetTodoList({});
const data = await api.fetch(helpers.ctx);
dispatch({
type: 'INIT_TODO',
data: data.result.result
});
return data.result.result;
}
},
reducer : {
todos,
visibilityFilter
}
})
@include({ AddTodo, FilterLink, VisibleTodoList })
class ColastyleDemo extends React.Component<Props, States> {
constructor(props: Props) {
super(props);
}
render() {
return <App />;
}
}
export default ColastyleDemo;

自定义 header 和 bundle 方式

koa-cola 渲染页面时,默认会找views/pages/layout.ts封装页面的 html,如果没有这个layout文件,则直接输出 page 组件返回的 html,如果 view 组件使用了doNotUseLayout 装饰器,则页面不会使用layout.ts输出,这时你可以自定义headerbundle装饰器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import * as React from 'react';
const {
header, bundle, doNotUseLayout
} = require('koa-cola/client');
@doNotUseLayout
@bundle([
"/bundle.js",
"/test.js"
])
@header(() => {
return <head>
<meta name="viewport" content="width=device-width" />
</head>
})
function Page (){
return <h1>koa-cola</h1>
};
export default Page

Model

controller层、react 组件的view层必须使用 decorator,而model层则没有这个限制,它完全没有耦合。您可以使用 koa-cola 风格来编写 model,以获得更一致的开发体验,也可以随意使用任何 orm 或者 odm,或者不需要 model 层也可以。

你可以直接在目录 api/models 下创建如 user.ts:

1
2
3
4
5
import * as mongoose from 'mongoose'
export default mongoose.model('user', new mongoose.Schema({
name : String,
email : String
}))

然后就可以在其他代码里面使用:

1
const user = await app.models.user.find({name : 'harry'})

使用 koa-cola 的风格写 model

首先在api/schemas目录创建user.ts

1
2
3
4
5
6
7
8
9
10
export const userSchema = function(mongoose){
return {
name: {
type : String
},
email : {
type : String
}
}
}

在目录api/models下创建 model 如user.ts

1
2
3
import * as mongoose from 'mongoose'
import userSchema from '../schemas/user'
export default mongoose.model('user', new mongoose.Schema(userSchema(mongoose)))

当然也可以使用 decorator 方式定义 model,还可以定义相关hook,详情可以参考mongoose-decorators

1
2
3
4
5
import { todoListSchema } from '../schemas/todoList';
const { model } = app.decorators.model;

@model(todoListSchema(app.mongoose))
export default class TodoList {}

使用 cli 生成 model 的 schema

koa-cola --schema 自动生成model的接口定义在typings/schema.ts

然后你可以在代码通过使用 typescript 的类型定义,享受 vscode 的 intellisense 带来的便捷

1
2
import {userSchema} from './typings/schema' 
const user : userSchema = await app.models.user.find({name : 'harry'})

在前面提到的为何需要在api/schemas定义 model 的 schema,除了上面的自动生成 schema 接口,还可以在浏览器端代码复用,比如数据Validate。详细可以查看文档

koa-cola提供了前后端universal的api接口定义,比如todolist demo的获取数据的接口定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { todoListSchema } from './typings/schema';
import { ApiBase, apiFetch } from 'koa-cola';

export class GetTodoList extends ApiBase<
{
// 参数类型
},
{
code: number;
result: [todoListSchema];
},
{
// 异常定义
}
> {
constructor(body) {
super(body);
}
url: string = '/api/getTodoList';
method: string = 'get';
}

在代码里面使用 api,并享受 ts 带来的便捷:

1
2
const api = new GetTodoList({});
const data = await api.fetch(helpers.ctx);

Drawing
Drawing

又比如参数body的定义,如果定义了必传参数,调用时候没有传,则 vscode 会提示错误

1
2
3
4
5
6
7
8
9
10
11
12
13
import { testSchema } from './typings/schema';
import { ApiBase, apiFetch } from 'koa-cola'
export interface ComposeBody{
foo : string,
bar? : number
}
export class Compose extends ApiBase<ComposeBody, testSchema, {}>{
constructor(body : ComposeBody){
super(body)
}
url : string = '/compose'
method : string = 'post'
}

Drawing