开始

# 开始

# 模板语法

  • {{ xxx }}:插值表达式(Mustache 语法),xxxjs表达式。

# 渐进式框架

Vue 从设计角度来讲, 虽然能够涵盖这张图上所有的东西,但是你并不需要一上手就把所有东西全用上(主张最少) ,因为没有必要。无论从学习角度,还是实际情况,这都是可选的。声明式渲染组件系统Vue的核心库所包含内容,而客户端路由状态管理构建工具都有专门解决方案。这些解决方案相互独立,你可以在核心的基础上任意选用其他的部件,不一定要全部整合在一起。

MVVMPattern
  • 声明式渲染:DOM 随状态(数据)更新而更新。(声明式:关注结果,命令式:关注过程(jQuery))

# mvvm 框架

  • 虽然没有完全遵循 MVVM 模型 (opens new window),但是Vue的设计也受到了它的启发。因此在文档中经常会使用 vm(ViewModel的缩写) 这个变量名表示 Vue实例。通过用 Vue 函数创建一个新的**Vue 实例**。

    var vm = new Vue({
      // 选项
    });
    
    1
    2
    3
  • MVVMModel–view–viewmodel)是一种软件架构模式 (opens new window)MVVM支持双向绑定,意思就是当 M 层数据进行修改时,VM层会监测到变化,并且通知 V 层进行相应的修改,反之修改 V 层则会通知 M 层数据进行修改,以此也实现了视图与模型层的相互解耦。View 的变动,自动反映在ViewModel,反之亦然。

  • 关系图

    MVVMPattern

  • 响应式原理

    1. 侵入式:this.a++(不调用方法)

      非侵入式:this.setState({a:this.state.a++})(调用方法)

    2. Object.defineProperty() (opens new window)

      //基本使用
      let obj = { b: 5 };
      Object.defineProperty(obj, "a", {
        //    value:5, //设置值
        //    writable: false, //是否可写(是否能被修改)
        //    enumerable:false,//是否可被枚举(循环对象是否能被输出)
        get() {
          console.log("访问a");
          return 6; //return的值是设置a的属性,没有return,下次访问a是undifined
        },
        set(newValue) {
          console.log("设置a");
          console.log(newValue); //被设置的值被当做参数传入set
        },
      });
      obj.a = 7;
      console.log(obj.a); //a=6,访问时又走了get,在使用临时变量保存改变的值,在set的时候赋值给临时变量,get中返回临时变量
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      //getter/setter需要临时变量周转才能工作
      let obj = {};
      let temp;
      Object.defineProperty(obj, "a", {
        get() {
          console.log("访问a");
          return temp;
        },
        set(newValue) {
          console.log("设置a");
          temp = newValue;
        },
      });
      obj.a = 7;
      console.log(obj.a);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      //封装成函数defineReactive
      let obj = {};
      function defineReactive(data, key, val) {
        //data:数据对象,key:键值,val替代临时变量
        //函数外部可以访问val的值,get和set闭包中的环境
        Object.defineProperty(data, key, {
          enumerable: true, //可枚举
          configurable: true, //可改变或删除
          get() {
            console.log("访问" + key);
            return val;
          },
          set(newValue) {
            console.log("设置" + key);
            if (val == newValue) return;
            val = newValue;
          },
        });
      }
      defineReactive(obj, "a", 10);
      obj.a = 4;
      console.log(obj.a);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
    3. 对象代理

      function Vue(options) {
          let _data = options.data()
          let _this = this
          Object.keys(_data).forEach(i => {//代理对象添加到实列原型
              Object.defineProperty(_this, i, {
                  get() {
                      console.log('Get'+i)
                      return _data[i]
                  },
                  set(newValue) {
                      _data[i]=newValue
                      return newValue
                  }
              })
          })
      
      }
      let vm = new Vue({
          data() {
              return {
                  name: 'Blueless Bird',
                  author: 'Joni'
              }
          }
      })
      vm.name='Hey Boi.'
      console.log(vm.name)
      
      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
    4. 其他

      //this.$options.data().xxx获取初始值
      //el > $mount
      //render > template 替换el的内容
      
      1
      2
      3

# 生命周期函数

  • 在某一时刻自动执行的函数(也叫钩子函数)

  • 图谱

    lifecycle
    1. beforeCreate在初始化 / 实例创建 之前执行的函数
    2. Created在初始化 / 实例创建 之后执行的函数,可以访问methods, data, computed等上的方法和数据
    3. beforeMount在组件内容被渲染到页面之前自动执行的函数

    (注意:可获取到vm.el,但并未挂载)

    1. Mounted在组件内容被渲染到页面之后自动执行的函数
    2. beforeUpdate在数据将要变化之前自动执行的函数
    3. updated在数据发生变化之后自动执行的函数
    4. beforeUnmountVUE实例销毁之前自动执行的函数
    5. unmountedVUE 实例销毁之后自动执行的函数
    6. errorCaptured 捕获一个来自子孙组件的错误时被调用

不要在选项 property 或回调上使用箭头函数 (opens new window),比如 created: () => console.log(this.a)vm.$watch('a', newValue => this.myMethod())。因为箭头函数并没有 thisthis会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function 之类的错误。

  • hook

    export default{
      methods:{
        fn(){
          const timer = setInterval(()=>{
            //具体执行代码
            console.log('1');
          },1000);
          this.$once('hook:beforeDestroy',()=>{
            clearInterval(timer);
            timer = null;
          })
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //父组件
    <rl-child @hook:mounted="childMountedHandle"
    />
    //vue3 改为@vnode-updated
    method () {
      childMountedHandle() {
      // do something...
      }
    },
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 父子组件生命周期执行顺序

    初始化:

    life

    原理:子组件作为父组件的DOM子节点,父组件实例化完成后要挂载这个父组件,调用父组件的render方法方向有子组件,则去 创建渲染子组件并缓存(因为可能有多层),子组件都完成完成父组件的挂载 子组件挂载完成后,父组件还未挂载

    更新 同理:先完成子组件的更新,再完成父组件

    父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated

    销毁

    父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed


# 计算属性

  • 计算属性:依赖 data 中的数据进行计算,与 methods 不同的是只有响应式依赖发生改变时才进行重新计算(计算属性是基于它们的响应式依赖进行缓存的)。模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。

    2
    <div>{{nmu1}}</div>
    <script>
        export default {
            data() {
                return {
                    num: 1,
                };
            },
            computed: {
                nmu1() {
                    //this指向vm实例,需要使用函数声明
                    return this.num + 1;
                },
            },
        };
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    显示代码 复制代码
  • 计算属性的 getter 和 setter

    当我们取计算属性值的时候会执行 get 方法(模板使用),设置的时候会执行 set 方法,computed 属性默认为 getter,但是它还提供了 setter,可以由因变量去影响自变量。

    wangcai

    <template>
    <input v-model="fullname" type="text" />
    <p>{{fullname}}</p>
    </template>
    <script>
        export default {
            data() {
                return {
                    firstName: "wang",
                    lastName: "cai",
                };
            }, //可以return fuc传参
            computed: {
                fullname: {
                    //这时fullname是对象
                    get: function () {
                        console.log("设置值");
                        return this.firstName + this.lastName;
                    },
                    set: function (value) {
                        console.log(value); //这里的value是fullname
                        this.firstName = "zhang"; //当改变fullname时执行set函数
                        this.lastName = "shang";
                    },
                },
            },
        };
    </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
    显示代码 复制代码

# 侦听器

  • 监听属性,属性变化时,自动执行里面的函数,参数:current(现在参数的值),prev(以前参数的值)

    <input v-model="firstName" type="text" />
    <p>{{fullname}}</p>
    <script>
      export default {
        watch: {
          firstName: function (n, o) {
            //改变firstName,执行函数,改变fullname
            this.fullname = n + this.lastName;
            console.log(o);
          },
        },
      };
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  • 深度监听

    1. handler方法和immediate属性

      最初绑定的时候是不会执行的,要等到 firstName 改变时才执行监听计算,修改一下 watch 写法使其初绑定的时候就执行。

      watch: {
          firstName: {
              handler(n, o) {
                  this.fullname =n+this.lastName
                  console.log(o)
              },
                  immediate:true
          },
      },
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
    2. deep属性

      watch里面还有一个属性 deep,默认值是 false,代表是否深度监听。

      <input v-model="obj.a" type="text" />
      <p>{{ obj.a }}</p>
      <script>
        export default {
            data(){
                return {
                    obj: { a: 123 },
                }
            }
            watch: {
            obj: {
            handler() {
                this.obj.a = 567;
                console.log("change");
            },
                //immediate: true,
                deep: true
        //obj.a变化时执行函数,给对象的所有属性都加上监听器
        },
        },
        }
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      //优化,使用字符串形式监听
      watch: {
          'obj.a': { //只给obj.a加监听器
              handler() {
                  console.log("change");
              },
                  immediate: true,
                      deep: true
          },
      },
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
  1. 注销 watch

    watch 写在组件中,会随着组件的销毁而销毁。如果使用下面这样的方式写 watch,那么就要手动注销。

    const unWatch = app.$watch("text", (newVal, oldVal) => {
      console.log(`${newVal} : ${oldVal}`);
    }); //返回unWatch方法
    
    unWatch(); // 手动注销watch
    
    1
    2
    3
    4
    5
  2. watch 监听路由

    可以使用 watch 来进行路由的监听

    watch: {
      changeShowType(value) {
          console.log("-----"+value);
      },
          '$route'(to,from){
              console.log(to);   //to表示去往的界面
              console.log(from); //from表示来自于哪个界面
              if(to.path=="/shop/detail"){
                  console.log("商品详情");
              }
          }
    },
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

# 过滤器

用于一些常见的文本格式化,用在双花括号插值和 v-bind 表达式中。

2024-08-05
{{date|format("prop")}}
<!-- date作为format函数的参数,format也可以传参 -->
<script>
  export default {
    data() {
      return {
        date: new Date(),
      }
    },
    filters: {
      format: function (value, prop) {
        //格式化日期
        //console.log(prop);
        const Y = value.getFullYear();
        const M = value.getMonth() + 1;
        const D = value.getDate();
        const times = Y + (M < 10 ? "-0" : "-") + M + (D < 10 ? "-0" : "-") + D;
        return times;
      },
    },
  };
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
显示代码 复制代码

# 指令

  • v-text

    1. 与花括号的区别是在页面加载时不显示{{}}。

    2. 在的前后可以添加其他的内容,而在 v-text 里面不行。

  • v-html

    更新元素的 innerHTML

    <p v-html="msg"></p>
    <script>
      export default {
        data() {
          return {
            msg: "<input/ placeholder='这是一个输入框'>",
          };
        },
      };
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    显示代码 复制代码

    注意

    1. 在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击 (opens new window)。只在可信内容上使用 v-html永不用在用户提交的内容上。
    2. 单文件组件 (opens new window)里,scoped 的样式不会应用在 v-html 内部,因为那部分 HTML 没有被 Vue 的模板编译器处理。如果你希望针对 v-html 的内容设置带作用域的 CSS,你可以替换为 CSS Modules (opens new window) 或用一个额外的全局 <style> 元素手动设置类似 BEM 的作用域策略。
    3. 浅谈XSS攻击的那些事 (opens new window)
  • v-show

    根据变量控制 dom 显示与隐藏,变量为 false 时,display:none,v-show 不支持 <template> 元素,也不支持 v-else

  • v-if

    根据变量控制 dom 显示与隐藏,变量为 false 时,直接移除 dom 元素。v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级。

  • v-bind

    缩写::绑定 html 标签中的属性值。

    <a :[type]="'http://www.baidu.com'">百度</a>
    <!--写静态要在""中加''-->
    <br />
    <a :[type]="url">百度</a>
    <script>
      export default {
        data() {
          return {
            type: "href",
          };
        },
      };
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  • v-on

    缩写: @事件监听器,

    1. 事件修饰符:@click.prevent(阻止默认行为)
    2. 模板中绑定事件:<Son @click=‘handleClick’> \</Son>
    3. $event传递原生事件:hanclick(count,$event),不传参数,第一个参数为e
  • # 修饰符

    表单:lazy,trim,number

    事件:stop(冒泡),prevent(阻止默认),capture(捕获,使事件触发从包含这个元素的顶层开始往下触发),once(只触发一次),self(只当在 event.target 是当前元素自身时触发处理函数),passive(onscroll事件加一个.lazy),native(监听根元素的原生事件)

    鼠标、键盘:left、right、middle:鼠标左、右、中键点击,keycode:对应键盘码


# 样式绑定

  1. 字符绑定:在 data()在定义变量,v-bind 绑定属性

    给时光以生命,给岁月以文明
    <div :class="str">{{message}}</div>
    <script>
      export default {
        data() {
          return {
            str: "red",
            message: "给时光以生命,给岁月以文明",
          };
        },
      };
    </script>
    <style>
      .red {
        color: red;
      }
      .green {
        color: green;
      }
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    显示代码 复制代码
  2. 对象绑定:传给:class 一个对象

    给时光以生命,而不是给生命以时光
    <div :class="{ red:isRed, blue:isBlue }">{{message}}</div>
    <script>
        export default {
            data() {
                return {
                    message: "给时光以生命,而不是给生命以时光",
                    isRed: false,
                    isBlue: true, //根据isBlue的值控制样式
                };
            },
        };
    </script>
    <style>
        .red{
            color:red;
        }
        .blue{
            color:skyblue
        }
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    显示代码 复制代码

    或者

    <div :class="{ red:false,green:true}">{{message}}</div>
    
    1
  3. 数组绑定:把数组传给 :class

    <div :class="['red', 'active']">{{ message }}</div>
    
    1

    或者 data()中定义数组

    或许是不知梦的缘故,流离之人追逐幻影。
    <div :class="classArray">{{ message }}</div>
    <script>
        export default {
            data() {
                return {
                    message: "或许是不知梦的缘故,流离之人追逐幻影。",
                    classArray: ["pink", "active"],
                };
            },
        };
    </script>
    <style>
        .pink{
            color:pink;
        }
        .active:hover{
            font-weight:bold
        }
    </style>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    显示代码 复制代码
  4. 行内动态样式

    1. 字符串形式

      The closer you look,the less you see.
      <div :style="red">{{ message }}</div>
      <script>
          export default {
              data() {
                  return {
                      message: "The closer you look,the less you see.",
                      red: "color:gold", //字符串样式
                  };
              },
          };
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      显示代码 复制代码
    2. 对象形式

      These violent delights have violent ends
      <div :style="styleObj">{{ message }}</div>
      <script>
        export default {
          data() {
            return {
              message: "These violent delights have violent ends",
              styleObj: { color: "greenYellow " }, //对象样式
            };
          },
        };
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      显示代码 复制代码

    其他

    <el-button :style="list[types]" @click="change" ref="btn">点击变色</el-button>
    <script>
        export default {
            data() {
                return {
                    list:[
                        "backgroundColor:red",
                        "backgroundColor:blue",
                        "backgroundColor:yellow"],
                    types:1
                };
            },
            methods: {
                change(){
                    this.types=++this.types%3
                }
            }
        }
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    显示代码 复制代码
    <el-button
            :class="{red:types==0,
                    blue:types==1,
                    yellow:types==2}"
            @click="types=++types%3"
            ref="btn"
            >
        点击变色
    </el-button>
    <script>
        export default {
            data() {
                return {
                    list:["red","blue","yellow"],
                    types:0
                };
            }
        }
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    显示代码 复制代码

# 列表循环

  • v-for:循环可迭代对象(数组,对象,字符串,数字),vue 要求增加 key 值**(字符串、数值)**避免重复渲染。

  • 零化域的缺失之环
  • 闭时曲线的碑文
  • 对偶福音的规约
  • 模糊像散的孤独
  • 二律背反的双重人格
  • 永劫回归的潘多拉
  • 回折叙唱的鹅妈妈
  • 盟誓的文艺复兴
<li v-for="(item,index) in list" :key="index">{{item}}</li>
<script>
  export default {
    data() {
      return {
        list: [
          "零化域的缺失之环",
          "闭时曲线的碑文",
          "对偶福音的规约",
          "模糊像散的孤独",
          "二律背反的双重人格",
          "永劫回归的潘多拉",
          "回折叙唱的鹅妈妈",
          "盟誓的文艺复兴",
        ],
      };
    },
  };
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
显示代码 复制代码

index 作为 key,逆序添加、逆序删除会导致key发生变化当成新的节点,产生没有必要的真实 DOM 更新

  • 数组方法:

    1. push():从数组尾部添加内容

    2. unshift():从数组头部添加内容

    3. pop():从数组末尾删除内容

    4. shift():从数组头部删除内容

    5. sort():对数组进行排序

    6. reverse():对数组进行取反

    7. splice():从数组中筛出具体的内容

  • 循环与判断不写在同一标签上,v-for 的优先级比 v-if 更高。


  • # 虚拟DOM

    • JS按照DOM结构来实现的树形结构对象

      graph LR
      A(数据改变)-->B(虚拟dom计算变更)-->C(真实dom)-->D(视图更新)
      
      1
      2
      //snabbdom
      import {init, classModule,propsModule, styleModule,eventListenersModule, h} from "snabbdom"; //使用snabbdom库
      const patch = init([classModule, propsModule, styleModule, eventListenersModule]); //初始化patch函数
      const vNode = h("ol", {}, [
          //标签,属性,孩子节点/文本节点(children和text只会存在一个)
          h("li", null, "one"),
          h("li", null, "two"),
      ]);//h函数创建vNode并返回
      const container = document.getElementById("container");//容器
      patch(container, vNode); //将vnode替换空容器,如果patch第一个参数为真实dom,创建vnode并关联dom
      const newVnode = h("ol", null, [
          h("li", null, "one"),
          h("li", null, "two"),
          h("li", null, "three"),
      ]);
      patch(vNode, newVnode)//更新dom,vNode已关联dom
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
    • diff

      同级比较:Diff算法比较只会在同层级进行, 不会跨层级比较

      patch:对比当前同层的虚拟节点是否为同一种类型的标签,是继续执行patch进行深层比对,不是把节点替换成新虚拟节点

      sameVnode:判断是否为同一类型节点(key值,标签名。。。)


    # 双向绑定

    • v-modle:本质上是语法糖

      1. texttextarea 元素使用 value property 和 input 事件;
      2. checkboxradio使用 checked property 和 change 事件;
      3. select 字段将 value 作为 prop 并将 change 作为事件

      记忆就像镜子,映照出灵魂的模样。

      <input type="text" v-model="message" size="30"/>
      <p>{{message}}</p>
      <script>
          export default {
              data() {
                  return {
                      message: '记忆就像镜子,映照出灵魂的模样。',
                  };
              },
          }
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      显示代码 复制代码

      An act of creation always begins with an act of destruction.

      原理

      <input type="text" :value="text" @input="text=$event.target.value" size="50"/>
      <!--value绑定值,input事件获取输入框输入的内容赋值给text-->
      <!--$event是事件对象,$event.target则指的是事件触发的目标,即哪一个元素触发了事件,这将直接获取该dom元素 -->
      <p>{{text}}</p>
      <script>
          export default {
              data() {
                  return {
                      text: 'An act of creation always begins with an act of destruction.',
                  };
              },
          }
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      显示代码 复制代码
    • 修饰符

      v-model.lazy 失去焦点才触发双向绑定

      v-model.number 用户输入的值自动转变为 number 类型

      v-model.trim去掉字符串前后空格

    • .sync 修饰符

      类似于v-modle,也是一个语法糖,使子组件能够修改父组件的值,不限制标签

      <!--父组件-->
      <div id="app">
        .sync的使用
        <el-button v-if="!show" @click="show=true">显示</el-button>
        <Son :visible.sync="show" />
        <!--:visible.sync="show"是
      @update:visible="(val)=>{show = val}" 和             
      :visible="show"的语法糖-->
      </div>
      <script>
        import Son from "./components/Son.vue";
        export default {
          name: "App",
          data() {
            return {
              show: false,
            };
          },
          components: {
            Son,
          },
        };
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <!--子组件-->
      <div v-if="visible">
        对话框
        <el-button @click="$emit('update:visible', false);">隐藏</el-button>
        <!--触发update:visible事件改变父组件的值-->
      </div>
      <script>
        export default {
            name: "Son",
            data(){
                return {
                },
                    props: {
                        visible: { type: Boolean, default: false },
                    },
            }
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17

    # 组件

    • 组件注册(定义==>注册==>使用)

      1. 定义子组件(子组件必须要有唯一的根标记)

        <div>我是子组件</div>
        <script>
          export default {
              name: "Son",
              data(){
                  return {//一个组件的 data 选项必须是一个函数,如果 Vue 没有这条规则,就会影响到其它所有实例
                  }
              }
        </script>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
      2. 注册组件

        //根组件中全局注册
        /*Vue.component('my-component-name', {
          // ... 选项 ...
        })*/
        import Son from "./components/Son.vue";
        import Vue from "vue";
        Vue.component("Son", Son);
        //组件命名 大驼峰引入,大驼峰或横线注册,html中大驼峰或横线使用
        
        1
        2
        3
        4
        5
        6
        7
        8
        <!--父组件中局部注册并使用-->
        <div>
          我是父组件
          <Son />
          <Son />
        </div>
        <script>
          import Son from "./components/Son.vue";
          import Son1 from "./components/Son1.vue";
          export default {
            components: {
              Son,
              //"Son":Son简写,为了和普通变量区分,建议首字母大写
              Son1,
            },
          };
        </script>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
    • 父子组件通信

      # 父子组件的关系可以总结为prop向下传递,事件向上传递。

      1. 父组件通过自定义属性传值,子组件props接收.

        camelCase(驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名

        不能修改传递过来的数据

        <!--父组件-->
        <div id="app">
          我是父组件
          <Son :message="msg" />
          <!--使用v-bind绑定动态Prop-->
          <Son :message="msg1" />
        </div>
        <script>
          import Son from "./components/Son.vue";
          export default {
            name: "App",
            data() {
              return {
                msg: "hello!",
                msg1: "Son",
              };
            },
            components: {
              Son,
            },
          };
        </script>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        <!--子组件-->
        <div>{{ message }}</div>
        <script>
          export default {
              name: "Son",
              data(){
                  return {
                  },
                      props: ['message'],//以字符串数组形式列出的 prop,prop不能与data中的数据重名
              }
        </script>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        Prop验证:props为带有验证需求的对象,prop验证失败的时候, Vue 将会产生一个控制台的警告

        props: {
            // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
            propA: Number,
                // 多个可能的类型
                propB: [String, Number],
                    // 必填的字符串
                    propC: {
                        type: String,
                            required: true
                    },
                        // 带有默认值的数字
                        propD: {
                            type: Number,
                                default: 100
                        },
                            // 带有默认值的对象
                            propE: {
                                type: Object,
                                    // 对象或数组默认值必须从一个工厂函数获取
                                    default: function () {
                                        return { message: 'hello' }
                                    }
                            },
                                // 自定义验证函数
                                propF: {
                                    validator: function (value) {
                                        // 这个值必须匹配下列字符串中的一个
                                        return ['success', 'warning', 'danger'].indexOf(value) !== -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
      2. 子组件触发自定义事件传值

        <!--子组件-->
        <button @click="increment">子组件(点击传值)</button>
        <script>
            export default {
                name: "Son",
                //如果向父组件传递的方法很多,可以在这里标识出来
                //emits 可以是数组或对象,从组件触发自定义事件,emits 可以是简单的数组,也可以是对象,后者允许配置事件验证。
                emits: ['increment'],
                methods: {
                    increment(){
                        this.$emit('increment',2,'第二个参数')//触发自定义事件传值,
                    },
                }
            }
        </script>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        <!--父组件-->
        <div id="app">
            {{count}}
            <!--自定义事件,通过$emit触发-->
            <Son @increment="add" />
        </div>
        <script>
            import Son from "./components/Son.vue";
            export default {
                name: "App",
                components: { Son },
                data() {
                    return {
                        count: 0,
                    };
                },
                methods: {
                    add(value, sec) {
                        //触发事件时传的值
                        this.count += value;
                        console.log(value);
                        console.log(sec);
                    },
                },
            };
        </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
      3. 父组件传方法,子组件调用

        <!--父组件-->
        <div id="app">
            {{count}}
            <Son :change="add" />
            <!--传递了add方法,父组件的方法能改变父组件的数据-->
        </div>
        <script>
            import Son from "./components/Son.vue";
            export default {
                name: "App",
                components: { Son },
                data() {
                    return {
                        count: 0,
                    };
                },
                methods: {
                    add(value) {
                        //子组件调用时传值
                        this.count += value;
                        console.log(value);
                    },
                },
            };
        </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
      4. ref 属性

        <!--子组件-->
        <div>子组件</div>
        <script>
            export default {
                name: "Son",
                data(){
                    return {
                        message:'子组件数据',
                    },
                }
        </script>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        <!--父组件-->
        <div id="app">
            {{msg}}
            <Son ref="son" />//绑定ref
        </div>
        <script>
            import Son from './components/Son.vue'
            export default {
                name: 'App',
                components: {Son}
                mounted(){
                    this.msg=this.$refs.son.message
                    console.log(this.$refs.son.message)
                    //通过$refs获取组件实例,得到组件数据和方法
                },
                    data(){
                        return{
                            msg:'',
                        }
                    },
        
            }
        </script>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
      5. slot 插槽

        父组件控制插槽是否显示、怎样显示,子组件控制槽在哪显示(占位符)

        slot写在组件template的什么位置,父组件传过来的模板将来就显示在什么位置。

        <!--父组件-->
        <div id="app">
            父组件
            <Son @change="add">插槽内容:{{msg}}</Son>
            <!--标签内容显示在子组件的<solt>元素位置,插槽内可以包含其他html标签或其它的组件,作用域为父组件作用域,没有<solt>元素则废弃标签内容-->
        </div>
        <script>
            import Son from './components/Son.vue'
            export default {
                name: 'App',
                components: {Son}
                data(){
                    return{
                        msg:'父组件数据',
                    }
                },
        
            }
        </script>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        <!--子组件-->
        <div>子组件</div>
        <slot>默认内容</slot>
        <!--父组件什么都不写的话显示默认内容-->
        <!--一个不带name的<slot>出口会带有隐含的名字“default”-->
        <!--任何没有被包裹在带有 v-slot 的 组件标签 中的内容都会被视为默认插槽的内容-->
        <!--v-slot只能添加在<template>上或自定义组件上(只有一种例外情况)-->
        
        1
        2
        3
        4
        5
        6
        7

        具名插槽:< slot name=”mySlot”>有命名时,组件标签中使用属性slot=”mySlot”的元素就会替换该对应位置内容

        <!--父组件-->
        <Son>
            <div slot="header">头部</div>
            <!--标签内部什么都不写显示默认内容-->
            <div slot="content">内容</div>
            <!--新写法`#`为`v-slot:`的缩写-->
            <div #footer>页脚</div>
        </Son>
        
        1
        2
        3
        4
        5
        6
        7
        8
        <!--子组件-->
        <slot name="header">默认内容</slot>
        <slot name="content">默认内容1</slot>
        <slot name="footer">默认内容2</slot>
        
        1
        2
        3
        4

        作用域插槽:子传父传参的方式,解决了普通slot在 parent 中无法访问 child 数据的问题

        父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

        <!--子组件-->
        <h1>子组件</h1>
        <slot :data="list" name="son">{{def}}</slot>
        <!--子组件把list传递给插槽,不写name,name为fefault-->
        <script>
            export default {
                name: "Son",
                data(){
                    return {
                        def:'默认内容',
                        list: [
                            "起源与终结的序章","时空跳跃的妄想症",
                            "并列过程的偏执狂","空理彷徨的会谈",
                            "电荷冲突的接点","蝶翼的偏差值",
                            "断层的变动率","梦幻的自平衡",
                            "时空境界的信条","自我相似的雌性同株",
                            "形而上的坏死","虚像歪曲的混合",
                            "因果律的溶解","镜面上的命运石之门"
                        ],
                    },
                }
        </script>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
           <!--父组件-->
         <Son>
             <template #son="prop">
               <!--父组件使用一个变量来接收,这里变量为prop(随便取名),也可以解构{list},prop作为参数传入函数-->
               <li v-for="(v,i) in prop.list" :key="i">{{v}}</li>
             </template>
           </Son>
        
        1
        2
        3
        4
        5
        6
        7

        https://juejin.cn/post/6954284127528943629

        http://www.jianboge.com/d148386


    # 动态组件 & 异步组件

    • 动态组件:通过动态属性is结合component标签实现,<component /:is="xxxx" />,通常和keep-alive一起使用保持组件状态,is的值为组件名、组件对象或函数式组件。

      Component1
      Component2
      Component1
      Component1
      <div>
          <div v-for="(v, i) in list" :key="i">
              <component :is="v.type" />
          </div>
      </div>
      <script>
          const Component1 = {
              render(h) {
                  return <span>Component1</span>;
              },
          };
          const Component2 = {
              render(h) {
                  return <span>Component2</span>;
              },
          };
          export default {
              data() {
                  return {
                      list: [
                          { type: "Component1" },
                          { type: "Component2" },
                          { type: "Component1" },
                          { type: "Component1" },
                      ],
                  };
              },
              components: {
                  Component1,
                  Component2,
              },
          };
      </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
      显示代码 复制代码
      <!--缓存组件(子组件为input)-->
      <!--使用keep-alive缓存组件状态,切换时input框输入的内容还存在-->
      <div>
          <keep-alive>
              <component :is="com" />
          </keep-alive>
          <el-button @click="change">change</el-button>
      </div>
      <script>
          const Component1 = {
              data() {
                  return {
                      value: "Component1",
                  };
              },
              render(h) {
                  return <input v-model={this.value} />;
              },
          };
          const Component2 = {
              data() {
                  return {
                      value: "Component2",
                  };
              },
              render(h) {
                  return <input v-model={this.value} />;
              },
          };
          export default {
              data() {
                  return {
                      com: "Component1",
                  };
              },
              components: { Component1, Component2 },
              methods: {
                  change() {
                      this.com === "Component1"
                          ? (this.com = "Component2")
                      : (this.com = "Component1");
                  },
              },
          };
      </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
      显示代码 复制代码
    • 异步组件:异步执行某些组件逻辑,加载页面时会下载所有组件代码,异步组件类似路由懒加载(按需加载),优化性能。

      <!--基本使用-->
      <div id="app">
        异步组件的使用
        <button @click="show=!show">{{show?'隐藏':'显示'}}</button>
        <Son v-if="show" />
      </div>
      <script>
            import Son from './components/Son.vue'
            export default {
                name: 'App',
                components: {
                    Son:()=>(import(/*webpackChunkName:'Son'*/'./components/Son.vue')),
        //当show为true时才加载js代码并缓存代码,webpackChunkName可以给该js文件取名
                }
                data(){
                    return{
                        show: false,
                  }
                },
      
            }
       </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <!--异步组件工厂函数-->
      <div id="app">
          异步组件的使用
          <button @click="show=!show">{{show?'隐藏':'显示'}}</button>
          <AsyncComponent v-if="show" />
      </div>
      <script>
          import Error from './components/Error.vue'
          import Loading from './components/Loading.vue'
          import Son from './components/Son.vue'
          //异步组件工厂函数
          const AsyncComponent=()=>({
              component:import (/*webpackChunkName:'Son'*/'./components/Son.vue'),//promise对象
              loading:Loading,
              error:Error,
              delay:200,//延迟200ms(先显示loading组件)
              timeout:3000//超过3000ms显示error组件
          })
          export default {
              name: 'App',
              components: {
                  AsyncComponent
              }
              data(){
                  return{
                      show: false,
                  }
              },
      
          }
      </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

      vue3 使用辅助函数defineAsyncComponent声明,component 选项更名为loader,选项可以添加onErorr回调重试

    • 动态异步组件

      //组件对象
      const loading = {
          name: "loading",//keep-alive排除组件
          template:
          '<div >加载中....</div>',
      };
      const error = {
          name: "error",
          template:
          '<div >加载错误</div>',
          inject: ["reload"],
          created() {
              this.reload && this.reload(); //重试
          },
      };
      const component = {
          template:
          '<div >I am async!</div>',
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <keep-alive :exclude="['error', 'loading']">
          <!-- keep-alive需要排除error,loading组件-->
          <component
                     :is="AsyncComponent"
                     ></component>
      </keep-alive>
      <script>
          export default {
              data() {
                  return {
                      attempts: 0,
                      isGetCom: false,
                  }
              },
              computed: {
                  AsyncComponent(){
                      return () => ({
                          component: new Promise((resolve) => {
                              setTimeout(() => {
                                  resolve(component);
                              }, 1500);
                          }),
                          error,
                          loading,
                          delay: 0,
                          timeout: 1000,
                      })
                  }
              },
          }
      </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
    • 异步组件失败解决

      <!--method1:利用key刷新父组件-->
      <async-com :key="attempts" />
      <script>
          //vm.$forceUpdate()可以迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
          //重要提示:这不会更新任何计算属性,调用`forceUpdate`仅仅强制重新渲染视图。
          import AsyncCom from "./components/AsyncCom.vue";
          export default {
              data() {return {
                  attempts: 0,
                  //key放动态组件上会因为timeou时间已过导致无限循环
              };
                     },
              provide: function () {
                  return {
                      reload: this.reload,
                      //error组件中调用
                  };
              },
              methods: {
                  reload() {
                      if (this.attempts < 10) console.log("尝试次数", ++this.attempts);
                  },
              },
              components: {
                  AsyncCom,
              },
      </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
      <!--method2:更新计算属性的依赖项更新计算属性,导致视图更新-->
      <script>
          export default {
              data() {return {
                  attempts: 0,
              }},
              computed: {
                  AsyncComponent() {
                      return () => ({
                          component, error,loading, delay: 0,timeout: 1000,
                          attempts: this.attempts,
                      })
                  }
              },
              methods: {
                  reload() {
                      if (this.attempts < 10) {
                          //与方法1类似error组件调用
                          console.log("尝试次数", ++this.attempts);
                      }
                  },
              }
          }
      </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
      <!--method3:获取组件后更新计算属性-->
      <script>
          let component//使用变量保存组件
          export default {
              data() {
                  return {
                      isGetCom: false,//改变依赖触发计算属性更新
                  }
              },
              computed: {
                  AsyncComponent() {
                      if (!this.isGetCom) 
                          return () => ({
                              component: new Promise((resolve) => {
                                  setTimeout(() => {
                                      const com={
                                          template:'<div>I am async!</div>',
                                      };
                                      component=com
                                      this.isGetCom=true//计算属性重新计算     
                                      //resolve(this.component);
                                  }, 1500);
                              }),
                              error,
                              loading,
                              delay: 0,
                              timeout: 1000,
                          });
                      else return component;
                  },
              }}
      </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

    # Mixin 混入

    • 提取组件公共逻辑(包含任意组件选项如 data,mehoods,生命周期)

      //定义minin
      export default {
          data() {
              return {
                  dataCommomd: "公共数据",
              };
          },
          methods: {
              methodsCommomd() {
                  console.log(this.type + "公共方法");
              },
          },
          mounted() {
              console.log("公共mounted");
          },
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <!--子组件A(组件B类似)-->
      <template>
      <div>
          <button @click="methodsA">
              组件A方法
          </button>
          <button @click="methodsCommomd">
              公共方法
          </button>
          </div>
      </template>
      <script>
          import mixin from './mixin'
          export default {
              mixins:[mixin],
              data(){
                  return{
                      aData:'组件A的数据',
                      type:"组件A"
                  }
              },
              methods: {
                  methodsA(){
                      console.log("组件A的方法")
                  }
              },
              mounted(){
                  console.log('组件A的mounted')
                  console.log(this.type+this.dataCommomd)
              }
          }
      </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
      <!--父组件-->
      <div id="app">
        <h1>Minin的使用</h1>
        <component-a />
        <hr />
        <component-b />
      </div>
      <script>
        import componentA from "./components/ComponentA.vue";
        import componentB from "./components/ComponentB.vue";
        export default {
          name: "App",
          components: {
            "component-a": componentA,
            "component-b": componentB,
          },
          data() {
            return {};
          },
        };
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
    • 优先级

      1. 数据和方法:组件 data ,methods 优先级高于 mixin data,methods 优先级
      2. 生命周期:生命周期函数,先执行 mixin 里面的,再执行组件里的
      3. 自定义的属性,组件中的属性优先级高于 mixin 属性的优先级
    • 其他

      1. 局部Mixin:mixins:[myMixin],myMixin 是一个自定义组件
      2. 全局 Mixin:app.mixin( )
      3. Vue3中尽量避免使用Mixin,因为可以使用 CompositionAPI 来代替,可维护性更高。

    # extends


    # 依赖注入

    祖先组件使用provide选项向后代提供数据/方法,后代组件通过inject 选项接收propertyproperty是非响应式的。

    <!--祖先组件-->
    <Son />
    <script>
        import Son from "./components/Son";
        const store = Vue.observable({ name: "张三" });
        export default {
            data() {
                return {
                    msg: "Some people choose to see the ugliness in this world.The disarray.I choose to see the beauty",
                    obj:{name:'dax1'},//同样是响应的
                };
            },
            provide: function () {//对象或返回一个对象的函数
                this.store = store;
                return {
                    store: this.store,//如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的
                    msg: this.msg,
                    parent: this, //当前组件实例
                };
            },
            mounted() {
                this.store.name = "李四";
            },
            components: {
                Son,
            },
        };
    </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
    <!--后代组件-->
    <div>{{this.msg}}</div>
    <script>
      export default {
        inject: ["msg", "parent"],//一个字符串数组,或一个对象
        mounted() {
          console.log(this.parent);
        },
      };
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 事件总线

    • vue2

      //定义eventbus
      //event-bus.js
      import Vue from "vue";
      export const eventBus = new Vue(); //eventBus就是一个Vue实例
      
      1
      2
      3
      4
      <!--组件1-->
      {{ age }}
      <el-button @click="add">加1</el-button>
      <script>
        import { eventBus } from "./event-bus.js"; //需要是同一个eventBus
        export default {
          data() {
            return {
              age: 18,
            };
          },
          methods: {
            add() {
              this.age++;
              eventBus.$emit("add-age"); //发送消息
            },
          },
        };
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <!--组件2-->
      {{ age }}
      <el-button @click="add">加1</el-button>
      <script>
          import { eventBus } from "./event-bus.js";
          export default {
              data() {
                  return { age: 18 }
              },
              methods: {
                  add() {
                      this.age++;
                  },
              },
              created() {
                  eventBus.$on("add-age", () => {
                      this.age++;
                  }); //接收消息
              },
              beforeDestroy() {
                  // 如果没有提供参数,则移除所有的事件监听器;
                  // 如果只提供了事件,则移除该事件所有的监听器;
                  // 如果同时提供了事件与回调,则只移除这个回调的监听器。
                  eventBus.$off("add-age", () => {
                      this.age++;
                  });
              },
          };
      </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
    • vue3

      //使用mitt
      import mitt from 'mitt'
      app.config.globalProperties.$bus = mitt();
      
      1
      2
      3
      <!--组件1触发-->
      <script lang="ts" setup>
          import { getCurrentInstance } from "vue";
          const { proxy } = getCurrentInstance();
          function emit() {
              proxy.$bus.emit('message',{msg:'北海虽赊,扶摇可接'})
          }
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      <!--组件2监听-->
      <script lang="ts" >
         created() {
          this.$bus.on('search',(e:Object)=>{
              console.log(e);
          })
        },
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8

    # directive

    • 定义方法

      1. 全局:app.directive('focus',{mounted(el){el.focus()}})

      2. 局部:局部指令需要在父组件注册后才可以使用

        directives: { focus: { // 指令的定义 inserted: function (el) { el.focus() } } }

      //navActive.js
      export default {
        //指令就是对象
        bind: (el, bind) => {
          // console.log('bind')
          // console.log(el) el为指令绑定的dom元素
          // console.log(bind); bind.value可以拿到绑定指令传的值
          el.onclick = () => {
            //点击更改样式变红,获取父元素把重置兄弟元素样式
            el.className = "el-button el-button--danger";
            const children = Array.from(el.parentNode.children);
            children.forEach((v, i) => {
              if (i !== bind.value.index)
                v.className = "el-button el-button--default";
            });
          };
        },
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <div>
        <!--3个按钮,指令传index,bing.value获取-->
        <el-button v-nav-active="{index:i}" v-for="(v,i) in 3" :key="i"
          >按钮{{v}}</el-button
        >
      </div>
      <script>
        import NavActive from "./navActive.js";
        export default {
          name: "Son",
          directives: {
            NavActive, //注册指令
          },
          data() {
            return {};
          },
        };
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
    • 生命周期函数


    # 渲染函数

    字符串模板之外的另一种选择,允许你充分利用 JavaScript 的编程功能。

    <!-- 不加:会解析为string类型 -->
    <Son :leave=1>黄金色的树林里分出两条路,</Son>
    <Son :leave=2>可惜我们不能同时去涉足,</Son>
    <Son :leave=3>但我们却选择了,</Son>
    <Son :leave=4>人迹罕至的那一条,</Son>
    <Son :leave=5>这从此决定了</Son>
    <Son :leave=6>我们的一生。</Son>
    
    1
    2
    3
    4
    5
    6
    7
    <p :style="`font-size:${fontSize}`"><slot/></p>
    <script>
        export default {
            name: "Son",
            props: ["leave"],
            computed: {
                fontSize: function () {
                    if (this.leave % 2) {
                        return 10 + (6 - this.leave) * 3 + "px";
                    } else {
                        return 10 + (6 - this.leave) * 2 + "px";
                    }
                },
            },
        };
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    使用render函数创建标签

    黄金色的树林里分出两条路,

    可惜我们不能同时去涉足,

    但我们却选择了,

    人迹罕至的那一条,

    这从此决定了
    我们的一生。
    <div>
        <Son :leave="1">黄金色的树林里分出两条路,</Son>
        <Son :leave="2">可惜我们不能同时去涉足,</Son>
        <Son :leave="3">但我们却选择了,</Son>
        <Son :leave="4">人迹罕至的那一条,</Son>
        <Son :leave="5">这从此决定了</Son>
        <Son :leave="6">我们的一生。</Son>
    </div>
    <script>
        const Son = {
            name: "Son",
            props: ["leave"],
            render(h) {
                //h是createlement函数
                return h(`h${this.leave}`, this.$slots.default);
                // 或使用jsx语法
                // const tag = "h" + this.leave;
                // return (<tag>{this.$slots.default}</tag>);
            },
        };
        export default {
            name: "Test",
            components: { Son },
        };
    </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
    显示代码 复制代码
    • 岁月流转,念念不忘,群碑未成,来日方长 
    • 日斜映影渐薄,犹待来者,重托衣钵 
    • 她实惠君良多,更有无穷,待君探索 
    • 此路或有蹉跎,君莫忘,必有灵犀相佐
    <script> 
        export default {
            data() {
                return {
                    list: [
                        "岁月流转,念念不忘,群碑未成,来日方长 ",
                        "日斜映影渐薄,犹待来者,重托衣钵 ",
                        "她实惠君良多,更有无穷,待君探索 ",
                        "此路或有蹉跎,君莫忘,必有灵犀相佐",
                    ],
                };
            },
            props: {
                tag: { type: String, default: "ul" },
            },
            render(h) {
                //使用template也会被编译为render函数
                return h(
                    this.tag, //也可以是一个组件
                    {},
                    this.list.map((i) => {
                        return h("li", { attrs: { style: "color:skyblue" } }, i); //需要return一个value
                    })
                );
            },
        };
    </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
    显示代码 复制代码

    # Vue-Router路由

    安装:Vue add router

    • 根据 URL 的不同,展示不同的内容

      //route/index.js文件
      import Vue from "vue";
      import Router from "vue-router";
      import layout from "../components/Layout.vue";
      import Son from "../components/Son.vue";
      import notFond from "../components/NotFond.vue";
      Vue.use(Router); //使用插件
      //该插件实现1.声明并全局注册router-link与router-view2.实现install:this.$router.push()
      export default new Router({
        //创建实例
        mode: "history", //路由模式,默认hash,history无#
        base: process.env.BASE_URL,
        //配置路由表(路由规则)
        routes: [
          {
            path: "/",
            name: "home",
            component: layout,
          },
          {
            path: "son",
            name: "son",
            component: Son,
          },
          {
            path: "*",
            name: "notFond",
            component: notFond,
          },
        ],
      });
      //匹配到path路径后找对应的component组件,在'router-view'处渲染
      //匹配不到路径渲染notFond组件
      
      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
      //还需要在main.js中将路由实例挂载给Vue实例
      import router from "./router/index.js";
      new Vue({ router, render: (h) => h(App) }).$mount("#app"); //添加配置项
      
      1
      2
      3
      <!--app.vue一级路由匹配到的组件将显示在路由出口-->
      <template>
        <router-view></router-view>
      </template>
      
      1
      2
      3
      4
    • 动态路由

      路由传参,通过 route.param 接收

      //路由index.js
      import Vue from "vue";
      import Router from "vue-router";
      const routes = [
        {
          path: "/",
          name: "home",
          component: layout,
          children: [
            {
              path: "/login",
              name: "login",
              component: Login,
            },
            {
              path: "/reg",
              name: "reg",
              component: Reg,
            },
            //动态路由,参数名为id
            {
              path: "/user/:id",
              name: "user",
              component: User,
            },
          ],
        },
        {
          path: "*",
          name: "notFond",
          component: notFond,
        },
      ];
      Vue.use(Router);
      export default new Router({
        mode: "history",
        base: process.env.BASE_URL,
        routes,
      });
      //解决重复点击报错
      const originalPush = Router.prototype.push;
      Router.prototype.push = function push(location) {
        return originalPush.call(this, location).catch((err) => err);
      };
      
      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
      <!--user组件-->
      <template>
        <div>用户信息{{id}}</div>
      </template>
      <script>
        export default {
          data() {
            return {
              id: this.$route.params.id,
              //直接这么写页面上的id并不会变化,路由变化并不会重新创建组件,id是初始化的时候赋值的所以不会改变
            };
          },
          watch: {
            //监听路由或使用组件内路由守卫改变id
            $route(n, o) {
              this.id = n.params.id;
              console.log(o);
            },
          },
        };
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
    • 嵌套路由

      //重新定义路由表
      routes: [
          {
              path: "/",
              name: "home",
              component: layout,
              children: [
                  {
                      path: "/login",
                      name: "login",
                      component: Login,
                  },
                  {
                      path: "/reg",
                      name: "reg",
                      component: Reg,
                  },
              ],
          },
          {
              path: "*",
              name: "notFond",
              component: notFond,
          },
      ],
      
      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
      <el-container>
        <el-aside width="200px">Aside</el-aside>
        <el-container>
          <el-header>Header</el-header>
          <el-main
            >Main
            <!--点击跳转路由切换router-view内容-->
            <router-link to="/login">登录</router-link>
            <router-link to="reg">注册</router-link>
            <!--二级路由将显示在这-->
            <router-view></router-view>
          </el-main>
          <el-footer>Footer</el-footer>
        </el-container>
      </el-container>
      <!--配合element的Container布局容器-->
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
    • 动态添加路由

      <el-container>
          <el-aside width="200px">Aside</el-aside>
          <el-container>
              <el-header>Header</el-header>
              <el-main
                       >Main
                  <a @click="login">登录</a>
                  <router-link to="/reg">注册</router-link>
                  <router-link to="/new" v-if="isLogin">动态添加的路由</router-link>
                  <router-view></router-view>
              </el-main>
              <el-footer>Footer</el-footer>
          </el-container>
      </el-container>
      <script>
      import New from "./New"; //新路由
      const newRouter = [
        {
          path: "/new",
          component: New,
        },
      ];
      export default {
        data() {
          return {
            isLogin: false,
          };
        },
        methods: {
          login() {
            this.isLogin = true;
            this.$router.push("/login");
            this.$router.addRoutes(newRouter);
          },
        },
      };
      </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

      addRoutes添加的路由为一级路由,,如果是二级路,那要把父路由、祖先路由也带上,否则不会识别。vue3改用 router.addRoute,可以添加一条新的路由规则记录作为现有路由的子路由。

    • 导航守卫

      路由守卫用于路由跳转的一些验证

      1. 全局守卫:前置守卫:beforeEach 后置钩子:afterEach

      2. 单个路由守卫:独享守卫:beforeEnter

      3. 组件内部守卫:beforeRouteEnter beforeRouteUpdate beforeRouteLeave

      4. 所有的路由守卫都是三个参数(to,from,next)

        to: 要进入的目标路由(去哪儿)

        from: 要离开的路由(从哪来)

        next: 是否进行下一步(要不要继续)

      5. next()相当于 next(true) 放行,不写 相当于next(false)终止执行,next(path)跳转 例如:next("/login"),注意:后置钩子afterEach没有next参数

      //main.js中设置全局路由前置守卫
      import router from "./router/index.js";
      // eslint-disable-next-line //忽略下行eslint校验
      const login = false;
      router.beforeEach((to, from, next) => {
        if (to.path !== "/user") next();
        //如果不去/user路由,放行
        else {
          if (login == true) {
            //如果登录,放行
            next();
          } else {
            //未登录跳转'/login'路由
            next("/login");
          }
        }
      });
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      //路由独享的守卫(进入这个路由之前)
      {
          path: "/user",
              name: "user",
                  component: User,
                      // eslint-disable-next-line
                      beforeEnter: (to, from, next) => {
                          if (to.path !== "/user") next();
                          else {
                              if (login == true)  next();
                              else  next("/login");
      
                          }
                      },
      },
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      <!--组件内的守卫-->
      <div>用户信息</div>
      <script>
        const login = false;
        export default {
          data() {
            return {
              login: true,
              //不!能!获取组件实例 `this`,因为当守卫执行前,组件实例还没被创建
              //可以通过next(vm => {// 通过 `vm` 访问组件实例})
              //beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调
            };
          },
          beforeRouteEnter(to, from, next) {
            // eslint-disable-next-line
            if (login == true) next();
            else next("/login");
          },
        };
      </、script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
    • api

      $router全局对象

      push(path)

      replace(path)

      back()

      go(-1/1...)

      $route当前路由对象

      params

      query

      path

      name(路由表中定义)

    • 其他

      1. Home 模板中使用的路由跳转标签
      2. 标签, 负责展示当前路由对应的组件内容
      3. 异步加载路由:component:( ) => import('../xxxx')

      4. 命名路由搭配 params,刷新页面参数会丢失

        this.$router.push({ name: "news", params: { userId: 123 } });
        
        1

        查询参数搭配 query,刷新页面数据不会丢失

        this.$router.push({ path: "/news", query: { userId: 123 } });
        
        1

    # vue-route原理 (opens new window)

    Vue.use(Router); //Router是插件,插件是函数或对象
    // var a=function(){//第一个参数是vue构造函数
    //console.log(vue);
    //      console.log("function")
    // }
    var a = {};
    a.install = function (vue) {
      //install方法的第一个参数是vue构造函数
      console.log(vue);
      console.log("install");
    };
    Vue.use(a); //use时会执行a中的代码,如果a有install属性,则执行install方法
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //vueRoute.js
    let Vue;
    class VueRoute {
        constructor(options) {
            // console.log(options);
            // this.current = "/";
            Vue.util.defineReactive(this, "current", "/"); //将current实现响应式
            console.log(this.current);
            this.routes = options.routes; //用户定义路由表
            this.modes = options.mode || "hash";
            this.init(); //监听路由改变
            this.push = () => {}; //...
        }
        init() {
            if (this.modes === "hash") {
                //第一次加载
                window.addEventListener("load", () => {
                    this.current = location.hash.slice(1);
                });
                //地址栏改变时
                window.addEventListener("hashchange", () => {
                    //通过hashchange监听
                    this.current = location.hash.slice(1);
                });
            } else {
                //处理history
                //利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法结合window.popstate事件(监听浏览器前进后退)。
                window.addEventListener('popstate',()=>{
                })
            }
        }
    }
    VueRoute.install = (_vue) => {
        Vue = _vue; //保存Vue
        //创建全局组件
        Vue.component("router-link", {
            props: {
                to: {
                    //使用组件需要传href
                    type: String,
                    require: true,
                },
            },
            render(h) {
                //创建html标签,router-link避免了重复渲
                return h("a", { attrs: { href: "#" + this.to } }, this.$slots.default);
            },
        });
        Vue.component("router-view", {
            render(h) {
                //变量为响应式的才执行
                const { current, routes } = this.$router;
                const com = routes.find((item) => item.path == current);
                //console.log("com", com);
                return h(com.component);
            },
        });
        //全局混入,给vue原型添加router属性
        Vue.mixin({
            beforeCreate() {
                if (this.$options.router) {
                    //根实例
                    Vue.prototype.$router = this.$options.router; //给vue原型添加$router属性
                }
            },
        });
    };
    export default VueRoute;
    
    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

    # 插件

    • 对象形式

      const obj = {
          install: function (app:any, options1:any,option2:any) {
              console.log("使用插件");
              console.log('app',app);
              console.log(options1,option2);
          }
      }
      app.use(obj,'option1','option2')//执行install方法,
      
      1
      2
      3
      4
      5
      6
      7
      8
    • 函数形式

      const fun=function(app:any,opt1:any){
          console.log(app);
          console.log(opt1);
          
      }
      app.use(fun,'option1')//执行函数
      
      1
      2
      3
      4
      5
      6

    # VueX (opens new window)

    • 单向数据流

      单向数据流
    • Vuex数据管理框架,VueX 创建了一个全局唯一的仓库,用来存放全局的数据。

      //基本使用(vue add vuex)
      //创建store下的index.js文件
      import Vue from "vue";
      import Vuex from "vuex";
      
      Vue.use(Vuex);
      //创建VueX对象
      export default new Vuex.Store({
        state: {
          //存放要管理的状态
          count: 0,
          list: [
            { tilie: "昔我往矣 (Wake up from the past)", time: "0:28" },
            { tilie: "镜花水月 (The mirror)", time: "0:55" },
            { tilie: "谁解其中味 (The secret)", time: "1:00" },
            { tilie: "夜来幽梦 (Wake up)", time: "0:26" },
            { tilie: "零落成泥 (Dust to dust)", time: "1:36" },
            { tilie: "在水一方 (Where is she)", time: "1:38" },
          ],
        },
        mutations: {
          //这里是set方法
          increment(state, payload) {
            state.count += payload;
          },
          decrement(state, payload) {
            state.count -= payload;
          },
        },
        actions: {
          //异步操作
          async({ commit }) {
            //Actions接受一个context对象参数,该参数具有和store实例相同的属性和方法
            setTimeout(() => {
              commit("increase", 3);
            }, 1000);
          },
        },
        getters: {
          // 这里是get方法
          newList(state) {
            state.list.push({
              tilie: "送别 (Farewell by Ms.Chen)",
              time: "1:00",
            });
            return state.list;
          },
        },
      });
      
      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
      //需要在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到
      import store from "./store/index";
      new Vue({ store, render: (h) => h(App) }).$mount("#app");
      //挂载
      
      1
      2
      3
      4
      <!--子组件-->
      <template>
        <div>
          <!--添加对多个参数不能直接加',',否则第二个参数是undifined,应该封装成对象或数组-->
          <el-button @click="$store.commit('increment', 2)">加2</el-button>
          <el-button @click="$store.commit('decrement', 2)">减2</el-button>
          总数:{{ $store.state.count }}
        </div>
      </template>
      <script>
        export default {
          name: "Son",
          data() {
            return {
              //count:this.$store.state.count
              //这么写改变数据时页面不更新
            };
          },
        };
      </>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
    • 核心内容

      1. state 存放状态
      2. mutations state 成员操作commit(‘mutation’,paylod)
      3. getters 加工 state 成员给外界
      4. actions 异步操作dispatch(‘action’,paylod)
      5. modules 模块化状态管理
    • 增删 state 中的成员

      //使用delete或者xx.xx = xx的形式去删或增,则Vue不能对数据进行实时响应。
      Vue.set(state, "age", 15); //添加
      Vue.delete(state, "age"); //删除
      
      1
      2
      3
    • 辅助函数

      <!--子组件-->
      <el-button @click="increase(2)">加2</el-button>
      <!-- {{ $store.state.count }} -->
      {{count}}
      <el-button @click="decrease(2)">减2</el-button>
      <el-button @click="async">async</el-button>
      <el-table :data="newList" style="width: 30%" class="table">
        <el-table-column type="index" label="编号" width="50"> </el-table-column>
        <el-table-column prop="tilie" label="曲目列表(中/英)" width="250">
        </el-table-column>
        <el-table-column prop="time" label="时长"> </el-table-column>
      </el-table>
      <script>
        import { mapState, mapGetters, mapMutations } from "vuex";
        export default {
          data() {
            return {
              // count: this.$store.state.count,
              //vuex中状态发生改变,并不会再次赋值给count,页面中数据也不会更新,直接在模板中使用state或使用mapState
            };
          },
          computed: {
            ...mapState(["list", "count"]),
            ...mapGetters(["newList"]),
            //  ...mapState({renamecount:count,list})//可以重命名
          },
          methods: {
            ...mapMutations(["increase", "decrease"]),
            ...mapActions(["async"]),
          },
        };
      </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
    • 规范目录结构

      //如果把整个store都放在index.js中是不合理的,所以需要拆分。比较合适的目录格式如下:
      store:.
      │  actions.js
      │  getters.js
      │  index.js
      │  mutations.js
      │  mutations_type.js   ##该项为存放mutaions方法常量的文件,按需要可加入
      │
      └─modules
              Astore.js
      //对应的内容存放在对应的文件中,和以前一样,在index.js中存放并导出store。state中的数据尽量放在index.js中。而modules中的Astore局部模块状态如果多的话也可以进行细分。
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
    • Vue.observable

      const state = Vue.observable({ count: 0 })
      const Demo = {
          render(h) {
              return h('button', {
                  on: { click: () => { state.count++ }}
              }, `count is: ${state.count}`)
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      <template>
          <div>
              {{ count }}
              <el-button @click="add">add</el-button>
          </div>
      </template>
      <script>
      import Vue from 'vue'
      const store = Vue.observable({
          count: 1
      })
      export default {
          name: "test",
          computed: {
              count() {//模板中使用用computed
                  return store.count
              }
          },
          methods: {
              add() {
                  store.count++
              }
          }
      }
      </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

    # iframe

    <!--父组件-->
    <!--view.html需要在public目录下能够访问-->
    <iframe
            ref="iframe"
            src="/view.html"
            frameborder="no"
            scrolling="no"
            ></iframe>
    <button @click="send('君且西去,春事依然')">send to iframe</button>
    <script>
        export default {
            mounted() {
                const iframe = this.$refs.iframe;
                iframe.onload = () => {
                    //iframe加载完成才能获取iframe的window对象
                    this.iframeWin = iframe.contentWindow;
                };
                window.addEventListener("message", e=> {
                    //监听到子页面的传参
                    console.log(e.data);
                });
            },
            methods:{
                send(msg) {
                    //通过postMessage向iframe传参
                    this.iframeWin.postMessage(msg, "*");
                },
            }
    
        }
    </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
    <h1>iframe</h1>
    <button onClick="send('秋鸿折单复难双,痴人痴怨恨迷狂。')">send to vue</button>
    <script>
        window.addEventListener('message', e => {
            //console.log(e.data);
        })
        function send(msg){
            window.parent.postMessage(msg,'*');
        }
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    Last Updated: 2024/8/2 17:43:36