Vue框架

Vscode-自定义代码片段

  • 配置步骤

    1. Vscode 基本配置过程

      vueTemplate
      vueTemplate
    2. 搜索html.json,添加已经给出的具体模板即可【注意: 更改Vue 本地路径至匹配路径】

    3. Vscode 中键入vue,按下回车,即可生成对应代码模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    {
    "Html5-Vue": {
    "prefix": "vue",
    "body": [
    "<!DOCTYPE html>",
    "<html lang=\"zh-CN\">\n",
    "<head>",
    "\t<meta charset=\"UTF-8\">",
    "\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
    "\t<meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">",
    "\tVue<hanla></hanla>模板文件<hanla></hanla>",
    "\t<script src=\"\"><\/script>", // 更改本地 Vue 文件路径
    "</head>\n",
    "<body>",
    "\t<div id=\"app\">$1</div>\n",
    "\t<script>",
    "\t\tconst app = new Vue({",
    "\t\t\tel: '#app',",
    "\t\t\tdata: {message:'Hello Word'},",
    "\t\t\tmethods: {}",
    "\t\t});",
    "\t</script>",
    "</body>\n",
    "</html>"
    ],
    "description": "快速创建在html5编写的vue模板"
    }
    }

Vue-初体验

  • CDN 使用

    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- 将 src 地址的内容在浏览器访问并下载到本地自定义名称为 vue.js 文件 -->
    <script src="../vue.js"></script>

    <body>
    <div id="app">
    <h1>{{message}}</h1>
    </div>
    </body>

    // 编程范式: 声明式编程
    <script>
    const app = new Vue({
    el: "#app", // 挂载元素
    data: { // 定义数据
    message: "HelloVue"
    }
    });
    </script>

Vue-指令

  • 具体指令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <div id="app">
    <ul>
    <li v-for="movie in movies"> // 电影在电影列表中遍历
    {{movie}} // 【渲染/输出】电影名称
    </li>
    </ul>
    </div>

    <script>
    const app = new Vue({
    el: "#app",
    data: {
    message: "HelloVue",
    movies: ['VueJS', 'Javascript', 'Java', 'c#']
    }
    });
    </script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <div id="app">
    <h2>当前计数: {{counter}}</h2>
    <button v-on:click="increment"> + </button>
    <button v-on:click="subtraction"> - </button>
    </div>


    <script>
    const app = new Vue({
    el: "#app",
    data: {
    counter: 0
    },
    methods: {
    // 加号函数事件
    increment: function () {
    console.log("正在点击加号");
    // counter 需要通过 this.counter 形式
    this.counter++;
    },
    // 减号函数事件
    subtraction: function () {
    console.log("正在点击减号");
    this.counter--;
    }
    }
    });
    </script>

    1
    2
    3
    4
    <h1 v-once> {{ message }} </h1>

    只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <h1 v-html="url">  </h1>

    <script>
    const app = new Vue({
    el: '#app',
    data: {
    message: 'Hello Word',
    url: '百度一下'
    },
    });
    </script>

    <h1 v-text="message"> </h1>


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <style>
    [v-cloak] {
    display: none;
    }
    </style>

    // 解决闪动问题
    <div id="app" v-cloak></div>

    <script>
    setTimeout(() => {
    const app = new Vue({
    el: '#app',
    data: {
    message: 'Hello Word'
    },
    methods: {}
    });
    }, 1000);
    </script>
    • 基础语法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      作用:  动态绑定属性
      缩写: : 【语法糖写法】


      <img v-bind:src="imgUrl" alt=""> // 普通写法
      <img :src="imgUrl" alt=""> // 语法糖写法
      <a :href="url">百度一下</a>

      <script>
      const app = new Vue({
      el: '#app',
      data: {
      message: 'Hello Word',
      imgUrl: "https://gitee.com/wang_hong_bin/repo-bin/raw/master/IMG_0021(20210418-000849).PNG",
      url: "https://www.baidu.com"
      },
      methods: {}
      });
      </script>
    • v-bind 动态控制类样式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      方式一:
      语法: <h1 :class="{active:isActive,line:isLine}"> {{message}} </h1>

      data: {
      message: 'HelloWord',
      isActive: true,
      isLine: true
      },

      方式二:
      语法: <h1 :class="getClass()"> {{message}} </h1>

      data: {
      message: 'HelloWord',
      isActive: true,
      isLine: true
      },
      methods: {
      getClass: function () {
      return {
      active: this.isActive,
      line: this.isLine
      }
      }
      }

      方法三: 数组语法
      data(){
      return{
      arrClass:['xxA','xxB']
      }
      }
      <p :class="arrClass">Text</p>


    • v-bind 动态绑定style(对象语法)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 

      {{message}}



      // 'red' 属性值必须添加单引号,否则会被解析为变量
      <h1 :style="{backgroundColor:'red'}"> {{message}} </h1>

      // 变量写法
      <h1 :style="{backgroundColor: finaColor}"> {{message}} </h1>
      data: {
      message: 'Hello Word',
      finaColor: 'blue'
      },
      • 渲染

        动态绑定
        动态绑定
    • 基础使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <div id="app">
      <input type="text" v-model="message">
      <h1> {{message}}</h1>
      </div>

      // 原理
      <div id="app">
      <input type="text" :value="message" v-on:input="函数名 valueChange">
      <h1> {{message}}</h1>
      </div>

      methods:{
      valueChange(event){
      this.message = event.target.value;
      }
      }

      // 其他
      v-model 结合 radio

      • 渲染

        v-model
        v-model
    • 案例练习v-model

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <form action="#">
      <label for="agree">
      <input type="checkbox" name="" id="agree" v-model="isAgree"><a href="javascript:;">查看许可协议</a>
      </label>
      <h1>协议许可: {{isAgree}}</h1>
      <button :disabled="!isAgree">下一步</button>
      </form>

      data:{
      isAgree: false
      }

    • 多选框v-model

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <input type="checkbox" v-model="hobbies" value="javascript">javascript
      <input type="checkbox" v-model="hobbies" value="java">java
      <input type="checkbox" v-model="hobbies" value="c#">c#
      <input type="checkbox" v-model="hobbies" value="python">python

      您选择的兴趣是: {{hobbies}}



      data:{
      hobbies: []
      }
    • v-for 值绑定

      1
      2
      3
      4
      5
      <!-- 值绑定 -->
      <label v-for="item in originHobbies" :for="item">
      <input type="checkbox" :value="item" v-model="hobbies">{{item}}
      </label>

    • 修饰符

      1. lazy 修饰符
        • 默认情况下,v-model 默认是在input 事件中同步输入框的数据的,也就是说,一旦有数据发生对应的 data 中的数据就会自动发生改变
        • lazy 修饰符可以让数据在失去焦点或者回车时才会更新
      2. number 修饰符
        • 默认情况下,在输入框中无论我们输入的是字母还是数组,都会被当作字符串类型进行处理,但是我们希望处理的数字类型,那么最好直接将内容当作数字处理
        • number 修饰符可以让输入框中输入的内容自动转换为数字类型
      3. trim 修饰符
        • 如果输入的内容首尾有很多空格,通常我们希望将其去除
        • trim 修饰符可以过滤内容左右两边的空格

Vue-MVVM

  • MVVM

    mvvm
    mvvm
  • 基础选项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    el: 
    类型: string | HTMLElement
    作用: 决定之后Vue实例会管理哪一个DOM
    data:
    类型: Object | Function
    作用: Vue实例对应的数据对象
    methods:
    类型: {[key:string]:Function}
    作用: 定义属于Vue的一些方法,可以再其他地方调用,也可以在指令中使用

键盘事件

  • Vue 中常用键别名

    • 回车=>enter
    • 删除=>delete捕获删除和退格按键
    • 退出=>esc
    • 空格=>space
    • 换行=>tab必须配合keydown使用
    • =>up
    • =>down
    • =>left
    • =>right
  • Vue 中为提供别名的案件,可以使用按键原始的key 值去绑定,但注意要转为短横线命名

  • 系统修饰键:ctrl、alt、shift、meta

    • 配合keyup 使用: 按下修饰键的同时,在按下其他键,随后释放其他键,事件才被触发

    • 配合keydown 使用: 正常出发事件

    • 使用

      1
      @keyup.ctrl.f (input 框获取聚焦)
  • vue.config.keyCodes.自定义键名=键码,可以去定制按键别名

计算属性

  • 计算属性使用时不需要添加小括号

    • 计算属性
      • 定义: 要用的属性不存在,要通过已有的属性计算得来
      • 原理: 底层借助了Object.defineproperty 方法提供的getter 和 setter
      • get 函数执行时机
        • 初次读取会执行一次
        • 当依赖的数据发生改变时会被再次调用
      • 优势: 与methods 实现相比,内部有缓存机制(可以实现复用),效率更高,调式方便
      • 备注
        • 计算属性最终会出现在vm 上,直接读取使用即可
        • 如果计算属性要被修改,那必须写set 函数去响应修改,set 中药引用计算时依赖的数据发生改变
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <div id="app">
    {{fullName}}
    </div>

    <script>
    const app = new Vue({
    el: '#app',
    data: {
    message: 'Hello Word',
    firstName: "Vue",
    lastName: ".js"
    },
    methods: {},
    computed: {
    // get 时,简写
    fullName: function () {
    return this.firstName + this.lastName
    }
    }
    });
    </script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <h1>{{totalPrice}}</h1>

    books: [{
    id: 1101,
    name: 'Unix编程艺术',
    price: 100
    },
    {
    id: 1102,
    name: '代码大全',
    price: 100
    },
    {
    id: 1103,
    name: '深入理解计算机原理',
    price: 100
    },
    {
    id: 1104,
    name: '现代操作系统',
    price: 100
    }
    ]

    // 计算属性
    computed: {
    totalPrice: function () {
    // 定义变量存储书籍价格
    let price = 0;
    // 循环遍历对象
    for (let book = 0; book < this.books.length; book++) {
    // 书籍价格累加
    price += this.books[book].price
    }
    return price;
    }
    }


    // Es6循环高级用法
    for(let i in this.books){

    }

    for(let i of this.books){
    console.log(i); // i == this.books[book]
    }

    计算属性与methods 使用区别
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <!DOCTYPE html>
    <html lang="zh-CN">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>计算属性和methods的对比</title>
    <script src="../vue.js"></script>
    </head>

    <body>
    <div id="app">
    <!-- methods -->
    <h1>{{getFullName()}}</h1>
    <h1>{{getFullName()}}</h1>
    <h1>{{getFullName()}}</h1>
    <h1>{{getFullName()}}</h1>

    <!-- computed -->
    <h2> {{fullName}} </h2>
    <h2> {{fullName}} </h2>
    <h2> {{fullName}} </h2>
    <h2> {{fullName}} </h2>

    </div>

    <script>
    const app = new Vue({
    el: '#app',
    data: {
    message: 'Hello Word',
    firstName: "Vue",
    lastName: ".js"
    },
    methods: {
    getFullName: function () {
    console.log("methods 输出··········");
    }
    },
    computed: {
    fullName: function () {
    console.log("计算属性输出···········");
    }
    }
    });
    </script>
    </body>

    </html>
    • 属性分析

      监视属性watch:

      1. 当监视属性变化时,回调函数自动调用,进行相关操作

      2. 监视的属性必须存在,才能进行监视

      3. 监视的两种写法

        • .new Vue时传入watch配置

        • 通过vm.$watch 监视

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          const vm = new Vue({
          data(){
          return{
          isHot: true
          }
          }
          });
          vm.$watch('isHot',{
          同上
          })
    • 基础使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      <template>
      <div>
      <el-button @click="changeWeatherInfo" type="success">获取天气信息</el-button>
      <h1>今天天气是: {{ info }} </h1>

      </div>
      </template>

      <script>
      export default {
      name: "Orders",
      data() {
      return {
      input: '',
      isHot: true
      }
      },
      methods: {
      changeWeatherInfo() {
      this.isHot = !this.isHot;
      },

      },
      computed: {
      info() {
      return this.isHot ? '凉爽' : '炎热';
      }
      },
      // 监视属性
      watch: {
      isHot: {
      immediate: true, // 初始化时调用一下
      // export type WatchHandler<T> = string | ((val: T, oldVal: T) => void);
      handler(newValue, oldValue) {
      console.log('获取的新值: ' + newValue);
      console.log('获取的旧值: ' + oldValue);
      }
      }
      }

      }
      </script>

      <style scoped>

      </style>
    • 渲染

      watch 基础使用
      watch基础使用
    • 深度监视

      • 深度监视:

        1. Vue 中的watch 默认不监视对象内部值的改变(一层)
        2. 配置deep:true 可以检测对象内部值改变(多层)
      • 备注:

        1. Vue 自身可以检测对象内部值的改变,Vue 提供的watch 默认不可以
        2. 使用watch 时根据数据的具体结构,决定是否采用深度监视
    • 简写

      1
      2
      3
      4
      5
      6
      7
      检测的属性: isHot
      watch:{
      // 代替 handler 非深度监视
      isHot(newValue,oldValue){

      }
      }

