vue笔记

191 阅读43分钟

vue

学习的 vue2.x

vue官网

vue是一个 mvvm js渐进式框架 特点: 1 声明式开发 命令式: 一个 应用程序 每一步 都需要 手动编辑 代码来实现 声明式开发: 只要告诉 应用程序我要干什么,怎么实现 我不管

vue: 一切以数据 为核心,不要想着操作dom 2 mvvm m v vm m数据 v views 视图 vm vue实例 数据改变视图自动刷新(vm控制)

3 组件化 组件 自定义标签

vm雏形

    const vm = new Vue({
      // 绑定实例到视图 (定义vm 控制视图的范围)
      el: '#app',
      // 定义vm的 m 数据 控制 视图状态
      // data中的数据直接挂载在vm上
      data: {
        msg: '你好vue'
      },
      // 定义实例上的方法
      methods: {
        fn(){
          this.msg = '数据改变了'
        }
      }
    })
  • 外部挂载实例 不在config添加el属性绑定视图 通过实例 $mount方法挂载
const vm = new Vue({
  ...
})
vm.$mount("#app")

模板 {{}}

在页面输出

1 js环境 常量:(字符串加引号) 2 变量 去实例上查找 3 必须写表达式 4 全局变量 vue设置了一个白名单 Infinity,undefined,NaN,isFinite,isNaN, parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,require' 特点: 白名单上 全局变量 不管是 函数还是值, 最终结果 都是一个值

回顾

vue是一个 js mvvm 渐进式框架
mvvm 数据改变 视图 自动刷新  m v vm
组件化开发
声明式开发  以数据驱动 
​
const vm = new Vue({
  el: '#app',
  data: {
    msg: '数据'
  },
  methods: {
    fn(){}
  }
})
vm.$mount('#app')
​
​
{{}}
  1 js环境 
  2 表达式
  3 变量 去 实例上查找
  4 全局变量 白名单

指令 directive

功能: 将视图上 标签 的 某种状态 和 vm上的数据进行绑定 注意: 由于标签有不同状态 比如 图片的src 比如 元素显示消失状态 比如 表单控件的value 所以 指令 也有很多,不同的指令 用来绑定标签 不同的状态

可以理解为指令扩展了 标签属性的功能 指令用法是

<标签 v-指令名="值">

常用指令

注意: 指令的值 引号 是js环境 同模板中的环境

v-text  // 将 数据 绑定 到 标签的 文本内容上 不重要
v-html 编译富文本内容(模板字符串)
v-model 将表单控件的值 与  数据进行双向绑定
  表单修饰符  修饰 v-model
  .number 将输入框输入的值 过滤为数字
        解析规则同 parseFloat 如果开头就出现非数字字符 直接失效,不转换成数字了
  .lazy  将 双向绑定 input事件改为 change事件
          input事件:当input的value值发生变化时就会触发,不用等到失去焦点
          change事件:只有失去焦点的时候触发
​
  .trim 自动去除输入框中开头和结尾空格
v-show指令
  将元素的显示隐藏状态 和 一个布尔值进行绑定
    true显示  false 隐藏
​
v-bind:属性
  属性可以是标签 默认属性(id src 等)也可以是自定义属性
​
  功能:将普通属性变成动态属性,将属性值 与变量 进行绑定
  简写
    :属性="值"
​
v-on:事件
  vue事件绑定 
  原理:合成了原生事件 为 vue事件
  简写:
    @事件名

vue中事件绑定

如何绑定

  • 通过v-on指令直接在行内修改数据 修改的是vm上的数据
<button @click="msg = '数据修改了'">按钮</button>

注意: 不推荐 逻辑代码嵌入到视图中

  • v-on指令绑定事件函数

    • 不加括号
      <button @click="fn">按钮</button>
    
      const vm = new Vue({
          // 绑定vm到 视图上, 定义vm控制视图的范围
          el: '#app',
          data: {
            msg: '你好世界'
          },
          methods: {
            fn(){
              this.msg = '事件函数中修改数据';
            }
          }
        })
    
    • 加括号
        <button @click="fn('传入数据')">按钮</button>
    
        const vm = new Vue({
            // 绑定vm到 视图上, 定义vm控制视图的范围
            el: '#app',
            data: {
              msg: '你好世界'
            },
            methods: {
              fn(msg){
                this.msg = msg;
              }
            }
          })
    

    注意:加括号使用场景 函数调用时需要传参

事件对象

  • 不加括号绑定 方法 实例上方法作为 事件函数存在 第一个参数就是事件对象
<button @click="fn">按钮</button>
        const vm = new Vue({
          // 绑定vm到 视图上, 定义vm控制视图的范围
          el: '#app',
          data: {
            msg: '你好世界'
          },
          methods: {
            fn(e){
              this.msg = 'dwhidhidw ';
              e.stopPropagation();
            }
          }
        })
  • 绑定fn时 ,加括号 vue提供给了全局变量 $event事件发生时,直接传入即可
<button @click="fn($event, '传入其他值')">按钮</button>
        const vm = new Vue({
          // 绑定vm到 视图上, 定义vm控制视图的范围
          el: '#app',
          data: {
            msg: '你好世界'
          },
          methods: {
            fn(e, msg){
              this.msg = msg;
              e.stopPropagation();
            }
          }
        })

事件对象作用

1 取消事件冒泡 e.stopPropagation() 2 阻止默认事件 e.preventDefault() 3 获取事件源 e.target

事件修饰符

.stop 取消冒泡 .prevent 阻止默认事件 .capture 事件在捕获阶段提前触发 .self 事件只能由自己触发 后代无法触发 .once 触发一次

v-for 循环

注意:循环今天有一个知识点没讲 (mvvm原理时 加上这个key属性)

  • 循环普通数组
    <ul>
      <li v-for="item in arr">
        {{ item }}
      </li>
    </ul>
const vm  = new Vue({
  data: {
    arr: ['a', 'b', 'c', 'd']
  }
})
  • 循环时得到下标
    <ul>
      <li v-for="(item,index) in arr">
        {{ item }}
        ->
        {{index}}
      </li>
    </ul>
const vm  = new Vue({
  data: {
    arr: ['a', 'b', 'c', 'd']
  }
})

注意: 循环时声明变量只能在 v-for指令绑定标签内部使用

复杂循环

循环嵌套循环 比如商城首页楼层效果

循环声明的变量在使用时,一定找离自己最近的 循环时声明变量来使用

注意: 循环嵌套循环,需要在内层循环中使用外层循环声明的变量,内外层声明变量一定不能冲突的(否则拿到内层变量)

const floors = [  {    title:'手机',    items: [      {        name:"商品1",        price: 10      },      {        name:"商品2",        price: 20      },      {        name:"商品3",        price: 30      }    ]
  },
  {
    title:'电脑',
    items: [
      {
        name:"商品1",
        price: 10
      },
      {
        name:"商品2",
        price: 20
      },
      {
        name:"商品3",
        price: 30
      }
    ]
  },
  {
    title:'家电',
    items: [
      {
        name:"商品1",
        price: 10
      },
      {
        name:"商品2",
        price: 20
      },
      {
        name:"商品3",
        price: 30
      }
    ]
  }
]
指令
  将组件(标签) 某种状态 和 数据 进行绑定
  v-html 渲染富文本 作为标签 内容
  v-model 将表单控件值 与 变量 进行双向绑定
    .number 过滤数字 过滤规则同parseFloat
    .lazy 改为change事件触发
    .trim 去除开头结尾空格
  v-show 元素显示隐藏 与 布尔值进行绑定
  v-bind:属性 将标签任意的属性 变成动态属性
    简写 :属性
  v-on:事件 绑定vue 合成事件
    1 直接修改数据、
    2 绑定 函数 (去实例上找实例方法)
      - 不加括号  事件函数 第一个参数是事件对象
      - 加括号 传参 $event
      e.stopPropagation()
      e.peventDefault
      e.target
    修饰符
      .stop 取消冒泡
      .prevent 阻止默认事件
      .capture 捕获阶段提前触发
      .self 只能自己调用 后代无法调用
      .once 绑定一次
  循环
    v-for
    <li v-for="item in arr">
    <li v-for="(item, index) in arr">
   
        
 # mvvm原理

当我们new Vue时, 传入 一个 data对象 Vue会立即遍历 这个data对象,并利用Object.defineProperty将对象中的所有的属性,转换成getter和setter,每一个vm还有一个 watcher(观察者 依赖就是data中的数据),当setter触发,立即(notify)通知 watcher,会 触发 render函数,生成新的 虚拟dom树, 和上一次保存在内存中的老的虚拟dom进行比较(利用diff算法进行比较)。找到真实dom操作最少方案来更新真实dom

![mvvm](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ad48459859e34b41bb8f5572849dd78b~tplv-k3u1fbpfcp-zoom-1.image)

## 虚拟dom

问题? vue是数据改变 视图自动刷新,如果没有任何 措施,简单v-for循环 数组 (有10条数据),会在循环每一次中都插入一个新的dom, 会操作dom树 重新构建 10次 会带来巨大性能问题 虚拟dom? 利用js 对象的语法 描述 dom树状结构 (一个标签就是一个对象) 虚拟dom作用? 每一次数据修改,更新最新的虚拟dom,和上一次保存在内存中的老的虚拟dom进行比较,目的提高真实dom渲染效率

真实dom结构

 

这是p

 这是span 这是文本内容
