SSR渲染
SSR:在服务器端将Vue渲染成HTML返回给浏览器
优点
- 对SEO支持好
- SPA单页渲染更快
npx create-nuxt-app nuxtDemo
修改开发地址
package.json文件中进行修改
"config":{
"nuxt":{
"host":"127.0.0.1",
"port":"8000"
}
}
生命周期
nuxtServerInit
- 请求先到达 nuxtServerInit 方法,图中也表明了适用场景是对 store 操作,函数仅在每个服务器端渲染中运行 且运行一次,只能定义在store的主模板当中
- 定义在store/index.js中
export const actions={
nuxtServerInit(store,context){
console.log('nuxtServerInit',store,context);
}
}
middleware
- 下来请求到达 middleware ,允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。可以运行在全局,或者某个页面组件之前,不会在components组件内部运行
- 中间件执行顺序
nuxt.config.js=>匹配布局=>匹配页面 - 有三中定义方式
- nuxt.conig.js中引入middleware/auth.js
// auth.js
export default({store,route,redirect,params,query,req,res})=>{
console.log('nuxt.config.js',store,route,redirect,params,query,req,res);
}
// nuxt.conig.js
router:{
middleware:'auth'
},
- 定义在layout默认组件中
<template>
<div>
<p>默认布局</p>
<nuxt/>
</div>
</template>
<script>
export default{
// middleware:'auth', // 页面中间级中间件
middleware(){
console.log('layout middleware');
}
}
</script>
- 定义在单个页面中
<template>
<div>
<div><nuxt-link :to="{name:'about'}">about</nuxt-link></div>
<div><nuxt-link :to="{name:'news'}">news</nuxt-link></div>
<div><nuxt-link :to="{name:'test'}">test</nuxt-link></div>
</div>
</template>
<script>
export default {
name: 'IndexPage',
middleware(context){
console.log('middleware',context);
},
// 参数有效性
validate({params,query}){
// 校验业务
console.log('validate');
return true;
},
// 读数据返回给组件
asyncDate(){
console.log('asyncDate');
},
// 读数据给VUEX
fetch(){
console.log('fetch');
},
beforeCreate(){
console.log('beforeCreate');
},
created(){
console.log('created');
}
}
</script>
asyncData & fetch
接下来达到 asyncData & fetch 方法,asyncData() 适用于在渲染组件前获取异步数据,返回数据后合并到data选项内部,fetch() 适用于在渲染页面前填充 vuex 中维护的数据。会在组件每次加载前被调用(在服务端或切换至目标路由之前),由于是在组件 初始化 前被调用的,所以在方法内是没有办法通过 this 来引用组件的实例对象
通过axios发送网络请求
- npm i @nuxtjs/axios
- nuxt.config.js中进行配置
modules: [
'@nuxtjs/axios'
],
axios:{
proxy:true, // 开启代理
},
proxy:{
'/api/':{
target:"http://localhost:3001", // 代理转发地址
changeOrigin:true,
pathRewrite:{
'^/api':''
}
}
},
- 发送请求读取 本地或网络数据
<template>
<div>
<h3>商品</h3>
<div><nuxt-link to="/goods/1">商品1</nuxt-link></div>
<div><nuxt-link to="/goods/1">商品2</nuxt-link></div>
<!-- <div>{{title}}</div> -->
<nuxt/>
</div>
</template>
<script>
export default{
async asyncData({$axios}){
let list=await $axios({url:'/data/list.json'}); // 读取本地static/data/list.json中数据
let list2=await $axios({url:'/api/home'}); // 读取网络http://localhost:3001数据,目前在3000端口里,通过nuxt配置进行代理
console.log(list);
return {
title:list.data
}
},
// async fetch({$axios,store}){
// let list=await $axios({url:'/data/list.json'});
// store.commit('M_NAME',list)
// console.log(list);
// }
}
</script>
axios配置
- nuxt.config.js中进行配置,引入axios配置插件
plugins: [
{
src:'~/plugins/axios',
ssr:true, // 服务端
}
],
- 在plugins文件夹下新建axios.js
export default function({$axios,redirect,route,store}){
// 基本配置
$axios.defaults.timeout=3000,
// 请求拦截
$axios.onRequest(config=>{
config.headers.token='';
return config;
})
// 响应拦截
$axios.onResponse(res=>{
if(res.data.err===2&&route.fullPath!=='login'){
redirect('/login?path='+route.fullPath)
}
return res;
})
// 错误处理
$axios.onError(error=>{
return error
})
}
render
最后进行渲染。将渲染后的页面返回给浏览器,用户在页面进行操作,如果再次请求新的页面,此时只会回到生命周期中的 middlerware 中,而非 nuxtServerInit ,所以如果不同页面间需要操作相同的数据请用 vuex 来维护,render钩子只能定义渲染时的配置,render内部不可以有业务逻辑,也不执行beforeCreate & created
mounted & updated
运行在客户端
注:没有keep-alive 那自然activated、deactivated这两个生命周期也没了
路由
- nuxt会把page下的自动生成路由
- 只需要在跳转时通过nuxt-link配置路径
<template>
<div>
<div><nuxt-link :to="{name:'about'}">about</nuxt-link></div>
<div><nuxt-link :to="{name:'news'}">news</nuxt-link></div>
</div>
</template>
<script>
export default {
name: 'IndexPage'
}
</script>
- 路由传参和VueRouter一致
<template>
<div>
<!-- <div><nuxt-link to="/news/123">123</nuxt-link></div> -->
<div><nuxt-link :to="{name:'news-id',params:{id:123}}">123</nuxt-link></div>
</div>
</template>
-
在对应page页面可以用_表示参数解析
- page文件夹
- goods页面
- _id.vue
- 非动态路由可对跳转路由为/goods/1进行解析,解析结果为id:1
- 动态路由:to=“{name:‘goods-id’,params:{id:1}}”,解析结果为id:1
- goods页面
- page文件夹
-
可对参数进行校验,如果参数格式不对则进入到nuxt准备的404页面
<template>
<div>
<div>new content</div>
<div>id:{{$route.params.id}}</div>
<div><nuxt-link to="/">home</nuxt-link></div>
</div>
</template>
<script>
export default{
validate({params}){
return /^\d+$/.test(params.id)
}
}
</script>
- 对路由跳转时添加动画效果
- 所有路由配置在全局引入的CSS文件里
/* 页面进入时,页面离开时 入 */
.page-enter-active,.page-leave-active{
transition: opacity 2s;
}
/* 页面离开时 退 */
.page-enter,.page-leave-active{
opacity: 0;
}
- 单个路由配置在全局引入的CSS文件里
.test-enter-active,.test-leave-active{
transition: all 2s;
font-size: 12px;
}
.test-enter,.test-leave-active{
font-size: 40px;
}
<template>
<div>test</div>
</template>
<script>
export default {
name: 'TestPage',
transition:'test', // 对应的页面进行相应配置
}
</script>
路由守卫
- 前置路由守卫
- 全局守卫:nuxt.config指向middleware,layouts定义中间件
- 组件独享守卫:middleware
- 插件全局后置守卫
- 后置路由守卫
- 使用vue的beforeRouterLeave钩子
- 插件全局后置守卫
默认模板和默认布局
默认模板
- 在项目根目录下,新建一个app.html文件,nuxt默认它是一个默认模板
<!DOCTYPE html>
<html lang="en">
<head>
{{HEAD}}
</head>
<body>
<p>默认模板</p>
{{APP}}
</body>
</html>
- 在项目根目录下建一个layouts文件夹,里面新建一个default.vue
<template>
<div>
<p>默认布局</p>
<nuxt/>
</div>
</template>
错误页面和个性meta
错误页面
- 在项目根目录下建一个layouts文件夹,里面新建一个error.vue
个性meta
- nuxt可在每个页面对head进行单独设置
<template>
<div>
<div>new content</div>
<div>id:{{$route.params.id}}</div>
<div><nuxt-link to="/">home</nuxt-link></div>
</div>
</template>
<script>
export default{
validate({params}){
return /^\d+$/.test(params.id)
},
data(){
return {
title:this.$route.params.title
}
},
head(){
return {
title:this.title,
meta:[
{hid:"不覆盖",name:'new1',content:'测试页面'}
]
}
}
}
</script>
loading页面
- 在进行路由跳转时可以给一些提示,也就是loading
- 在nuxt.config.js中进行配置
// 自定义loading效果
// loading:{color:'#399',heigth:'3px'},
loading:'~/components/loading.vue'
- components新建loading.vue
<template>
<div v-if="loading">loading....</div>
</template>
<script>
export default{
data:()=>({
loading:false,
}),
methods:{
start(){
this.loading=true;
},
finish(){
this.loading=false;
}
}
}
</script>
vuex
模块方式
- 在nuxt中集成了vuex
- store目录下每个js文件都会被转换称为状态树,当然index是根模块
模块数据管理
- 主模块
// 主模块
// state
export const state=()=>({
data1:false,
})
export const mutations={
M_UPDATE_DATE1(state,payload){
state.data1=payload
}
}
// actions
export const actions={
nuxtServerInit(store,context){
// console.log('nuxtServerInit',store,context);
}
}
// getters
export const getters={
getData1(state){
return state?'真':'假'
}
}
- home模块
export const state=()=>({
home1:false,
test:'',
})
export const mutations={
M_UPDATE_HOME1(state,payload){
state.home1=payload.home1
},
M_UPDATE_TEST(state,payload){
state.test=payload.test
}
}
export const actions={
A_UPDATE_HOME({commit,state},payload){
// 可以做异步处理
commit('M_UPDATE_HOME1',{home1:true})
},
A_UPDATE_TEST({commit,state},payload){
console.log(payload);
commit('M_UPDATE_TEST',payload)
}
}
- 也面中使用
<template>
<div>
<div><button @click="changClick">测试home中数据</button></div>
</div>
</template>
<script>
export default {
name: 'IndexPage',
methods:{
changClick(){
this.$store.dispatch('home/A_UPDATE_TEST',{test:'test'});
this.$store.commit('home/M_UPDATE_HOME1',{home1:true})
}
}
}
</script>
<style scoped>
.app-header-active{
color: red;
background-color: pink;
}
</style>
vuex状态持久化管理
- npm i cookie-universal-nuxt
- nuxt.config.js中配置
modules: [
'@nuxtjs/axios',
'cookie-universal-nuxt'
],
// 注册后,在全局上下文对象中多了个$cookies
- 在登陆页面,在登陆成功后同步数据到cookie和vuex
<template>
<div>
<button @click="login">登录</button>
</div>
</template>
<script>
export default {
name:'login',
methods:{
login(){
this.$cookies.set('user',{name:'jack'});
this.$store.commit('M_UPDATE_USERINFO',{name:'jack'})
}
}
}
</script>
- 强刷时,nuxtServerInit中将cookie中数据同步带vuex中
// 主模块
// state
export const state=()=>({
data1:false,
})
export const mutations={
M_UPDATE_DATE1(state,payload){
state.data1=payload
}
}
// actions
export const actions={
// 同步数据
nuxtServerInit(store,{app:{$cookies}}){
let user =$cookies.get('user')?$cookies.get('user'):{err:1,token:'',name:''};
store.commit('user/M_UPDATE_USERINFO',user)
}
}
// getters
export const getters={
getData1(state){
return state?'真':'假'
}
}
引入ElementUI组件库
- nuxt.config.js中ElementUI以插件方式引入
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'nuxtDemo',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [
'~/asstes/css.css',
'element-ui/lib/theme-chalk/index.css'
],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
{
src:'~/plugins/axios',
ssr:true, // 服务端
},
{
src:'~/plugins/element-ui',
ssr:true,
}
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
'@nuxtjs/axios',
'cookie-universal-nuxt'
],
axios:{
proxy:true, // 开启代理
},
proxy:{
'/api/':{
target:"http://localhost:3001", // 代理转发地址
changeOrigin:true,
pathRewrite:{
'^/api':''
}
}
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
transpile:[/^element-ui/],
}
}
- 在plugins中新建element-ui.js
import Vue from 'vue';
// 整体引入
import ElementUI from 'element-ui';
Vue.use(ElementUI);
// 按需引入
// import {Button} from 'element-ui';
// Vue.use(Button);
- 页面中使用
<template>
<div>
<div><el-button type="primary">EL按钮</el-button></div>
</div>
</template>
<script>
export default {
name: 'IndexPage',
}
</script>
全局方法,过滤器, 全局指令,全局组件
全局方法
- nuxt.config.js中配置
export default {
plugins: [
{
src:'~/plugins/mixins'
}
],
}
- plugins文件夹下新建mixins
// 全局方法
import Vue from 'vue';
const show=()=>console.log('全局方法');
Vue.prototype.$show=show; // 服务端钩子不可以用
- 在页面中通过this.$show()调用
过滤器
- nuxt.config.js中配置
- assets/js新建filters.js文件
export function fillzero(n){
return n<10?'0'+n:n+'';
}
export const date=time=>{
let d = new Date();
d.setTime(time);
const year=d.getFullYear();
const month=d.getMonth()+1;
const date=d.getDate();
return `${year}年${month}月${date}日`
}
- plugins文件夹下mixins中写入
// 全局方法
import Vue from 'vue';
const show=()=>console.log('全局方法');
Vue.prototype.$show=show; // 服务端钩子不可以用
// 全局过滤器
import * as filters from '../asstes/js/filters';
Object.keys(filters).forEach(key=>Vue.filter(key,filters[key]))
- 页面中使用
<template>
<div>
<div>time:{{time|date}}</div>
</div>
</template>
<script>
export default{
data(){
return {
time:new Date(),
}
}
}
</script>
全局指令
- nuxt.config.js中配置
- assets/js新建directives.js文件
function direc1(el,binding,vnode){
console.log('自定义指令');
}
export default {
bind(el,binding,vnode){
direc1(el,binding,vnode)
}
}
- plugins文件夹下mixins中写入
// 全局方法
import Vue from 'vue';
const show=()=>console.log('全局方法');
Vue.prototype.$show=show; // 服务端钩子不可以用
// 全局过滤器
import * as filters from '../asstes/js/filters';
Object.keys(filters).forEach(key=>Vue.filter(key,filters[key]))
// 自定义指令
import direc1 from '../asstes/js/directives';
Vue.directive('direc1',direc1)
- 在页面中按正常指令使用
<template>
<div>
<div v-direc1>自定义指令</div>
</div>
</template>
<script>
export default {
name: 'IndexPage',
}
</script>
全局组件
- nuxt.config.js中配置
- components/global新建globalTest.vue文件
<template>
<div>全局组件测试</div>
</template>
- plugins文件夹下mixins中写入
// 全局方法
import Vue from 'vue';
const show=()=>console.log('全局方法');
Vue.prototype.$show=show; // 服务端钩子不可以用
// 全局过滤器
import * as filters from '../asstes/js/filters';
Object.keys(filters).forEach(key=>Vue.filter(key,filters[key]))
// 自定义指令
import direc1 from '../asstes/js/directives';
Vue.directive('direc1',direc1)
// 全局组件
import globalTest from '../components/global/globalTest';
Vue.component('globalTest',globalTest)
- 页面中按组件正常使用,并且不需要再次引入
<template>
<div>
<GlobalTest/>
</div>
</template>
<script>
export default{
}
</script>
资源指向
static
- static中资源不会经过处理,直接搬到资源环境下,映射到资源根目录下
- 可以通过绝对路径拿到static下资源
<img src='/img.test.png'/>
assets
- assets下资源会被处理
- 可以通过相对路径拿到assets下资源
<img src='../assets/img.test.png'/>
<img src='~assets/img.test2.png'/>