V-on修饰符

  • 修饰符

    • 基础使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <div>
      // element-ui 不传参
      <el-button @click="getInnerHTML" type="success">event</el-button>
      </div>


      <div>
      // 传递参数
      <el-button @click="getValue(66,$event)" type="success">event</el-button>
      </div>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      getInnerHTML(event) {
      // 获取按钮的文本信息
      console.log(event.target.innerHTML)
      }


      getInnerHTML(number,event) {
      console.log(number);
      console.log(event)
      }

    • 渲染

      事件参数
      事件参数
    1. stop

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      ------------------------------
      【@click.stop="函数"】 阻止冒泡
      ------------------------------

      <div id="app" @click="divClick">
      <button @click.stop="btnClick">按钮</button>
      </div>

      <script>
      const app = new Vue({
      el: '#app',
      data: {
      message: 'Hello Word'
      },
      methods: {
      divClick: function () {
      console.log('divClick·······');
      },
      btnClick: function () {
      console.log('btnClick··············');
      }

      }
      });
      </script>
    2. prevent 阻止默认事件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <form action="https://www.baidu.com">
      <input type="submit" value="提交按钮" @click.prevent="submitClick">
      </form>

      methods:{
      submitClick: function () {
      console.log('打印不提交····');
      }
      }

      --------------------------------------------------------

      noHref: function () {
      console.log('不会发生跳转的超链接··········');
      }
      <a href="https://www.baidu.com" @click.prevent="noHref">百度一下,这里不会发生跳转

    3. 监听键帽的点击

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <!-- 监听键帽点击 -->
      <input type="text" @keyup.enter="keyUp">

      methods:{
      keyUp: function(){
      console.log("enter 键触发监听事件");
      }
      }

    4. 组件点击监听

      1
      <cpn @click.native="cpnClick"> </cpn>
    5. @click.once

      1
      2
      // 事件只触发一次
      <button @click.once="onceClick">点击

条件渲染

  • 条件渲染

    • 条件渲染

      1. v-if
      • v-if='表达式'
        • v-else-if='表达式'
        • v-else
        • 适用于: 切换频率较低的场景
        • 特点: 不展示DOM元素直接被移除
      1. v-show
        1. 写法: v-show='表达式'
        2. 适用于: 切换频率较高的场景
        3. 特点: 不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
      2. 使用v-if 时,元素可能无法获取到,而是用v-show 一定可以获取到
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <!DOCTYPE html>
    <html lang="zh-CN">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue模板文件</title>
    <script src="../../vue.js"></script>
    </head>

    <body>
    <div id="app">
    <div v-if="isUser">
    <label for="user">
    账号登录 <input type="text" id="user">
    </label>
    </div>
    <div v-else>
    <label for="email">
    邮箱登录 <input type="text">
    </label>
    </div>
    <button @click="isUser=!isUser">切换登录类型</button>
    </div>

    <script>
    const app = new Vue({
    el: '#app',
    data: {
    message: 'Hello Word',
    isUser: true
    },
    methods: {}
    });
    </script>
    </body>

    </html>
    • 问题描述

      1. 如果我们在没有输入内容的情况下,切换了类型,我么会发现文字依然显示之前输入的内容
      2. 由于已经发生切换,我们并没有再另外一个input 元素中输入
    • 问题解答

      这是由于Vue 在进行 DOM 渲染时,处于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素

    • 解决方案

      • 如果我们不希望 Vue出现类似重复利用的问题,可以给对应的 input 添加key
        • 保证key 的唯一性

列表渲染

  • 列表渲染

    • 基础使用

      1
      2
      3
      4
      5
      <ul>
      <li v-for="user in userInfoList" :key="user.id">
      {{ user }} ----- {{ user.id }} + {{ user.userName }}
      </li>
      </ul>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      data(){
      return {
      userInfoList: [{
      id: '1001',
      userName: 'userA'
      }, {
      id: '1002',
      userName: 'userB'
      }, {
      id: '1003',
      userName: 'userC'
      }]
      }
      }
    • 渲染

      列表渲染
      列表渲染
    • index 作为key

      index 作为key
      index作为key
    • 面试题

      1. 虚拟DOM key 的作用

        key 是虚拟DOM 对象的标识,当数据发生变化时,Vue 会根据[新数据]生成[新的虚拟DOM],随后Vue 进行[新虚拟DOM][旧虚拟DOM]的差异比较,比较规则如下:

      2. 对比规则

        • 旧虚拟DOM 中找到了与新虚拟DOM 相同的key

          1. 若虚拟DOM 中内容没变,直接使用之前的真是DOM
          2. 若虚拟DOM 中内容变了,则生成新的真实DOM,随后替换页面中之前的真实DOM
        • 旧虚拟DOM 中未找到与新虚拟DOM 相同的key=> 创建新的真实DOM,随后渲染到页面

      3. index 作为key 可能会引发的问题

        若对数据进行: 逆序添加、逆序删除等破化顺序操作:会产生没有必要的真实DOM 更新=>界面效果没问题,但效率低

      4. 如果结构中还包含输入类的DOM: 会产生错误DOM 更新=>界面有问题

      5. 开发中如何选择key

        1. 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
        2. 如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index 作为key 是没有问题的

过滤器

  • 过滤器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <style>
    table {
    border: 1px solid #e9e9e9;
    border-collapse: collapse;
    border-spacing: 0;
    }

    th,
    td {
    padding: 8px 16px;
    border: 1px solid #e9e9e9;
    text-align: left;
    }

    th {
    background-color: #f7f7f7;
    color: #5c6b77;
    font-weight: 600;
    }
    </style>
    • 布局

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      <table>
      <thead>
      <tr>
      <th></th>
      <th>书籍名称</th>
      <th>出版日期</th>
      <th>价格</th>
      <th>购买数量</th>
      <th>操作</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="(item,index) in books">
      <td> {{item.id }}</td>
      <td> {{item.name }}</td>
      <td> {{item.date }}</td>
      <td> {{item.price|showPrice }}</td>
      <td>
      <button @click="decrement(index)" :disabled="item.count<=1"> - </button>
      {{ item.count }}
      <button @click="increment(index)"> + </button>
      </td>
      <td> <a href="javascript:;" @click="removeH(index)">删除</a> </td>
      </tr>
      </tbody>
      </table>
    • 测试数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      books: [{
      id: 1,
      name: "<<算法与导论>>",
      date: "2006-3",
      price: 85.00,
      count: 1
      }, {
      id: 2,
      name: "<<算法与导论>>",
      date: "2006-3",
      price: 85.00,
      count: 1
      }, {
      id: 3,
      name: "<<算法与导论>>",
      date: "2006-3",
      price: 85.00,
      count: 1
      }, {
      id: 4,
      name: "<<算法与导论>>",
      date: "2006-3",
      price: 85.00,
      count: 1
      }]
      },
    • 详细使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      filters: {
      showPrice(price) {
      return '¥' + price.toFixed(2);
      }
      }

      事件监听:

      // 添加按钮事件
      increment(index) {
      console.log('添加');
      this.books[index].count++;
      },
      // 减少按钮事件
      decrement(index) {
      console.log('减少');
      this.books[index].count--;
      },
      // 删除按钮事件
      removeH(index){
      this.books.splice(index,1);
      }


    • 数据渲染

      书籍名称 出版日期 价格 购买数量 操作
      1 <<算法与导论>> 2006-3 ¥85.00 1 删除
      2 <<算法与导论>> 2006-3 ¥85.00 - 1 + 删除
      3 <<算法与导论>> 2006-3 ¥85.00 - 1 + 删除
      4 <<算法与导论>> 2006-3 ¥85.00 - 1 + 删除
    • 详细使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      computed: {
      totalPrice() {
      --------------------------------------------------------------
      // 写法一:
      let totalPrice = 0;


      for (let i = 0; i < this.books.length; i++) {
      totalPrice += this.books[i].price * this.books[i].count;
      }
      --------------------------------------------------------------
      // 写法二:
      for (let i in this.books) {
      console.log(i);// 索引
      totalPrice += this.books[i].price * this.books[i].count;
      }
      --------------------------------------------------------------
      // 写法三:
      for (let item of this.books) {
      console.log(item); // 获取到书籍
      totalPrice += item.price * item.count;
      }

      --------------------------------------------------------------
      // 写法四[高阶函数]:
      return this.books.reduce(function(preValue,book){
      return preValue + book.price*book.count;
      },0)
      }
      --------------------------------------------------------------
      return totalPrice // 普通方法都要返回 totalPrice,
      }

    • watch 实现过滤器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      <template>
      <div>
      <el-input style="width: 70%" v-model="keyWord"></el-input>

      <ul>
      <li v-for="user in filterUserInfo" :key="user.id">{{ user.userName }}</li>
      </ul>
      </div>
      </template>

      <script>
      import Vue from "vue";

      export default {
      name: "Orders",
      data() {
      return {
      keyWord: '',
      userInfo: [
      {id: '1001', userName: '马冬梅'},
      {id: '1002', userName: '周冬雨'},
      {id: '1003', userName: '周杰伦'},
      {id: '1004', userName: '温兆伦'}
      ],
      filterUserInfo: []
      }
      },
      // watch 实现过滤器 不推荐
      watch: {
      keyWorld: {
      immediate: true,
      handler(val) {
      this.filterUserInfo = this.userInfo.filter((info) => {
      return info.userName.indexOf(val) !== -1
      })
      }
      }
      }
      }
      </script>

      watch-filter
      watch-filter
    • 计算属性实现过滤器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      <template>
      <div>
      <el-input style="width: 70%" v-model="keyWord"></el-input>

      <ul>
      <li v-for="user in filterUserInfo" :key="user.id">{{ user.userName }}</li>
      </ul>
      </div>
      </template>

      <script>
      import Vue from "vue";

      export default {
      name: "Orders",
      data() {
      return {
      keyWord: '',
      userInfo: [
      {id: '1001', userName: '马冬梅'},
      {id: '1002', userName: '周冬雨'},
      {id: '1003', userName: '周杰伦'},
      {id: '1004', userName: '温兆伦'}
      ]
      }
      },
      computed: {
      filterUserInfo() {
      return this.userInfo.filter((info) => {
      return info.userName.indexOf(this.keyWord) !== -1
      })
      }
      }

      }
      </script>

    • 添加排序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      <template>
      <div>
      <div class="flContainer hidden">
      <el-input v-model="keyWord"></el-input>

      <el-button @click="sortType=2" type="primary">年龄升序</el-button>
      <el-button @click="sortType=1" type="success">年龄降序</el-button>
      <el-button @click="sortType=0" type="danger">原始顺序</el-button>
      </div>

      <ul>
      <li v-for="user in filterUserInfo" :key="user.id">{{ user.userName }} {{ user.age }}</li>
      </ul>
      </div>
      </template>

      <script>
      import Vue from "vue";

      export default {
      name: "Orders",
      data() {
      return {
      // 搜索关键字
      keyWord: '',
      // 排序标识 0 原始顺序,1降序,2升序
      sortType: 0,
      userInfo: [
      {id: '1001', userName: '马冬梅', age: 14},
      {id: '1002', userName: '周冬雨', age: 23},
      {id: '1003', userName: '周杰伦', age: 18},
      {id: '1004', userName: '温兆伦', age: 10}
      ]
      }
      },
      computed: {
      filterUserInfo() {
      const array = this.userInfo.filter((info) => {
      return info.userName.indexOf(this.keyWord) !== -1
      })
      // 判断一下 sortType 是否需要排序
      if (this.sortType) {
      array.sort((obj1, obj2) => {
      console.log(obj1, obj2)
      return this.sortType === 1 ? obj2.age - obj1.age : obj1.age - obj2.age;
      })
      }
      return array
      }
      }

      }
      </script>

      <style scoped>
      .flContainer {
      width: 100%;
      }

      .el-input {
      float: left;
      width: 50%;
      margin-right: 20px;
      }

      .el-button {
      float: left;
      margin: auto 5px;
      }

      .hidden {
      overflow: hidden;
      }
      </style>
      • 原始顺序分析

        原始顺序分析
        原始顺序分析
      • :key 的验证

        :key 的验证,破坏顺序,未发生乱序
        :key的验证
    • 分析

      分析
      再次分析
    • 失败

      1
      2
      // 原则上是改变了,但是 vuedev-tool 不认 
      this.userInfo[0] = {id: '1001', userName: '李四', age: 28};
    • 原理分析

      1. Vue 会监视data 中所有层次的数据

      2. 如何检测对象中的数据

        • 通过setter 实现监视,且要在new Vue 时就传入要监测的数据

        • 对象中后追加的属性,Vue 默认不做响应式处理

        • 如需给后添加的属性做响应式,请使用如下API

          Vue.set(target,propertyName/index,value) | vm.$set(target,propertyName/index,value)

      3. 如何检测数组中的数据

        • 通过包裹数组更新元素的方法实现,本质就是做了两件事
          1. 调用原生对应的方法对数组进行更新
          2. 重新解析模板,进而更新页面
      4. Vue 修改数组中的某个元素一定用如下方法

        1. 使用这个API:push、pop、shift、unshift、splice、sort、reverse
        2. Vue.set() 或 vm.$set
      5. 特别: vue.set() 和 vm.$set() 不能给vm 或 vm 的根数据对象添加属性

    • 数据劫持

      1
      student: [{ id: 1, name: 'A' }]
      拥有setter getter
      set和get先行处理