```

虚拟dom结构如下

{
  tagName: 'div',
  attrs: {
    id: 'box'
  },
  children: [
    {
      tagName:'p',
      attrs:{
        className: 'op'
      },
      children: ['这是p']
    },
    {
      tagName: 'span',
      attrs: {},
      children: ['这是span']
    },
    '这是文本内容'
  ]
}

diff算法

在虚拟dom比较,优化比较性能 1 做到同层比较 2 同层 有key属性 同层同key比较

循环中 必须给循环元素 加 独一无二的key属性

原因:提高虚拟dom diff算法的比较效率 (同层同key比较)

循环中 用id做key和下标做key区别

下标 在数据改变会,自动改变 id是不会的

vue数据修改不视图不刷新情况

针对数组

  • 数组[下标] = 值
  • 直接修改数组的length属性 原因: Object.defineProperty 捕获数组 以上两种操作, setter不触发 怎么解决? this.set(对象,属性,);this.set(对象,属性,值); this.set(数组,下标,值) 修改对象的属性,且自动刷新 length问题? 推荐不要使用改变length删除数组,使用splice()

动态添加实例的属性

初始化在data中未定义, 后续 动态添加的一个实例的属性 数据修改视图不刷新(没有setter)

解决方案: 所有的数据 一定要在data中定义 合理的初始值 数据是对象,在重新赋值时,不要 直接清空里面的属性(不要赋值为空对象)

解决模板闪烁问题

由于代码加载顺序从上往下, 先加载的html,在实例未初始化完成之前,html中的 {{}} 模板,解析成普通的字符串,实例加载完成,实例 灌入视图,模板渲染数据 时间差,产生闪烁

解决方案: vue提供了 v-cloak指令

原理: 当实例初始化完成, vue自动去除 视图上所有的 v-cloak

<style>
    [v-cloak] {
      display:none;
    }
  </style>
  <table v-cloak class="table table-bordered" style="width: 600px;margin: 30px auto;">

vue数据修改 视图更新异步问题

vue修改数据后,视图刷新是异步的,无法立即获取到最新的 dom节点

怎么解决? vue提供了 $nextTick方法,监听 每一次视图更新 (视图更新完成后触发)

// 修改数据后 立即 调用 this.$nextTick(cb) 在回调中获取
this.$nextTick(() => {
  // 在这里获取
})

侦听器 watch

侦听 vm上数据的变化,数据改变 侦听器函数触发

  • 简写

    • 侦听哪个vm上属性,就在config对象中新增watch属性,在watch添加同名的方法即可
const vm = new Vue({
  data: {
    msg: ''
  },
  watch: {
    msg(newVal, oldVal){
      // 方法会在msg改变后触发
    }
  }
})

注意: 初始化 函数不执行,只有在 数据发生改变时触发

  • 初始化就立即执行一次侦听器函数
const vm = new Vue({
  data: {
    msg: ''
  },
  watch: {
    msg: {
      handler(newVal, oldVal){},
      immediate: true
      // 让侦听器函数 立即触发
    }
  }
})

侦听引用类型

对象 数组: 数组的length改变,可以之前通过简写方式监听,直接修改数组的某个值侦听不了 注意是一下情况: 针对侦听对象 侦听数组 非length改变

  • 直接侦听对象的某个属性
{
  watch: {
    'obj.a'(newVal){}
  } 
}
  • 深度监听
{
  watch: {
    obj: {
      handler(newVal){},
      deep: true // 深度监听
    }
  }
}

计算属性 computed

场景1: 一直 一个 todoLists [ { content: 'xxx', done: true }, { content: 'xxx', done: false }, { content: 'xxx', done: true } { content: 'xxx', done: true } ]

实际开发过程中: 经常会需要使用 某个数据,这个数据 并不存在, 需要经过 其他的若干数据 经过计算才能得到 举例: 上面待办事项列表,需要得到 已经完成待办事项的总数 后端返回一个数据json数组,json 字段 不能直接渲染,需要计算改变一下才能数据输出

  • 计算基础写法(简写) 在 config中新增 computed 定义一个方法 基于 已有数据进行计算,得到结果,return结果即可
{
  data: {
    msg: '你好世界',
    todos: [
      {
        content: 'xxx',
        done: true
      },
      {
        content: 'xxx',
        done: false
      }
      ...
      ...
    ]
  },
  computed: {
    // msg使用时 反向的
    reverseMsg(){
      return this.msg.split('').reverse().join('')
    },
    // 得到 todos 已完成待办事项的总数
    todoDoneLen () {
      return this.todos.filter(todo => todo.done).length;
    }
  }
}

注意: 1 计算属性 写的时候是方法,编译后 作为vm的属性存在,使用同data中的数据 2 不要和实例已有的属性和方法同名 3 优势: 计算计算到结果后,会 立即 基于 所有的依赖,将结果进行缓存, 在我们多次使用时,只要 依赖没有改变 不会重新计算,拿缓存的结果 使用

? 计算属性 的值 可以直接修改嘛

  # mvvm原理

当我们new Vue时, 传入 一个 data对象 Vue会立即遍历 这个data对象,并利用Object.defineProperty将对象中的所有的属性,转换成getter和setter,每一个vm还有一个 watcher(观察者 依赖就是data中的数据),当setter触发,立即(notify)通知 watcher,会 触发 render函数,生成新的 虚拟dom树, 和上一次保存在内存中的老的虚拟dom进行比较(利用diff算法进行比较)。找到真实dom操作最少方案来更新真实dom

mvvm

虚拟dom

问题? vue是数据改变 视图自动刷新,如果没有任何 措施,简单v-for循环 数组 (有10条数据),会在循环每一次中都插入一个新的dom, 会操作dom树 重新构建 10次 会带来巨大性能问题 虚拟dom? 利用js 对象的语法 描述 dom树状结构 (一个标签就是一个对象) 虚拟dom作用? 每一次数据修改,更新最新的虚拟dom,和上一次保存在内存中的老的虚拟dom进行比较,目的提高真实dom渲染效率

真实dom结构

<div id="box">
  <p class="op">这是p</p>
  <span>这是span</span>
  这是文本内容
</div>

虚拟dom结构如下

{
  tagName: 'div',
  attrs: {
    id: 'box'
  },
  children: [
    {
      tagName:'p',
      attrs:{
        className: 'op'
      },
      children: ['这是p']
    },
    {
      tagName: 'span',
      attrs: {},
      children: ['这是span']
    },
    '这是文本内容'
  ]
}

组件 component(组件)

广义: 组成网页的上独立 区域 广义上说 html标签也是 组件(html预定义的组件) 简单概念: 自定义标签

组件分类

注意: 1 组件 类 继承了 Vue这个父类(vm上有的功能组件都有)

2 每个组件都有自己封闭作用域,数据方法等 只能在内部使用

3 ❤组件的data必须是一个函数 return对象 (组件会多次使用,每一次都会实例化,如果data是对象,所有组件实例公共一个data这个对象, 修改一个其他都会变化)

4 组件的template必须包裹在一个闭合标签中,必须有一个大的盒子进行包裹所有的

全局组件

可以在 vm控制范围内 任意一处使用

// 定义全局组件
    /* 
      参数1 组件名 命名推荐 大驼峰 CommonHead 或者 全部小写 连接符 common-head
       注意推荐两个单词组成(避免和关键字冲突)
       不管以上哪种命名 使用时变成小写加连接符  <common-head/>
    */
    // component方法相当于 定义了 一个 VueComponent 类(创建组件的类) 使用时<组件名> 实例化一个组件  
    Vue.component('CommonHead', {
      // template 当前组件的视图结构
      template: `
        <div>
          <h2>这是一个公共的头部</h2>  
          <button @click="changeMsg">改变msg</button>
          {{ msg }}
          {{ reverseMsg }}
        </div>
      `,
      data(){
        return {
          msg: '这是一个组件'
        }
      },
      methods: {
        changeMsg(){
          this.msg = '值改变了';
        }
      },
      watch: {
        msg(newVal){
          console.log('msg改变了', newVal);
        }
      },
      computed: {
        reverseMsg(){
          return this.msg.split('').reverse().join('')
        }
      }
    })
    const vm = new Vue({
      el: '#app'
    })

局部组件

在一个组件内部注册一个子组件,只能在父组件内部 template中使用 语法: 在父组件 config中新增 components属性 (对象),注册局部组件,属性名子组件的组件名, 值 子组件 config对象 注意: 1 子组件 也是有封闭作用域,实例属性方法不能在其他组件中使用(父组件中拿不到),父组件的也不能在子组件中 2 子组件只能在父组件的template中使用

  Vue.component('CommonHead', {
      // template 当前组件的视图结构
      template: `
        <div>
          <h2>这是一个公共的头部 父组件</h2>  
          <common-logo></common-logo>
        </div>
      `,
      components: {
        CommonLogo: {
          template:`
            <div>
              <h3>这是局部组件</h3>  
              {{ msg }}
            </div>
          `,
          data(){
            return {
              msg: '这是子组件的数据'
            }
          }
        }
      }
    })

template三种定义方式

  • 1.直接定义字符串

      template:`
             <div>
               <h3>这是局部组件</h3>  
               {{ msg }}
             </div>
           `
    
  • 2.在外部定义script 加上选择器 在标签内部定义 视图结构

  <script id="tpl" type="text/html">
    <div>
      <h2>这是全局组件</h2>
    </div>
  </script>

组件config template是 标签选择器即可

    const CommonHead = {
      // template 当前组件的视图结构
      template: "#tpl"
    }
    Vue.component('CommonHead', CommonHead);
  • 3.在外部定义template 加上选择器 在标签内部定义 视图结构
  <template id="tpl" type="text/html">
    <div>
      <h2>这是全局组件</h2>
    </div>
  </template>

组件config template是 标签选择器即可

    const CommonHead = {
      // template 当前组件的视图结构
      template: "#tpl"
    }
    Vue.component('CommonHead', CommonHead);

动态组件

vue提供了 一个component组件,定义is属性,值是要渲染哪个组件的名字 名字改变 component可以渲染对应组件

<component is="commonHead"></component>
<!-- 在这个位置 实例化 组件名为commonHead组件 -->

组件通信

组件是封闭作用域,默认 父子组件之间的数据,是不能通用,很多场景下,需要父向子传数据 子向父传数据

父向子通信

原理: 通过 子组件标签的自定义属性携带数据

  • 父组件中 给子组件标签 定义自定义属性,携带数据
<child a="静态的值" :msg="msg"></child>
<!-- msg会找父组件data中的msg -->
  • 子组件config对象新增props属性来接收 父组件传入自定义属性
const Child = {
  props: ['a', 'msg']
}

注意: props接收自定义 是 挂载在子组件实例上 1 使用同 data中的数据 2 不能和子组件实例中已存在的 属性或者方法重名(data methods 计算属性)

问题?

子组件中接收的props 无法验证,拿到的值 完全取决于 父组件传递什么值 比如 子组件中一个props 必须传入数组,否则语法会报错

props没有验证?

props验证

接收 props使用对象接收,对象属性就是待接收prop值就是验证规则

    props: {
        // 只要求数据类型
        a: String,
        // 只要求数据类型,类型可以给定类型的一个
        b: [String, Number],
        // 要求数据类型 且 必须传值
        c: {
          type: Number,
          required: true
        },
        // 要求数据 不传有默认值
        d: {
          type: String,
          default: '默认值'
        },
        // 数据类型如果是数组或者对象 默认值必须时函数return默认值
        e: {
          type: Array,
          default: () => ['a', 'b', 'c', 'd']
        }
      },

常用数据类型: String Number Boolean Array Object Date Function Symbol

props可以修改嘛

不可以修改

单向数据流 (状态提升)

props是由父组件传递给子组件,而子组件不能修改

组件通信 之 子向父通信

原理: 触发子组件的 自定义事件 在父组件中 通过子组件标签 监听这个自定义事件

  • 子组件中 通过 $emit 触发 子组件实例自定义事件
this.$emit('自定义事件名'[,携带参数])
// 这个事件名可以任意定义 可以是 click piupiu piapia mouseover等
// eg
this.$emit('biubiu', this.msg)
  • 父组件中 通过 v-on指令 在子组件标签 监听这个事件触发
<child @biubiu="fn"></child>

父组件methods定义事件函数 参数就是携带的数据

{
  methods: {
    fn(msg){
      // msg就是 子组件 $emit携带的参数
    }
  }     
}
  # 组件通信

父向子 props

子向父 自定义事件

兄弟组件通信

事件中心总线 event bus 原理:触发自定义事件

任何 Vue的实例都可以通过 $emit触发这个实例的自定义事件 触发

this.$emit('事件名'[,参数])

监听自定义事件

  • 父子组件 在父组件中通过 子组件标签 v-on指令来监听
    <child @biubiu="fn"/>
  • 实例除了 emit还有emit 还有 on 监听 实例自己 emit触发的自定义事件注意:哪个实例emit触发的自定义事件 注意: 哪个实例 emit触发自定义事件,就 只能由这个实例自己 $on来监听

步骤: 定义一个第三方实例,在兄弟a中 methods中emit触发实例自定义事件,在兄弟b还是拿到第三方实例emit触发 实例自定义事件,在兄弟b还是拿到第三方实例 on监听即可(mounted)

  • 外部创建第三方实例 在两个兄弟组件的外部创建
const bus = new Vue();
  • 兄弟组件a中 bus.$emit触发自定义事件
  bus.$emit(自定义事件名[,参数])
  • 兄弟组件b中 bus.$on来监听自定义事件
bus.$on('自定义事件名', (payload) => {
  //payload 就是携带的数据
})

ref通信

通过在父组件中,在子组件的标签上加ref属性,并赋值,能够在父组件触发时调用子组件的方法和属性。

​
 template:`
               <div>
                  <h1>鱼香肉丝</h1>
                  <h2 ref='title'>我的肉在哪里呢?</h2>
                  <child1 ref='child'></child1>
