Vue学习之高级系列(vue-cli,vue-router,vuex)

诸龙野
2023-12-01

Vue高级 - Vue全家桶技术

1. vue-cli 脚手架
2. vue-router 路由
3. vuex 多组件状态管理

vue-cli

  1. 它是vue提供的一个用于构建vue项目的脚手架,目前它的版本已经到了4.x

  2. 版本

    • vue-cli 2.x 版本
    • vue-cli 2以上版本【 3 4 】 市面上
  3. 安装

    • 如何让一台电脑既可以使用vue-cli 2 也 可以使用 2以上版本?
    • $ cnpm/npm i @vue/cli @vue/cli-init -g
    • @vue/cli 2以上版本
    • @vue/cli-init 调节工具,可以让2以上版本也可以构建2版本的项目
  4. 创建项目

    1. 2以上版本创建方式【 两者创建出来的项目完全一样 】
      1. 标准形式
        • $ vue create 项目名称
      2. GUI创建
        • $ vue ui
    2. 2版本创建方式
      1. 标准形式
        • $ vue init webpack 项目名称
      2. 简易形式 【 单案例 】
        • $ vue init webpack-simple 项目名称
  5. 项目启动

    • serve 表示开发环境项目启动
    • build 表示生产环境项目打包
    • lint 表示语法检测【 eslint 】
  6. yarn vs npm

    • yarn 安全性更好
    • yarn 可以锁定项目中第三方包的版本号,保证团队项目版本一致
  7. vue-cli

    • 底层实现: webpack
    • webpack底层语言: Node.js
  8. 版本不同

    • 2版本比2以上版本多了两个文件夹,分别为: build 、 config 目录
    • 这两个文件是项目中webpack配置文件夹和项目脚本执行执行文件夹
    • 2以上版本将这两个文件夹放到了node_modules/@vue/cli-service中
  9. 四中vue-cli不同创建项目形式总结

    • 不同点一
      • webpack配置和项目脚本执行文件放在不同地方
      • 2以上版本放在了node_modules/@vue/cli-service中
      • 2版本标准形式放在了 build \ config
      • 2版本建议形式直接放在webpack.config.js文件中
    • 不同点二
      • 2以上版本性能要更好
      • 配置插件自由、插件也更丰富了
  10. 熟悉脚手架构建的目录【 2以上版本 】

    1. yarn run serve -> node_modules/@vue/cli-service/bin/vue-cli-service.js

    2. 调用webpack功能

      1. 用来编译src下所有东西
        1. . stylus -> stylus-loader
        2. .vue -> vue-loader
        3. 高版本es语法 -> babel-config.js优雅降级配置文件来编译
    3. node_modules/@vue/cli-service 会有临时文件来启动一个界面

  11. src 源代码开发目录【 工作目录 】

    1. assets目录 静态资源目录【 会被webpack编译 】
      1. img
        1. 如果图片 < 4K, 那么这个图片会被编译为 base64 图片
        2. 如果图片 > 4K, 那么这个图片被会复制到dist/img 中
      2. js
        1. 编译压缩js文件
      3. css文件
        1. 编译所有css文件
    2. components目录 -> 项目公共组件【 头部、底部、列表 】
    3. App.vue组件
      1. 项目中继跟组件后最大的组件
      2. 用于放置我们创建的组件
    4. main.js 项目入口文件
      1. 最先执行的文件,其他文件依赖于这个文件执行
        (用于引入项目自适应文件rem.js,vant组件,路由模块routes,并且在new Vue中全局注入路由,可以获得两个属性 r o u t e 和 route和 routerouter)
  12. vue-cli 使用了 es6 模块化规范

  13. 单文件组件

    • xxx.vue文件
    • 一个文件就是一个组件
    • 通过 vue-loader 来将 单文件组件 编译为 浏览器可以执行 js 文件
    • 构成
      • template 模板 【 jsx 结构】 必须有
      • script 脚本 - 组件的配置 可有可无
      • style 样式 可有可无
    • vscode 要安装Vetur插件,才能识别vue文件
  14. 使用vue-cli

  15. stylus

  16. 简化了样式需要符号

  17. 严格要求缩进

  18. scoped

    • 可以给样式设置独立作用域,也就是说: 如果我们给vue文件中的样式添加了scoped,那么这个样式只会在当前这个组件生效
    • 如果不加,那么这个样式作用域是全局的
    • 建议:
      • 不要随便使用标签名
      • 每一个vue文件样式都加 scoped
  19. vue-cli 配置 - vue.config.js