数组-高阶函数

  • filter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    filter中回调函数有一个要求: 必须返回一个 boolean 值
    true: 当返回 true 时,函数内部会自动将这次回调的【n】加入到新的数组中
    false: 当返回 false时,函数内部会过滤掉这次的 【n】

    // 1. filter 高阶函数
    const nums = [100, 110, 89, 43, 299];
    let filterNums = nums.filter((n) => {
    return n > 100
    });
    console.log(filterNums);

  • map

    1
    2
    3
    4
    5
    // 2. map 高阶函数
    let mapNums = nums.map(function (n) {
    return n * 2
    })
    console.log("乘积: ", mapNums);
  • reduce

    1
    2
    3
    4
    5
    6
    // reduce 作用对数组中所有内容进行汇总
    var apps = [1, 2, 3, 4]
    let total = apps.reduce(function (preValue, n) {
    return preValue + n
    }, 0);
    console.log(total);
  • 高阶函数的汇总使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 定义一个数组过滤掉 小于 100,对过滤后的数组*2,再对数组求和

    // 函数式编程
    let newArrays = [900, 120, 89, 54, 210]
    let newA = newArrays.filter((n) => {
    return n < 100 // 过滤掉小于 100 的数
    }).map((n) => {
    return n * 2 // 对新数组内容*2
    }).reduce((preValue, n) => {
    return preValue + n
    }, 0)
    console.log(newA);

    // 简化操作

    {
    let newArrays = [900, 120, 89, 54, 210];
    let newA = newArrays.filter(n => n < 100).map(n => n * 2).reduce((preValue, n) => preValue + n)
    console.log(newA);
    }

生命周期

  • 分析

    生命周期阶段分析
    生命周期
  • 总结

    • 创建之前指的是数据监测、数据代理

组件化

  • 详细使用

    • 传统方式

      传统方式编写应用
      传统方式编写应用
    • 组件化

      组件化
      组件化
    • 定义

      组件化的定义
      组件化的定义
    • 流程

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      <!DOCTYPE html>
      <html lang="zh-CN">

      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>初始组件化</title>
      <script src="../../../vue.js"></script>
      </head>

      <body>
      <div id="app">
      <!-- 组件化开发 -->
      <my-cpn></my-cpn>
      <my-cpn></my-cpn>
      <my-cpn></my-cpn>
      <my-cpn></my-cpn>
      </div>

      <script>
      // 1. 创建组件构造器对象
      const cpnContructor = Vue.extend({
      template: `
      <div>

      哈哈哈1111


      哈哈哈1111


      </div>
      `
      });
      // 2. 注册组件
      // Vue.component('注册组件的标签名','组件构造器');
      Vue.component('my-cpn', cpnContructor);
      // 3. 使用组件
      const app = new Vue({
      el: '#app',
      data: {
      message: 'Hello Word'
      },
      methods: {}
      });
      </script>
      </body>

      </html>
    • 渲染数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      哈哈哈1111
      哈哈哈1111

      哈哈哈1111
      哈哈哈1111

      哈哈哈1111
      哈哈哈1111

      哈哈哈1111
      哈哈哈1111

局部组件

  • 局部组件

    • 疑问

      怎么注册的组件才是局部组件?

    • 局部组件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      <!DOCTYPE html>
      <html lang="zh-CN">

      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>局部组件</title>
      <script src="../../vue.js"></script>
      </head>

      <body>
      <div id="app">
      <cpn></cpn>
      </div>

      <script>
      // 创建组件构造器
      const cpnC = Vue.extend({
      template: `

      组件构造器

      `

      });
      const app = new Vue({
      el: '#app',
      data: {
      message: 'Hello Word'
      },
      methods: {},
      components: {
      // 创建组件标签名: 组件构造器
      cpn: cpnC
      }
      });
      </script>
      </body>

      </html>
    • 总结

      结论: 在Vue 实例中创建的组件为局部组件

父子组件

  • 父子组件

    注意: Unknown custom element: <cpn1>

    错误解决方案: 组件注册要么在全局,只有注册了的组件才可用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
     
    <!DOCTYPE html>
    <html lang="zh-CN">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>父子组件</title>
    <script src="../../../vue.js"></script>
    </head>

    <body>
    <div id="app">
    <cpn2></cpn2>
    <cpn1></cpn1>
    </div>

    <script>
    const cpn1 = Vue.extend({
    template: `
    <div>
    <h1> 哈哈哈 cpn1 </h1>
    </div>
    `
    });
    const cpn2 = Vue.extend({
    template: `
    <div>
    <cpn1></cpn1>
    <h1> 哈哈哈 cpn2 </h1>
    <cpn1></cpn1>
    </div>
    `,
    components: {
    cpn1
    }
    });

    const app = new Vue({
    el: '#app',
    data: {
    message: 'Hello Word'
    },
    methods: {},
    components: {
    cpn2
    }
    });
    </script>
    </body>

    </html>
    • 关于VueComponent

      1. xx 组件本质是一个名为VueComponent 的构造函数,且不是编程人员定义的,Vue.extend 生成的

        组件的本质
        组件的本质
      2. 我们只需要写<school/>,Vue 解析时会帮我们创建school 组件的实例对象,Vue 帮我们执行new VueComponent(options)

        验证调用次数
        验证调用次数
      3. 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent

        全新VueComponent
        全新VueComponent
      4. 关于this 指向

        • 组件配置中data函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的this 均是VueComponent 实例对象(vc)

        • new Vue() 配置中:data函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的this 均是Vue 实例对象(vm)

        • this 指向

          this 指向
          this指向
      5. VueComponent 的实例对象,以后简称vc,Vue 的实例对象简称vm

    • 严格来说vm!=vc

      • vm 可以有el,而vc 不能指定

      • vm 允许data 为函数或对象,vc 不行

      • vc 有的功能vm 都有

      • 内置关系

        内置关系
        内置关系
      • 一个重要的内置关系VueComponent.prototype.__proto__===Vue.prototype

      • 为什么要有这个关系: 让组件实例(vc)可以访问到Vue 原型上的属性,方法

组件模板抽离

  • 组件模板抽离

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // type="text/x-template"  标识:  id="cpnT"
    <script type="text/x-template" id="cpnT">
    <div>
    <h1> 模板抽离 </h1>
    </div>
    </script>

    // 创建组件构造器
    // 组件化: 语法糖
    Vue.component('cpn1', {
    template: '#cpnT'
    })

    // 使用
    <cpn1></cpn1>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <template id="cpn2">
    <h2> template 模板</h2>
    </template>


    Vue.component('cpn2', {
    template: '#cpn2'
    })

    // 使用
    <cpn2></cpn2>

组件可以访问Vue实例数据吗

  • 组件数据

    答案: 不可以

    • 组件是一个单独功能模块的封装
      • 这个模块有属于自己的 HTML 模板,也应该由于属于自己的数据 data
    • 访问测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <div id="app">
      <cpn></cpn>
      </div>

      <template id="cpnT">
      <div>
      <h1> {{title}} </h1>
      </div>
      </template>
      <script>
      //
      Vue.component('cpn', {
      template: '#cpnT',
      data() {
      return {
      title: "Title"
      }
      }
      });
    • 为什么组件中的 data 必须是函数

      Vue 组件中data 值不能为对象,因为对象是引用类型,组件可能会被多个实例同时引用。如果data 值为对象,将导致多个实例共享一个对象,其中一个组件改变data 属性值,其它实例也会受到影响

    • 使用检测

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      <body>
      <div id="app">
      <cpn></cpn>

      <cpn></cpn>

      <cpn></cpn>

      <cpn></cpn>
      </div>
      <template id="cpnT">
      <div>
      <h1>计数器: {{count}}</h1>
      <button @click="increment"> + </button>
      <button @click="descment"> - </button>
      </div>
      </template>
      <script>
      // 注册组件
      Vue.component('cpn', {
      template: '#cpnT',
      data() {
      return {
      count: 1
      }
      },
      methods: {
      increment() {
      this.count++
      },
      descment() {
      this.count--
      }
      }
      });
      const app = new Vue({
      el: '#app',
      data: {
      message: 'Hello Word'
      },
      methods: {}
      });
      </script>
      </body>

      • data 类型使用对比

        data 为函数时 data 为对象时
        data是函数 data为对象

单文件组件

  • 命名

    • 全小写 | 短杠my-student.vue
    • 首字母大写MyStudent.vue
  • 单文件组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <div>
    <!-- 组件结构 -->
    </div>
    </template>

    <script>
    // 组件交互相关的代码(数据、方法等等)
    </script>

    <style scoped>
    /* 组件的样式 */
    </style>
  • 使用

    1
    2
    3
    4
    5
    6
    <scrip>
    import xxx from 'path'
    components:{
    xxx
    }
    </script>

