Nuxt学习笔记

    科技2024-04-15  10

    安装:

    npx create-nuxt-app xx

    Choose rendering mode Universal (SSR)

    开发:npm run dev

    服务器端

    服务端和客户端都运行

    beforeCreate && created

    生命周期

    nuxtServerInit 对stroe操作

    middleware

    中间件执行流程顺序:

    nuxt.config.js->匹配布局->匹配页面

    middleware nuxt.config outside->middleware layouts->middleware pages

    validate

    页面级别

    //参数的有效性 validate({params,query}){ //校验业务 console.log('validate' return true; }

    asyncData和fetch

    //读数据,返回给组件 asyncData(context){ //异步业务逻辑,读取服务端数据 console.log( 'asyncData ') return { b:2 }//读数据, vuex fetch({store}){ //异步业务逻辑,读取服务端数据提交给vuex console.log( 'fetch') }

    路由

    路由是约定式

    展示区: name:路由名目录名-其他目录-文件名 params: key要对等文件名 子路由:目录代表子路由,子路由内部同级的文件,代表是同级一级路由 配置 声明式跳转:<nuxt-link :to=" name: 'product-id' ,params:{id:3] ,query:{a:111,b:222}}">商品e3</nuxt-link>

    动态路由,_名称 ,加下划线,代表变量

    路径从pages出发为依据

    <nuxt-link to="/goods/2?a=11&b=22">商品02</nuxt-link> //id对应文件名为_id.vue <nuxt-link :to="{name:'goods-id' ,params:{id:3}, query:{a:111,b:222}}">商品03/nuxt-link

    要用二级的内容替换一级的内容,只需要在二级的文件夹内新建index.vue,并把写在外面的路由和展示区转移进去即可

    要使进入页面是是页面自身(详情列表)而不是详情页,需要加入index.vue(详情列表)

    pages/一级展示/二级展示 /index.vue会在一级展示 /index.vue空文档代表有默认页,不会找寻其他_详情.vue

    exact-active-class="xxx",xxx样式,加上exact,代表严格匹配

    扩展性路由

    router:{ //扩展路由 extendRoutes(routes,resolve){ routes.push( name: ' home', path: ' /index' , component: resolve(___dirname, 'pages/index.vue') ) } }

    参数校验

    错误页面写在layouts内error.vue

    //接受错误信息error:{statuscode,message} props:'error' 编程式导航 <button @click="$router.replace('/index')>跳转到首页</button>

    统一动效

    在nuxt.config.js内配置对应css全局样式

    css:[ 'assets/css/transition.css' ]

    单独的组件内部引用动画,要写上transition:'动画名'

    路由守卫

    前置:依赖中间件middlware,插件

    ​ 全局守卫: nuxt.config指向middleware ​ layouts定义中间件 ​ 组件独享守卫: middleware(写法同layouts)

    ​ 插件全局前置守卫

    后置:组件独享后置守卫,使用vue的beforeRouteLeave钩子

    ​ 插件全局后置守卫

    //全局守卫前置业务 //nuxt.config文件 router:[ middleware: 'auth', ] //middleware/auth.js文件 export default ({store,route,redirect,params, query,req,res})=>{ // context服务端上下文 //store状态树信息 //route一条目标路由信息 // redirect强制跳转 // params, query校验参数合理性 console.log('middleware nuxt.config outside') redirect('xx') } ------------------------ //ayouts定义中间件 middleware(istore,route,redirect,params ,query}){ console.log('middleware nuxt.config outside') } ------------------------- //插件全局前置守卫 //nuxt.config文件,~代表根目录 plugins:[ '~/plugins/router' ], //plugins/router.js export default ({app,redirect,params,query,store})=>{ console.log('插件') // app == vue实例 app.router.beforeEach((to,from,next)=>{ -------------------- //全局前置的守卫,插件 //next(true)/next(false) //next( '/login ') × redirect 跳转函数 √ }) --------------------- //插件全局后置守卫 app.router.afterEach((to,from)=>{ console.log('插件全局后置守卫') }) } ------------------------ //组件独享后置守卫 export default { beforeRouteLeave(to,from,next){ let bl=window.confirm('是否要离开'); next(b1) } }

    数据交互,跨域

    安装@nuxtjs/axios、@nuxtjs/proxy

    //nuxt.config文件 modules:[ '@nuxtjs/axios' ], plugins: [ '~/plugins/router', { src: '~/plugins/axios', ssr:true//服务端 }, //组件 //$axios可使用modules内定义的 async asyncData({$axios}){ let res = await $axios({url: '/data/list.json'}) console.log('读取到的静态资源',res.data) //此时将数据写入template,利于seo }

    解决跨域

    //nuxt.config文件 axios:{ proxy:true, //允许跨域 //prefix:'api'//baseUrl }, proxy:{ '/api/':{ target: "http://localhost: 3001',//代理转发的地址 changeOrigin:true, pathRewrite:{ '^/api':'' } }

    拦截器配置与token携带

    //nuxt.config文件 plugins: [ '~/plugins/router', { src: '~/plugins/axios', ssr:true//服务端 } ], //plugins/axios.js export default function({$axios,redirect,route,store}){ //基本配置 $axios.defaults.timeout=10000; //请求拦截 $axios.onRequest(config=>{ console.log('请求拦截') config.headers.token='加token '; return config; }) //响应拦截 $axios.onResponse(res=>{ if(res.data.err === 2 && route.fullPath !== '/login'){ redirect('/login?path='+route.fullPath) return res }) //错误处理 $axios.onError(error=>i //处理 return error; ) }

    loading页配置与定制

    //nuxt.config文件 //定义系统默认loading效果 loading: {color:'#399', height: ' 3px'} //或指定loading组件 loading: '~/components/loading.vue’

    loading.vue

    <template> <div v-if="loading" >loading...</div> </template> <script> export default { data:()=>({ loading:false }), methods:{ //start和finish是nuxt的loading内置的方法 start(){ this.loading = true; } finish(){ this.loading = false; } } } </script>

    vuex定义和使用

    模块方式: store目录下的每个.js文件会被转换成为状态树指定命名的子模块(当然,index是根模块)

    Classic(不建议使用): store/index.js返回创建Vuex.store实例的方法。

    state必须是一个函数(规定),其他可以为对象,然后批量导出index内的state,mutations,actions,getters

    ...mapstate({home:state=>state.home.data}), //名称冲突时可以将data重命名为home

    状态持久化与token校验

    安装cookie-universal-nuxt:状态持久化,需要到配置文件的mudules内添加一下,请求自动携带cookie

    思想:登录时,

    同步vuex 8& cookie,强制刷新后(vuex失效),用nuxtServerInit钩子,取出cookies,同步vuex,axios拦截器读取vuex(vuex存在内存上,读取速度更快;cookies存在磁盘,读取速度慢)

    1.同步vuex && cookie

    this.$cookies.set('user',res.data) this.$store.commit('user/M_UPDATE_USER ,res.data) //跳转: 1.登录或注册跳转到用户页,2.哪里来回哪里 if(!this.$route.query.path || /login|reg/.test(this.$route.query.path)){ this.$router.replace( '/user') }else{ this.$router.replace(this.$route.query.path)

    2.强刷后,利用nuxtServerInit取出cookies同步vuex

    store/index.js

    //actions export const actions = { nuxtServerInit(store,{app:{$cookies}}) { //初始化token东西到store当中 let user = $cookies.get('user')?$cookies.get('user'):{err:2,msg:'未登录',token:''} store.commit('user/M_UPDATE_USER',user) } }

    3.axios拦截器读取vuex

    //请求拦截 $axios.onRequest(config=>{ config.headers.token = store.state.user.token return config; }) //取不到token,则发送的请求得到的响应是错误的,可以利用响应拦截跳转到登录也 //响应拦截 $axios.onResponse(res=>{ if(res.data.err === 2 && route.fullPath !== '/login'){ redirect('/login?path= '+route.fullPath) return res })

    element ui

    下载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)

    nuxt.config.js

    css:[ 'element-ui/lib/theme-chalk/index.css' ], plugins: [ { src: "~/plugins/element-ui", ssr:true//不支持ssr的插件只会在客户端运行不要给true //mode: 'server'//client// v2.44 } ], build:{ transpile:[ /^element-ui/] }

    反向激活菜单高亮

    watch:{ $route:{ immediate:true,//首次运行 handler(route){ let find = false; this.navs.map((item,index)=>{ if(item.path =='/') this.$router.push({name : 'root'}) if(route.path==item.path){ this.activeIndex = index + ''; find=true; } if(!find) this.activeIndex="-1" }) }}

    用户离开注册页提示

    beforeRouteLeave(to,from,next){ if(this.username||this.password){ let bl=window.confirm('是否要离开'); next(bl) }else{ next(true) } }

    注销

    logout(){ //删除cookie,情况vuex this.$cookies.remove( 'user') this.$store.commit( 'user/M_UPDATE_USER',{ err:1, msg:'未登录', token: '', data:{} }) this.$router.push( ' /login ') }

    全局方法

    plugins:[ //配置插件 '~/plugins/mixins' ]

    plugins/mixins.js定义全局方法

    import Vue from 'vue' let show = ()=>console.log('全局方法') Vue.propotype.$show = show //服务端钩子内部不可以使用,this不会执行vue实例

    组件调用

    mouted(){ this.$show() //打印全局方法 }

    全局过滤器

    assets/script/filters.js

    export function fillzero(n){ //补0 return n < 10 ? '0’ +n : ''+n; } ...

    过滤器放到插件的文件plugins/mixins.js内

    //全局过滤器,导出所有过滤器 import * as filters from '../assets/script/filters'; Object.keys(filters).forEach(key=>Vue.filter(key,filters[key]));

    组件使用

    {{4|fillzero}} //04

    全局指令

    directive.js

    bind绑定的时候触发,inserted插入的时候触发,componentUpdated更新的时候触发

    function direc1(el,binding,vnode){ console.log('全局指令1',el,binding,vnode) } export default{ bind(el,binding, vnode){ direc1(al,binding, vnode) } }

    全局指令放到插件的文件plugins/mixins.js内

    import direc1 from '../assets/script/directives'

    组件使用

    <div v-direc1="'red'">direc1</div>

    全局组件

    定义组件于components/global/uc-button/index.vue,组件名为’uc-button'

    全局组件放到插件的文件plugins/mixins.js内,index.vue可以省略写

    import UcButton from '../ components/global/uc-bgctorl'; Vue.component('uc-button',UcButton)

    组件使用

    <uc-button/>

    全局样式

    nuxt.config.js全局引入

    meta信息注入

    网站描述头部信息,优化seo

    nuxt.config.js

    module.exports = { mode: 'universal', head:{ //环境变量的名称 title:process.env.npm package _name||'统一标题', meta:[ {charset: "utf-8'}{name: 'viewport ',content: 'width=device-width,initial-scale=1'}, {hid: 'description' ,name: process.env.npm_package_description|| 'description' ,content: ''} ], link:[ {rel: 'icon'.type: 'image/x-icon',href: '/favicon1.ico'} ] } ... }

    局部特色meta

    head(){ return { meta:[ name: 'keywords ' ,content:this.collectionNameD }
    Vue.mixin

    minxin.js内定义混入方法

    Vue.mixin({ methods:{ $seo(title,content,payload = []){ return { title, meta:[{ hid: 'description', name: 'keywords', content }].concat(payload) } } })

    组件使用

    head(){ return this.$seo(this.data.title,this.data.des,[]) }

    scss使用

    下载node-sass sass-loader

    使用

    <style lang="scss" scoped> .. </style>

    全局主题导入

    下载@nuxtjs/style-resources,需要配置modules,还需要指定styleResources内的scss文件

    styleResources:{ scss:[ './assets/scss/global.scss' ] },

    assets/scss/gloabl.css

    $theme-bg:#393;

    使用

    .box2{ background: $theme-bg }

    定义化html模板

    项目根路径下创建app.html文件,约定模板

    <! DOCTYPE html> <html {{HTML_ATTRS]}> <head {{HEAD_ATTRS)1 {{HEAD}} <!--加入个性的内容--> </head> <body {{BODY_ATTRS}}> {{APP}} </body> </html>

    资源指向与引入

    ~代表根路径

    static无优化,不参与打包

    accets打包优化,转base64

    <!--相对路径找到一些需要压缩的资源--> <img src="~assets/img/btns.png" alt=""> <!--绝对路径找到一些无需压缩的资源--> <img src="/img/bg.jpg" alt=""> <div class="bgimg"></div> .bgimg{ height: 5opx; background: ur1(~assets/img/takeSbmComment.png) }

    全局引入资源

    公共文件可以在app.html通过src引入可以通过nuxt.config.js内的script:[{src:'...'}]进行添加,或者用link链接

    阻塞加载

    局部引入

    head:{ script:[ {src:'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'} ]}

    改装成ts

    https://www.bilibili.com/video/BV13Z4y1T74J?p=29

    整个项目重写

    初始化运行时候会报错

    在tsconfig.json内加上"skipLibcheck" : true,

    js修改成ts

    下载@nuxt/typescript-build,配置nuxt.config.js

    以下记录部分代表性的修改

    buildModules: [‘@nuxt/typescript-build']

    添加tsconfig.json,types/vue-shim.d.ts配置文件,文件内容在https://typescript.nuxtjs.org/guide/setup.html#configuration

    在tsconfig.json内加上

    { "skipLibcheck":true, "experimentalDecorators":true, }

    下载vue-property-decorator和vue-class-component,用类的方式定义vue组件

    ps: 项目必须在工作区首个,vscode的bug

    组件内的ts

    <script lang="ts"> import {Vue, Component,Watch}from 'vue-propelty-decorator' @Component export default class Loading extends Vue{ //data元数据==实例属性 loading: boolean = false //methods的方法类内的实例方法 start():void{ this.loadiag = true; } //无需,隔开 finish():void{ this.loading = false; } } </script>

    ts对象数组的处理

    type TNavs = {path:string,title:string} @component export default class AppHeader extends Vue{ activeIndex:string = '-1'; navs:TNavs[]=[ {path: '/index',title:'首页'}{path: '/goods',title:'商品'}{path: ' /user' ,title:'用户'} ] }

    watch改装

    需要引入Route

    import {Route} from 'vue-router' @Watch('$route' ,{immediate:true,deep:true}) onRoutechange(route:Route){ let find=false; this.navs.map((item,index)=>{ if(item.path=='/' ) this.$router.push({name: 'root'}) if(route.path==item.path) { this.activeIndex=index+''; find=true; } }) if(!find) this.activeIndex="-1"; }

    default.vue改装

    @component({ middleware({store,route,redirest,params,query}){ //store状态树信息 //route一条目标路由信息 // redirect强制跳转 //params,query校验参数合理性 console.log( 'middleware layouts全局守卫前置业务') // redirect( ' /reg') }, //引入的组件 components:{AppHeader} })

    ts需要加上.vue后缀

    error.vue改装

    import {Vue, Component,Prop}from 'vue-propelty-decorator' @Component export default class Error extends Vue{ @Prop() readonly error:stringlundefined }

    服务器的钩子(validate,asyncData,transition,head)直接放到component装饰器的内使用

    a x i o s = = > a p p . axios == > app. axios==>app.axios

    axios配置,才能类型推算,返回值可以写成(res:any)

    types/vue.d.ts

    import Vue from 'vue'; import {NuxtAxiosInstance} from'@nuxtjs/axios' declare module 'vue/types/vue'{ interface Vue{ $axios:NuxtAxiosInstance; $seo:Function; detail:{title:string,des:string};//实现约定好 $show:()=>void; $cookies: NuxtCookies; } }

    事件类型Event

    类型断言

    this.$route.query.path as string <string>this.$route.query.path

    vuex加入装饰器

    https://www.bilibili.com/video/BV13Z4y1T74J?p=31

    下载vuex-class

    import {State,Getter,Action,Mutation} from 'vuex-class'

    记录部分代表性的修改

    index.vue

    //改装 //...mapstate(['bNav']), @State bNav:boolean |undefined; // ...mapState({home: state=>state.home.data}), @State(state=>state.home.data)home?:object // ...mapstate( 'user',['data']), @State('user') data!:oject;//外部state.user做组件内的data使用 // ...mapGetters(['getNav']), @Getter getNav!:string; //抓取getters的key,作为组件实例属性使用 // . ..mapActions( 'user',['A_UPDATE_USER']), @Action('user/A_UPDATE_USER') A_UPDATE_USER!:(payload:object)=>void

    types/index.ts

    类型主题模块

    interface Istorestate{ bNav:boolean; bLoading: boolean; interface IstoreHome{ err:number; msg?:string; data:Array<{ _id:string; des:string; time :number; title:string; detail?:{ auth:string; auth_icon :str1ng3 content:string; } }> } interface IstoreUser{ interface storeUser{ err: number; msg:string; token:string; data?:Partial<{ //Partial全部加上可选 _id:string; nikename :string; fans:number; follow: number; time:number; icon:string; }> } export {IStoreUser,IStoreState,IstoreHome}

    stroe/index.js

    //改装 import {IStoreState} from '@/types' export const state = function():IStoreState{ return{ bNav: false, bLoading: false }

    stroe/home.js

    //创建个类型约束 type TAcion = { commit:(type:string,payload:object)=>void; state:iStoreHome } export const actions = { A_UPDATE_HOME({commit,state}:TAcion,payload:IStoreHome){ commit( 'M_UPDATE_HOME' ,{err:0,data:{title:"home模块actions所传递的数据"}}) } }

    nuxt部署

    https://www.bilibili.com/video/BV13Z4y1T74J?p=32

    nuxt通过代理,将请求转发带真实服务器,部署时,nuxt项目即前端工程3000和真实服务器即后端工程9001都要部署。

    nuxt通过server/index.js开启自身端口服务3000

    nuxt先打包npm run build,放到服务器3000端口的有:.nuxt /server /static /package-lock.json /package.json /nuxt.config.js

    真实服务器即后端工程9001

    先配置一下nuxt.config.js

    module.exports = { mode: 'universal', server:1 port: 3000,//default 3000 host:'0.0.0.0' //因为都被放在服务器上 ... }

    通过pm2管理阿里云 需要开启安全组:3000,9001 (阿里后台)

    远程工具链接阿里云(finallshell)

    pm2 start /usr/local/981/bin/www --name=node9001 cd /usr/local/3000/ pm2 --name=nuxt3000 start npm -- run start
    Processed: 0.016, SQL: 9