1 | # vgg
|
2 |
|
3 | vgg是根据vue技术栈的最佳实践总结而来的技术架构,它能以插件的形式扩展框架本身。支持单页面应用
|
4 | 的服务端和客户端渲染。
|
5 |
|
6 | 架构如下:
|
7 |
|
8 | ```
|
9 | |--------------------|
|
10 | | vgg + vgg-plugin-* |
|
11 | |--------------------|
|
12 | | egg-vgg |
|
13 | |--------------------|
|
14 | | eggjs |
|
15 | |--------------------|
|
16 | ```
|
17 |
|
18 | **重要:**我们基于[egg-vgg](https://github.com/acthtml/egg-vgg)可快速开始开发。
|
19 |
|
20 | ## 1. 文件目录结构
|
21 |
|
22 | ```
|
23 | - api api service层
|
24 | - modules api对应的接口模块
|
25 | - user.js
|
26 | - index.js api实例扩展
|
27 | - common 通用资源
|
28 | - context vue上下文,这里的资源会绑定到vue原型,允许你以this.$的形式访问。
|
29 | - filter vue filter
|
30 | - directive vue directive
|
31 | - plugins vue plugin
|
32 | - utils vgg.utils工具库
|
33 | - commponents 全局组件
|
34 | - index.js
|
35 | - config 配置
|
36 | - config.default.js
|
37 | - config.local.js
|
38 | - config.stage.js
|
39 | - config.prod.js
|
40 | - plugin.json 插件配置
|
41 | - plugins 插件约定存放文件夹
|
42 | - plugin_a
|
43 | - router 路由
|
44 | - router.js router实例扩展
|
45 | - routes.js 路由注册
|
46 | - store vuex store
|
47 | - modules store对应命名空间的模块
|
48 | - user.js
|
49 | - index.js vuex实例扩展
|
50 | - views 单页面应用视图层
|
51 | - home.vue
|
52 | - app.template.html 服务端渲染基础html
|
53 | - app.vue 根组件
|
54 | - pages 多页面应用视图层
|
55 | - page_a.vue
|
56 | - app.js 根实例扩展
|
57 | ```
|
58 |
|
59 | 从目录结构我们可以看出,框架分为如下几个重要的部分:
|
60 |
|
61 | - 配置(config)
|
62 | - 通用vue资源(common和commponents)
|
63 | - store
|
64 | - api服务层
|
65 | - 路由系统
|
66 | - 插件系统
|
67 |
|
68 |
|
69 | ## 2. 环境与配置
|
70 |
|
71 | 根据当前的环境读取对于的配置,运行环境配置参考[eggjs运行环境](https://eggjs.org/zh-cn/basics/env.html)。
|
72 |
|
73 | 配置上的约定也跟eggjs一致,即config.default.js为默认配置,环境配置覆盖默认配置,覆盖采用
|
74 | `_.defaultsDeep`方式。
|
75 |
|
76 | 通过vgg.env获取当前环境,vgg.config获取当前配置。
|
77 |
|
78 | ```js
|
79 | // config/config.default.js
|
80 | export default {
|
81 | auth: {
|
82 | enabled: false,
|
83 | mod: 'sso'
|
84 | }
|
85 | }
|
86 |
|
87 | // config/config.local.js
|
88 | export default {
|
89 | auth: {
|
90 | enabled: true
|
91 | }
|
92 | }
|
93 |
|
94 | // 当在本地开发环境(local)时
|
95 | vgg.config.auth.enabled === true;
|
96 | vgg.config.auth.mod === 'sso';
|
97 | ```
|
98 |
|
99 |
|
100 | ## 3. 通用vue资源
|
101 |
|
102 | 框架支持几种vue资源全局性的注入,分别是:
|
103 |
|
104 | - 组件(componetns)
|
105 | - 上下文(common/context)
|
106 | - vue的过滤器、指令、插件
|
107 | - 工具类库(utils)
|
108 |
|
109 | 只要按照约定,框架会自动注入你配置的资源。
|
110 |
|
111 | ### 3.1 组件(components)
|
112 |
|
113 | 在`components/index.js`中声明组件,key为组件名称,value为需要引入的组件。
|
114 |
|
115 | ```js
|
116 | // components
|
117 | import MyApp from './app.vue';
|
118 | export default {
|
119 | MyHeader: () => import('./header.vue'),
|
120 | MyApp
|
121 | }
|
122 |
|
123 | // 也可以使用辅助函数来自动添加前缀。
|
124 | let components = vgg.utils.prefix('My', {
|
125 | Header: () => import('./header.vue'),
|
126 | App: () => import('./app.vue')
|
127 | });
|
128 | export default components;
|
129 | ```
|
130 |
|
131 | ### 3.2 上下文(common/context)
|
132 |
|
133 | 在`common/context/index.js`中声明上下文,key为上下文名称,value为上下文创建函数。
|
134 |
|
135 | ```js
|
136 | // common/context/index.js
|
137 | import axios from 'axios';
|
138 | export default {
|
139 | /**
|
140 | * 创建一个axios实例,并注入上下文。
|
141 | * @param {[type]} appContext koa app context,是服务端返回的上下文。
|
142 | * @param {[type]} context 注入好的上下文。
|
143 | * @return {[type]} 上下文对应的内容。
|
144 | */
|
145 | http: (appContext, context) => {
|
146 | return axios.create();
|
147 | }
|
148 | }
|
149 | ```
|
150 |
|
151 | 注入好之后,你能在其他appContext中使用,例如appContext.http,或则在组件中以this.$http来
|
152 | 访问。
|
153 |
|
154 | 框架内置的上下文有:cookies, http, logger。
|
155 |
|
156 | ### 3.3 vue的过滤器、指令、插件
|
157 |
|
158 | 分别对应这些文件夹:`common/filter、common/directive、common/plugin`。
|
159 |
|
160 | 这3类资源跟context的声明方式类似,在各自文件夹下的`index.js`中声明,key为资源名称,value为
|
161 | 创建方法,最终都会交给Vue来进行全局性的注入:
|
162 |
|
163 | ```js
|
164 | // 对于filter
|
165 | Vue.filter(key, filters[key]);
|
166 | // 对于directive
|
167 | Vue.directive(key, directives[key]);
|
168 | // 对于plugin
|
169 | Vue.use(plugins[key]);
|
170 | ```
|
171 |
|
172 | ### 3.4 工具类库(common/utils)
|
173 |
|
174 | 全局性的工具类库,注入到vgg.utils中。
|
175 |
|
176 | ```js
|
177 | // 在`common/utils/index.js`中进行注入对应的方法。
|
178 | export default {
|
179 | now(){return new Date()}
|
180 | }
|
181 |
|
182 | // 其他地方使用
|
183 | import vgg from 'vgg';
|
184 | vgg.utils.now();
|
185 | ```
|
186 |
|
187 | ## 4. store
|
188 |
|
189 | store是vuex的实现,[文章和架构参考](https://vuex.vuejs.org/zh-cn/structure.html)。
|
190 |
|
191 | ### 4.1 创建store module
|
192 |
|
193 | 在文件夹`store/modules`中创建模块,创建之后,即可使用`store.register`对模块进行注册,文件
|
194 | 命名采用`snake_case`规则。
|
195 |
|
196 | ```js
|
197 | // 创建store模块
|
198 | // store/modules/my_book.js
|
199 | export default (namespace, appContext) => {
|
200 | return {
|
201 | namespaced: !!namespace,
|
202 | state(){
|
203 | return {}
|
204 | },
|
205 | // getters,
|
206 | // mutations,
|
207 | // actions
|
208 | }
|
209 | }
|
210 | ```
|
211 |
|
212 | ### 4.2 使用store module
|
213 |
|
214 | 要使用模块,先要使用`store.register()`进行模块注册。注册的本质是获取对应的store模块,调用
|
215 | `store.registerModule()`注册到对应的命名空间中。
|
216 |
|
217 | ```js
|
218 | // 某某组件:my_component.vue
|
219 | export default {
|
220 | async asyncData({store}){
|
221 | // 注册模块到指定命名空间上
|
222 | // store.register(namespace, modulepath, ...args)
|
223 | // 向命名空间example/some添加模块example/some
|
224 | store.register('example/some', 'example/some');
|
225 | // 该语句有个简写
|
226 | store.register('example/some');
|
227 | },
|
228 | created(){
|
229 | this.$store.register('example/some')
|
230 | },
|
231 | methods: {
|
232 | ...mapState('example/some', ['some'])
|
233 | }
|
234 | }
|
235 | ```
|
236 |
|
237 | 其他一些api,参考[create_store.js](./src/core/create_store.js)。
|
238 |
|
239 | - store.register(namespace[, modulepath[, ...args]])
|
240 | - store.unregister(namespace)
|
241 | - store.isRegistered(namespace)
|
242 | - store.once(type, namespace, name[, ...args])
|
243 | - store.ensure(type, namespace, name)
|
244 | - store.has(type, namespace, name)
|
245 | - store.try(type, namespace, name, ...args)
|
246 |
|
247 | ``注意点``
|
248 |
|
249 | 1. 如果模块需要在组件beforeCreate生命周期(包含beforeCreate)前使用,那么这个模块需要在路由
|
250 | 组件的asyncData中注入。[参考服务器端数据预取](https://ssr.vuejs.org/zh/data.html)
|
251 | 2. 保留命名空间'route'。[参考vue-router-sync](https://github.com/vuejs/vuex-router-sync)
|
252 |
|
253 | ## 5. api服务层
|
254 |
|
255 | 类似于service层,用于跟后端进行数据交互。
|
256 |
|
257 | ### 5.1 api的创建
|
258 |
|
259 | api在文件夹`api/modules`中创建,创建后可在组件、store中使用,文件名采用`snake_case`规范。
|
260 |
|
261 | ```js
|
262 | // 创建api book。
|
263 | // 文件:api/modules/book.js
|
264 | // 整个模块的默认格式为如下,其函数返回内容即为这个api的可使用接口。
|
265 | export default (http, api, logger) => {
|
266 | return {
|
267 | async getList(){
|
268 | return http( /* ...do some http request */)
|
269 | },
|
270 | async getDetail(){
|
271 | return http( /* ...do some http request */)
|
272 | }
|
273 | }
|
274 | }
|
275 | ```
|
276 |
|
277 | ### 5.2 api的使用
|
278 |
|
279 | 在`store`中,api被注入到上下文appContext中。
|
280 |
|
281 | ```js
|
282 | export default (namespace, appContext) => {
|
283 | const {api} = appContext;
|
284 | return {
|
285 | // ....
|
286 | // 某某store
|
287 | actions: {
|
288 | async init(){
|
289 | let data = await api('book').getList();
|
290 | }
|
291 | }
|
292 | }
|
293 | }
|
294 | ```
|
295 |
|
296 | 在组件中可通过`$api`这个对象访问:
|
297 |
|
298 | ```js
|
299 | // 某某组件中
|
300 | export default {
|
301 | methods: {
|
302 | async init(){
|
303 | let data = await this.$api('book').getList();
|
304 | }
|
305 | },
|
306 | // 但在asyncData方法中,因为组件还没有实例化,所以通过参数进行注入了。
|
307 | async asyncData({api}){
|
308 | let data = await api('book').getList();
|
309 | }
|
310 | }
|
311 | ```
|
312 |
|
313 |
|
314 | ## 6. 路由
|
315 |
|
316 | 路由是基于[vue-router](https://router.vuejs.org/zh-cn/)实现的,在`router/routes.js`
|
317 | 中添加路由,配置同`vue-router`。
|
318 |
|
319 | 在config的router属性中配置路由初始化属性,配置项[参考](https://router.vuejs.org/zh/api/#router-%E6%9E%84%E5%BB%BA%E9%80%89%E9%A1%B9)
|
320 |
|
321 | 通过hook系统可扩展router实例本身:
|
322 |
|
323 | - hook router.alter
|
324 | - hook router.onError
|
325 | - hook router.beforeEach
|
326 | - hook router.afterEach
|
327 |
|
328 |
|
329 | ## 7. 插件
|
330 |
|
331 | 框架通过插件来扩展自身,可以扩展框架的所有内容。
|
332 |
|
333 | ### 7.1 使用插件
|
334 |
|
335 | 在文件夹`config/plugin.js`中来声明要使用的插件。
|
336 |
|
337 | ```js
|
338 | // config/plugin.js
|
339 | export default {
|
340 | // 命名空间。(插件的命名空间,使用的时候,因为技术限制,不能自定义命名空间。命名空间是由插件作者指定好的。)
|
341 | pluginA: {
|
342 | // 对应插件的包名称或相对地址。例如'./plugin/plugin_a'
|
343 | package: 'plugin-a',
|
344 | // 默认注入组件。关闭之后需要手动注入,这样有助于减少打包体积。
|
345 | components: true,
|
346 | // 默认注入路由。关闭之后需要手动注入,这样有助于减少打包体积。
|
347 | routes: true
|
348 | },
|
349 | // 当components和routes都是需要注入时,配置可以简写:
|
350 | // pluginA: 'plugin-a'
|
351 | }
|
352 | ```
|
353 |
|
354 | 引入其他插件的资源:`import('$pluginA/foo/bar')`。这样就是找根目录是插件`pluginA`,文件
|
355 | 相对地址是`./foo/bar`的模块。
|
356 |
|
357 | 使用插件的api和store:
|
358 |
|
359 | ```js
|
360 | // api
|
361 | this.$api('myapi', 'pluginA');
|
362 | // store
|
363 | this.$store.register('pluginA/user', '$pluginA/user');
|
364 | // 等同于
|
365 | this.$store.register('$pluginA/user');
|
366 | ```
|
367 |
|
368 | ### 7.2 创建插件
|
369 |
|
370 | @todo
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|