组件通信

  • 组件通信

    • 原理图

      组件通信的两种方式
      组件通信的两种方式
    • props 的值有两种方式

      1. 字符串数组,数组中的字符串就是传递时的名称
      2. 对象,对象可以摄制为传递时的类型,也可以设置为默认值等

      注意: 抽离 template 时需要一个最外层div 包裹,否则会产生如下错误:

      1
      2
      3
      4
      5
      6
      vue.js:634 [Vue warn]: Error compiling template:

      Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-

      else-if to chain them instead.

    • 父子组件通信

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      <!DOCTYPE html>
      <html lang="zh-CN">

      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>组件通信-父组件向子组件传递数据</title>
      <script src="../../../vue.js"></script>
      </head>

      <body>
      <div id="app">
      <h1>父组件</h1>
      <cpn :ccourse="course"> </cpn>
      </div>
      <template id="cpnT">
      <div>
      <h1>子组件</h1>
      <h2> {{ccourse}}</h2>
      </div>
      </template>

      <script>
      // 建立新的子组件
      const cpn = {
      template: '#cpnT',
      // 父传子: props
      props: ['ccourse']
      }
      // app 作为父组件
      const app = new Vue({
      el: '#app',
      data: {
      message: 'Hello Word',
      // 父组件数据
      course: ['JavaScript', 'Java', 'Python']
      },
      methods: {},
      components: {
      cpn
      }
      });
      </script>
      </body>

      </html>
    • poops

      当类型是对象或者数组时,默认值必须是一个函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      ccourse:{
      type: Array, // 可以指定类型
      default: [],
      require: true [布尔值: 必填项]
      }

      // 改为如下写法

      ccourse:{
      type: Array, // 可以指定类型
      default(){
      return []
      },
      require: true [布尔值: 必填项]
      }

      // 允许自定义验证 【coderwhy p58】

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      props:{
      // 类型限制
      ccourse: Array
      // 提供默认值
      ccourse:{
      type: String // 可以指定类型
      default: '传递默认值', // 可以传递默认值 【没有进行数据绑定时 --> v-bind, 又进行使用时 --> {{course]}},将获取默认值 】
      require: true [布尔值: 必填项]
      }
      }

      // 支持的所有数据类型
      String
      Number
      Boolean
      Array
      Object
      Date
      Function
      Symbol

    • 驼峰标识问题

      v-bind 中暂不支持驼峰标识, 需要将驼峰标识转换为:

      HelloWorld -- 转换为 --> :hello-world 绑定

      注意: 在子组件需要使用父组件的数据时,那么应想到props,子组件使用父组件数据时是在模板配置时

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      // 子组件的模板
      <template id="cpnT">
      <div>
      <h2> 子组件 </h2>
      <h1> 子组件使用: {{cinfo}}</h1>
      </div>
      </template>

      // 绑定 cinfo 子组件使用时定义的变量名称 info: 父组件的变量
      // 通过 v-bind
      <cpn :cinfo="info"></cpn>


      // 分析 cinfo 和 info
      const cpn = {
      template: "#cpnT",
      props: {
      // cinfo 子组件定义的变量名
      cinfo: {
      type: Object,
      default(){
      return {}
      }
      }
      }
      };
      const app = new Vue({
      el: '#app',
      data: {
      message: 'Hello Word',
      // info 父组件的变量名
      info: {
      name: "张三",
      age: 18,
      sex: "男"
      }
      },


子传父

  • 数据传递-子组件传向父组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    <!DOCTYPE html>
    <html lang="zh-CN">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <hanla></hanla>组件通信-子传父<hanla></hanla>(自定义事件)
    <script src="../../../vue.js"></script>
    </head>

    <body>
    <div id="app">

    父组件


    <cpn @itemclick="cpnclick"> </cpn>
    </div>
    <template id="cpnT">
    <div>
    <button v-for="item in categories" @click="btnclick(item)">{{item.name}}</button>
    </div>
    </template>

    <script>
    // 子组件
    const cpn = {
    template: "#cpnT", // 绑定模板
    // 自定义事件
    data() {
    return {
    categories: [{
    id: 'aaa',
    name: "热门推荐"
    },
    {
    id: 'bbb',
    name: "手机数码"
    },
    {
    id: 'ccc',
    name: "家用家电"
    },
    {
    id: 'ddd',
    name: "电脑办公"
    }
    ]
    }
    },
    methods: {
    btnclick(item) {
    // 自定义事件 发射
    // this.$emit("事件名称","参数");
    this.$emit('itemclick', item);
    console.log("子组件: ", item);
    }
    }
    };
    const app = new Vue({
    el: '#app',
    data: {
    message: 'Hello Word'
    },
    methods: {
    cpnclick(item) {
    console.log("父组件: ", item);
    }
    },
    // 注册组件
    components: {
    cpn
    }
    });
    </script>
    </body>

    </html>
    • 分析

      btnclick (子组件事件)

      <cpn @itemclick=”cpnclick”> itemclick 事件为子组件发射的自定义事件名称 【this.$emit(“自定义事件名”,“参数”)

      cpnclick(父组件所监听的子组件的处理事件名称)

    • 自定义事件流程

      1. 在子组件中,通过 $emit() 来触发

      2. 在父组件中,通过 v-on 来监听子组件事件

父子组件的访问方式

  • 父子组件的访问方式

    • 父组件访问子组件: 使用 $children$refs

    • 实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      <!DOCTYPE html>
      <html lang="zh-CN">

      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>组件通信-ref的使用</title>
      <script src="../../../vue.js"></script>
      </head>

      <body>
      <div id="app">
      <cpn ref="refName"></cpn>
      <button @click="btnClick"> 按钮 </button>
      </div>
      <template id="cpnT">
      <div>
      <h1> 我是子组件 </h1>
      </div>
      </template>
      <script>
      const app = new Vue({
      el: '#app',
      data: {
      message: 'Hello Word'
      },
      methods: {
      btnClick() {
      console.log(this.$refs.refName.cpnValue);
      }
      },
      components: {
      cpn: {
      template: "#cpnT",
      data() {
      return {
      cpnValue: "子组件Value将被获取"
      }
      },
      methods: {

      }
      }
      }
      });
      </script>
      </body>

      </html>
    • 子组件访问父组件: 使用$parent

组件化编码流程(通用)

  • 组件化

    1. 实现静态组件: 抽取组件,使用组件实现静态页面效果
    2. 展示动态数据
      • 数据的类型,名称是什么?
      • 数据保存在那个组件中
    3. 交互: 从绑定事件监听开始

Nano ID

  • 是什么

    一个小巧、安全、URL 友好、唯一的 JavaScript 字符串ID 生成器。

  • 安装

    1
    npm install --save nanoid
  • 使用

    1
    import { nanoid } from 'nanoid'

TodoList案例

  • 案例

    • 效果渲染

      todolist
      todolist
    • 子组件与父组件数据传递

      子组件与父组件数据传递
      子组件与父组件数据传递
    • 在未使用其他方式之前,数据可以通过逐层传递实现

      在未使用其他方式之前,数据可以通过逐层传递实现
      在未使用其他方式之前,数据可以通过逐层传递实现
    • TodoList 案例总结

      • 组件化编码流程
        • 拆分静态组件: 组件要按照功能点拆分,命名不要与html 元素冲突
        • 实现动态组件: 考虑好数据要存放的位置,数据是一个组件在用,还是一些组件再用
          • 一个组件在用: 放在组件自身即可
          • 一些组件再用: 放在他们共同的父组件上(状态提升)
        • 实现交互: 从绑定事件开始
      • props 适用于
        • 父组件 ==> 子组件 通信
        • 子组件===> 父组件 通信(要求父先给子一个函数)
      • 使用v-model 时要切记: v-model 绑定的值不能是props 传过来的值,因为props 是不可以修改的
      • props 传过来的若是对象类型的值,修改对象中的属性时Vue 不会报错,但不推荐这样做

组件自定义事件

  • 组件自定义事件

    • 回顾(子组件向父组件传递数据,通过函数)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      // App:
      <template>
      <div>
      <h1>App: {{ receiveSchoolName }}</h1>
      <school :receiveSchoolSendName="receiveSchoolSendName"></school>
      </div>
      </template>

      // School
      <template>
      <div class="school">
      <h1>School => 学校名称: {{ schoolName }}</h1>
      <h1>School => 学校地址: {{ schoolAddress }}</h1>
      <el-button type="success" @click="sendSchoolName">上传名称给App</el-button>
      <hr>
      <student></student>
      </div>
      </template>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      // App:
      data(){
      return{
      receiveSchoolName: ''
      }
      },
      methods:{
      // 给 App 通过函数传递数据
      receiveSchoolSendName(name) {
      console.log('App收到了学校名: ', name)
      this.receiveSchoolName = name
      }
      }

      // School
      <script>
      import Student from "@/views/Student"

      export default {
      name: "Test",
      props: ['receiveSchoolSendName'],
      data() {
      return {
      schoolName: 'coder-itl',
      schoolAddress: 'BJ'
      }
      },
      components: {
      Student
      },
      methods: {
      sendSchoolName() {
      this.receiveSchoolSendName(this.schoolName)
      }
      }
      }
      </script>
    • 渲染结果

      子传父(函数实现)
      子传父(函数实现)
    • 组件自定义事件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      <template>
      <div class="student">
      <h1>Studnet=> 学生姓名: {{ stuName }}</h1>
      <h1>Studnet=> 学生年龄: {{ stuAge }}</h1>
      <el-button type="success" @click="sendStudentName">上传学生名称给 App</el-button>
      </div>
      </template>

      <script>
      export default {
      name: "Student",
      data() {
      return {
      stuName: '里斯',
      stuAge: 18
      }
      },
      methods: {
      sendStudentName() {
      // 触发 Student 组件实例身上的 coder-itl 事件
      this.$emit('coderitl', this.stuName)
      }
      },
      mounted() {

      }
      }
      </script>

      <style scoped>
      .student {
      background-color: skyblue;
      }

      .el-button {
      margin: 10px;
      }
      </style>
    • 渲染

      this.$emit('自定义事件名',参数)
      在这里插入图片描述
    • $on ref 配合mounted 使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      // App
      <!-- 自定义事件绑定在哪就去哪里做触发 -->
      <student ref="studentRef"></student>


      methods:{
      getStudentName(name) {
      this.receiveSchoolName = name
      console.log('coderitl被调用了...', name);
      }
      },
      mounted() {
      this.$refs.studentRef.$on('coderitl', this.getStudentName)
      }
    • 解绑一个

      1
      this.$off('coderitl')
    • 解绑多个自定义事件

      1
      this.$off(['coderitl','b'])
    • 解绑所有

      1
      this.$off()
    • 绑定自定义事件

      1. 第一种方式,在父组件中:<Demo @coderitl="test"></Demo>

      2. 第二种方式,在父组件中

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        <Demo ref="DemoRef"/>

        ...
        methods:{
        test(){
        ...
        }
        }

        mounted(){
        this.$refs.xxx.$on('coderitl',this.test)
        }

      3. 若想让自定义事件只能触发一次,可以使用once 修饰符,$once 方法

    • 触发自定义事件:this.$emit('coderitl',数据)

    • 解绑自定义事件:this.$off('coderitl')

    • 组件上也可以绑定原生DOM 事件,需要使用native 修饰符

    • 注意: 通过this.$refs.xxx.$on('coderitl',回调) 绑定自定义事件时,回调要么配置在methods,要么用箭头函数,否则this 指向会出问题!

SLOT 插槽

  • 插槽

    • 实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      <!DOCTYPE html>
      <html lang="zh-CN">

      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>slot</title>
      <script src="../../../vue.js"></script>
      </head>

      <body>
      <div id="app">
      <!-- 普通插槽的使用 -->
      <cpn1>
      <h1> <font color="purple">内容替换</font> </h1>
      </cpn1>
      <!-- 添加具有默认值的插槽 -->
      <cpn2></cpn2>
      <!-- 多元素覆盖 -->
      <cpn3>
      -----------------------
      <h1> 标题一 </h1>
      <h2> 标题二 </h2>
      <p> 文本内容 </p>
      -----------------------
      </cpn3>

      </div>
      <template id="cpnT">
      <div>
      <h1> 插槽使用: </h1>
      <slot></slot>
      </div>
      </template>

      <template id="cpnD">
      <div>
      <h1> 插槽使用: </h1>
      <slot> <button> 默认值 </button> </slot>
      </div>
      </template>

      <template id="cpnM">
      <div>
      <h1> 插槽使用: </h1>
      <slot></slot>
      </div>
      </template>


      <script>
      const app = new Vue({
      el: '#app',
      data: {
      message: 'Hello Word'
      },
      methods: {},
      components: {
      cpn1: {
      template: "#cpnT"
      },
      cpn2: {
      template: "#cpnD"
      },
      cpn3: {
      template: "#cpnM"
      }
      }
      });
      </script>
      </body>

      </html>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 定义 slot
    <template id="cpnT">
    <div>
    <h1> 聚名插槽的使用</h1>
    <slot name="left"> 左侧</slot>
    <slot name="center">中间 </slot>
    <slot name="right"> 右侧</slot>
    </div>
    </template>


    // 使用
    <cpn> <span slot="right">我从右边变为其他了</span></cpn>


    cpn 虽然是子组件,但却在 app 实例中使用,此时的 isShow 就是属于对应实例中的 ,isShow:true,所以显示当前内容

    dataScope
    dataScope

    注意: 官方指出 父组件模板的所有东西都会在父级作用域内编译,子组件模板的所有东西都会在子级作用域内编译

    解释作用域插槽: 父组件替换插槽的标签,但是内容由子组件来提供

    作用域插槽
    作用域插槽
    • 实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      <body>
      <div id="app">
      <!-- 默认展示方式 -->
      <cpn></cpn>
      <!-- 获取子组件数据 -->
      <cpn>
      <template slot-scope="slot">
      <span>{{slot.data.join(' - ')}}</span>
      </template>
      </cpn>
      </div>
      <template id="cpnT">
      <div>
      <h1> 子组件</h1>
      <slot :data="languages">
      <!-- 默认展示方式 -->
      <ul>
      <li v-for="item in languages">{{item}}</li>
      </ul>
      </slot>
      </div>
      </template>
      <script>
      const app = new Vue({
      el: '#app',
      components: {
      cpn: {
      template: "#cpnT",
      data() {
      return {
      languages: ['JavaScript', 'Java', 'MYSQL', 'Mongodb']
      }
      }
      }
      }
      });
      </script>
      </body>

Vue-CLI

  • 安装

    1
    npm install -g @vue/cli
  • 项目创建

    1
    vue create proj-name[名称只能是小写]
  • 项目创建流程

    • 创建流程

      创建流程
      创建流程
    • 初始安装选择项

      空格选择,,回车确定,进行下一步
      空格的使用
    • 版本与路由模式

      版本与路由模式,回车确定
      版本与路由模式
    • 选择独立文件管理

      选择独立文件管理
      选择独立文件管理
    • 是否要保存为模板 n | 回车

      Save this as a preset for future projects? (y/N) y,如果进行了保存,则输入保存的名称

    • 项目运行

      项目运行 手机访问测试
      项目运行 手机访问测试
      • 手机截图

        手机端访问测试结果
        手机端访问测试结果
    • 目录分析

      • public

        • index.html

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          <!DOCTYPE html>
          <html lang="">
          <head>
          <meta charset="utf-8" />

          <meta http-equiv="X-UA-Compatible" content="IE=edge" />
          <!-- 开启移动端的理想视口 -->
          <meta name="viewport" content="width=device-width,initial-scale=1.0" />
          <!-- 配置页签图标 -->
          <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
          <!-- 配置网页标题 -->
          <title><%= htmlWebpackPlugin.options.title %></title>
          </head>
          <body>
          <!-- 当浏览器不支持 js 时 noscript 的元素就会被渲染 -->
          <noscript>
          <strong>
          We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't
          work properly without JavaScript enabled. Please enable it to
          continue.
          </strong>
          </noscript>
          <!-- 容器 -->
          <div id="app"></div>
          <!-- built files will be auto injected -->
          </body>
          </html>

      • main.js 入口文件

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        //  引入Vue
        import Vue from 'vue'; // "module": "dist/vue.runtime.esm.js",
        // 引入 App 组件,是所有组件的父组件
        import App from './App.vue';
        // 引入路由
        import router from './router';
        // 关闭生产提示
        Vue.config.productionTip = false;
        // vm 实例对象
        new Vue({
        router,
        render: h => h(App),
        }).$mount('#app');
        • rander

          1
          render?(createElement: CreateElement, hack: RenderContext<Props>): VNode;
      • webpack: Vue 脚手架隐藏了所有webpack 相关配置,若想查看具体的webpack 配置,可以执行如下

        1
        2
        3
        4
        5
        6
        vue inspect > output.js

        // 对文件添加如下就可以避免错误,用来查看webpack配置
        export default {
        ...
        }
    • 脚手架配置文件vue.config.js

      Vue-CLI vue.config.js
      Vue-CLI vue.config.js
      • lintOnSave: false 关闭语法检查

      • 最新脚手架项目中的配置项

        transpileDependencies
        transpileDependencies

Ref属性

  • 获取dom

    1
    2
    3
    4
    5
    6
    <template>
    <div>
    <h1 ref="getInfoRef">coder-itl</h1>

    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    methods: {
    getH1() {
    // 获取 HTML 标签
    console.log(this.$refs.getInfoRef);
    // 获取组件标签 => VueComponent

    },
    },

浏览器本地存储

  • localStorage

    • 存储localStorage | 读取localStorage 数据 | 删除localStorage 数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="IE=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>localStorage</title>
      </head>
      <body>
      <h1>localStorage 的使用</h1>
      <hr />
      <h1>读取 localStorage 的数据:</h1>
      <p class="getLocalStorage"></p>
      <button id="btnSave">点我保存数据</button>
      <button id="btnRead">点我读取数据</button>
      <button id="btnRemove">点我删除数据</button>
      <script>
      const btnSave = document.querySelector('#btnSave');
      const btnRead = document.querySelector('#btnRead');
      const btnRemove = document.querySelector('#btnRemove');
      const getLocalStorage = document.querySelector('.getLocalStorage');

      btnSave.addEventListener('click', () => {
      let userInfo = {
      userName: 'coder-itl',
      userAge: 18,
      };
      localStorage.setItem('userInfo', JSON.stringify(userInfo));
      });

      btnRead.addEventListener('click', () => {
      const result = localStorage.getItem('userInfo');
      getLocalStorage.innerHTML = result;
      console.log(JSON.parse(result));
      });

      btnRemove.addEventListener('click', () => {
      localStorage.removeItem('userInfo');
      console.log('数据已经被删除了!');
      });
      </script>
      </body>
      </html>

      • 渲染

        存储数据
        存储数据
    • 清空所有localStorage

      1
      localStorage.clear()
  • sessionStorage

  • webStorage 是两者的统称

    1. 存储内容大小一般支持5MB 左右(不同浏览器可能不一样)
    2. 浏览器通过Window.sessionStorage Window.localStorage 属性来实现本地存储机制
    3. 备注
      1. sessionStorage 存储的内容会随着浏览器窗口关闭而消失
      2. lolcaStorage 存储的内容,需要手动清除才会消失
      3. xxxStorage.getItem(xx) 如果xxx 对应的value 获取不到,那么getItem 的返回值是null
      4. JSON.parse(null) 的结果是null

全局事件总线(GlobalEventBus)

  1. 一种组件间通信方式,适用于任意组件间通信

  2. 安装全局事件总线

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //  vm 实例对象
    new Vue({
    router,
    render: h => h(App),
    beforeCreate() {
    // 安装全局事件总线 this == vm
    Vue.prototype.$bus = this
    }
    }).$mount('#app');
  3. 使用事件总线

    1. 接受数据,:A 组件想接受数据,则在A 组件中给$bus 绑定自定义事件,事件的回调留在A 组件自身

      1
      2
      3
      4
      5
      6
      7
      8
      9
      methods:{
      demo(data){
      ...
      }
      }

      mounted(){
      this.$bus.$on('xxx',this.demo)
      }
    2. 提供数据:this.$bus.$emit('xxx',数据)

  4. 最好在beforeDestroy 钩子中,$off, 去解绑当前组件所用到的事件(绑定事件处解绑)

消息订阅与发布(pubsub)

  • 消息订阅与发布: 一种组件间通信方式,适用于任意组件通信

  • 使用步骤

    • 安装

      1
      npm install pubsub-js
    • 引入

      1
      import pubsub from 'pubsub-js'
    • 接受数据:A 组件想接受数据,则在A 组件中订阅消息,订阅的回调留在 A 组件自身

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      methods:{
      getSendStuName(_, data) {
      this.getChildrenName = data
      console.log('有人订阅了消息 coderitl!')
      }
      }
      mounted() {
      // 订阅消息
      this.pubId = pubsub.subscribe('消息名', this.getSendStuName)
      },
      beforeDestroy() {
      // 在销毁之前解绑
      pubsub.unsubscribe(this.pubId)
      }
    • 提供数据

      1
      2
      3
      4
      5
      6
      methods: {
      sendSchoolName() {
      // 提供数据
      pubsub.publish('消息名', 数据)
      }
      }
    • 最好在beforeDestroy 钩子中,pubsub.unsubscribe(this.pubId) 取消订阅

nextTick

  1. 语法

    1
    this.$nextTick(回调函数)
  2. 作用: 在下一次 DOM 更新结束后执行其指定的回调

  3. 什么时候用: 当改变数据后,要基于更新后的新DOM 进行某些操作,要在nextTick 所指定的回调函数中执行

Webpack

  • webpack

    • 什么是webpack

      webpack 是前端醒目工程化的具体解决方案

    • 主要功能

      他提供了有好的前端模块化开发支持,以及代码压缩混淆、处理浏览器端 Javascript 的兼容性、性能优化等强大的功能

      webpack
      webpack
    • 创建列表隔行变色项目

      • 新建项目空白目录,并运行npm init -y 命令,初始化包管理器配置文件package.json
      • 新建src 源代码目录
      • 新建src -> index.html 首页和src -> index.js 脚本文件
      • 初始化首页基本的结构
      • 运行npm install jquery -S 命令,安装jQuery
      • 通过ES6 模块化的方式导入jQuery,实现列表隔行变色效果
      • 安装webpack,解决错误
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <title>Title</title>
      <!-- <script src="./index.js"></script> -->
      <script src="../dist/main.js"></script>
      </head>
      <body>
      <ul>
      <li>这是第 1 个 li</li>
      <li>这是第 2 个 li</li>
      <li>这是第 3 个 li</li>
      <li>这是第 4 个 li</li>
      <li>这是第 5 个 li</li>
      <li>这是第 6 个 li</li>
      <li>这是第 7 个 li</li>
      <li>这是第 8 个 li</li>
      <li>这是第 9 个 li</li>
      <li>这是第 10 个 li</li>
      </ul>
      </body>
      </html>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // index.js
      // 1. 使用 es6 导入语法,导入 jQuery
      import $ from 'jquery'

      // 2. 定义 jQuery 的入口函数
      $(function () {
      // 3. 实现隔行换色
      $("li:odd").css('background-color', 'pink')
      $("li:even").css('background-color', 'skyblue')
      })
    • webpack 的基础使用

      • 安装

        1
        2
        # -D: --save-dev 只在开发阶段使用的依赖
        npm install webpack@5.42.1 webpack-cli@4.7.2 -D
      • 在项目根目录中,创建名为webpack.config.js webpack 配置文件,并初始化如下的基本配置

        1
        2
        3
        module.exports = {
        mode: 'development' // mode 用来指定构建模式,可选值有 development 和 production
        }
      • package.json scripts 节点下,新增dev 脚本

        1
        2
        3
        4
        "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "dev": "webpack" // 在scripts 节点下的脚本,可以通过 npm run 执行,例如 npm run dev
        },
      • 在终端中执行npm run dev 命令,启动webpack 进行项目的打包构建

      • index.html 中引入在dist/main.js

      • 注意点

        • 当前环境使用的node 不要太高,16即可
      • 效果

        效果
    1
    2
    3
    4
    5
    6
    7
    # 全局安装
    npm install webpack -g
    # 安装指定版本
    npm install webpack@3.6.0 -g
    # 检测安装
    webpack -v

    • 目录

      1
      2
      dist: distribution (发布)
      src: 源码
    • 导出

      1
      2
      3
      4
      5
      6
      7
      8
      function add(n1, n2) {
      return n1 + n2
      }

      // 模块导出
      module.exports = {
      add
      };
    • 导入

      1
      2
      3
      4
      const {add} = require('./src/mathUntils.js');
      // 使用
      console.log(add(10,20));

    • 打包

      1
      webpack ./src/main.js ./dist/bundle.js
    • 配置文件webpack.config.js

      • 入口(entry): 可以是字符串、数组、对象,

      • 出口(output): 通常是一个对象,里面至少包含两个重要属性,path filename

      • 配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        // path --> node 
        const path = require("path");
        module.exports = {
        entry: "./src/main.js", // 入口文件
        // 输出文件
        output: {
        path: path.resolve(__dirname,'dist' ), // 获取绝对路径
        filename: 'bundle.js' // bundle.js 打包
        }
        };

        // 终端
        webpack 【不需要再添加路径参数】

    • 脚本命令映射

      build => webpack
      build
    • Error

      错误原因 解决方案
      error error
      解决方案一: 更换webpack 版本 解决方案二: 更换 style-loader、css-loader 版本
    • less-loader webpack 版本问题

      • 查看所需安装的版本

        1
        npm view less | webpack | xxx versions
      • 安装指定版本

        1
        npm install less-loader@版本号
    • 配置文件基础形式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      // 配置文件名称 webpack.config.js
      const path = require('path');
      module.exports = {
      entry: "./src/main.js",
      output: {
      path: path.resolve(__dirname, 'dist'), // 获取绝对路径
      filename: 'bundle.js'
      },
      module: {
      rules: [{
      test: /\.css$/,
      use: ['style-loader', 'css-loader'] // 需下载对应包
      }]
      }
      };
      // current 当前的

    • 目录结构

      目录结构
      目录结构
    • less 预处理

      lessloader-error
      lessloader-error
    • 使用less-loader 的具体思路

      • 前往官网获取基本使用方式 【 less-loader
      • 在项目中添加less 文件
      • main.js 中依赖less 文件
      • 在进行安装对应工具
    • 安装less-loader

      • 安装

        1
        npm install --save-dev less-loader less
      • 配置文件webpack.config.js

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        const path = require('path');
        module.exports = {
        entry: "./src/main.js",
        output: {
        path: path.resolve(__dirname, 'dist'), // 获取绝对路径
        filename: 'bundle.js'
        },
        module: {
        rules: [{
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
        },
        // 新增 less 配置
        {
        test: /\.less$/,
        use: [{
        loader: "style-loader" // creates style nodes from JS strings
        }, {
        loader: "css-loader" // translates CSS into CommonJS
        }, {
        loader: "less-loader" // compiles Less to CSS
        }]
        }
        ]
        }
        };
        // current 当前的
      • webpack-less 打包

        • 运行

          build
          build
        • 网页结构

          打包
          webapck-打包,main中引入bundle.js
    • 未安装依赖

      img-loader 未安装对应loader 就使用时
      img-loader
      • 安装

        1
        2
        3
        npm install --save-dev url-loader
        # limit 大于时使用
        npm install file-loader --save-dev
      • 图片资源大小96.0 KB

      • 配置文件新增

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        // 配置 img
        {
        test: /\.(png|jpg|gif)$/,
        use: [{
        loader: 'url-loader',
        options: {
        /*
        当加载的图片,小于 limit 时,会将图片编译成 base64字符串形式
        当加载的图片,大于 limit 时,需要使用 file-loader 模块进行加载
        */
        limit: 8192
        }
        }]
        }
      • img 使用

        添加图片资源 图片资源打包后
        添加图片资源 图片打包
      • 图片命名及其他配置

        • options添加选项
          1. img:文件要打包的文件夹
          2. name: 获取图片原来的名字,放在该位置
          3. hash:8: 为了防止图片名称冲突,依然使用 hash,只保留 8 位
          4. ext: 使用图片原来的扩展
      • 效果图

        打包效果
        打包效果
    • Es6 处理

      • 安装依赖

        1
        npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
      • Es6 相关配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: { loader: 'babel-loader',
        options: {
        presets:['es2015']
        }
        }
        }
    • 安装

      1
      npm install vue-loader vue-template-compiler --save--dev # 降低 vue-loader 版本 本地测试中
    • 添加配置webpack.config.js

      1
      // vue: webpack 配置{    test:/\.vue$/,    use:['vue-loader']}
    • webpack 配置Vue

      • main.js

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        // 依赖 Vue : npm install --save

        // 使用 Vue 模块式开发
        import Vue from "vue"
        import App from './vue/App.vue'

        new Vue({
        el: '#app',
        // 由于 app会被替换,将 App 作为一个组件
        template: '<App/>',
        components: {
        // 注册组件 App
        App
        }
        });
      • APP.vue

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        <template>
        <div>
        <h1>{{message}}</h1>
        <h1 class="count">计数器: {{count}}</h1>
        <button @click="increment"> + </button>
        <button @click="descrement"> - </button>
        </div>
        </template>

        <script>
        name: "App", // 组件名称
        data() {
        return {
        count: 1
        }
        },
        methods: {
        increment() {
        // 增加按钮点击事件
        this.count++;
        },
        descrement() {
        // 减少按钮点击事件
        if (this.count <= 1) {
        return this.count = 1
        } else {
        this.count--
        }
        }
        }
        </script>

        <style lang="scss" scoped>
        // 字体颜色
        .count {
        color: purple;
        }
        </style>