配置文件修改了,必须重启

  1. 项目启动,自动打开网页
  2. 反向代理
  3. 实现跨域
  4. 标识符: 选取域名后的第一个路径作为标识符
  5. target: 目标源 , 选择 协议 + 域名
module.exports = {
   devServer:{
      open:true,
      proxy:{
         '/ajax:{
            target:'http://m.maoyan.com',
            changeOrigin:true
         }
      }
   }
}
  1. 路径别名
  • 可以将我们麻烦的 ./ …/ ./…/…/ 去掉了
const path=require('path')
module.exports = {
   chainWebpack: config => {
      config.resolve.alias
      .set('assets',path.join(__dirname,'./src/assets'))
      .set('components',path.join(__dirname,'./src/components'))
      .set('@',path.join(__dirname,'./src'))
   }
}
  1. eslint语法检测关闭
  2. 如果.vue文件里不能执行console.log()
  • 解决方法
    • 用window.console.log()代替
    • 增加一个eslintrc.js文件
      module.exports = {
         root: true,
         env: {
            node: true
         },
         'extends': [
            'plugin:vue/essential',
            'eslint:recommended'
         ],
         rules: {
            'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
            'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
         },
         parserOptions: {
            parser: 'babel-eslint'
         }
      }
      
      

构建一个项目

  1. webapp 项目

  2. 移动端自适应配置

    • rem + flex
    • rem配置
      • 网易方案
         /* 
         * 配置rem
         */
      
         function font () {
         document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75  + 'px'
         }
      
         font() // 表示页面一开启就转换单位 
      
      
         window.onresize = font  // 表示页面尺寸发生改变时,再次计算rem
      
      • 淘宝方案
         /* 
         通过js来动态添加rem 
         */
      
         (function(designWidth, maxWidth) {
            var doc = document,
            win = window,
            docEl = doc.documentElement,
            remStyle = document.createElement("style"),
            tid;
      
            function refreshRem() {
               var width = docEl.getBoundingClientRect().width;
               maxWidth = maxWidth || 540;
               width>maxWidth && (width=maxWidth);
               var rem = width * 100 / designWidth;
               remStyle.innerHTML = 'html{font-size:' + rem + 'px;}';
            }
      
            if (docEl.firstElementChild) {
               docEl.firstElementChild.appendChild(remStyle);
            } else {
               var wrap = doc.createElement("div");
               wrap.appendChild(remStyle);
               doc.write(wrap.innerHTML);
               wrap = null;
            }
            //要等 wiewport 设置好后才能执行 refreshRem,不然 refreshRem 会执行2次;
            refreshRem();
      
            win.addEventListener("resize", function() {
               clearTimeout(tid); //防止执行两次
               tid = setTimeout(refreshRem, 300);
            }, false);
      
            win.addEventListener("pageshow", function(e) {
               if (e.persisted) { // 浏览器后退的时候重新计算
                  clearTimeout(tid);
                  tid = setTimeout(refreshRem, 300);
               }
            }, false);
      
            if (doc.readyState === "complete") {
               doc.body.style.fontSize = "16px";
            } else {
               doc.addEventListener("DOMContentLoaded", function() {
                  doc.body.style.fontSize = "16px";
               }, false);
            }
         })(375, 750); 
      
         // 备注: 这里的375本身就应该写成750 ,但是写成750之后,我们设计稿的尺寸要/50,不好算,我就想,除以100更好算,所以我改成了375
      
      • 阿里方案
          (function(doc, win) {
          const docEl = doc.documentElement,
             resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
             recalc = function() {
                const clientWidth = docEl.clientWidth;
                if (!clientWidth) return;
                let max = 24;
                let min = 9.3125;
                let size = 20 * (clientWidth / 320);
        
                size = Math.min(size, max);
                size = Math.max(size, min);
                docEl.style.fontSize = size + 'px';
                console.log(docEl.style.fontSize, 'em= =====')
             };
          if (!doc.addEventListener) return;
          win.addEventListener(resizeEvt, recalc, false);
          doc.addEventListener('DOMContentLoaded', recalc, false);
          })(document, window);
        
  3. 构建项目目录

    • layout/index.vue 项目的布局外壳
    • 创建公共组件(在component里创建,文件名和组件名一般统一【大驼峰命名】)
      • 例如建—标题栏/tabbar -> LayOut组件中使用(要将组件在LayOut/index.vue中【先引入->注册->使用】)
    • utils 项目公共封装库
      • rem.js
      • request.js
  4. 数据请求封装 request.js (src/utils/request.js) --在该文件引入import axios from ‘axios’

  5. 创建路由配置文件夹 router (src/router)

    • router
      • index.js
        1. 引入需要的模块(使用前安装 yarn add vue-router)

          import Vue from 'vue'
          import VueRouter from 'vue-router
          import routerTable from './routes.js'  //引入路由表
          
        2. 安装插件

          • Vue.use( VueRouter )
        3. 创建路由表

          • const routerTable=[] --可以将它当作一个模块放入一个routes.js文件中
        4. 创建路由模块

          const router=new VueRouter({
             mode:'history'  
             routes:routes  --此属性名固定为routes
          })
          
          • 最后导出路由 export default router
      • routes.js (路由表)
            const routerTable=[
            {
               path:'/',
               redirect:'/home'   //重定向
            },
            {
               path:'/home',
               component:Home, //组件名
               meta:{  //元信息
                  include:Home   //include:组件名
               }
               children:{  //二级路由
                  {
                     path:'hot',  //不用带斜杠 /
                     component:HotMovieComp
                     name:'hot'  //给二级路由取别名, <router-link :to="{name:'hot'}"></router-link>使用
                  }
               }
            }
         ]
         export default routerTable
        
  6. 新建文件夹 pages/views , 它是路由路径对应的组件

    • home/index.vue
      • home文件夹放二级路由的组件 --HotMovieComp.vue
    • mine/index.vue
  7. 新建文件夹 store 它是状态管理 vuex 的配置文件夹

  8. 新建文件夹mock.serveice, 用于数据模拟

  9. 创建项目样式全局变量文件

    • assets/stylesheets/constant.stylus
      • border 0 1px 0 0, #ccc //border是个函数,传两个参数(border,color)
  10. 在public/index.html中引入fontawesome cdn

    1. fontawesome是一个icon图标库
  11. Tab.vue构建

  12. TabBar.vue

    • 数量 2 - 5 个
  13. 1px 线兼容问题 ?

  1. vue路由模式中 hash 和 history

    • hash 浏览器的hashchange事件来实现的,兼容度更好
    • history h5的history api 和popstate事件 来实现的
      • go back forword pushstate replacestate
  2. 一级路由、子路由、元信息、router-view 、router-link

  3. 项目中配置组件库

  1. 总结:

    • 如果数据存在渲染两次情况,其中一次时undefined/null,那么我们需要通过判断来排除undefined/null
      • 例如:return this.floors && this.floors.floors
  2. 动态路由中的路由传参 - 路由接参

    • $route
    • 传参
      <router-link :to="{
                     name: 'list',
                     //通过id跳转指定列表
                     params:{
                         id:elm.api_cid
                     },
                     query:{
                         a:1
                     }
                     }">
                     <img :src="elm.img" alt="">
                     <span>{{ elm.name }}</span>
                 </router-link>
      
    • 接参
        <router-link :to="{
               name:'detail',
               params:{
                   id:item.id
               },
               query:{
                   ...item
               }
           }">
      
       //同时还要再请求数据
        const result=await request({
        url:'/index.php',
        params:{
              r: 'class/cyajaxsub',
              page: 1,
              cid: this.$route.params.id, //$route传过来的id
              px: 't',
        }
        })
      
  3. 编程式导航

    • $router
      • push vs replace
        • push会将我们操作放入历史记录,点击浏览器返回会一层一层返回
        • this. r o u t e r . p u s h ( 路 由 名 字 ) 或 者 t h i s . router.push(路由名字)或者this. router.push()this.route.push({路由名字/参数等})
        • replace不会将我们的操作放入历史记录,点击浏览器返回反回2层
      • go / back
  4. 组件身上添加原生事件,要加native修饰符