​
                  <button ref='btn'>点菜</button>
               </div>
            `,
      mounted(){
                console.log(this.$refs);
                this.$refs.btn.style.background='red'
                this.$refs.child.fn()
                this.$refs.child.msg='修改msg'
                console.log(this.$refs.child.msg)
            }         

另外两种

面试题 可以回答即可

parentparent和children

parent获取一个组件的唯一父组件实例返回的就是父组件实例,在子组件中调用父组件的parent 获取一个组件的唯一父组件实例 返回的就是父组件实例,在子组件中调用父组件的 children 获取一个组件 的所有子组件实例 返回的是 数组,在父组件中调用子组件的

provide和inject

父组件 provide提供数据 子组件通过inject 注入 父组件 provide提供的数据

ref 转发 dom对象或者子组件实例

功能: 在父组件中获取dom或 子组件实例

  • 给需要获取的 标签 定义 ref属性
const Father = {
   template: `
        <div>
          <h2 ref="title">这是父组件</h2>  
          <hr/>
          <child1 ref="child"></child1>
          <button ref="btn">按钮</button>
        </div>
      `,
}
  • 通过this.$refs属性来获取 ref 转发的dom或者组件
this.$refs.xxx // xxx定义的 标签 ref属性值

组件生命周期

组件从初始化(实例化),到组件销毁的整个过程 不同阶段 不同的 生命周期钩子函数触发 (vm也有生命周期)

生命周期阶段 开始为字符串

初始化阶段 (从实例化 到 template编译成真实dom的过程)

  • 实例创建

    • new Vue开始之后(vm直接new Vue 或者 <组件/>) 立即先 初始化 实例 事件监听,以及定义所有的生命周期钩子函数 完成后: 触发 beforeCreate (实例创建完成之前) 此时组件实例的 属性方法 等还未创建
    • 初始化 实例的属性方法(自己的原型链上),以及响应式数据(遍历data .. getter setter watcher) 完成后: 触发 created (实例创建完成) 实例上所有的属性方法(原型链上)都可以使用,且可以修改数据,视图会自动刷新了
  • 编译template 为虚拟dom 需要先编译为 虚拟dom 变成真实dom

    • 编译template 并调用render 生成虚拟dom 完成后: 触发 beforeMount钩子函数 注意: beforeMount 虚拟dom已经创建但是 真实dom还未渲染
    • 将虚拟编译成真实dom 并挂载 在视图上 完成后: 触发mounted

更新阶段(有数据发生改变)

  • 当数据修改 触发: beforeUpdate 注意: beforeUpdate连新的虚拟dom都未 创建
  • 生成新 的虚拟dom 然后patch 比较新老虚拟dom,并更新真实dom 触发: updated

组件卸载阶段

哪些场景下会卸载组件

  • 调用实例 $destroyed方法 一般不用
  • 条件渲染值 为false时
  • 路由组件 切出 当前路由

为什么要卸载组件

垃圾回收,释放内存

每一个组件实例 都需要在运行内存中保存,这个组件停止使用时,vue会自动从内存中 移除这个 空间

\

  # 生命周期钩子使用场景

初始化阶段

created

一般用来发送 接口请求函数,请求数据

    const vm = new Vue({
      el: '#app',
      data: {
        // 1data中定义初始值
        cates: []
      },
      methods: {
        // 2定义方法 发送请求
        fetchCates(){
          axios.get('https://api.it120.cc/conner/shop/goods/category/all').then(res => {
            if(res.data.code === 0) {
              this.cates = res.data.data
              // 这个数组等于传递过来的数组
            }
          })
        }
      },
      created(){
        // 3调用请求方法
        this.fetchCates();
      }
    })

mounted

特点: 组件的template编译成真实dom并挂载到视图上 触发,初始化阶段只有在mounted中才能拿到 template中的dom 使用场景: 一切在初始化阶段需要获取dom操作都需要在mounted中完成,注意,大部分情况下,开发者不能自己直接获取dom, 大部分都是 使用第三方插件的时,实例化插件时 需要传入一个dom作为容器(swiper echarts 富文本 高德百度地图)

updated

在这个钩子中 可以获取数据更新后最新的dom,但是一般不推荐使用,因为 1 无法 具体定位某个 数据改变(updated任何一个数据 更新都会触发 这个钩子) 2 造成代码严重割裂

推荐使用 this.$nextTick取代updated

卸载阶段 使用 beforeDestroy

为什么卸载: 释放内存,从内存移除这个组件实例 这个钩子用来干什么

问题? 在组件初始化时,如果给全局 window上添加了某些属性(属性、方法、定时器、事件等),在组件卸载时,移除 实例,window一直存在,window上特性都还在,手动在 卸载之前 移除全局特性

在组件卸载前 在 beforeDestroy 移除初始化阶段在全局window上添加的一些特性

组件缓存

一个组件在停止使用时,组件不卸载 好处: 当再一次 使用这个组件, 不会重新加载 还保留上一次离开的状态 场景: 首页列表,点击进入详情页,从详情页 再一次返回首页,希望首页不刷新还保持原来的状态

注意: 今天只学习一部分,剩余的 在 学习路由时讲(讲脚手架时) 谨慎使用,导致 垃圾回收 失败

实现: vue提供了一个组件 keep-alive 使用keep-alive组件包裹 需要状态切换的组件即可

    <keep-alive>
      <common-head v-if="isShow"></common-head>
    </keep-alive>
  • 需求2 被缓存在组件 再一次 使用时,局部数据刷新 问题? 这个数据请求函数 在哪里调用问题? 因为组件已经缓存,再一次显示时,没有任何一个钩子触发的

vue提供了两个新的生命周期钩子函数,专门针对 被keep-alive包裹的组件使用

  • activated 被缓存组件 使用时触发
  • deactivated 被缓存的组件 停用时触发

这时,只有beforeDestroy不被调用,不触发,因此如果有全局window的事件,不能够在mounted中触发,只能够在activated中调用,并且停用的时候在deactivated移除全局的window事件。 activated与created都会初始化组件,因此只要保留一个即可。

组件中的 混合(混入) mixin

需求: 多个组件 公共 组件 特性 (公共属性、方法、侦听器、计算属性等)

解决需求: vue提供混入 语法,在混入对象中 定义 所有组件公共的特性(属性、方法、侦听器、计算属性。。。。),在需要使用组件中 灌入 混入中特性即可

实现步骤: 1 定义公共的混入对象,挂载多个组件公共的特性(混入对象结构和 组件config 保持一致没有template)

    const mixin = {
      data(){
        return {
          msg:'这是公共数据'
        }
      },
      methods: {
        changeMsg(){
          this.msg = '值改变了'
        }
      },
      computed: {
        reverseMsg(){
          return this.msg.split('').reverse().join('')
        }
      }
    }

2 需要灌入这些公共特性组件中 定义 mixins属性 灌入后,组件实例自动具有混入上定义的所有特性

const Home = {
  template: ``,
  mixins: [mixin]
}

注意: 当组件 自己 的 某个特性(比如数据)和混入中冲突了,以组件自己的为准

插槽 slot

场景: 一个组件 在多处使用时, 视图 某个区块,结构是不一样的 解决思路: vue提供了 占位组件 可以在 子组件 template某一处 定义这个占位标签

使用 子组件标签时,使用双标签,嵌套内容,嵌套的视图内容,最终会在子组件 template 内部 占位组件 位置处渲染

步骤: 1 子组件template定义slot组件 占位

    const CommonLogo = {
      template: `
        <div>
          <header>这是头部</header>  
          <slot></slot>
          <footer>这是尾部</footer>  
        </div>
      `
    }

2 父组件中 子组件嵌套 html内容 渲染到 子组件slot位置处

    <child>
      <div>
        xxx
        xxx
        xx
        ...
      </div>
    </child>

场景2: 一个组件 在多处使用时, 视图 多个区块,结构是不一样的 解决方案:

具名插槽

原理: 定义多个slot,每个slot添加name属性,在父组件使用子组件 嵌套多个 视图结构,每个结构 定义slot属性值 为 对应 slot的name属性的值 步骤: 1 子组件中 定义多个slot 每个slot 添加name属性

    const CommonLogo = {
      template: `
        <div>
          <header>这是头部</header>  
          <slot name="a"></slot>
          <footer>这是尾部</footer>  
          <slot name="b"></slot>
        </div>
      `
    }

2 父组件中 子组件标签 嵌套多个结构块,定义slot属性为对应slot的name的值

          <common-logo>
            <div slot="a">
              <button>按钮</button>  
            </div>  
            <div slot="b">
              <mark>按钮</mark>  
            </div>  
          </common-logo>

作用域插槽

适用于:普通插槽 和 具名插槽都可以 场景: 插槽 嵌入 html结构,需要使用 子组件中的数据 问题: 定义的时候是在父组件的template中定义的,如果直接使用数据一定会去父组件中查找数据,

如果想要获取子组件的数据,需要利用slot标签进行获取,

子组件的template 中的slot标签上,

  <slot msg="这是静态数据" :arr="arr"></slot>

这是子组件上定义的数据

      data(){
        return {
          arr: ['a', 'b', 'c', 'd']
        }
      }

在父组件中在子组件标签里面定义div标签,在div标签上写上slot-scope="data" slot-scope传递过来的是一个对象类型, 可以在父组件中使用这个对象

 const CommonHead = {
      template: `
        <div>
          <h1>这是公共组件</h1>
          <common-logo>
            <div slot-scope="data">
              {{ data.msg }}
              <ul>
                <li v-for="item in data.arr" :key="item">
                  {{ item }}  
                </li>  
              </ul>
            </div>  
          </common-logo>
          <hr/>
        </div>
      `,

\

  # 过滤器 filter

模板在使用数据时,某些可以进行简单的过滤在输出

举例: 后端返回 一个数据 代表金额

分类 filter

  • 全局过滤器
  • Vue.filter(过滤器名字,函数)
  • return的值就是 最终输出的值 第一个参数就是需要过滤的那个值
Vue.filter('过滤器名字', fn);
// eg 使用时不传参
Vue.filter('currency', (v) => {
  return '¥' + v // return的值就是 最终输出的值  第一个参数就是需要过滤的那个值
})
// 传参 从第二个参数开始接收 传递的参数
Vue.filter('currency', (v, sign="¥") => {
  return sign + v  // {{ sign|currency('$') }}
})
  • 局部过滤器
const Home = {
  template: ``,
  filters: {
    过滤器名字: () => {}
  }
}

使用过滤器

  • 简单使用
{{ 值 | 过滤器名字 }}
  • 使用时传参
{{ 值 | 过滤器([...params]) }}
  • 过滤器串联使用
  • 过滤器1是过滤的值,过滤器2是过滤器1过滤的值,依次类推
{{ 值 | 过滤器1 | 过滤器2 | 过滤器3 ... }}

自定义指令 directive

全局指令

Vue.directive('指令名', {
  // 定义生命周期钩子函数
  // 初始化 绑定阶段
  /* 
    el绑定那个dom对象
    binding 指令参数对象(指令名字、value)
  */
  bind(el, binding){
    /* 
      只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
    */
  },
  inserted(el, binding){
    /* 
      被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
    */
  },
  // 更新阶段
  update(){
    /* 
      所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
    */
  },
  componentUpdated(){
    /* 
      指令所在组件的 VNode 及其子 VNode 全部更新后调用
    */
  },
  // 解绑阶段 
  unbind(){
    /* 
      指令与标签解绑时触发(标签 卸载时触发 解绑)
      需要清除 在指令初始化阶段 给全局 绑定一些特性
    */
  }
})

总结: 主要使用两个钩子 1 初始化 inserted 2解绑的 unbind

局部指令

const Home = {
  directives: {
    '指令名': {
      bind(){},
      inserted(){},
      update(){},
      componentUpdated(){},
      unbind(){}
    }
  }
}

父子组件 生命周期钩子的触发顺序

父beforeCreate - 父created - 父beforeMount - 子beforeCreate - 子 created -子beforeMount-子mounted- 父mounted 更新阶段 父 beforeUpdate - (子组件会判断 修改数据有没有props传给我) - 子beforeUpdate - 子updated - 父upadted

vue的render函数

注意: vue的组件 视图一定必须是虚拟dom,(数据更新需要 比较),但是开发者自己写虚拟结构过于繁琐, vue允许开发者 使用template定义 字符串的html结构,自己去编译template,调用render 自己生成虚拟dom

比如组件视图结构如下

<div id="box" class="a box">
    <p class="op">这是p</p>
    <span>这是span</span>
    这是文本内容
</div>

render函数

const Home = {
  // 一个组件 可以不定义template 直接 使用render生成组件的虚拟dom视图结构
  render: createElement => {
    /* 
      render函数return当前组件的虚拟dom
        参数createElement函数 用来生成虚拟dom的

        createElement参数结构如下
        createElement(
          '标签名',
          {}, // 属性列表
          [] // 内容
        )
    */
   return createElement(
          'div',
          {
            class: ['a', 'box'],
            attrs: {
              id: 'container'
            }
          },
          [
            createElement(
              'p',
              {
                class: 'op'
              },
              '你是p'
            ),
            createElement(
              'span',
              {},
              '你是span'
            ),
            '这是一个文本内容'
          ]
        )
  }
}

render函数 createElement方法的第二种参数

createElement 第二种参数 参数可以是另一个组件的config对象,可以将这个config中的template编译成虚拟dom,作为当前组件的视图

    const tpl = {
      template: `
        <div id="box">
          <h2>你好render</h2>  
        </div>
      `
    }

    const Home = {
      render: createElement=> {
        return createElement(tpl)
      }
    }

axios

它是一个库,基于promise封装的,可以在node中使用,在http中可以发送ajax请求,除了小程序以外,其他的都是使用axios。

1.在浏览器上发ajax请求

2.在node中发http请求

3.支持promise API,调用后可以直接then

4.可以拦截和响应

5.可以进行转换

6.中途可以取消请求

7.将请求的数据转换成JSON

8.可以防止注入攻击

axios发送get请求

√首先在引入 axios.js,接着在实例上定义methods方法,在方法中写上axios请求,axios.get或者axios.post,在括号中,要请求的地址,接着在then里面返回后端的数据,在data中定义一个数组,把后端返回的数据放入这个数组中,请求的发送都是在钩子函数初始化的时候,所以利用created()方法进行发送接口请求函数,请求数据。

   data: {
        cates: []
      },
 methods: {
        fetchCates(){
          axios.get('https://api.it120.cc/conner/cms/category/list?a=10&b=20').then(res => {
            if(res.data.code === 0) {
              this.cates = res.data.data
            }
          })
        }
      }
  created(){
        this.fetchCates();
      }

不携带config 参数在地址上携带

axios.get('url?page=1&pageSize=10').then().catch()
  • 请求携带config get请求第二个参数 就是config
axios.get(url, {
  params: {
    page: 1,
    pageSize: 10
  }
}).then(
  res=>{

  }
).catch()

axios发送post请求

axios.post(url, data[,config])

注意: 第二个参数对象是请求的参数,第三个参数才是请求的config

axios restfulapi 支持

axios.get() // 获取数据
axios.delete() // 删除数据
axios.post() // 提交数据
axios.put() // 修改

delete用法同get put同post # axios 方法 发送请求

axios(config)
{
  url: '',
  method: 'post', // 默认是get
  params: { // get请求的参数
​
  },
  data: { // 字符串query 或者 对象 post请求的参数
​
  },
  headers: {}
}

axios请求 config 配置的常用属性

{
  url: '/user',
  method: 'get', // default
  // 请求基础url 最终发送完整地址应该是 baseURL+url 一般定义baseURL为接口源 url 定义为接口path
  baseURL: 'https://some-domain.com/api',
  // 请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},
  // get请求的参数
  params: {
    ID: 12345
  },
  // post请求的参数 传递参数是 application/json格式
  data: {
    firstName: 'Fred'
  },
  // 格式是 urlencoded格式
  data: 'Country=Brasil&City=Belo Horizonte',
  // 请求超时时间
  timeout: 6000
}

定义所有请求的 默认配置

  • 创建axios的实例
/* 
  create方法参数对象
  可以定义 配置 默认值
*/
const request = axios.create({
  baseURL: 'xxxx',
  timeout: 6000
})
  • 所有请求发送要通过 实例来发送 请求 实例发送请求个axios一模一样
request.get()
      .post()
      .delete()
      .put()
​
request({})
  • 定义axios的defaults属性
axios.defaults.属性名= 值;
axios.defaults.baseURL = 'XXX';
axios.defaults.timeout = 6000;

注意: 这个需要axios发送请求

axios请求和响应拦截器

会拦截所有请求和响应(axios)

  • axios发送请求
// 添加一个请求的拦截器 拦截所有 axios发送的请求
axios.interceptors.request.use(function (config) {
    /* 
      config就是请求 配置
      可以在发送之前 config 修改发送配置参数
​
      return config 就是放行
    */
    return config;
  }, function (error) {
    // 请求失败的拦截 不return catch不执行
    return Promise.reject(error);
  });
​
// 添加响应拦截器
axios.interceptors.response.use(function (res) {
    /* 
      http状态码 为2xx走 响应成功拦截器
​
      res就是 axios 返回内容 {data:} data才是后端返回内容
      在return之前 给res中添加一些内容
​
      return就是放行 不return则所有请求 不执行then
    */
    return res;
  }, function (error) {
    // 响应失败拦截
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
  });
  • axios实例发送请求的拦截器
// 添加一个请求的拦截器 拦截所有 axios发送的请求
request.interceptors.request.use(function (config) {
    /* 
      config就是请求 配置
      可以在发送之前 config 修改发送配置参数
​
      return config 就是放行
    */
    return config;
  }, function (error) {
    // 请求失败的拦截 不return catch不执行
    return Promise.reject(error);
  });
​
// 添加响应拦截器
request.interceptors.response.use(function (res) {
    /* 
      http状态码 为2xx走 响应成功拦截器
​
      res就是 axios 返回内容 {data:} data才是后端返回内容
      在return之前 给res中添加一些内容
​
      return就是放行 不return则所有请求 不执行then
    */
    return res;
  }, function (error) {
    // 响应失败拦截
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
  });

vue-router

开发模式:

mvc

前后端分离(spa single page application 单页面应用)

vue-router 实现 vue 通过 路由地址的变化,来渲染不同的组件

实现: 前后端分离 优势: 代码分离,前后端不会出现扯皮 情况 开发效率高(同步进行,前端后端接口没好情况下,可以自己mock接口)

单页面应用 spa(面试题:单页应用优缺点) 优势 网页切换速度快(不同于传统mpa 每一次切换都要加载网页) 实现了前后端分离(前后端分离优点) 缺点: 1 首页加载较慢(加载 单页面应用index.html 以及其他组件代码) (可以解决、路由懒加载) 2 对于seo 服务器引擎优化, 不友好 (vue有 ssr 服务端渲染 nuxt)

官网

vue-router官方文档

大多数vue插件使用方法

  • 传统环境下 只需要在 vue.js后面 通过 script引入 即可自动注册插件
  • 模块化 vue工程化环境(可以使用es6模块化)
import Vue from 'vue'
import VueRouter from 'vue-router'
// 模块化环境下 需要 显示 Vue.use(插件)Vue.use(VueRouter)

基础使用

  • 准备好路由组件的 config对象
  const Home = {
      template: `
        <div>
          <h1>我是home</h1>  
        </div>
      `
    }
    
  • 初始化 路由 实例 并 定义路由规则
    const routes = [
      // 单个路由规则 是对象
      {
        path: '/',
        component: Home
      },
      {
        path: '/news',
        component: News
      },
      {
        path: '/about',
        component: About
      }
    ]
    // 创建路由 实例
    const router = new VueRouter({
      // routes 路由规则
      routes
    })
  • 将路由实例 挂载到 vm上
    const vm = new Vue({
      el: '#app',
      router
    })
  • 视图 #app上 定义 router-view组件作为路由出口
<router-view/>

导航组件

router-link 属性如下

to // 定义导航跳转 路由path
tag // 路由渲染的标签
replace // 跳转时 覆盖当前历史记录
内置匹配导航高亮样式处理
  router-link-active ** 通过 组件  active-class修改高亮类名
  router-link-exact-active
      <router-link tag="button" to="/home">首页</router-link>
      <router-link tag="button" to="/news/3/4">新闻页</router-link>
      <router-link replace tag="button" to="/about">关于我们页</router-link>

重定向 redirect

{
  path: '/',
  redirect: '/home'
}

404问题

场景: 当用户访问 一个 路由地址 不匹配 希望显示 是一个 404页面

vue路由提供了 特殊的 路径 * 匹配任意的 地址(优先级最低)

{
  path: '*',
  component: NotFound
}
  // 定义404组件
    const NotFound = {
      template: `
        <div>
          <h1>呕吼,404啦</h1>  
        </div>
      `
    }

注意: 404路由规则最好 放到路由规则最后一个 (更语义)

动态路由

字面理解: 路由path是可变,动态路由完整路由地址 一定是由多个 path组成,其中部分path是可变(需要有不可变的path) 逻辑上: 用于路由跳转传参使用,可以在 路由地址中通过 /:参数名 定义动态参数 跳转时 给这个参数传递值,在目标路由组件拿到这个传递参数的值

路由地址可以分成 多个 path 每个path 以/分割

地址: /a /a/b

  • 定义动态路由规则
{
  path: '/detail/:id', // id是变量
  component: Detail
}
 <router-link tag="button" to="/news/3/4">新闻页</router-link>
  • 跳转时 一定复合定义动态路由 规则
// 路由规则         跳转path
/detail/:id         /detail/3
/a/:b/c/:d          /a/2/c/5
  • 动态参数获取

    目标路由组件中

this.$route.params.动态参数名

注意: 优点:刷新数据不丢失 缺点:参数在地址栏显示 #(丑)

vue中使用路由之后,给每一个Vue实例添加了两个属性

route存储当前路由静态参数信息对象(路由地址一些参数)(主要用来获取各种路由参数)route 存储 当前路由静态参数信息 对象 (路由地址 一些参数) (主要用来获取各种路由参数) router

  mounted(){
        console.log(this.$route);
      }

\

  # 嵌套路由

场景: 后台管理中 admin主页 点击 导航 切换admin中间body区域,整体没有切换 嵌套: 某些路由可以嵌套在 父级路由,子路由切换, 父级路由组件 不会切换,在 父级路由组件 内部切换 子路由组件

  • 1.准备子路由组件config
  const NativeNews = {
      template: `
        <div>
          <h2>我是国内新闻</h2>  
        </div>
      `
    }
    const AbroadNews = {
      template: `
        <div>
          <h2>我是国外新闻</h2>  
        </div>
      `
    }
  • 2.在父级路由规则 中定义children 嵌套子路由规则
      {
        path: '/news',
        component: News,
        // 定义children属性 定子路由规则
        children: [
          {
            path: '/news',
            redirect: '/news/native'
          },
          {
            path: '/news/native', // 建议携带一级路由 path作为 子路由地址 前缀
            component: NativeNews
          },
          {
            path: '/news/abroad',
            component: AbroadNews
          }
        ]
      }
  • 3.父路由组件 template 合适的定义router-view作为子路由组件出口
<router-view/>

注意: 子路由组件path 最好携带 一级 路由path作为前缀 举例: 一级 /news 二级 /news/native /news/abroad

 const Home={
            template:`
               <div id='box'>
                <h1>你好</h1>
                <router-link to="/home/sea">海洋</router-link>
                <router-link to="/home/load">大陆</router-link>
                <router-view ></router-view>
                </div>
            `,

原因: 有层级关系 更为语义 router-link-active 只要当前 路由地址 以 to属性开头即可添加这个类,,这样跳转子路由时,父级路由导航组件也会添加router-link-active 类

router-link-exact-active 只有当前路由地址 是精准的一模一样才能够定位到,添加router-link-exact-active类,

只是添加router-link-exact-active这个类时,如果每个标签都存在有这个类,如果希望上级添加了router-link-active这个类的高亮不被改变,那么就需要给这个类设置样式的时候添加 id或者class定位到这一层级

 #box .router-link-exact-active{
            background-color: aqua;
        }
  template:`
             <div id='box'>
                <h1>你好</h1>
                <router-link to="/home/sea">海洋</router-link>
                <router-link to="/home/load">大陆</router-link>
                <router-view ></router-view>
                </div>
            `

命名路由

就是给每个路由添加name属性即可

{
  path:'',
  name:'xxxx', // 语义即可
  component:xxx
}

优点: 更为语义 跳转参数(如router-link to属性以及编程式导航) 除了 字符串 还可以是对象

to----表示一个常量

:to:{对象} 表示一个变量

      <router-link tag="button" :to="{path: '/home'}">首页</router-link>
      <router-link tag="button" :to="{name:'news'}">新闻页</router-link>
      <!-- 路由名字进行跳转 -->

编程式导航

1.声明式导航

利用标签 进行路由操作

2.编程式导航 利用js api进行导航操作

使用路由后,vue给所有的组件添加了 两个属性 route是当前路由静态参数信息对象route是 当前路由静态参数信息对象 router 其实就是 初始化的 router对象实例

原型上 有很多方法 用来操作路由

addRoute // 动态添加路由
  /* 
    1 插入 一个 一级路由 routeConfig 路由规则对象
      addRoute(routeConfig)
    2 作为一个已存在路由 子路由插入 二级路由
      addRoute(父级路由name, 子routeConfig)
  */
getRoutes // 获取当前所有路由信息 返回是 json数组
go(n) // 操作历史记录
  1 前进一步
  -1 后退一步
  0 刷新
push()  // 普通路由跳转 参数 同 router-link的to属性 ,push的内容是不能够跳转页面中已经存在的内容,否则会出错
replace() // 路由跳转,跳转时 会覆盖当前历史记录

addRoute使用

1.先准备一个config组件

  const JoinUs = {
      template: `
        <div>
          <h1>加入我们</h1>  
        </div>
      `
    } 

2.定义一个点击事件

 <div id="app">
     <button @click="add">新增一个路由</button>
 </div>

3-1.一级路由添加

由于是在app中直接添加,所以方法是添加在VM实例上,利用this.$router.addRoute()添加路由,可以用push来看是否已经新增了这个路由

  add(){
          //一级路由         
      this.$router.addRoute(
                        {
                            path:'/food3',
                            name:'food3',
                            component:Food3
                        }
                    )
                },

3-2.二级路由添加

  methods:{
                add(){
                    this.$router.addRoute(
                        //❤这里必须是父级的name
                        'home', 
                        {
                            path:'/food3',
                            name:'food3',
                            component:Food3
                        }
                    )
                },

在你所指定的父路由中必须得有name属性

  {
                path:'/home',
                name:'home',
                component:Home
            },

go使用

直接使用 $router.go()就可以使用go

   <button @click="$router.go(0)">刷新</button>

replace使用

会覆盖上次历史记录,回退到上上次历史记录。

   <button @click="$router.replace('/food')">到关于我们</button>

路由跳转传参 (不能回答错误)

  • 动态路由传参
  • query传参 注意: 不关注跳转方式 只关注跳转参数
  • 跳转参数
   <router-link :to="{
     path: '/detail',
     query: {
       a: 10,
       b: 29
     }
   }">到详情页</router-link>

   this.$router.push({
     path: '/detail',
     query: {
       a: 10,
       b: 29
     }
   })
  • 获取
    this.$route.query.参数名

注意: 1 在地址上携带参数以query格式 2 刷新数据不会丢失 3 最好使用path,在地址栏上name也是可以的。(面试不要说)

  • params传参(必须通过name传参)

    不能在地址栏上通过params这种方式传参

    • 跳转参数
     this.$router.push({
                name: 'news',  //必须通过name传参
                params: {
                  a: 10,
                  b: 20
                }
              })
    
    • 获取
    this.$route.params.参数名

注意:

隐式传参:不会在地址栏上面显示,而是在$route.params中才会显示 刷新会丢失 必须通过路由name跳转

重定向和别名

“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b,那么“别名”又是什么呢?

/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

 const routes=[
            {
                path:'/',
                alias:'/aaa',   //添加别名
                component:Home
            },
<router-link tag="button" to="/aaa">首页</router-link>

vue-router 路由模式

const router = new VueRouter({
  mode: 'hash', // hash或history  hash模式是默认值
})

路由模式有两个 1.hash 原理: url后面hash值变化,来模拟路由 跳转,通过 window.onhashchange事件来监听,实现对应组件的渲染和卸载 优点: hash值变化不会产生404问题(hash值改变 url路径指向是不变的) 缺点: 丑 多个 #

  1. history 去掉 # 看着比较 正常的路径

    在实例中添加

    const router = new VueRouter({
          mode: 'history',
          // routes 路由规则
          routes
        })
    

    严重问题: ​ 如果没有服务器 配置 一定很产生404问题(后端的问题,找后端),没有服务器直接就是D://new,

    有服务器后是 服务器的源加上路由地址.

    原理: ​ 使用 h5 给history新增了的两个方法 pushState 和replaceState方法 改变地址栏 状态

掘金 setmentfault

路由守卫

功能: 实现对于路由的变化进行拦截 或者 监听

全局守卫 2个

监听或者拦截 所有的路由的变化

  • 全局前置路由守卫
router.beforeEach((to, from, next) => {
  /* 
        to 即将跳转到 路由静态参数信息对象
        from 离开路由       静态参数信息
        next 函数,放行,不执行 路由无法跳转 (拦截器)
          注意:
              1 next 如果传参,参数同 router-link to属性
              2 路由守卫回调每次触发 保证 next必须且只能执行一次,分支得写清楚,否则会产生死循环
      */
  next();
})
  • 全局后置 路由守卫 路由变化时,已经跳转目标路由,只能对于全局路由变化进行监听
router.afterEach((to, from) => {

})

路由登录健全

前后端分离,如何登录?

点击提交,会发送ajax请求,给后端进行校验,如果验证成功会返回token:密钥,token会经常过期,前端拿到token,会把token进行缓存,前端如何判断用户登录状态,通过判断是否有token来确定是否登录,但是不安全,可以自己手动的在localStorage中setItem写入token,能够骗过前端。

前端只能判断token是否存在,而不能判断token是否正确,只能通过后端来进行判断。前端要在请求头中把token发送给后端,后端要对token进行校验,如果不正确,会返回401:token 不正确或者过期,或者403:没有传token,前端判断,如果不正确,会弹出错误提示,重新跳转登录页。

登录跳转死循环:

 if(login()){
                next()
            }else{
                next('/login')
            }
         })

如果有登录状态进行登录,如果没有登录状态跳转到登录页面,但是登录页面并没有登录状态因此,还是会再次走第二个分支,会造成死循环,不能够进入内容区域。

解决方法:

  const login=()=>!!localStorage.getItem('token')
         router.beforeEach((to,from,next)=>{
            const nextPath=['/login','/register']
            if(nextPath.includes(to.path)){
                next()
            }else{
                if(login()){
                    next()
                }else{
                    next('/login')
                }
            }
         })

点击跳转成功能够跳转到首页

   this.$router.replace('/home')

通过路由元信息来解决哪些路由需要登录问题。

 {
        path: '/home',
        meta: {
          needLogin: false
        },
        component: Home
      },

值为false,不需要,值为true,需要登录验证,注意:/login页面不需要登录验证

 //判断localStorage中是否存在token这个属性
 const isLogin = () => !!localStorage.getItem('token');
    router.beforeEach((to, from, next) => {
      /* 
        判断哪些路由需要登陆
          不需要 直接放行
          需要
            判断是否登陆
              登陆放行
              没有登录 去登录
              to 表示到具体的地址
              from 表示从哪个地址来的
              next 拦截器,是否放行路由的内容显示
      */ 
      if(to.meta.needLogin){
        //进行判断,是否需要登录的这个needLogin这个值是否是true就是是否需要登录.
        //是否登录,如果值为false的情况下,就直接访问路由,如果值为true的情况就需要
        //进行登录判断。
        if(isLogin()){
          //如果有token值,那么就直接进入路由,如果没有需要重新访问到登录页面
          next();
        }else{
            //next是可以进行传参的
          next({
            //去到登录页
            path: '/login',
            //返回的地址是,来自的地址是,to.path是现在目前的所在的地址,将现在的地址存储起来,
            //然后地址的信息都是存储在实例上面的。点击登录的时候,直接判断是否之前的地址,
            //如果有返回,如果没有返回首页
            query: {
              from: to.path
            }
          })
        }
      }else{
          //needLogin的值为false的时候,就可以直接查看路由
        next();
      }
    })

在“登录”按钮点击的时候进行判断,$route里面query的from的值

  doLogin(){
          setTimeout(() => {
            const token = 'ftr56tyj7uk8l9io0';
            localStorage.setItem('token', token);
            // 调回到首页
             //获取路由实例上的query传回的值
            if(this.$route.query.from){
                //将路由地址替换成from中的内容
              this.$router.replace(this.$route.query.from)
            }else{
                //否则跳转到主页
              this.$router.replace('/home')
            }
          }, 1000)
        }

路由独享守卫 1个

拦截 特定某个路由 定义路由规则内部守卫

{
  path: '/xxx',
  component: xxx,
  beforeEnter: (to, from, next) => {

  }
}

组件内部守卫 3个

 进入这个路由前进行验证,是否要进入这个路由
beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
   多个路由地址对应同一组件的时候,使用场景动态路由,传递参数的时候
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
    离开这个路由的时候,进行判断是否确认离开这个路由
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }

beforeRouteEnter获取组件实例,之前并不可以,之前this指向的是window,现在通过next,vm回调函数指向实例

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}
  const News={
            template:`
              <div>
                <h1>新闻</h1>
                </div>
            `,
            beforeRouteEnter(to,from,next){
                if(confirm('你确认进入这个网站吗')){
                    next()
                }
            },

beforeRouteLeave

const News={
            template:`
              <div>
                <h1>新闻</h1>
                </div>
            `,
            beforeRouteEnter(to,from,next){
                if(confirm('你确认进入这个网站吗')){
                    next()
                }
            },
              beforeRouteLeave(to,from,next){
                if(confirm('你确认离开这个网站吗')){
                    next()
                }
            }
        }

beforeRouteUpdate

const News={
            template:`
              <div>
                <h1>新闻</h1>
                </div>
            `,
            beforeRouteUpdate(to,from,next){
                 console.log(to)
                 console.log(from)
                 next()
            },
            watch:{
                //每次内容不一样,会把上一次的内容去掉,换成新的内容地址
                $route(to,from){
                    console.log(to,'to')
                    console.log(from,'from')
                }
            }
        }

路由元信息

可以在路由规则中 添加meta属性,属性中的所有值,都可以在路由静态参数信息中获取到

{
  path: '/xxx',
  meta: {
    a: 10,
    needLogin: true
  },
  component: xxx
}

监听动态路由参数变化《面试》

  • beforeRouteUpdate
  • watch侦听 $route
      beforeRouteUpdate(to, from, next){
        console.log(to, 'to');
        console.log(from, 'from');
        next();
      },
      watch: {
        $route(to, from){
          console.log(to, '11');
          console.log(from, '22');
        }
      }

\

  # vue-router 滚动行为
const router = new VueRouter({
  mode: 'hash',
  routes: [],
  scrollBehavior (to, from, savedPosition) {
    /* 
      在每次路由变化时触发(包括操作历史记录)
      to,from 路由参数信息对象
      savedPosition
        保存是当前路由组件上一次 滚动滚动条位置{x: 上次的位置,y:上次位置}
      方法 需要return一个对象
      {
        x: 0, // 切换某个路由时,水平方向滚动条位置
        y: 0 // 切换路由 垂直方向滚动条位置
      }
    */
    if(savedPosition){
      return savedPosition;
    }else{
      return {
        x: 0,
        y: 0
      }
    }
  }
})

组件切换动效

一个组件使用和停用之间状态切换(移除节点 v-if和路由组件路由跳转)

单组件状态切换 -transition

只能包裹单个组件状态切换 使用transition包裹 状态切换组件,同时定义name属性后会在不同状态添加不同类

    <transition name="ani1">
      <div id="box" v-if="isShow"></div>
    </transition>
  • 入场
.name-enter // 入场开始瞬间状态的类  ❤
.name-enter-active // 入场 中间 状态类 ❤
.name-enter-to // 入场最终状态 (不重要,自己定义选择就是最终状态)
  • 出场
.name-leave // 出场瞬间状态
.name-leave-active // 出场中间过渡状态类 ❤
.name-leave-to// 出场最终状态 ❤

定义出场和入场开始和结束状态的样式,中间过渡 类 使用 transition即可

    #box{
      width: 200px;
      height: 200px;
      background-color: rgb(108, 190, 82);
      margin: 20px auto;
    }
    .ani1-enter{
      transform: translateX(-500px) scale(0.1) rotate(-360deg);
      opacity: 0;
    }
    .ani1-leave-to{
      transform: translateY(500px) scale(0.1) rotate(-360deg);
      opacity: 0;
    }
    .ani1-enter-active, .ani1-leave-active{
      transition: all 1s;
    }

transition结合动画

由于 动画自己的有关键帧,只需要 使用 两个过渡类,指定入场 使用哪个动画 出场使用哪个动画即可

    @keyframes enter {
      0%{
        transform: translateX(-500px) scale(0.1) rotate(-360deg);
        opacity: 0;
      }
      50%{
        transform: translateX(-250px) scale(0.5) rotate(-180deg);
        opacity: 0.5;
      }
      100%{
        transform: translateX(0) scale(1) rotate(0);
        opacity: 1;
      }
    }
    @keyframes exit {
      0%{
        transform: translateY(0) scale(1) rotate(0);
        opacity: 1;
      }
      50%{
        transform: translateY(250px) scale(0.5) rotate(-180deg);
        opacity: 0.5;
      }
      100%{
        transform: translateY(500px) scale(0.1) rotate(-360deg);
        opacity: 0.1;
      }
    }
​
    .ani1-enter-active{
      animation: enter 2s;
    }
    .ani1-leave-active{
      animation: exit 2s;
    }

transition 包裹多个组件

注意:transition组件同时只能包裹一个组件,需要包裹多个组件,必须是同时入场和出场的组件只能 有一个(不能同时多个入场和多个出场)

如果要两个div的元素进行动画效果的切换,必须要给他们加上key属性

 <div key="1" id="box" v-if="isShow"></div>
      <div key="2" id="box2" v-else></div>

根据虚拟dom,diff算法的比较原理,id是会改变的,但是key是不会的,因此会造成第二个元素消失不见了。所以需要使用key

eg: 使用条件渲染 v-if v-else-if v-else 路由组件的router-view 由于这种情况会出现 同时有一个入场 和 一个出场,可能会导致一个没有完全出去另一个就进来的,滚动条会造成混乱。

新增一个属性 指定 入场和出场 之间顺序 both 默认值 同时进行 in-out 先入场再出场 out-in 先出场再入场

 <transition name="ani1"  mode="out-in">


# 修改配置

const { defineConfig } = require('@vue/cli-service')
const path = require('path')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    host: 'localhost',
    port: 3000,
    open: true
  },
  lintOnSave: false,
  // 自定义路径别名
  chainWebpack: config => {
    config.resolve.alias
      .set('@', path.join(__dirname, 'src'))
      .set('_views', path.join(__dirname, 'src/views'))
      .set('_components', path.join(__dirname, 'src/components'))
      .set('utils', path.join(__dirname, 'src/utils'))
      .set('_api', path.join(__dirname, 'src/api'))
  }
})
​

如何在vueCli中 开发环境下做反向代理

vue.config.js

{
  devServer: {
    // 开发环境下所有的请求必须以/api开头 才能触发反向代理
    '/api': {
      // 代理的源 开发和生产一般不一样
      target: 'http://xxxx.com',
      // 是否切换源
      changeOrigin: true,
      // 路径重写 将 请求的前缀 /api重写为 什么值(最终请求完整地址中 没有/api会被替换为 路径重写的值)
      pathRewrite: {
        '^/api': '/conner'
        // 所有请求中 前缀/conner 真实发请求时会被替换为/conner
      }
      /* 
        举例 
          请求是
          axios.get("/api/aaa/c")
          反向代理真实地址时候
          http://xxxx.com//conner/aaa/c
      */
    }
  }
}

mock接口

前后端分离开发,很多情况下前端和后端开发时同步的,意味着前端在开发时后端接口还没好 此时就可以自己mock接口

规范: 要求 mock所有的接口 path一定要和真实后端接口保持一致 所有接口返回的数据格式 一定要和真实接口保持一致

代码本地使用mockjs 包 进行mock接口

基础使用

  • 安装
npm install mockjs -D
  • mock方法
const Mock = require('mockjs');
Mock.mock(path[,type],template)
/* 
  参数是模拟接口 路径
  参数模拟接口 请求类型
  参数 模拟接口返回数据
*/
Mock.mock('/api/getItemLists', 'post', {
  code: 200,
  msg: 'success',
  data: '这是数据'
})
  • mockjs产生随机数的语法
  • 量词 (修饰 数组 字符串 number(就是大小)id) 注意:需要定义在 字段名中 加引号
// 针对数组
{
  code: 200,
  msg: 'success',
  'data|10': [] // 数组长度就是确定10
  'data|10-20': [] // 数组长度就是确定10-20
}
// 针对number
{
  'num|10-100':10 // 生成10-100随机整数
}
// 针对字符串
{
  'str|5': '☆' // 产生'☆☆☆☆☆'
}
{
  'str|5-10': '☆' // 产生5到10五角星
}
// 针对id
{
  'id|+1': 1 //自增1 
}
  • 随机数据 占位符 注意: 用在 字段值中 需要加上引号 语法 '@占位符'
{
  isShow: '@boolean'
}
      Basic 基础占位符
        boolean, natural, integer, float, character, string, range, date, time, datetime,
        now
      Image 图片相关
        image, dataImage
        使用时,可以定义 生成图片的大小 颜色,以及 图片的中文本
        "@image('100x100','@color', '牛夫人')"
      Color 随机颜色
        color
      Text 随机文本
        paragraph, sentence, word, title, cparagraph, csentence, cword, ctitle
      Name 随机名字
        first, last, name, cfirst, clast, cname
      Web 随机网络地址
        url, domain, email, ip, tld
      Address 随机地址
        area, region
      Helper
        capitalize, upper, lower, pick, shuffle
      Miscellaneous
        guid, id
​
// 完整案例
​
Mock.mock('/api/getItemLists', 'post', {
  code: 200,
  msg: 'success',
  'data|10-20': [
    {
      'id|+1': 1,
      name: '@ctitle',
      'price|0-1000': 0,
      'cateId|1-100': 1,
      'sale|0-1000': 0,
      'store|0-500': 0,
      onOff: '@boolean',
      createAt: '@datetime',
      thumb: '@image("100x100","@color","@cname")'
    }
  ]
})

在线mock平台 (产生模拟数据也是mockjs语法)

easyMock fastMock rap2.taobao.org (软件:apiPost) 在线mock平台: 利用mockjs语法生成随机数,但是真实线上接口,请求真的可以发送出去

webpack中 支持模块化

支持 es6和nodejs 区别? import 必须在代码编译前提前引入 require可以

一般在开发中 使用require 引入 开发时需要用到包(上线时会手动删除)

对于项目接口处理

  • 二次封装axios
  • 二次封装请求model函数

项目基础路由

一级路由 登录 admin页 二级路由 首页 /dashBoard 商品分类管理 (商品分类列表) 商品管理 (商品列表、商品增加、商品修改) 设置

公钥

element-ui form表单验证

  • 定义表单rules属性 值为 验证规则
  • 验证规则
rules: {
  a: [
    {
      required: true, // 必填验证
      message: 'xxx必须添加', // 验证不通过的提示文本
    },
    {
      min: 3,
      max: 5,
      message: 'xxx长度必须是3-5之间',
      trigger: 'change' // 默认是blur触发 验证触发时机
    },
    {
      type: 'array',
      message: 'xx'
    },
    // 正则验证
    {
      pattern: /\w{3,6}/,
      message: 'xxcvb'
    },
    // 自定义验证规则
    {
      validator: (rule, value, callBack) => {
        /* 
          value每次验证字段的值
          验证通过直接 调用callBack()
          不通过 调用callBack(new Error('这就是错误的提示信息'))
        */
        if (value == 'xxx') {
          callBack()
        } else {
          callBack(new Error('这就是错误的提示信息'))
        }
      },
      trigger: 'blue'
    }
  ]
}

操作时间的js插件

moment.js day.js

操作cookie cookie.js

登录鉴权

路由鉴权: 在路由前置守卫中判断token是否存在,存在 则判定登录 允许访问,否则不能访问 只有路由鉴权不安全,token只是判断是否存在(无法判断正确性) 接口鉴权: 在请求接口过程中,一般将token 放到请求头中,传递给后端,后端判断token正确性,判断成功 返回不同的code 代表不同token状态 200 正确 401 token过期 403 没有传token(没登录)

角色鉴权

后台管理项目中,不同的用户 会分配不同的角色,不同的角色,所拥有的的权限是不同的 权限: 路由权限,不同角色可以访问的路由是不同的 侧边导航 不同角色 侧边也不同 方法权限 一个路由组件,不同角色都可以访问,但是其中的某个方法,需要特定角色才能使用

静态角色鉴权

所有路由和侧边导航都是在前端定义(全部定义),每个路由和导航都添加一个属性 roles(数组,保存了可以访问当前路由或者导航的所有角色),登录成功后,后端返回role字段,根据导航或者路由的roles 数组 判断是否包含当前用户的role,包含了可以访问,不包含则当前用户是没有权限访问的 优点: 简单,不需要和后端沟通 缺点: 固定,如果后续需要新增新的角色,需要修改源码再重新上线 适用于: 中小型后端管理。且角色较为固定

 # 安装vue-cli
npm i @vue/cli -g

启动项目

vue create 项目名 // 项目名不能包含大写字母

项目目录

public // 开发环境服务器监听的根目录
src // 源码目录
    assets // 开发时需要用到静态资源目录 css 字体图标等
    components // 存储 公共子组件
    router // 路由配置文件
    store // vuex的目录
    views // 路由组件的存储目录
    App.vue // 根组件
    main.js // 入口文件
.browserslistrc // postcss 浏览器兼容性
.eslintrc.js // 当前项目eslint的配置 想要修改配置可以在 rules中添加新的配置
.gitignore // git忽略文件
babel.config.js // babel配置文件
vue.config.js // 自定当前项目配置

项目运行架构

vueCli是 基于 webpack 构建 vue 前端模块工程环境

webpack 将 原理网页的 依赖变成 webpack的依赖, 只需要将网页的依赖在main.js 入口文件中引入即可在浏览器上运行

最先运行就是 main.js,main.js引入 依赖(css、字体图标)以及写的代码 自动 打包自动在 public index.html上运行

main.js运行了 唯一实例 进行 vm挂载,vm通过render函数将 App.vue(有路由组件的出口)编译 组件config对象的template当前 vm视图,替换到 index.html上的#app位置处

es6模块化

安装在node_modules中的第三方模块

直接引入即可 不需要写路径

import Vue from 'vue'
import VueRouter from 'vue-router'

导入一个 没有 导出任何接口的模块 (在webpack支持 引入 非js文件)

直接导入即可 注意:需要写路径 相当于在导入位置处运行

import '文件名路径'
import './assets/js/a.js'

ps:模块引入是 .js .json .vue可以省略后缀名的

一个文件 导出多个接口

  • 导出

    • 使用多个export
        export const a = 10
        export const b = [1, 2, 3, 4]
        export const c = (a) => {
            console.log(a)
        }
    
    • 使用一个export统一导出
        const a = 10
        const b = [2, 3, 4, 6]
        const c = () => {
            console.log(1111)
        }
        // 下面是固定语法 不是对象 省略了 值
        export {
            a,
            b,
            c
        }
    
  • 导入

    • 全部导入
        import * as obj from '路径'
        // as 声明一个变量obj 接收 模块导出的值 (对象)
    
    • 对象解构赋值 按需导入
        import { a, b, c as d } from '路径'
        // as定义一个别名保存 d接口的值  当前文件中已经有变量 c了
    

一个文件 导出默认值

只要访问了这个模块 默认返回的值(导出的值 不需要考虑结构) 直接使用变量接收即可

只导出默认值

export default

导出默认值同时 导出其他普通接口

export defaultexport const a = 10
/* const b = 200
export {
    b
} */

导入

  • 只导入默认值 直接使用变量接收默认值即可
import 变量名 from '路径'
import Vue from 'vue'
import num  from './assets/js/c'
  • 导出默认值接口情况 只导入 其他接口
import { a, b } from './assets/js/c'
  • 导入默认值同时 再导入其他接口
import 变量, {结构赋值语法} from '路径'
import num, { a, b } from './assets/js/c'

单文件组件 sfc single file component

基于webpack vue-loader 将.vue结尾文件 解析成 一个 组件 config对象js文件

<template>
    <!--定义组件的视图-->
    <div>
        <h2>这是home</h2>
    </div>
</template>
<script>
    // js文件 所有js业务代码定义这个标签中
    export default {
        data () {
            return {
                msg: '你好vue'
            }
        }
    }
</script>
<style lang="scss" scoped>
    /* 
        lang 使用什么css 预处理器  less  sass scss stulys
        scoped 作用域 加上这个属性 当前 写选择器样式只针对当前组件有效
​
        问题?
            实际开发使用 ui组件库,经常要修改 某个组件库 组件默认样式
        如何在父组件 加scoped情况下 修改 子组件的样式
        vue提供了穿透选择器
        父组件中的选择器 /deep/ 子组件选择器  /deep/有兼容性 (sass新的版本不兼容)
​
        ::v-deep
​
        [父组件中的选择器] ::v-deep 子组件选择器
    */</style>

脚手架中的环境变量

代表项目运行环境的变量 项目环境一般有三个值 生产环境 测试环境 开发环境 系统内置一个环境变量

process.env // 对象 保存了所有的环境变量
process.env.NODE_ENV // 值 开发时 值为 development 上线后置为 production

自定义环境变量

自定义环境变量以 VUE_APP_变量名 步骤: 项目根目录下 定义 .env.development和.env.production分别定义开发环境和生产环境的环境变量的值

VUE_APP_BASEURL = /api // .env.development
VUE_APP_BASEURL = 生产环境的源 // .env.production

使用时

process.env.VUE_APP_BASEURL
// eg
import axios from 'axios'
// 定义所有请求默认配置const request = axios.create({
  // baseURL: process.env.NODE_ENV === 'development' ? '/api' : '生产的源'
  baseURL: process.env.VUE_APP_BASEURL
})

路由懒加载

解决:spa应用首页加载速度过慢问题 路由懒加载原理: 将 每一个路由组件 切割为 单个js文件,在加载首页时 只会引入 首页路由组件对应js文件, 切换到 某个组件时,才开始 引入对应组件的js文件

  • 利用import
    {
    path: '/about',
    name: 'about',
    component: () => import('../views/AboutView.vue')
  }
  • 利用require
{
    path: '/about',
  name: 'about'
    component: resolve => require(['../views/AboutView.vue'], resolve)
} 

在webpack构建的项目中 既支持 es6模块化 也支持 require引入

keep-alive在路由下使用

  • 包裹router-view
<template>
  <div id="app">
    <keep-alive>
      <router-view/>
    </keep-alive>
  </div>
</template>

问题: 直接包裹 router-view 会造成 所有路由组件都被缓存,有些路由组件不能缓存, 需要传参 根据参数获取不同数据(列表页 详情页等)

keep-alive 两个属性

include - 字符串或正则表达式。只有名称匹配的组件会被缓存。 exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。

        <keep-alive>
      <router-view include="a,b,c"/>
    </keep-alive>
        <!-- a b c是需要缓存路由组件的名字 -->

定义两个 router-view 结合路由元信息 定义 路由组件是否需要缓存

            <keep-alive>
      <router-view v-if="$route.meta.keepAlive"/>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive"/>

哪个路由组件需要缓存 只需要在路由配置 meta定义keepAlive 为true即可

    {
    path: '/about',
    name: 'about',
    meta: {
      keepAlive: true
    },
    component: () => import('../views/AboutView.vue')
  }

要求所有路由组件 config定义name属性

自定义 项目开发配置

项目根目录下 的 vue.config.js 项目 开发配置 注意注意:修改配置 重启生效

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    host: 'localhost',
    port: 3000,
    open: true
  },
  // eslint 关闭 保存代码就 检查代码格式
  lintOnSave: false
})
​

安装vscode的 eslint插件,注意 vscode必须 打开当前项目 (当前项目的目录一定是vscode中以根目录形式打开)

vscode对于vue 插件

vuter .vue 代码高亮 vue-vscode-snippets

调试vue项目 chrome插件

chrome的 vue-devtools

vuex

vuex官方文档

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

vuex是一个vue 用于 状态管理(公共数据管理) 的 插件,采用集中式管理方式,来管理所有的组件的公共状态, 且状态改变 必须符合vuex定义规则来修改

vuex使用场景

适用于 中大型 公共数据较多的情况 只用于管理组件中公共的数据

vuex基础使用

  • 安装
npm i vuex -S
  • 创建仓库 src目录下创建 store目录 定义 vuex代码 index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 创建仓库实例
const store = new Vuex.Store({
  strict: true,
  // 存储公共状态
  state: {
    num: 10
  },
  // mutations中的方法是用来修改 数据
  mutations: {
    CHANGE_NUM (state, n) {
      state.num += n
    },
    REDUCE_NUM (state, n) {
      state.num -= n
    }
  }
})
​
export default store
​

在main.js引入 并挂载到实例上

new Vue({
    store
})
  • 在组件中使用仓库

    • 获取state

          this.$store.state.参数名
      
    • 提交mutation

          this.$store.commit('mutation名字'[,携带参数])
      

vuex中的action

场景: 很多公共的状态,需要请求 数据接口才能拿到数据 mutation不能发送异步请求

Action 提交的是 mutation,而不是直接变更状态。 Action 可以包含任意异步操作

action使用场景: 一般在action中 发送异步请求,成功拿到数据,commit提交一个mutation 携带 请求成功数据,由 mutation来赋值

疑问? 直接在某个组件methods中定义方法,发送异步请求,在 created调用,成功后,this.$store.commit触发mutation携带请求成功数据? 功能角度看,没问题可以实现

业务看? vuex管理状态状态获取都没有在vuex中定义,管理是不全面

步骤

const store = new Vuex.Store({
    state: {
        items: [] // 请求接口才能拿到
    },
    mutations: {
        INIT_ITEMS (state, items) {
            // 给items赋值
            state.items = items
        }
    },
    actions: {
        // 在action发送异步请求,请求 数据接口
        FETCH_ITEMS (context, params = {}) {
            /* 
                参数1 context就是store这个实例
            */
            axios.post('xxxx', params).then(res => {
                if (res.data.code === 200) {
                    // 请求成功 触发mutation 将请求的结果传递 进行赋值
                    context.commit('INIT_ITEMS', res.data.data)
                }
            })
        }
    }
})
  • 在组件中触发action
this.$store.dispatch('action名字'[,参数])
​
this.$store.dispatch('FETCH_ITEMS', {page:1,pageSize: 10})

vuex中的getters

很多时候,我们使用 某些 store的state时候,不是原值使用,需要派生出新的状态 (通过已有state进行计算 得到 新的 值) 解决方案1: 组件中定义computed 进行计算 (不推荐,只能在 定义 组件内部使用)

解决方案2: vuex中定义getters

getter 可以理解为 vuex中的计算属性,定义是一个方法,但是 编译到仓库是一个属性,且每一次计算后也会基于依赖进行缓存,多次使用时, 只要依赖没有变化,不会重新计算

const store = new Vuex.Store({
    state: {
        num: 10
    },
    getters: {
        doubleNum (state) {
            return state.num * 2
        }
    }
})

组件中使用

this.$store.getters.doubleNum

vuex常用助手函数

问题: 在组件中操作 vuex还是很麻烦,获取state 提交mutation 触发action 获取getter

mapState

在组件中获取 仓库state问题

import { mapState } from 'vuex'
​
mapState(['num', 'blogs'])
// 结果如下
{
    num () {
        return store.state.num
    },
    blogs () {
        return store.state.blogs
    }
}
// 正确用法
{
    computed: {
        ...mapState(['num', 'blogs'])
    }
}
// 定义两个计算属性 num和blogs值就是 仓库中对应 的 num和state

mapMutations

在组件中 提交mutation助手函数

import { mapMutations } from 'vuex'mapMutations(['ADD_NUM', 'REDUCE_NUM'])
​
// 调用结果如下
{
    ADD_NUM (payload) {
        store.commit('ADD_NUM', payload)
    },
    REDUCE_NUM (payload) {
        store.commit('REDUCE_NUM', payload)
    }
}

mapActions

组件中用于触发 action的助手函数

import { mapActions } from 'vuex'
{
    methods: {
        ...mapActions(['FETCH_BLOGS'])
    },
    created () {
        this.FETCH_BLOGS(请求参数)
    }
}

mapGetters

import { mapGetters } from 'vuex'// 正确用法
{
    computed: {
        ...mapGetters(['doubleNum'])
    }
}

modules

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

const modulea = {
    state: {},
    mutations: {},
    actions: {},
    getters: {}
}
const moduleb = {
    state: {},
    mutations: {},
    actions: {},
    getters: {}
}

挂载到仓库中

const store = new Vuex.Store({
    modules: {
        moda: modulea,
        modb: moduleb
    }
})

原理: 将 state 原来由大对象 切割成了 若干小对象(就是大对象 属性(属性名就是模块名))

注意注意注意: 只是将state分层了, mutation action getter并没有 如果提交一个 mutation 如果多个模块有同名mutation都会同时触发

解决以上问题,

modules中的命名空间

原理: 加上命名空间后,将所有模块的中的mutation action getter名字 自动变成 '模块名/原来的名字'

const modulea = {
    namespaced: true,
    state: {},
    mutations: {},
    actions: {},
    getters: {}
}

模块化 在组件中 操作vuex

// 获取state
this.$store.state.模块名.状态名
// 提交mutation
this.$store.commit('模块名/mutation名字'[,参数])
// 触发mutation
this.$store.dispatch('模块名/action名字'[,参数])
// 获取getter
this.$store.getters['模块名/getter名字']

在模块化命名空间中使用助手函数

  • mapState 参数直接变成对象即可
{
    computed: {
        ...mapState({
      userNum: state => state.user.num,
      cartNum: state => state.cart.num
    })
    }
}
  • mapMutations 两个参数 参数1 固定 是 模块名 参数2 两种写法

        数组  
        对象 可以重命名
    
{
    methods: {
        ...mapMutations('user', ['ADD_NUM', 'REDUCE_NUM']),
        // 定义两个 方法 分别是 ADD_NUM和REDUCE_NUM提交对应mutation
        ...mapMutations('user', {
            // 重命名 组件 定义方法  userAddNum 用于提交 user模块中的ADD_NUM
            userAddNum: 'ADD_NUM'
        }),
    }
}
  • mapActions 请看 mapMutations
  • mapGetters
{
    computed: {
        ...mapGetters('user', ['doubleNum']),
        // 定义一个计算属性 doubleNum 获取 user模块中的 doubleNum
        ...mapGetters('user', {
            userDoubleNum: doubleNum
            // 可以重命名为 userDoubleNum 获取 user模块中的 doubleNum
        }),
    }
}

webpack环境中 require引入和 import引入区别

import 必须在代码 顶部 引入,且 运行在代码编译之前(参与代码编译 最终执行的代码是编译之后,不是 你import代码)

require可以 编译后运行(举例 在一个代码中间,一个事件函数中 require)

require可以 编译后执行 (写在代码任意地方)

(在css中 给一个元素 加背景图片 图片放到本地 assets)

使用时: 大部分情况下使用import 使用require 1 在代码中引入 非 生产环境的包时 2 按需引入一些包

 # 自定义路径别名
const { defineConfig } = require('@vue/cli-service')
const path = require('path')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    host: 'localhost',
    port: 3000,
    proxy: {
      // 反向代理
    }
  },
  lintOnSave: false,
  chainWebpack: (config) => {
    // 修改文件引入自定义路径
    config.resolve.alias
      .set('@', path.join(__dirname, 'src'))
      .set('_assets', path.join(__dirname, 'src/assets'))
      .set('_api', path.join(__dirname, 'src/api'))
      .set('_components', path.join(__dirname, 'src/components'))
      .set('_utils', path.join(__dirname, 'src/utils'))
      .set('_views', path.join(__dirname, 'src/views'))
  }
})
​

后台管理常用路由

admin 仪表盘 商品管理 商品列表 商品增加 商品修改 商品分类管理 商品分类管理

个人中心 设置

路由组件命名要求

1 路由组件 在views目录下遵循一下 目录结构 views ItemLists components index.vue 要求 每个路由组件 定义name属性 值 同 外面目录名(便于 vue-devtools审查项目)

vue项目 开发中 解决跨域问题

配置反向代理

devServer: {
    host: 'localhost',
    port: 3000,
    proxy: {
      // 反向代理
      // 所有请求必须 以 /api 才会触发 当前这个反向代理
      '/api': {
        // 代理的源
        target: 'https://api.it120.cc',
        // 是否切换源
        changeOrigin: true,
        // 路径重写
        pathRewrite: {
          '^/api': '/api'
          /*
          路径重写的值 取决于 实际 地址源 后有没有/api有则重写为 /api 没有重写为 ''
          */
        }
      }
      /*
        代理服务器 发的请求真实地址
          应该是 target+路径重写的值+请求path
          假设路径重写 为 /api
          axios.get('/api/a/b/c')
          真正请求地址https://api.it120.cc/api/a/b/c
​
          // 假设 /api重写为了 ''
           axios.get('/api/a/b/c')
           真正请求地址https://api.it120.cc/a/b/c
      */
    }
  }

axios二次封装

model单独管理

将 所有接口请求 单独封装成函数 提取到外部进行管理

mock接口

前后端分离 前后端同时开发的, 前端在开发时,没有真实接口,前端根据接口文档 来mock接口 要求: 1 每个接口 和请求的方式 path和后端真实接口保持一致 2 每个接口 返回的数据格式和真实接口保持一致

本地mock

利用mockjs mock 接口 原理:拦截ajax 请求,请求触发 但是没没有真实发出去(network审查不到)

  • 安装
npm i mockjs -D
  • mock api
Mock.mock('/api/getItemLists', 'post', {})
// 参数1  模拟接口path  参数2 请求的方式 参数3 返回数据
  • 产生随机数api
// 量词 修饰 数组(长度) 字符串(字符串长度) number (大小)
​
  'data|10': [] // 固定长度 10个数组
  'data|10-20': [] // 范围长度 10-20
  'str|2': '☆☆' // ☆☆☆☆
  'str|2-3': '☆☆' // ☆☆☆☆ -  ☆☆☆☆☆☆
  'num|0-100':0
  // 量词 修饰 id
  "id|+1": 1// 自增1// 随机数据 占位符 占位符需要在 字段值中 在引号中 前面要加@才能生效
Basic   boolean, natural, integer, float, character, string, range, date, time, datetime, now
Image   image, dataImage
Color   color
Text    paragraph, sentence, word, title, cparagraph, csentence, cword, ctitle
Name    first, last, name, cfirst, clast, cname
Web url, domain, email, ip, tld
Address area, region
Helper  capitalize, upper, lower, pick, shuffle
Miscellaneous   guid, id
​
​
// eg
Mock.mock('/api/getItemLists', 'post', {
  code: 200,
  msg: 'succss',
  // 量词 数组 长度 字符串 number(大小范围)
  'data|10-20': [
    {
      'id|+1': 1,
      name: '@ctitle',
      'price|0-1000': 0,
      'cateId|1-100': 1,
      desc: '@cparagraph',
      'sale|0-1000': 0,
      onSale: '@boolean',
      thumb: '@image("100x100","@color","牛夫人")',
      createAt: '@date'
    }
  ]
})
​

在线接口mock平台

可以真的发出请求, 产生随机数 利用 mockjs语法 easymock fastmock www.fastmock.site/#/ rap2.taobao.org

apiPost

webpack环境中 require引入和 import引入区别

import 必须在代码 顶部 引入,且 运行在代码编译之前(参与代码编译 最终执行的代码是编译之后,不是 你import代码)

require可以 编译后运行(举例 在一个代码中间,一个事件函数中 require)

require可以 编译后执行 (写在代码任意地方)

(在css中 给一个元素 加背景图片 图片放到本地 assets)

使用时: 大部分情况下使用import 使用require 1 在代码中引入 非 生产环境的包时 2 按需引入一些包

vuex状态持久化

当刷新浏览器 vuex状态 会丢失(初始化) 如何保持状态

原理: 将状态 在 缓存中备份(数据改变时),当刷新时 ,取缓存中的备份

  • 使用插件 vuex-persist 缺点: 不够灵活,无法使用 某个模块 中某些状态持久化,某些不持久化,无法使用某些状态 使用sessionStorage 持久化 另一些 localStorage持久化
npm i vuex-persist -S
​
import VuexPersistence from 'vuex-persist'const vuexLocal = new VuexPersistence({
  // 决定什么什么缓存备份
  storage: window.localStorage,
  // 决定哪些状态需要缓存
  reducer: state => {
    return {
      nav: state.nav
    }
  }
})
​
​
export default new Vuex.Store({
  strict: true,
  modules: {
    nav
  },
  plugins: [vuexLocal.plugin]
})
​
  • 手写 注意: 如果这个数据是多次都要修改,那么记住 只要变化就需要缓存备份一下

操作日期格式 js库

操作cookie cookie.js lodash.js

dayjs moment.js

element 表单验证规则

rules: {
  // 属性是字段名,代表对于该字段校验
  a: [ // 每一个 字段验证规则是数组
     // 必填的验证
    {
      required: true,
      // 验证不通过 错误提示文不太能
      message: 'xxxx字段必须填写',
      trigger: 'blur/change' // 校验触发时机
    },
    // 范围验证
    {
      min: 3,
      max: 5,
      message: '长度必须时3-5字符'
    },
    //  常用数据类型校验 值 数据类型 构造函数的 字符 首字母小写即可 array date 
    {
      type: 'date',
      message:'xxx'
    },
    // pattern 正则校验
    {
      pattern: /[a-zA-Z]\w{3,7}/,
      message: 'xxxfghj'
    },
    // 自定义校验
    {
      validator: (rule, value, cb) => {
        /* 
          rule 当前 字段所有校验规则
          value 校验时这个字段值 (用value判断)
          cb
            希望校验通过 直接调用cb() 不传参数
            希望校验不通过 cb(new Error('这就是错误提示文本'))
        */
        const reg = /dwefgrhtyjup/;
        if (reg.test(value)) {
          cb();
        }else{
          cb(new Error('必须按照你提供格式输入'))
        }
      }
    }
  ]
}

项目所有跟上传文件相关接口

比如后台管理中 新增各种数据 商品 商品分类 xxxx所有数据中很多都需要上传一个文件(图片)

接口处理逻辑: 接口一定会单独 有一个独立接口 叫 upload 调用这个接口上传 会立即返回上传成功后的 文件地址,增加商品接口拿到 上传的 图片地址后,一起提交给新增商品接口

vue富文本选择

使用原神富文本

使用原生富文本结合vue 封装插件

wangeditor

quill 基于vue封装 vue-quill-editor

后台管理常用第三方插件有

echarts 导入导出excel xlsx 库 实现导入导出 富文本编辑器 生成pdf 并打印

即时通信 客服 客服机器人 (环信 腾讯云 )

导出excel 基于 xlsx 二次封装vue组件 vue-json-excel

登录鉴权

路由鉴权 有些路由 没有登录 是不允许访问的 实现思路: 在 路由前置守卫中,判断是否有token 有则放行 没有跳转登录页重新登录

问题:
  只有路由鉴权 是不安全的因为 路由守卫中只判断token是否存在,无法判断token正确性

接口鉴权: 很多接口 需要 在请求头中携带token才能访问到数据的(后端 获取请求头中token用于校验token正确性 不正确 后端返回code 401 403),前端会判断 code码 如果是 401或者403会立即 弹出错误提醒,并清除 跟登录相关各种缓存,跳转到登录页重新进行登录

常见code 错误码 400 请求方式出错 401 登录状态过期 token(不正确) 403 没有登录(请求头中没有携带token) 404

二次封装axios 思路 登录鉴权思路 登录状态过期 前端怎么处理

登录

角色鉴权

在后台管理中 不同的用户 拥有不用角色的, 比如 超级管理员、普通管理员,普通员工登录(具体角色不同业务中角色名 不一样)

不同的角色进入后台管理主页 拥有权限不同

侧边导航 不一样(角色权限高 导航就多) 可以访问 页面路由是一样的 同一个页面,不同角色访问,这个中的某个功能 可以某个角色无法使用

实现思路? 2种 静态角色鉴权 动态角色鉴权

静态角色鉴权

1 所有侧边导航 数组 和 路由 数组 都在 前端定死,每个导航和路由添加一个属性 roles数组 (roles: ['a', 'b', 'c']) 存储的是 当前这个导航或者 路由 可以访问的所有的角色 有哪些, 当用户登录成功后,后端返回了 role字段 代表当前用户的角色

根据role字段 和 每个 导航 和 路由 roles字段进行判断(roles中是否包含role) 包含 这个导航就保存,当前路由就可以访问 否则 导航过滤掉,用户 直接跳转 某个不能访问的路由,做路由拦截 去 一个没有权限的页面