VueRouter(路由)

  • 路由

    • vue-router 的理解

      vue 的一个插件库,专门用来实现SPA 应用

    • SPA 应用的理解

      1. 单页Web 应用(Single Page Web Application => SPA)
      2. 整个应用只有一个完整的页面
      3. 点击页面中的导航连接不会刷新页面,只会做页面的局部刷新
      4. 数据需要通过ajax 请求获取
    • 什么是路由

      1. 一个路由就是一组映射关系key - value
      2. key 为路径,value 可能是function component
    • 路由分类

      • 后端路由

        1. 理解: value function,用于处理客户端提交的请求
        2. 工作过程: 服务器接收到一个请求时,根据请求路径知道匹配的函数来处理请求,返回响应数据
      • 前端路由

        1. 理解: value component,用于展示页面内容

        2. 工作过程: 当浏览器的路径改变时,对应的组件就会显示

          component
          component
    • 安装插件

      1
      2
      3
      4
      # 永远安装最新版本
      npm install --save vue-router
      # 如果项目为 vue2,则需要安装为 vue-router 的 3
      npm install --save vue-router@3
    • 使用vue-router

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      /*
      1. 在 src 目录下新建文件夹 router,再在该文件夹下新建 "index.js" 文件
      2 在 main.js 中引入 router,注册
      3. 注意: routes 名称
      */

      index.js(路由基础使用):
      -----------------------------------------------------------
      import Vue from "vue"
      import VueRouter from "vue-router"

      // 路由引入: 方式一
      import Home from '../components/home/Home'
      // 路由引入: 路由懒加载(方式二)
      const Home = () => import('../components/home/Home');


      // 安装插件
      Vue.use(VueRouter)

      // 映射
      const routes =
      // 路由重定向
      {
      path: '/',
      redirect: '/home'
      },
      // 路由映射
      {
      path: '/home',
      component: Home
      }
      ]

      // 创建对象
      const router = new VueRouter({
      mode: 'history',
      routes
      })

      // 导出
      export default router

    • 注意事项

      一般组件与路由组件
      一般组件与路由组件
    • 几个注意点

      • 路由组件通常存放在pages 文件夹,一般组件通常存放在components 文件夹
      • 通过切换, 隐藏 了的路由组件,默认是被销毁的,需要使用的时候再去挂载
      • 每个组件都有自己的$route 属性,可以通过组件的$router 属性获取到
    • 多级路由

      • 配置路由规则,使用children 配置项

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        const routes = [
        {
        path: '/about',
        component: () => import('@/views/About'),
        },
        {
        path: '/home',
        component: () => import('@/views/Home'),
        // 通过 children 配置子级路由
        children: [
        {
        path: 'news', // 此处是不能在路径上添加 /,即不能是 /news
        component: () => import('@/pages/News')
        }
        ]
        },
        ];
      • 跳转要写为完整路径

        1
        <router-link to="/home/news">news</router-link>
    • 使用渲染效果

      路由与嵌套路由使用
      路由与嵌套路由使用
    • 路由携带query 参数

      • 字符串写法

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        <template>
        <div class="about">
        <h1>About</h1>
        <ul>
        <li v-for="message in messageList">

        <router-link :to="`/about/detail?id=${message.id}&title=${message.title}`"> {{ message.title }}</router-link>
        </li>
        </ul>
        <router-view></router-view>

        </div>
        </template>

        <script>
        import {nanoid} from "nanoid";

        export default {
        name: "About",
        data() {
        return {
        messageList: [
        {id: '001', title: '消息001'},
        {id: nanoid(), title: '消息002'},
        {id: nanoid(), title: '消息003'}
        ]
        }
        }
        }
        </script>

        <style scoped>

        </style>
        • 路由实现数据传递

          路由实现数据传递
          路由实现数据传递
        • 数据渲染

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          <template>
          <div class="detail">
          <h1>Detail</h1>
          <ul>
          <li>消息编号: {{ $route.query.id }}</li>
          <li>消息名称: {{ $route.query.title }}</li>
          </ul>

          </div>
          </template>

          <script>
          export default {
          name: "Detail",
          mounted() {
          // $route 可以获取路由相关的配置信息
          console.log(this.$route);
          }
          }
          </script>

          <style scoped>

          </style>
      • 对象写法

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        <template>
        <div class="about">
        <h1>About</h1>
        <ul>
        <li v-for="message in messageList">
        <router-link :to="{
        path:'/about/detail',
        query:{
        id: message.id,
        title:message.title
        }
        }">
        {{ message.title }}
        </router-link>
        </li>
        </ul>
        <router-view></router-view>

        </div>
        </template>

        <script>
        import {nanoid} from "nanoid";

        export default {
        name: "About",
        data() {
        return {
        messageList: [
        {id: '001', title: '消息001'},
        {id: nanoid(), title: '消息002'},
        {id: nanoid(), title: '消息003'}
        ]
        }
        }
        }
        </script>

        <style scoped>

        </style>
      • 数据渲染结果

        query 对象写法
        query对象写法
    • 命名路由

      • 作用: 可以简化路由跳转

      • 使用

        • 给路由命名

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          const routes = [
          {
          // 一级路由
          path: '/about',
          component: () => import('@/views/About'),
          // 多级路由
          children: [
          {
          path: 'detail',
          component: () => import('@/pages/Detail'),
          // 多级路由
          children: [
          name: 'hello'
          path: 'Welcome',
          component: () => import('@/pages/xxx/Hello'),
          ]
          }
          ]
          }
          ];

        • 简化跳转

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          <!-- 简化之前: 需要写完整的路径  -->
          <router-link to="/about/detail/hello">news</router-link>

          <!-- 简化之后: 直接通过名称跳转 -->
          <router-link :to="{name:'hello'}">news</router-link>

          <!-- 简化写法配合传递参数 -->
          <router-link :to="{
          name:'hello',
          query:{
          id: xxx,
          title: xxx
          }
          }">

          </router-link>
    • params 参数

      • 路由配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        const routes = [
        {
        path: '/about',
        name: 'about',
        component: () => import('@/views/About'),
        children: [
        {
        // param参数配置路由方式
        path: 'detail/:id/:title',
        name: 'detail',
        component: () => import('@/pages/Detail')
        }
        ]
        }
        }
      • 参数传递

        • 数据来源

          param 类型参数的数据来源
          param类型参数的数据来源
      • 数据渲染

        param 获取参数数据
        param获取参数数据
    • 对象写法时

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <router-link :to="{
      name:'hello', // 只能是 name 而不能是 path
      params:{
      id: xxx,
      title: xxx
      }
      }">

      </router-link>
    • 路由的props

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      {
      name: 'xxx',
      path: '/xxx',
      component: ()=>import('xxx')

      // 第一种写法: props 值为对象,该对象中所有的 key-value 的组合最终都会通过 props 传给需要数据的目标组件(Eg: Detail)
      // 第二种写法: props 值为布尔值,布尔值为 true,则把路由收到的所有 praams 参数通过 props 传给 Detail 组件
      // 第三种写法: props 值为函数,该函数返回的对象中每一组 key-value 都会通过 props 传给 Datail 组件
      props($route){
      return{
      id: query.id,
      title: query.title
      }
      }
      }
      • 测试

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        const routes = [
        {
        path: '/about',
        name: 'about',
        component: () => import('@/views/About'),
        children: [
        {
        path: 'detail',
        name: 'detail',
        component: () => import('@/pages/Detail'),
        // props
        props($route) {
        return {
        id: $route.query.id,
        title: $route.query.title
        }
        }
        }
        ]
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        // 字符串形式无法获取数据
        <template>
        <div class="about">
        <h1>About</h1>
        <ul>
        <li v-for="message in messageList">
        <router-link :to="{
        path:'/about/detail',
        query:{
        id:message.id,
        title:message.title
        }
        }">{{ message.title }}
        </router-link>
        </li>
        </ul>
        <router-view></router-view>
        </div>
        </template>

        <script>
        import {nanoid} from "nanoid";

        export default {
        name: "About",
        data() {
        return {
        messageList: [
        {id: '001', title: '消息001'},
        {id: nanoid(), title: '消息002'},
        {id: nanoid(), title: '消息003'}
        ]
        }
        }
        }
        </script>

        <style scoped>

        </style>
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        <template>
        <div class="detail">
        <h1>Detail</h1>
        <ul>
        <li>消息编号: {{ id }}</li>
        <li>消息名称: {{ title }}</li>
        </ul>

        </div>
        </template>

        <script>
        export default {
        name: "Detail",
        // props 接受数据
        props: ['id', 'title'],
        mounted() {
        console.log(this.$route);
        }
        }
        </script>

        <style scoped>

        </style>
    • 解决路由多次点击报错问题

      1
      2
      3
      4
      const VueRouterPush = VueRouter.prototype.push
      VueRouter.prototype.push = function push(to) {
      return VueRouterPush.call(this, to).catch(err => err)
      }
    1. replace 属性
      • 作用: 控制路由跳转时操作浏览器历史记录的模式
      • 浏览器的历史纪录有两种写入方式
        • push 是追加历史记录,默认push
        • replace 替换当前记录
        • 开启replace=><route-link replace >xxx</router-link>
    • keep-alice 以及其他问题

      1. keep-alive Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染

      2. router-view 也是一个组件,如果直接被包在keep-alive 里面,所有路径匹配的视图组件都会被缓存

      3. 作用: 让不展示的路由组件保持挂载,不被销毁

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        // 视图缓存 使用 keep-alive 包裹要展示路由的占位符 <router-view>
        <keep-alive include="组件名">
        <router-view></router-view>
        </keep-alive>


        beforeRouterLeave(to,from,next){
        this.path =this.$route.path;
        next()
        }


        keep-alive属性:
        include="组件名"
        exclude

        // 缓存多个
        :include="['A','B']"
    • include 名称

      include 为组件名
      include为组件名
    1. 作用: 路由组件所独有的两个钩子,用于捕获组件的激活状态

    2. 具体名字

      • activated 路由组件被激活时触发

      • deactivated 路由组件失活时触发

        1
        2
        3
        4
        5
        6
        activated() {
        console.log('news组件激活');
        },
        deactivated() {
        console.log('news组件失活');
        }
    • 全局前置路由守卫

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      {
      // name 可以简化路径
      name:'',
      // path 跳转的路径
      path:'',
      // children 多级路由
      children:[],
      // meta 元
      meta:{
      isAuth: true // 对需要开启验证的路由添加 meta
      }
      }

      // 全局前置路由守卫=> 初始化的时侯被调用、每次路由切换之前被调用

      router.beforeEach((to, from, next) => {
      console.log(to, from)
      // 拥有该属性的路由进行鉴权(判断是否需要鉴权)
      if (to.meta.isAuth) {
      if (localStorage.getItem('coderitl') === 'coderitl') {
      // 放行
      return next()
      } else {
      // console.log('权限不足!')
      Vue.prototype.$message({
      message: '权限不足!',
      type: 'warning'
      });
      }
      } else {
      // 对不需要鉴权的路由放行
      return next()
      }
      })
      点击about 导航,输出to,from
      在这里插入图片描述
    • 权限测试

      前置导航守卫
      前置导航守卫
    • 后置路由导航守卫

      1
      2
      3
      4
      router.afterEach((to, from) => {
      // 路由跳转时 title 发生变化 对 meta 添加新的配置项 title
      document.title = to.meta.title || 'coder-itl'
      })
    • 独享路由守卫

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      {
      path: '/home',
      name: 'home',
      component: () => import('@/views/Home'),
      children: [
      {
      path: 'news',
      name: 'news',
      component: () => import('@/pages/News')
      }
      ],
      meta: {
      isAuth: true,
      title: 'coder-itl'
      },
      // 独享路由守卫
      beforeEnter: (to,from,next) => {

      }
      }
    • 组件内路由守卫

      • 进入某一特定组件

        1
        2
        3
        4
        // 进入守卫 通过路由规则 进入该组件时被调用
        beforeRouteEnter(to,from,next){}
        // 离开守卫 通过路由规则 离开该组件时被调用
        beforeRouteLeave(to,from,next){}
    1. 对于一个url 来说,什么是hash 值?#及其后面的内容就是hash

    2. hash 值不会包含在HTTP 请求中,hash 值不会带给服务器

    3. hash 模式

      1. 地址中永远带着#号,不美观
      2. 若以后将地址通过第三方手机app 分享,app 校验严格,则地址会被标记为不合法
      3. 兼容性号
    4. history 模式

      1. 地址干净,美观
      2. 兼容性和hash 模式相比较差
      3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404 的问题
    5. 配置

      1
      2
      3
      4
      const router = new VueRouter({
      mode:'history',
      routes,
      });
    6. 后端插件node

      1
      2
      3
      4
      5
      6
      7
      # 安装 https://www.npmjs.com/package/connect-history-api-fallback
      npm install --save connect-history-api-fallback
      # 使用
      const history = require('connect-history-api-fallback');
      app.use(history)
      # 开放静态文件
      app.use(express.static(__dirname+'/static'))
    • 路由抽离

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      // food.js

      export const food = {
      path: '/food',
      name: 'home',
      component: () => import('@/components/Home.vue'),
      }

      export default {
      path: '/add',
      name: 'add',
      component: () => import('@/components/Card.vue'),
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // index.js

      // export const food
      import { food } from './food'
      // default
      import add from './food'


      const routes = [food, add]

      路由渲染
      路由渲染

Mixin

  • 基础使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <template>
    <div class="food">
    <ul>
    <li v-for="(value, name, index) in authorInfo">
    {{ value }} - {{ name }} - {{ index }}
    </li>
    </ul>
    </div>
    </template>
    <script>
    // 引入
    import { authorInfo } from '@/mixin/index'

    export default {
    mixins: [authorInfo],
    }
    </script>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    export const authorInfo = {
    data() {
    return {
    authorInfo: {
    name: 'coder-itl',
    age: 18,
    },
    }
    },
    }

    mixin
    mixin

编程式路由导航

  1. 作用: 不借助<route-link> 实现路由跳转,让路由跳转更加灵活

  2. 具体编码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // $router的两个 api
    pushShow(message) {
    console.log(this.$router);
    this.$router.push({
    path: '/about/detail',
    query: {
    id: message.id,
    title: message.title
    }
    })
    },
    replaceShow(message) {
    this.$router.replace({
    path: '/about/detail',
    query: {
    id: message.id,
    title: message.title
    }
    })
    }

    this.$router.back() // 后退
    this.$router.forward() // 前进
    this.$router.go() // 后退 | 前进 (需要传递参数,正负决定前进或后退)

Vuex(状态管理)

  • 状态管理

    • 下载

      1
      npm install --save vuex
      • 报错

        报错
        报错
      • 解决

        由于在 202227vue3 成为了默认版本,现在的npm i vue 安装的直接就是vue3 了,而在vue3 成为默认版本的同时,vuex 也更新到了4 版本,所以现在的npm i vuex 安装的是vuex4,vuex4版本只能在vue3中使用vue2 中,要用vuex 3 版本,vue3 中,要用vuex 4 版本

        1
        npm install --save vuex@3
        成功安装vuex
        成功安装vuex
    • 概念: 专门用在Vue 中实现集中式状态(数据)管理的一个Vue 插件,Vue 应用中多个组件共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信

    • 什么时候使用vuex

      • 多个组件依赖于同一状态(数据)
      • 来自不同组件的行为需要变更同一状态(数据)
    • 数据读写

      全局事件总线数据读写 Vuex 双向,读写共享数据变得简单
      数据读写 双向
    • 分析

      单项数据流 vuex-官网
      单项数据流 vuex-官网
      分析过程
      分析过程
    • 安装,注意脚手架版本

    • 引入vuex,并在main.js 中挂载store

    • 创建store 文件夹,在其目录下创建index.js

      • index.js 中应用(安装)vuex
      • 创建state、action、mutations 对象
    • action 没有相关业务逻辑时可以省略

      省略action
      省略action
    • 组件中读取vuex 中的数据,$store.state.sum(this如果在js脚本文件中需要添加)

    • 组件中修改vuex 中的数据:$store.dispatch('action中的方法名',数据) $store.commit('mutations中的方法名',数据)

    • 若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接写commit

    • 安装Vue.js devtools

      5.3.4
      5.3.4
    • F12 打开控制台,选择如下的vue6.0+

      在这里插入图片描述
    • 视图类型切换

      视图工具切换
      视图工具切换
    • vuex 使用分析

      vuex 监视面板
      vuex监视面板
    • 视图

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      <template>
      <div class="vue-before">
      <h1>(Vuex使用中)当前求和为:{{ $store.state.sum }} </h1>
      <div>
      <el-select v-model.number="number" placeholder="请选择">
      <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value">
      </el-option>
      </el-select>

      <el-button @click="increment" type="primary">+</el-button>
      <el-button @click="decrement" type="primary">-</el-button>
      <el-button @click="incrementOdd" type="primary">当前和为奇数再加</el-button>
      <el-button @click="incrementWait" type="primary">等一等再加</el-button>
      </div>
      </div>
      </template>

      <script>
      export default {
      name: "InVuex",
      data() {
      return {
      // sum: 0,
      number: 1, // 默认值为 1
      options: [{
      value: '1',
      label: '1'
      }, {
      value: '2',
      label: '2'
      }, {
      value: '3',
      label: '3'
      }, {
      value: '4',
      label: '4'
      }, {
      value: '5',
      label: '5'
      }]
      }
      },
      methods: {
      // 求和
      increment() {
      this.$store.commit('Increment', this.number)
      },
      // 做差
      decrement() {
      this.$store.commit('Decrement', this.number)
      },
      // 奇数相加
      incrementOdd() {
      this.$store.dispatch('incrementOdd', this.number)
      },
      // 等待后再加
      incrementWait() {
      this.$store.dispatch('incrementWait', this.number)
      },
      },
      mounted() {
      console.log(this)
      }
      }
      </script>

      <style scoped>
      .el-select {
      margin-right: 10px;
      }
      </style>
    • store

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      // 该文件用于创建 vuex中最为核心的 store
      import Vue from "vue";

      // 引入 vuex
      import Vuex from 'vuex'

      // 安装
      Vue.use(Vuex)
      // 准备 state 用于存储数据
      const state = {sum: 0}

      // 装备 actions 用于响应组件中的动作
      const actions = {
      // increment(context, value) {
      // console.log('action aa', value)
      // context.commit('Increment', value)
      // },
      // decrement(context, value) {
      // console.log('action aa', value)
      // context.commit('Decrement', value)
      // },
      incrementOdd(context, value) {
      console.log('action aa', value)
      context.commit('IncrementOdd', value)
      },
      incrementWait(context, value) {
      console.log('action aa', value)
      context.commit('IncrementWait', value)
      },
      }
      // 准备 mutations 用于操作数据(state)
      const mutations = {
      Increment(state, value) {
      console.log('mutations Increment', state, value)
      state.sum += value
      },
      Decrement(state, value) {
      state.sum -= value
      },
      IncrementOdd(state, value) {
      if (state.sum % 2) {
      state.sum += value
      }
      },
      IncrementWait(state, value) {
      setTimeout(() => {
      state.sum += value
      }, 500)
      }
      }


      // 创建并暴露 store
      export default new Vuex.Store({
      actions, mutations, state
      })
    • 效果渲染

      store
      store
    • 概念: 当state 中的数据需要经过加工后在使用,可以使用getters 加工

    • 定义

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const getters = {
      bigSum(state){
      return state.sum*10
      }
      }

      // 创建并暴露 store
      export default new Vuex.Store({
      ...
      getters
      })
    • 组件中读取数据:$store.getters.bigSum

    1. mapState 方法:用于帮助我们映射state 中的数据为计算属性

      问题显示
      问题显示
      • 优化

        使用computed
        使用computed
        1
        2
        3
        4
        5
        6
        7
        8
        9
        // 优化
        import {mapState} from 'vuex'

        computed:{
        // 借助 mapState 生成计算属性: sum school subject(对象写法)
        ...mapState({sum:'sum',school:'school','subject:'subject'})
        // 借助 mapState 生成计算属性: sum school subject(数组写法) 使用前提是 state 属性与计算属性函数名同名
        ...mapState(['sum','school','subject'])
        }
    2. mapGetters 方法: 用于帮助我们映射getters 中的数据为计算属性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 优化
      import {mapState} from 'vuex'

      computed:{
      // 借助 mapState 生成计算属性: bigSum (对象写法)
      ...mapState({bigSum:'bigSum'})
      // 借助 mapState 生成计算属性: bigSum (数组写法)
      ...mapState(['bigSum'])
      }
    3. mapActions 方法: 用于帮助我们生成与actions 对话的方法,即包含$store.dispatch(xx) 的函数

      1
      2
      3
      import {mapMutations} from 'vuex' 


      mapMutations 简化dispatch
      mapMutations简化dispatch
    4. mapMutations 方法:用于帮助我们生成与mutations 对话的方法,即:包含$store.commit(xxx) 的函数

      1
      2
      3
      4
      5
      6
      7
      8
      import {mapMutations} from 'vuex'

      methods: {
      // 对象写法
      ...mapMutations({increment: 'Increment', decrement: 'Decrement'})
      // 数组写法
      ...mapMutations([ 'Increment', 'Decrement'])
      }
      优化对象写法 数组写法
      优化对象写法 数组写法
    • 模块化

      模块化
      模块化
    • 具体实现

      • Count 组件

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        <template>
        <div class="vue-before">
        <h1>(Vuex使用中)当前求和为:{{ sum }} </h1>
        <h1>(Vuex使用中)将 sum扩大10倍为:{{ bigSum }} </h1>
        <h1>(Vuex使用中)学校名称为:{{ school }} </h1>
        <h1>(Vuex使用中)学习科目为:{{ subject }} </h1>
        <h1 style="color: red">(Vuex使用中)Persons人员总数为:{{ personList.length }} </h1>
        <div>
        <el-select v-model.number="number" placeholder="请选择">
        <el-option
        v-for="item in options"
        :key="item.value"
        :label="item.label"
        :value="item.value">
        </el-option>
        </el-select>

        <el-button @click="Increment(number)" type="primary">+</el-button>
        <el-button @click="Decrement(number)" type="primary">-</el-button>
        <el-button @click="incrementOdd(number)" type="primary">当前和为奇数再加</el-button>
        <el-button @click="incrementWait(number)" type="primary">等一等再加</el-button>
        </div>
        </div>
        </template>

        <script>
        import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'


        export default {
        name: "InVuex",
        data() {
        return {
        // sum: 0,
        number: 1, // 默认值为 1
        options: [{
        value: '1',
        label: '1'
        }, {
        value: '2',
        label: '2'
        }, {
        value: '3',
        label: '3'
        }, {
        value: '4',
        label: '4'
        }, {
        value: '5',
        label: '5'
        }]
        }
        },
        methods: {
        ...mapMutations('aboutCount', ['Increment', 'Decrement']),
        ...mapActions('aboutCount', ['incrementOdd', 'incrementWait'])
        },
        mounted() {
        console.log(this)
        },
        computed: {
        // 对象写法: ...mapState('aboutCount',{sum:'sum'})
        ...mapState('aboutCount', ['sum', 'school', 'subject']),
        ...mapState('aboutPersons', ['personList']),
        ...mapGetters('aboutCount', ['bigSum'])
        }
        }
        </script>

        <style scoped>
        .el-select {
        margin-right: 10px;
        }
        </style>
      • count store 模块

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        export default {
        namespaced: true,
        actions: {
        incrementOdd(context, value) {
        console.log('action aa', value)
        context.commit('IncrementOdd', value)
        },
        incrementWait(context, value) {
        console.log('action aa', value)
        context.commit('IncrementWait', value)
        }
        },
        mutations: {
        Increment(state, value) {
        console.log('mutations Increment', state, value)
        state.sum += value
        },
        Decrement(state, value) {
        state.sum -= value
        },
        IncrementOdd(state, value) {
        if (state.sum % 2) {
        state.sum += value
        }
        },
        IncrementWait(state, value) {
        setTimeout(() => {
        state.sum += value
        }, 500)
        }
        },
        state: {
        sum: 0,
        school: '育才中学',
        subject: 'Java',
        bigSum: 0
        },
        getters: {
        bigSum(state) {
        return state.sum * 10
        }
        }
        }
    • Persons 组件

      • Persons

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        <template>
        <div class="persons">
        <h1>人员列表</h1>
        <h1 style="color: red">Count 组件的和为: {{ sum }}</h1>
        <h1 style="color: red">列表中第一个人的名字是: {{ firstPersonName }}</h1>
        <el-input type="text" v-model="personListInfo" placeholder="请输入名字"></el-input>
        <el-button type="success" @click="add">添加人员信息</el-button>
        <el-button type="success" @click="addLiu">添加一个姓刘的人员</el-button>
        <el-button type="success" @click="addDay">添加server人员信息</el-button>
        <ul>
        <!-- <li v-for="person in $store.state.personList" :key="person.id">{{ person.name }}</li> -->
        <li v-for="person in personList" :key="person.id">{{ person.name }}</li>
        </ul>
        </div>
        </template>

        <script>
        import {nanoid} from "nanoid";

        export default {
        name: "Persons",
        data() {
        return {
        personListInfo: ''
        }
        },
        mounted() {
        console.log('aaaaaaaaa', this.$store)
        },
        computed: {
        // 通过计算属性简化 state 数据获取
        personList() {
        return this.$store.state.aboutPersons.personList
        },
        sum() {
        return this.$store.state.aboutCount.sum
        },
        firstPersonName() {
        return this.$store.getters["aboutPersons/firstName"]
        }
        },
        methods: {
        add() {
        const personObj = {id: nanoid(), name: this.personListInfo}
        // commit 提交给 mutations
        this.$store.commit('aboutPersons/ADD_PERSON', personObj)
        this.personListInfo = ''
        },
        addLiu() {
        const personObj = {id: nanoid(), name: this.personListInfo}
        // commit 提交给 mutations
        this.$store.dispatch('aboutPersons/addPersonLiu', personObj)
        },
        addDay(){
        this.$store.dispatch('aboutPersons/addServer')
        }
        }
        }
        </script>

        <style scoped>
        .el-input {
        width: 30%;
        margin-right: 15px;
        }

        </style>
      • Persons store

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        import Vue from "vue";
        import {nanoid} from "nanoid";
        import axios from "axios";

        export default {
        namespaced: true,
        actions: {
        addPersonLiu(context, value) {
        if (value.name.indexOf('刘') === 0) {
        context.commit('ADD_PERSON', value)
        // console.log('权限不足!')
        Vue.prototype.$message({
        message: '添加人员信息成功!',
        type: 'success'
        });
        } else {
        // console.log('权限不足!')
        Vue.prototype.$message({
        message: '添加的人员必须姓 刘!',
        type: 'error'
        });
        }
        },
        addServer(context) {
        axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(res => {
        console.log('res',res);
        context.commit('ADD_PERSON', {id: nanoid(), name: res.data})
        Vue.prototype.$message({
        message: '请求成功!',
        type: 'success'
        })
        }, error => {
        Vue.prototype.$message({
        message: '请求失败!',
        type: 'error'
        });
        })
        }
        },
        mutations: {
        ADD_PERSON(state, value) {
        // 列表添加 通过数组方法
        if (value.name.length > 0) {
        state.personList.unshift(value)
        Vue.prototype.$message({
        message: '添加人员信息成功!',
        type: 'success'
        });
        } else {
        Vue.prototype.$message({
        message: '添加人员不能为空!',
        type: 'warning'
        });
        }
        }
        },
        state: {
        personList: [
        {id: nanoid(), name: '张三'},
        {id: nanoid(), name: '里斯'},
        {id: nanoid(), name: '王五'}
        ]
        },
        getters: {
        firstName(state) {
        console.log(state)
        return state.personList[0].name
        }
        }
        }

    • store index 文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      // 该文件用于创建 vuex中最为核心的 store
      import Vue from "vue";
      // 引入 vuex
      import Vuex from 'vuex'

      import aboutCount from './count'
      import aboutPersons from './person'

      // 安装
      Vue.use(Vuex)

      // 创建并暴露 store
      export default new Vuex.Store({
      modules: {
      aboutCount,
      aboutPersons
      }
      })
    • 模块中开启命名空间后,数据读取

      • 组件中读取state 数据

        1
        2
        3
        4
        // 方式一: 自己直接读取
        this.$store.state.personAbout.sum
        // 方式二: 借助 mapState 读取
        ...mapState('personAbout',['sum','xxx'])
      • 组件中读取getters 数据

        1
        2
        3
        4
        // 方式一: 自己直接读取
        this.$store.getters['personAbout/bigSum']
        // 方式二: 借助 mapGetters 读取
        ...mapGetters('personAbout',['bigSum','xxx'])
      • 组件中读取dispatch 数据

        1
        2
        3
        4
        // 方式一: 自己直 dispatch
        this.$store.dispatch('personAbout/addPersonLiu',person)
        // 方式二: 借助 mapActions
        ...mapActions('personAbout',['incrementOdd'])
      • 组件中调用commit

        1
        2
        3
        4
        // 方式一: 自己直接 commit
        this.$store.commit('personAbout/ADD_PERSON',person)
        // 方式二: 借助 mapMutations
        ...mapMutations('countAbout',['increment'])

Vuex-面包屑导航思想

  • vuex 实现面包屑导航问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <template>
    <div class="food">
    <ul>
    <li v-for="(value, name, index) in authorInfo">
    {{ value }} - {{ name }} - {{ index }}
    </li>
    </ul>

    <h1 @click="show">显示活跃路由:</h1>
    <ul>
    <li v-for="path in this.$store.state.activeRouterPath">
    {{ path }}
    </li>
    </ul>
    </div>
    </template>
    <script>
    import { authorInfo } from '@/mixin/index'

    export default {
    mixins: [authorInfo],
    methods: {
    show() {
    // 调用 dispatch => 将活跃的路由路径传入
    this.$store.dispatch('activePath', this.$route.path)
    },
    },
    }
    </script>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import Vue from 'vue'
    import Vuex from 'vuex'

    Vue.use(Vuex)

    export default new Vuex.Store({
    state: {
    // 活跃时显示的路由
    activeRouterPath: [],
    },
    actions: {
    activePath(context, value) {
    console.log('active....', value)
    // 处理路径
    const activePath = {
    id: '001',
    path: value,
    }
    // 接着调用 commit
    context.commit('Mul', activePath)
    },
    },
    mutations: {
    Mul(state, value) {
    // 将 commit => path 加入 state 管理的 activeRouterPath
    console.log('mul....', state, value)
    state.activeRouterPath.push(value)
    },
    },
    getters: {},
    modules: {},
    })

    面包屑导航vuex 实现思想
    面包屑导航vuex实现思想

Axios

  • axios

    • 下载

      1
      npm install --save axios
    • 封装

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      import axios from 'axios'

      export function request(config) {
      // 1.创建axios的实例
      const instance = axios.create({
      baseURL: 'http://123.207.32.32:8000',
      timeout: 5000,
      })

      // 2.axios的拦截器
      // 2.1.请求拦截的作用
      instance.interceptors.request.use(
      (config) => {
      return config
      },
      (err) => {
      console.log(err)
      },
      )

      // 2.2.响应拦截
      instance.interceptors.response.use(
      (res) => {
      return res.data
      },
      (err) => {
      console.log(err)
      },
      )

      // 3.发送真正的网络请求
      return instance(config)
      }


    • 使用

      1
      2
      3
      4
      5
      6
      7
      8
      import { request } from './request'
      // 定义新的文件进行引用,单独管理文件
      export function getHomeMultidata() {
      return request({
      url: '/home/multidata',
      })
      }

脚手架配置

解决跨域

  • 配置代理

    1
    2
    3
    devServer: {
    proxy: "http://localhost:5000"
    }
    • 说明
      1. 优点: 配置简单,请求多个资源直接发个前端(8080)即可
      2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
      3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源)
  • 编写vue.config.js 配置具体代理规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    devServer: {
    proxy: {
    '/api1': {
    // 匹配以所有 '/api1'开头的请求
    target: 'http://localhost:5000', // 代理目标基础路径
    changeOrigin: true,
    pathRewrite: {'^/api1': ''}
    },
    '/api2': {
    // 匹配以所有 '/api2'开头的请求
    target: 'http://localhost:5001', // 代理目标基础路径
    changeOrigin: true,
    pathRewrite: {'^/api2': ''}
    }
    }
    }

    changeOrigin 设置为 true 时,服务器收到的请求头中的 host 为: localhost:5000

    changeOrigin 设置为 false 时,服务器收到的请求头中的 host 为: localhost:8080

    changeOrigin 默认值为 true

  • 案例练习

    • 测试请求

      测试接口
      测试接口
    • 数据渲染

      数据渲染
      数据渲染

启动项目时自动打开浏览器

  • 配置

    • 方式一

      1
      2
      3
      4
      5
      // package.json
      "scripts": {
      "serve": "vue-cli-service serve --open --host localhost", // localhost 也可以为 127.0.0.1
      ...
      },
    • 方式二

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const {defineConfig} = require('@vue/cli-service');
      module.exports = defineConfig({
      transpileDependencies: true,
      lintOnSave: false, // 关闭 eslint 语法检查
      // 配置启动
      devServer: {
      open: true, // npm run server 时打开浏览器
      host: 'localhost' // ip 配置,否则为 0.0.0.0:8080 无效地址
      }
      });