导航守卫

  1. 别名

    • 路由钩子
    • 路由守卫
    • 路由拦截
  2. 导航守卫作用

    • 对路由的跳转进行拦截
      • 白话: 从一个路由跳转到另一个路由,能不能跳转过去,导航守卫决定
  3. 类型

    • 全局导航守卫
      • 对整个项目做控制
    • 路由独享守卫
      • 对单个路由的路由配置做控制
      • 写在路由表中
    • 组件内守卫
      • 对组件对应的路由做控制
  4. 使用

    1. 全局导航守卫(在router文件夹下创建)
      1. 全局前置守卫 [ router.beforeEach((to,from,next)=>{}) ]
           router.beforeEach(( to,from,next ) => {
              console.log("jwj: to", to)
              console.log("jwj: from", from)
        
              if ( to.path == '/home/hot' ) {
                 next()
              }
        
              const token = getCookie(' _token')
              console.log('token',token)
        
              if ( token || to.path == '/login') {//对所有页面进行拦截
                 next()
              } else {
                 next('/login')
              }
        
      2. 全局后置守卫 [ ==router.afterEach((to,from)=>{}) ==]
      3. 全局更新守卫【 可忽略, 使用和前置守卫一样 】
        1. 区别: 是否完整遍历路由表
          1. 更新守卫要遍历完整路由表之后才跳转页面
    2. 导航守卫参数 /home -> /category
      1. from
        1. 表示当前路由 /home
      2. to
        1. 表示目标路由 /category
      3. next [必须要写的]
        1. next表示两者之间的连接
        2. 取值
          1. next() //默认连通
          2. next( true ) // 表示连通
          3. next( false ) // 不表示断开连接
          4. next(路由路径)
            1. next(’/home’)
            2. next({ name: ‘home’, params: {},query: {} })
          5. next( vm => { } ) // vm指的是目标组件
    3. 路由独享守卫[ ==beforeEnter ==]
      1. 在路由内写
    4. 组件内守卫
      1. 组件前置守卫【 钩子 】[ beforeRouteEnter ]
        1. 拦截组件的进入
        2. 执行: 创建组件前(beforeCreate)执行,这个时候没有组件,没有this
        3. 功能
          1. 判断是否有token,然后是否能进入当前页面
          2. 数据预载
            1. 进入组件前,提前得到数据,并将这个数据赋值给组件
              • 对象的合并 Object.assign() -> Vue.set/this.$set( 属性,属性值 ) ----原理(将data选项中的某个对象和请求到的数据合并,因为只有data里的数据是双向绑定的)
                • vm.$set(属性名,属性值)
            async beforeRouteEnter(to,from,next){
            //数据预载
            console.log(to,from,next)
            const res= await request({
                     url: '/ajax/search',
                     params: {
                     kw: '万达',
                     cityId: 1,
                     stype: 2
                     }
                     
            })
            console.log(res.data.cinemas.list)
            next(vm => {
                  //vm就是当前组件,解决没有this的问题
                  //因为只有data选项中的数据是数据绑定的,将data中的cinemas对象和请求到的数据进行合并 
                  // Vue.set/this.$set(属性,属性值),原理:Object.assign()
                  // console.log(vm.cinemas)
                  vm.$set(vm.cinemas,JSON.stringify(res.data.cinemas.list))
                  
            })
            
      2. 组件后置守卫【 钩子 】 [ beforeRouteLeave ]
        1. 拦截组件离开
        2. 执行: 离开组件前,这个时候有组件,有this
      3. 组件更新守卫
        1. 专用于: 动态路由

动画效果

  1. 实现 -> animate.css css3动画效果
  2. 使用
    • 安装: yarn add animate.css
    • 在动画组件/元素外层加一个 Vue 内置组件 transition
       <transition 
       mode = "out-in"
       enter-active-class="animated slideInLeft"
       leave-active-class="animated slideOutLeft"
       >
             <router-view></router-view>
       </transition>
    
 类似资料: