小仙男·言在前
关于框架:为了解决VUE的SPA单页应用对SEO搜索引擎优化不友好的问题,这几天一直在调研各种SSR框架。比如doc.ssr-fc.com/ 和 fmfe.github.io/genesis-do 都是比较不错,且有自己理念和想法的框架。但是对于公司来说技术规范差异太大,团队学习成本比较高,思来想去,还是基于NUXT.JS自己搭建一套SSR框架慢慢完善吧。
关于本文档:本文档是从官网文档中摘录的一些重点内容,以及加入了自己的一些调整和对官网内容的理解和解释。
关于官网:NUXT中文网 特别适合新手学习,文档及案例十分清楚详尽,可以说有手就行。但是,中文网的更新不及时,有些章节(比如fetch钩子中不能使用this)甚至存在明显错误,所以有一定技术水平的宝子,建议直接查看 NUXT英文官网 。
【一、框架概述】
1、框架介绍
-
SSR
技术(即服务端渲染
技术),区别于原先纯Vue框架的SPA
应用(即单页应用
)。SPA
应用只有一个index.html的入口文件,页面显示的所有内容均靠客户端JS进行渲染,对于搜索引擎(SEO
)优化来说,整个网站只有一个空页面,十分不友好。而服务端渲染
技术,是借助node.js
作为框架服务端,在初次访问一个页面的时候,先在服务端预请求接口,并在服务端组装完成的html页面后,返回给客户端呈现。 -
Nuxt.js
是基于Vue
框架的一款服务端渲染
框架,提供了特有的框架结构和服务端渲染声明周期。
2、开发环境
- 本框架基于
Node.js+Webpack+vue+Nuxt.js
进行开发,提供ElementUI
作为UI框架。开发前需全局安装Node.js
与webpack
开发环境。 - 框架推荐
Node.js
版本为v16.15.0
,最低版本不得低于12
,推荐安装nvm
或n
等node版本管理工具。
3、分支要求
- 遵循[前端团队git仓库及版本管理规范],即
master
分支只用于拉取框架代码,xxx_dev
为开发分支,xxx_test
为开发分支,xxx
为生产分支。
3、关于本文档
- 本文档所述内容,已经是从官网中摘录的【重中之重】,开发前请【详尽】【仔细】【通读】本文档!!!尤其是【五、数据请求】 与 【六-8、重要Q&A】!!!
- 文档描述存在错误的地方,请以【NUXT英文官网】为准。
【二、启动与部署】
# 安装框架以来
$ npm install
# 启动本地开发环境,默认端口号:3000
$ npm run dev
# 编译并在生产环境启动
$ npm run build
$ npm run start
# 将网站打包成静态化页面
$ npm run generate
【三、框架结构】
- 详细目录结构介绍及使用,参照【六、其他规范与Q&A】
-- 框架根目录
-- .nuxt Nuxt运营和编译自动生成
-- dist 执行Nuxt静态化时生成
-- api 全局通用的Api请求函数(非Nuxt提供)
-- assets 静态资源目录,存放全局css、image等
-- components 自定义组件目录,此目录下组件无需引入,按需使用即可
-- layout 布局文件,参考https://www.nuxtjs.cn/guide/views
-- middleware 中间件,类似于路由守卫
-- modules 模块,用于设置全局监听等,参考https://www.nuxtjs.cn/guide/modules
-- pages 页面目录,Nuxt会根据此目录自动生成路由,参考https://www.nuxtjs.cn/guide/routing
-- plugins 插件目录,自定义各种插件,参考https://www.nuxtjs.cn/guide/plugins
> global.js (全局变量与全局方法)
> plugin.js (全局引入第三方组件)
> request.js (全局请求封装)
> filter.js (全局过滤器封装)
> util.js (全局工具函数封装)
> all.client.js(仅在客户端执行插件,暂时替代原app.vue)
-- static 不需要webpack编译的静态文件,一般存放ico等文件
-- store Vue状态树,与原写法有所不同,参考https://www.nuxtjs.cn/guide/vuex-store
-- utils 工具类包 (非Nuxt提供)
.editorconfig
.gitignore
env.js 环境变量配置,分dev、test、pro三种环境
nux.config.js Nuxt的所有配置项,参考https://www.nuxtjs.cn/api/configuration-build
package-lock.json
package.json
README.md 框架使用文档
ReleaseNote.md 版本更新说明
【四、生命周期】
-- Nuxt完整声明周期
【服务端渲染】
-- 全局
nuxtServerInit 第一个:nuxt中第一个运行的生命周期
RouteMiddleware 第二个:中间件,类似于原框架的路由导航守卫
-- 组件
validate 是用来校验url参数符不符合
asyncData Nuxt专属声明周期,可用于数据请求,只有page可用,子组件内部不可用
beforeCreate Vue声明周期,但是服务端会执行(不可用于数据请求,数据请求相关操作会在客户端执行)
created Vue声明周期,但是服务端会执行(同上)
fetch Nuxt专属声明周期,可用于数据请求, page和子组件都可用
【客户端渲染】
-- 全局
* `@/plugins/all.client.js` (并非Nuxt声明周期,是只在客户端运行的插件。此框架中用于暂时替代原框架中在App.vue中进行的全局初始化操作。)
-- 组件
beforeCreate
created
beforeMount
mounted
... (其他Vue后续声明周期)
几点说明:
-
beforeCreate/created
是Vue的生命周期,但是会在服务端和客户端各执行一次,但这两个钩子,仅供了解,不能用于数据请求。 -
asyncData
和fetch
都是Nuxt提供的声明周期,都可用于数据请求。只是写法略有不同(参考后续章节【五、数据请求】)。 -
@/plugins/all.client.js
并非Nuxt声明周期,是只在客户端运行的插件。但是Nuxt
框架去掉了app.vue
,此插件的声明周期,近似于原来的app.vue
,故暂时用于替代原框架中在App.vue中进行的全局初始化操作(是否恰当暂时不知)。
【五、数据请求】
1. 数据请求钩子
1.1 钩子相关说明
-
asyncData
和fetch
都是Nuxt提供的声明周期,都可用于数据请求,都会在服务端预请求数据进行组装; -
asyncData
只能在pages
级别的页面中调用,在子组件内部不能调用;fetch
则可以同时在页面和子组件中调用; - 官方建议数据请求均采用
asyncData
,但为了保持与老框架写法的一致,本框架暂时建议采用fetch
(后果未知) -
fetch
请求相比于asyncData
的已知缺陷有:- ① 数据请求较慢,本框架Demo,从index页进入Detail页,当使用
fetch
请求时,可明显看到浏览器选项卡的title出现一瞬间undefined
- ① 数据请求较慢,本框架Demo,从index页进入Detail页,当使用
- 尽管
beforeCreate/created
也可以在服务端渲染,但是这两个钩子的数据请求操作只会在客户端执行,非特殊情况,切勿用于页面初始化。
1.2 asyncData
- asyncData 中不能访问this,但是可以在第一参数中,拿到context上下文,使用context.app访问Vue根示例;
- context上下文还包含store、route、params、query等数据,详见context上下文
- asyncData中无法拿到组件实例,不能访问组件实例中的data method等方法。
- 详细介绍:asyncData
- 【请求示例】
// ① 使用return返回的对象,将直接初始化到组件`data`中
async asyncData({app, params}) {
const { code, data } = await app.$get('/policy/findById/'+params.id)
return {detail: data}
},
// ② return一个Promise,将在Promise执行完成后,将数据初始化到组件`data`中
asyncData({app, params}) {
return app.$get('/policy/findById/'+params.id).then(res => {
return {detail: data}
})
},
// ③ 第二个参数为callback回调函数,可直接传入数据,初始化到组件`data`中
asyncData({app, params}, callback) {
app.$get('/policy/findById/'+params.id).then(res => {
callback(null, {detail: data})
})
},
1.3 fetch
- fetch 分两种情况(新版本后支持第二种情况):
- ① 第一个参数接受context上下文,则与asyncData一样,不能访问this和组件实例; (这种情况,也不支持像asyncData一样通过return或者回调函数修改data内容)
- ② 不接受任何参数时,则可以正常访问this。(可以近似的看成created的用法,区别是 必须要使用await 或者return一个primary)
- 详细介绍:fetch英文文档 (中文文档严重延迟,存在错误)
- 【请求示例】
// ① 使用return返回一个Promise
fetch() {
return this.getDetail()
},
// ② 使用await/async
async fetch() {
await this.getDetail()
},
methods: {
// ① 使用await编写methods方法
async getDetail(id){
const { code, data } = await this.$get('/policy/findById/'+this.$route.params.id)
this.detail = data
}
// ② 使用return Promise编写methods方法
getDetail(id){
return this.$get('/policy/findById/'+this.$route.params.id).then(resw => {
this.detail = res.data
})
}
}
2. 数据请求方式
2.1 【框架推荐】 使用vue实例直接调用
- 本框架会将
$request/$get/$post
挂在到vue根示例,建议直接只用this
或上下文context.app
调用 - 【请求示例】
// 以this调用为例,如果是在`asyncData`中,需要使用上下文`context.app`调用
// ① get
this.$get('/policy/findById/'+this.$route.params.id)
// ② post
this.$post('/policy/findAll/',{page:1,size:10,params:{}})
// ③ request
this.$request({
url: '/policy/findAll/',
method: 'post',
data: {page:1,size:10,params:{}}
})
2.2 兼容老框架的api分离式调用
- 本框架推荐使用
五 2.1
的方式调用,但是也兼容了老框架的api分离式调用,用于提取可复用的公共请求
。 - 公共请求的api文件,统一放在
@/api/*.js
管理。 - 【请求示例】
/**
* @/api/index.js
*/
import request from '@/utils/request'
export function getPageList(data) {
return request.post('/policy/findAll', data)
}
/**
* @/pages/index.vue
*/
import { getPageList } from "@/api/index.js"
export default {
fetch() {
return this.getPageList(this.pageDto)
},
methods: {
getPageList(pageDto) {
return getPageList(pageDto).then(res => {
this.pageList = res.data.result
})
}
},
}
3. 其他注意事项
- 原则上,所有初始化渲染数据的请求,都要在服务端渲染函数(
asyncData
或fetch
)中进行,极个别无法在服务端渲染的请求,可以在Vue的生命周期(created
或mounted
)中初始化; - 服务端渲染的生命周期(即
asyncData/fetch
),不能使用任何浏览器专属的对像(如DOM
对象),也就是document
和window
,以及window
的各种对象和方法,例如setTimeout
、setInterval
、localStorage
、sessionStorage
等;
有上述需求的初始化逻辑,可以放到created
或mounted
中初始化。
【六、其他规范与Q&A】
1. 关于pages
- 本框架路由采用
约定式路由
,即不再使用route.js
进行路由声明,而是由框架根据pages
目录自动生成路由,详见路由 - 文件夹或者文件,如果以
_
开头,表示此为动态路由,可以传入不同参数,在组件内容,可以使用上下文或者this.$router取到路由参数;- 例如:
/pages/news/detail/_id.vue
、/pages/news/detail/_id/index.vue
- 访问:
http://domain.com/pages/news/detail/12345
(上述两种写法均为这一路径)
- 例如:
【注意】
- ① 使用
_id.vue
的写法,表示id
为可选参数,即可以通过http://domain.com/pages/news/detail
访问。如果要对id进行限制或验证,可以在组件内使用validate()
验证; - ② 使用
/_id/index.vue
的写法,表示id
为必选参数,访问http://domain.com/pages/news/detail
会报404。如果只要求id必填,而没有其他格式限制,可以使用此方式。 - ③
validate()
验证示例
// return true表示验证通过,return false表示验证失败 404
validate({ params }) {
return /^\d+$/.test(params.id)
},
2. 关于plugins
- 用于自定义框架所需的各种插件,声明插件后在
nuxt.config.js
中引入插件即可,类似于原框架main.js
相关功能。详见插件 - 框架已有的插件包(具体用户参照各插件的
顶部注释
):-
plugin.js
用于全局引入各种npm包; -
global.js
用于声明全局变量与全局方法; -
request.js
实现了全局请求封装(对应@/utils/request.js
); -
filter.js
实现了全局请求封装(对应@/utils/filter.js
); -
util.js
实现了全局请求封装(对应@/utils/util.js
); -
all.client.js
只在客户端引入,用于替代原框架中app.vue
中的各种初始化操作;
-
- 其他插件可根据需要自行定义,
*.js
表示服务端客户端均导入;*.client.js
表示仅在客户端导入;*.server.js
表示只在服务端导入;
3. 关于layout
- 用于定义框架中的各种布局文件,可根据需要自行定义,详见布局与视图
- 默认视图为
default.vue
,默认所有页面都将调用;error.vue
是错误视图,当页面出现问题时,自动调用; - 其他视图,可根据需要自行定义,并在组件内部声明引用。
- 【组件调用示例】
export default {
// 需要调用的视图名称,不写默认调用default.vue
layout: 'onlyBody',
data(){
return {}
}
}
4. 关于components
- 用于定义框架中的各种自定义组件,可根据需要自行定义。
- 自定义组件中的数据,一般应从页面传入,如果需要再组件内部获取数据,应该使用
fetch
(子组件中不支持asyncData
)。 -
components
中声明的各种组件,在使用时,无需import
导入。直接使用组件名按需调用即可。 - 【使用示例】
<template>
<div>
// Header组件
<Header />
</div>
</template>
5. 关于store
-
store
文件夹为Nuxt提供用于定义Vuex状态树的文件夹,详细文档参照:Vuex状态树。 - 此文件夹下面的xxx.js,分别表示一个模块,例如
index.js
对应$store.state.xxx
,而user.js
对应$store.state.user.xxx
- 本框架中
store
中模块的定义与普通Vue框架大体相同,只是Nuxt框架会自动引用Vuex并加载到构建配置重,无需我们自己new Vuex()
- 【使用示例】
/**
* 【注意区别】
* state mutations action不再是包裹在一个对象中,并在new Vuex()的时候传入。 而是分别作为单独模块使用export导出即可。
*/
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state) {
state.counter++
}
}
6. 关于middleware
-
middleware
是框架中用于声明中间件的文件夹,声明后在nuxt.config.js
中配置中间件即可,详细文档参照:中间件。 -
@/middleware/router.js
为已经升级声明好的路由守卫中间件
,可替代原框架中router.beforeEach
中的路由守卫功能;
7. 关于modules
- 用于自定义模块的文件夹,可以在模块中对Nuxt启动部署的各种声明周期设置监听,详细文档参照:模块。
-
@/modules/generator.ts
实现了一个对静态化结束generate:done
时进行监听并处理的示例。
const generator: any = function () {
this.nuxt.hook('generate:done', (context: any) => {
// TODO samething
})
}
export default generator
- 类似
this.nuxt.hook('generate:done',() => {})
的Nuxt框架hooks
还有很多,例如:ready
、error
、render:before
、build:compile
等等……详细参见INTERNALS
8. 其他Q&A
1)每个页面,必须使用head
设置title
,必要时还需在详情页设置description
。(!!!切记!!!)
export default {
head() {
return {
// title必须设置!!! 列表可以直接写“xxx列表”,详情页等有不同标题的,要用新闻标题、商品标题等作为title前缀。
title: this.detail.title + '_新闻详情',
meta: [
// 详情页,需要设置不同的description。 this.$getDesc 为全局封装的从富文本中截取100字符的description
{ hid: 'description', name: 'description', content: this.$getDesc(this.detail.details) },
],
}
}
}
2)pages
目录中的层级结构,务必按照功能梳理清楚,比如“news(新闻)”
的列表、详情都要在一个文件夹中。
(!!!目录结构一旦确定,原则上不可再调整!!!)
3)框架中的其他重要文件之【CSS
篇】!!
- 框架各种
css
文件,位于@/assets/css/
中。框架推荐使用scss
语言,使用"sass": "~1.32.13"
进行编译; -
common.scss
为全局公共CSS,请将全局样式表声明于此。或自行定义CSS文件,并在此文件中import
导入; -
font.scss
用于定义本框架各种字体、图标库等; -
variables.scss
声明了框架的各种全局Scss变量,可以在所有页面使用。- 注意:全局主题色,请用
$mainColor
表示,不要在各自文件中自行声明!
- 注意:全局主题色,请用
-
element-variables.scss
是ElementUI的主题声明文件,如需全局调整ElementUI的配色,请在此调整;