Vue2 - 基础回顾

194 阅读4分钟

前置

  • 全局安装脚手架

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

    1. 💥终端命令创建项目

      vue create 项目名称
      

      image.png

      三个选项:vue2、vue3、手动选择特性

    2. 终端输入命令图形化创建项目

      vue ui
      
  • 单文件的创建

    image.png

基础

基础指令

插值表达式

<template>
  <div>
    <h1>插值表达式</h1>
​
    <!-- 1. 基础使用:直接渲染指定变量的内容,💥数据必须先定义好 -->
    <div>{{ msg }}</div>
    <!-- 2. 可以使用 api -->
    <div>{{ msg.toUpperCase() }}</div>
    <!-- 3. 可以字符串拼接 -->
    <div>{{ msg + ' world' }}</div>
    <!-- 4. 可以使用三元表达式 -->
    <div>{{ msg === 'hello' ? '你好' : '不太好' }}</div>
    <!-- 注意:
    1. 不能在插值表达式中使用例如 for、if 等逻辑结构
    2. 不要在插值表达式中使用自增、自减和单目运算符
     -->
  </div>
</template><script>
export default {
  data() {
    return {
      msg: 'hello',
    };
  },
};
</script>

v-text

<template>
  <div>
    <h1>v-text</h1>
    <!-- 作用:类似于插值表达式  -->
​
    <!-- 1.直接使用 -->
    <div v-text="msg"></div>
    <!-- 2. 可以使用 api -->
    <div v-text="msg.toUpperCase()"></div>
    <!-- 3. 可以字符串拼接 -->
    <div v-text="msg + ' world'"></div>
    <!-- 4. 可以使用三元表达式 -->
    <div v-text="msg === 'hello' ? '你好' : '不太好'"></div>
​
    <!-- 区别:
    插值表达式可以部分更新,v-text 则是全部覆盖
     -->
    <div v-text="text">已存在的内容</div>
  </div>
</template><script>
export default {
  data() {
    return {
      msg: 'hello',
      text: '覆盖的内容',
    };
  },
};
</script>

v-html

<template>
  <div>
    <h1>v-html</h1>
​
    <!-- 1.直接使用 -->
    <div v-html="msg"></div>
    <!-- 2. 可以使用 api -->
    <div v-html="msg.toUpperCase()"></div>
    <!-- 3. 可以字符串拼接 -->
    <div v-html="msg + ' world'"></div>
    <!-- 4. 可以使用三元表达式 -->
    <div v-html="msg === 'hello' ? '你好' : '不太好'"></div>
    <!-- 5. 类似 v-text,替换全部内容 -->
    <div v-text="text">已存在的内容</div>
​
    <!-- 特点:可以解析 html 结构 -->
    <div v-html="node"></div>
  </div>
</template><script>
export default {
  data() {
    return {
      msg: 'hello',
      text: '覆盖的内容',
      node: '<h4>后台返回的html结构</h4>',
    };
  },
};
</script>

v-for

<template>
  <div>
    <h1>v-for</h1>
    <!-- key 的作用:
    1. 使用 v-for 时,一定要指定 key 值,既提升性能又防止列表状态紊乱
    2. key 值只能是字符串或数字类型
    3. key 值必须具有唯一性
    4. 建议把 id 作为 key ,因为 id 具有唯一性。不推荐 index 作为 key,因为 index 不具有唯一性,没有任何意义
     -->
​
    <!-- 遍历数组 -->
    <!-- 1.完整语法 -->
    <div v-for="(value, index) in arr">{{ index + ':' + value }}</div>
    <!-- 2.只需要值 -->
    <div v-for="value in arr">{{ value }}</div>
    <!-- 3.key 的使用 -->
    <div v-for="value in arr" :key="value">{{ value }}</div>
​
    <!-- 遍历对象 -->
    <!-- 1.完整语法 -->
    <div v-for="(value, key, index) in obj">{{ key + ' - ' + value + ' - ' + index }}</div>
    <!-- 2.只需要值 -->
    <div v-for="value in obj">{{ value }}</div>
    <!-- 3.key 的使用 -->
    <div v-for="(value, id) in obj" :key="id">{{ value }}</div>
​
    <!-- 遍历对象数组 -->
    <!-- 1.基础使用 -->
    <div v-for="item in list">
      <div>{{ item.name + item.age }}</div>
    </div>
    <!-- 2.key 的使用 -->
    <div v-for="item in list" :key="item.id">
      <div>{{ item.name + item.age }}</div>
    </div>
  </div>
</template><script>
export default {
  data() {
    return {
      arr: [1, 2, 3, 4, 5],
      obj: {
        name: '张三',
        age: 43,
        job: '律师',
        id: '0',
      },
      list: [
        {
          name: '小明',
          age: '20',
          id: 1,
        },
        {
          name: '小白',
          age: '21',
          id: 2,
        },
        {
          name: '大黄',
          age: '22',
          id: 3,
        },
      ],
    };
  },
};
</script>

v-model

<template>
  <div>
    <h1>v-model</h1>
    <!-- 
        作用:实现数据与元素的双向绑定
        限制:只有 input、textarea、select 可以使用
        使用场景:展示默认数据或收集用户数据
        修饰符:
            .number:把用户输入的值转化为数值类型
            .trim:去除首尾空白字符
            .lazy:在输入框失焦时触发并更新数据
     -->
​
    <!-- 1. 基础使用 -->
    <input v-model="msg" type="text" />
    <div>展示数据:{{ msg }}</div>
    <!-- 2. 修饰符 .number -->
    <input v-model.number="num" type="number" />
    <div>展示数据:{{ num + 10 }}</div>
    <!-- 3. 修饰符 .trim -->
    <input v-model.trim="tri" type="text" />
    <div>展示数据:{{ tri }}</div>
    <!-- 4. 修饰符 .lazy -->
    <input v-model.lazy="laz" type="text" />
    <div>展示数据:{{ laz }}</div>
  </div>
</template><script>
export default {
  data() {
    return {
      msg: '',
      num: 1,
      tri: '',
      laz: '',
    };
  },
};
</script>

v-on

<template>
  <div>
    <h1>v-on</h1>
    <!-- 
        作用:为元素绑定事件,事件类型由 v-on 后面的参数决定,可以简写为 @ 
        语法:v-on:事件类型="事件处理函数" || @事件类型="事件处理函数"
        常用修饰符:
            .prevent:阻止默认行为 - 例如阻止 a 标签的跳转、表单的提交
            .once:事件只触发一次
            .stop:阻止事件冒泡
            .{keyCode | keyAlias}:特定按键触发
     -->

    <!-- 1. 无参事件:会有一个默认的事件源对象 -->
    <button v-on:click="handle1">无参事件</button>
    <!--    简写 -->
    <button @click="handle1">无参事件-简写</button>
    <hr />

    <!-- 2. 有参事件:
    传递参数后,默认的事件源对象就不再传递了
    如果还想用事件源对象则需手动传递 $event(不能更改)
     -->
    <button @click="handle2('参数')">有参事件</button>
    <button @click="handle3('参数', $event)">有参事件+事件源对象</button>
    <hr />

    <!-- 修饰符 .prevent -->
    <a @click.prevent="handlepre" href="http://www.baidu.com">点击不跳转页面</a><br />
    <!-- 修饰符 .once -->
    <button @click.once="handleonce">只触发一次事件</button><br />
    <!-- 修饰符  .{keyCode | keyAlias}  回车触发:.13 | .enter  -->
    <input @keydown.enter="handleEnter" type="text" />
  </div>
</template>

<script>
export default {
  methods: {
    handle1(e) {
      console.log('触发1');
      console.log(e);
    },
    handle2(e) {
      console.log(e);
    },
    handle3(name, event) {
      console.log(name, event);
    },
    handlepre() {
      console.log('点击不跳转页面');
    },
    handleonce() {
      console.log('只触发一次事件');
    },
    handleEnter() {
      console.log('回车触发事件');
    },
  },
};
</script>

v-bind

<template>
  <div>
    <h1>v-bind</h1>
    <!-- 
        作用:为元素的属性动态绑定值,可以为任意属性动态帮定
        语法:v-bind:属性名="变量" | :属性名='变量'
     -->

    <!-- 基础使用 -->
    <img v-bind:src="yourSrc" alt="" />

    <!-- 动态绑定样式 -->
    <button @click="isColl = !isColl">点击折叠</button>
    <!-- <div :class="{ box: true, collopse: isColl }"></div> -->
    <!-- <div :class="['box', { collopse: isColl }]"></div> -->
    <!-- 三种写法都可以,以下写法比较常用 -->
    <div class="box" :class="{ collopse: isColl }"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      yourSrc:
        '图片地址',
      isColl: false, // 是否折叠
    };
  },
};
</script>

<style lang="less">
.box {
  width: 200px;
  height: 200px;
  background-color: pink;
  transition: all 1s;
}
.collopse {
  width: 100px;
}
</style>

v-show 与 v-if

<template>
  <div>
    <h1>v-show 和 v-if</h1>
    <!--  v-show
            作用:通过为元素设置 display 样式实现元素的显示与隐藏
            语法:v-show="bool值"
          v-if
              作用:根据表达式的值有条件的渲染元素,true 则创建并渲染元素,false 则移除元素
              语法:v-if='bool值' | v-if='bool值' v-else | v-if='bool值'  v-else-if v-else
      共同点:都是用来来控制元素的显示与隐藏
      不同点:
        1.实现原理不同
          v-if:通过创建或移除 DOM 元素来实现显示与隐藏
          v-show:通过设置元素的样式来实现显示与隐藏
        2.性能消耗不同
          v-if 有更高的切换开销 
          v-show 有更高的初始渲染开销

      结论:频繁切换用 v-show,反之用 v-if
        
    -->

    <h2>v-show</h2>
    <button @click="isShow = !isShow">控制元素显示与隐藏</button>
    <div v-show="isShow">元素本身</div>
    <hr />
    <h2>v-if</h2>
    <button @click="ifShow = !ifShow">控制元素显示与隐藏</button>
    <div v-show="ifShow">元素本身</div>

    <!-- 多条件的元素渲染 -->
    <div>
      <input v-model="score" type="number" />
      <div v-if="score >= 90">A</div>
      <div v-else-if="score >= 80">B</div>
      <div v-else-if="score >= 70">C</div>
      <div v-else>D</div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: true,
      ifShow: true,
      score: 60,
    };
  },
};
</script>

基础进阶

ref 的使用

<template>
  <div>
    <h1>ref 的使用</h1>
    <!-- 
        作用:
            获取 DOM 元素和组件的引用,相当于为元素设置一个标识
            ref 像元素的唯一标识,所以不要让他重复
            每个 vue 组件的示例上都包含一个 $refs 对象,存储这对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象
        使用方法:
            1. 设置 ref 标识
            2. 通过 this.$refs 可以获取所有设置了 ref 标识的元素,返回一个对象

        注意:如果 ref 重名,则会被覆盖
    -->

    <!-- 场景:在页面一打开就自动聚焦文本框 -->
    <input ref="myInput" type="text" />
  </div>
</template>

<script>
export default {
  // 生命周期:当组件挂载到页面完成之后触发,常用于发起请求
  mounted() {
    // console.log(this.$refs);
    this.$refs.myInput.focus();
  },
};
</script>

自定义指令

局部自定义指令
<template>
  <div>
    <h1>局部自定义指令</h1>
    <!-- 
        定义:通过 directives 定义局部指令
        使用:v-自定义指令名称
		语法: directives: {
                    指令名称: {
                        inserted(el, binding) {
                        },
                    },
                },
     -->

    <!-- 场景:在页面一打开就自动聚焦文本框 -->

    <!-- 1.无参指令 -->
    <input v-myFocus type="text" />
    <hr />
    <!-- 2.有参指令 -->
    <input v-myColor="'blue'" type="text" />
  </div>
</template>

<script>
export default {
  directives: {
    myFocus: {
      // inserted:元素绑定了指令,已经解析好了,等待渲染,只会触发一次
      // el:指令所绑定的元素,可以用来直接操作 DOM
      inserted(el) {
        // 为元素设置聚焦功能
        el.focus();
      },
    },
    myColor: {
      inserted(el, binding) {
        el.style.color = binding.value;
      },
    },
  },
};
</script>
全局自定义指令(不推荐)

了解即可,不推荐使用

  1. 创建全局指令

    创建 utils/myDirectives.js

    // 主要用于封装项目中所需要使用到的指令()
    
    /*
        全局自定义指令
            创建:通过 Vue.directive 创建命令,一次只能创建一个命令
            语法:Vue.directive('指令名称', {
                inserted(el, binding) {},
                });
            使用:通过 Vue 创建的指令挂载到全局,任何组件可以直接引入使用,不用暴露
    */
    
    import Vue from 'vue';
    
    // 他是挂载到全局的,所以编译后常驻内存不被释放
    Vue.directive('myColor', {
      inserted(el, binding) {
        el.style.color = binding.value;
      },
    });
    
  2. 使用

    <template>
      <div>
        <h1>使用全局指令</h1>
    
        <input v-myColor="'red'" v-myFocus type="text" />
      </div>
    </template>
    
    <script>
    // 引入 全局指令
    import myColor from '@/utils/myDirectives.js';
    export default {};
    </script>
    
指令封装(推荐)
  1. 封装指令

    utils/myDirectives.js

    // 指令封装
    // 单独的封装一个指令,并不会挂载到全局
    export const mycolor = {
      inserted(el, binding) {
        el.style.color = binding.value;
      },
    };
    
    export const myfocus = {
      inserted(el) {
        el.focus();
      },
    };
    
  2. 使用

    <template>
      <div>
        <h1>指令的封装和使用</h1>
        <!-- 3. 使用指令 -->
        <input v-mycolor="'green'" type="text" />
      </div>
    </template>
    
    <script>
    // 1. 引入指令
    import { mycolor } from '@/utils/myDirectives.js';
    
    export default {
      // 2. 注册指令:引入一个成员,当成指令来使用
      //   directives:可以创建局部指令,也可以注册局部指令
      directives: {
        mycolor,
      },
    };
    </script>
    

过滤器

自定义局部过滤器
<template>
  <div>
    <h1>自定义局部过滤器</h1>
    <!-- 
      作用:可以用于一些常见的文本格式化
      定义位置:filters 结构中定义
      语法: filters: {
              过滤器名称: function (数据源, [其他参数......]) {
                return;
                },
              },
      使用:需通过管道符 | 来使用过滤器
      语法:
          不传参:数据源 | 过滤器名称
          传参:数据源 | 过滤器名称([参数])
      参数说明:
              不传参,默认传递管道符前面的数据源
              传参,不影响默认数据的传递,用户自定义参数是从参数列表的第二个开始
      -->

    <!-- 基础使用 -->
    <div>{{ msg | filterDemo }}</div>

    <!-- 过滤时间格式 -->
    <div>{{ mockList.time | filterTime('~') }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'hello',
      mockList: { id: 2, name: 'Wechat', time: new Date() },
    };
  },
  // 可以定义、注册过滤器
  filters: {
    // data:过滤器的默认参数,就是管道符前面的数据源
    filterDemo: function (data) {
      return 'abc';
    },
    filterTime: function (data, spe) {
      data = new Date(data);
      let y = data.getFullYear();
      let m = data.getMonth() + 1;
      let d = data.getDate();
      return `${y}${spe}${m}${spe}${d}`;
    },
  },
};
</script>
自定义全局过滤器(不推荐)

了解即可,不推荐使用

  1. 创建全局过滤器

    创建 utils/myFilters.js

    /*
    全局过滤器的创建
    语法:Vue.filter('过滤器名称', function (源数据, [其他参数......]) { 
                // 业务处理
                return 结果
            })
    */
    
    import Vue from 'vue';
    
    Vue.filter('dateFormat', function (data, spe = '-') {
      console.log(data, spe);
    
      data = new Date(data);
      let y = data.getFullYear();
      let m = data.getMonth() + 1;
      let d = data.getDate();
      return `${y}${spe}${m}${spe}${d}`;
    });
    
  2. 使用

    <template>
      <div>
        <h1>使用全局指令</h1>
    
        <input v-myColor="'red'" v-myFocus type="text" />
      </div>
    </template>
    
    <script>
    // 引入 全局指令
    import myColor from '@/utils/myDirectives.js';
    export default {};
    </script>
    
过滤器的封装(推荐)
  1. 封装并导出

    utils/myFilters

    // 封装过滤器
    export const dateFormat = function (data, spe = '-') {
      data = new Date(data);
      let y = data.getFullYear();
      let m = data.getMonth() + 1;
      let d = data.getDate();
      return `${y}${spe}${m}${spe}${d}`;
    };
    
  2. 使用

    <template>
      <div>
        <h1>过滤器的封装和使用</h1>
    
        <!-- 3. 使用 -->
        <div>{{ mockList.time | dateFormat('|') }}</div>
      </div>
    </template>
    
    <script>
    // 1. 引入
    import { dateFormat } from '@/utils/myFilters.js';
    export default {
      data() {
        return {
          mockList: { id: 2, name: 'Wechat', time: new Date() },
        };
      },
      // 2. 注册
      filters: {
        dateFormat,
      },
    };
    </script>
    

计算属性和侦听器

计算属性
<template>
  <div>
    <h1>计算属性</h1>
    <!-- 
        特点:只要计算属性中依赖项发生改变,就会自动触发计算属性
        注意点:
            必须在 computed 中定义
            计算属性是一个 function
            必须有返回值
        使用:类似 data 中的普通属性使用

        计算属性缓存与 methods 方法的差异:
            计算属性是基于他们的响应式依赖进行缓存的,只有依赖项发生改变,计算属性才会重新执行,否则还是会使用上次执行结果,因为上次执行结果已经存储到缓存了
            这就是与函数最本质的区别,意味着多次调用的情况下,计算属性性能更好。

     -->

    <input v-model="msg" type="text" />
    <div>数据展示:{{ msg }}</div>
    <div>计算属性处理后的数据展示:{{ getReverse }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'hello',
    };
  },
  // 定义计算属性
  // 此时计算属性的依赖项就是 this.msg
  computed: {
    getReverse() {
      return this.msg.split('').reverse().join('');
    },
  },
};
</script>
侦听器

侦听器的特定啊(与计算属性的不同):

  1. 命名不能随意,必须与你想要侦听的属性名称完全一致
  2. 不能手动调用,他是自动触发
  3. 侦听器侧重于单个数据的变化,最终指定特定的业务处理,不需要有任何返回值。如果需要一般是将结果赋值给另外一个变量
  4. 可以侦听异步操作中数据的变化
<template>
  <div>
    <h1>侦听器</h1>
    <!-- 
        概念:
            1. 可以监听指定的属性值的变化, 只要  属性值发生了变化, 就  会自动触发相应的侦听器
            2. 比计算属性更通用,特别是在有异步操作的场景下
        定义:
            1. 使用 watch 结构定义
            2. 定义为函数的形式,因为侦听函数不能手动调用,他是自动触发的。所以函数的名称必须和你想侦听的属性名称完全一致
        参数:新值,旧值
        其他选项:
            1. immediate:默认情况下,组件加载完毕后不会调用 watch 侦听器。如果想侦听器立即被调用,则需使用immediate
            2. deep:深度侦听,常用于侦听对象的属性的变化
     -->

    <input type="text" v-model="msg" />
    <div>侦听器处理的结果展示:{{ res }}</div>
    <hr />
    <div>
      <input type="text" v-model="obj.name" />
      <input type="number" v-model="obj.age" />
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: '',
      res: '',
      obj: {
        name: '张三',
        age: 20,
      },
    };
  },
  watch: {
    // 基础使用
    msg(newValue, oldValue) {
      //   console.log('msg 侦听器触发');
      // 侦听器处理的结果必须使用中间变量进行转储
      this.res = this.msg.toUpperCase();
      console.log(newValue, oldValue);
    },

    // 对象写法
    msg: {
      // handler:就是侦听器的侦听处理函数,对等于基础使用写法
      handler() {
        this.res = this.msg.toUpperCase();
      },
      immediate: true,
    },

    // 侦听对象所有属性
    obj: {
      handler(newValue) {
        console.log(newValue);
      },
      deep: true,
    },

    // 侦听对象单个属性
    'obj.age'(newValue) {
      console.log(newValue);
    },
  },
};
</script>

nextTick

<template>
  <div>
    <h1>nextTick</h1>
    <!-- 
        场景目的:点击显示输入框后,显示输入框并聚焦
        场景问题:
            点击显示输入框后,因为使用的是 v-if ,所以模板需要创建输入框,然后再绑定 ref
            但实际中,创建还未完成,就以已经开始聚焦操作,所以报错
        解决:
            1. 使用 nextTick 延迟到下一个任务队列
            2. 或者使用 setTimeout
        nextTick 使用语法:
            this.$nextTick(() => {  
                //业务处理  
            });
     -->

    <button @click="handleClick">显示输入框</button> &nbsp;
    <input type="text" v-if="isShow" ref="myInput" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: false,
    };
  },
  methods: {
    handleClick() {
      this.isShow = true;
      this.$nextTick(() => {
        this.$refs.myInput.focus();
      });
    },
  },
};
</script>

实现过渡动画

使用自定义类样式实现过渡动画

了解即可,不推荐使用

<template>
  <div>
    <h1>使用自定义样式实现过渡动画</h1>
    <!-- 
        实现步骤:
            1. 为元素添加 v-if 或 v-show
            2. 必须将需要添加过渡动画的元素包裹再 transition 标签中
            3. 为六个时机设置自定义动画样式。如果是使用自定义样式时,为 transition 设置 name,name值则为后期类样式的前缀
        动画执行的六个时机:
            1. v-enter:开始进入
            2. v-enter-active:进入的过程
            3. v-enter-to:进入完毕
            4. v-leave:准备离开
            5. v-leave-active:离开的过程
            6. v-leave-to:离开完毕
     -->

    <button @click="isShow = !isShow">切换</button>

    <transition name="move">
      <p v-show="isShow">展示示例</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: false,
    };
  },
};
</script>

<style lang="less" scoped>
p {
  width: 100px;
  height: 100px;
  background-color: darkturquoise;
  color: white;
}

// 开始进入
.move-enter {
  margin-left: 300px;
  opacity: 0;
}

// 进入的过程
.move-enter-active {
  transition: all 2s;
}

// 进入完毕
.move-enter-to {
  margin-left: 0px;
  opacity: 1;
}

// 准备离开
.move-leave {
  margin-left: 0px;
  opacity: 1;
}

// 离开的过程
.move-leave-active {
  transition: all 2s;
}

// 离开完毕
.move-leave-to {
  margin-left: 300px;
  opacity: 0;
}
</style>
使用第三方动画库实现过渡动画(推荐)
  1. 下载

     npm install animate.css --save
     or
     yarn add animate.css
    
  2. 引入

    全局引入:所有文件都能使用

    局部引入:只有当前文件才能使用

    import 'animate.css';
    
  3. 使用

    <template>
      <div>
        <h1>使用第三方动画库实现过渡动画</h1>
        <!-- 
            注意:不要忘记 animate__ 前缀
         -->
    
        <button @click="isShow = !isShow">切换</button>
    
        <transition enter-active-class="animate__animated animate__tada" leave-active-class="animate__animated animate__bounceOutRight">
          <p v-show="isShow">第三方动画库实现过渡动画</p>
        </transition>
      </div>
    </template>
    
    <script>
    // 局部引入,在组件内部引入,只有当前组件可以使用
    import 'animate.css';
    export default {
      data() {
        return {
          isShow: false,
        };
      },
    };
    </script>
    
    <style lang="less" scoped>
    p {
      width: 100px;
      height: 100px;
      background-color: darkturquoise;
      color: white;
    }
    </style>
    

组件数据传递

组件的创建和渲染

创建文件

image-20220828173355074

父组件中引入、注册、使用

<template>
  <div class="fa">
    <!--  -->

    <h1>父组件:father</h1>

    <!-- 3. 使用 -->
    <son></son>
    <sister></sister>
  </div>
</template>

<script>
// 1. 引入
import son from './components/son.vue';
import sister from './components/sister.vue';
export default {
  // 2. 注册
  components: {
    son,
    sister,
  },
};
</script>

父传子

绑定传值(常用)
  • 子组件

    子组件定义 props 用来接收传递数据的变量,并根据需求是否添加校验

    <template>
      <div class="son">
        <!--  -->
    
        <h1>子组件:son</h1>
        <div>接收到父组件的数据:{{ myMoney }}</div>
      </div>
    </template>
    
    <script>
    export default {
      /**
       *  props:用来接收父组件所传递的数据
       *  props定义:
       *          1. 数组类型:数组中的成员就相当于 data 中定义的成员,他是变量的名称
       *                 缺点:无法为每个 prop 指定具体的数据类型,也无法进行相应的校验
       *          2. 对象类型:通过对象的方式定义 props 成员,可以为每个 prop 成员指定规则(类型,校验...)
       *               常用有:
       *                      1. 基础的类型检查
       *                      2. 多个可能的类型
       *                      3. 必填项校验
       *                      4. 属性默认值
       *                      5. 自定义校验函数
       *  注意:必填校验 与 默认值 选其一,避免同时使用
       */
      // 数组写法
      // props: ['myMoney'],
    
      // 对象写法
      props: {
        // 指定单一类型
        // myMoney: Number,
    
        // 多种可能的类型
        // myMoney: [Number, String],
    
        // 同时设置多个规则
        myMoney: {
          // 规定类型
          type: [Number, String],
            
          // 是否必传
          // required: true,
            
          // 默认值
          default: 100,
            
          // 自定义校验
          validator(value) {
            if (value < 1000) return false;
            return true;
          },
        },
      },
    };
    </script>
    
  • 父组件

    使用 v-bind 绑定子组件中用来接收数据的变量进行传值

    <template>
      <div >
        <h1>父组件:father</h1>
        <input type="number" v-model="money" />
    
        <!-- 1. 父传子 -->
        <son :myMoney="money"></son>
      </div>
    </template>
    
    <script>
    import son from './components/son.vue';
    export default {
      components: {
        son,
      },
      data() {
        return {
          money: 10000,
        };
      },
    };
    </script>
    
provide(少用)
  • 父组件

    <template>
      <div >
        <h1>父组件</h1>
      </div>
    </template>
    
    <script>
    import son from './components/son.vue';
    export default {
      components: {
        son
      },
      data() {
        return {
          provideValue: '祖传数据',
        };
      },
      /**
       * 父节点可以通过 provide 方法对其子孙组件共享数据
       */
      provide() {
        return {
          provideValue: this.provideValue,
        };
      },
    };
    </script>
    
  • 子组件

    <template>
      <div >
        <h1>子组件</h1>
        <div>{{ provideValue }}</div>
      </div>
    </template>
    
    <script>
    export default {
      /**
       * inject:用于接收父级通过 provide 共享的数据
       */
      inject: ['provideValue'],
    };
    </script>
    

子传父

  • 子组件

    通过事件来传递数据

    事件中使用 this.$emit 方法传递自定义函数名称和数据

    <template>
      <div>
        <h1>子组件:son</h1>
          
         <!-- 1. 通过事件来传递数据 -->
        <button @click="handelEmit">点击子传父</button>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          msg: '子组件传递给父组件的数据',
        };
      },
      methods: {
        handelEmit() {
          /**
           * 通过内置的方法 $emit 发出事件并传递数据
           * 语法:this.$emit(自定义事件类型,需要传递的数据)
           */
          // 2. 事件中使用 this.$emit 方法传递自定义函数名称和数据
          this.$emit('getEmit', this.msg);
        },
      },
    };
    </script>
    
  • 父组件

    监听子组件传递过来的自定义事件

    在监听事件中拿到传递来的数据并转储

    显示在页面上或其他处理

    <template>
      <div>
        <h1>父组件:father</h1>
    
        <!-- 3. 监听子组件传递过来的自定义事件 -->
        <son @getEmit="handelEmit"></son>
      </div>
    </template>
    
    <script>
    import son from './components/son.vue';
    export default {
      components: {
        son,
      },
      data() {
        return {
          emitData: '',
        };
      },
      methods: {
        // 4. 在监听事件中拿到传递来的数据并转储  
        handelEmit(data) {
          console.log(data);
          this.emitData = data;
        },
      },
    };
    </script>
    

兄弟组件传递

eventBus

缺点:需要多 new 一个 vue 实例出来,性能开销大,一般不用

了解即可

  • 创建 utils/eventBus.js

    import Vue from 'vue';
    
    // 暴露默认成员 = vue 实例
    export default new Vue();
    
  • 兄弟组件1

    引入事件总线

    通过事件传递

    在事件中通过 事件总线传递 自定义事件 和 数据

    <template>
      <div>
        <!-- 2. 通过事件传值 -->
        <button @click="brother">兄弟组件传值</button>
      </div>
    </template>
    
    <script>
    // 1. 引入事件总线
    import eventBus from '@/utils/eventBus.js';
    export default {
      data() {
        return {
          bMsg: '兄弟组件传值的数据',
        };
      },
      methods: {
          // 3. 通过 事件总线传递 自定义事件 和 数据
        brother() {
          eventBus.$emit('brotherEmit', this.bMsg);
        },
      },
    };
    </script>
    

    兄弟组件2

    引入事件总线

    在 mounted 或 created 中使用 事件总线.$on 监听传递来的自定义事件

    <template>
      <div>
        <div>接收兄弟传来的数据:{{ bData }}</div>
      </div>
    </template>
    
    <script>
     // 4. 引入事件总线    
    import eventBus from '@/utils/eventBus.js';
    export default {
      // 5. 在 mounted 中添加事件的监听,也可以在 created 中监听
      mounted() {
        eventBus.$on('brotherEmit', data => {
          console.log(data);
          this.bData = data;
        });
      },
      data() {
        return {
          bData: '',
        };
      },
    };
    </script>
    

组件封装

动态组件

  • 创建组件

image-20220828195454969

  • parent

    <template>
      <div>
        <h1>动态组件</h1>
        <!-- 
            添加动态组件 - component 相当于一个展示指定组件的容器
            is 就是当前组件所需要渲染的组件名称
         -->
        <button @click="comName = 'component1'">显示组件1</button>
        <button @click="comName = 'component2'">显示组件2</button>
        <button @click="comName = 'component3'">显示组件3</button>
    
        <!-- keep-alive 可以在动态组件切换后也能保留当前组件的状态 -->
        <keep-alive>
          <component :is="comName"></component>
        </keep-alive>
      </div>
    </template>
    
    <script>
    import component1 from './components/component1.vue';
    import component2 from './components/component2.vue';
    import component3 from './components/component3.vue';
    export default {
      components: {
        component1,
        component2,
        component3,
      },
      data() {
        return {
          comName: 'component1',
        };
      },
    };
    </script>
    
  • component2.vue

    <template>
      <div>
        <h2>组件2</h2>
        <hr />
        <button @click="count++">+1</button>
        <div>{{ count }}</div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          count: 0,
        };
      },
    };
    </script>
    

插槽

匿名插槽
  • index.vue

    <template>
      <div>
        <h1>匿名插槽的使用</h1>
        <hr />
        <nimingBtn>登录</nimingBtn>
      </div>
    </template>
    
    <script>
    import nimingBtn from './components/nimingBtn.vue';
    export default {
      components: { nimingBtn },
    };
    </script>
    
  • 匿名插槽

    <template>
      <div class="btn">
        <!-- 
            定义:没有设置 name 属性即位匿名插槽
            用户输入的值会自动填入到 slot 中
    		匿名只能有一个 slot,多个 slot 会导致 用户输入一个值所有 slot 都自动填入 
         -->
    
        <slot>匿名插槽默认值</slot>
      </div>
    </template>
    
    <script>
    export default {};
    </script>
    
    <style lang="less" scoped>
    .btn {
      width: 100%;
      height: 50px;
      border-radius: 20px;
      background-color: cornflowerblue;
      color: white;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    </style>
    
具名插槽
  • index.vue

    <template>
      <div>
        <h1>具名插槽的使用</h1>
        <hr />
        <jumingHeader backgroundColor="pink">
          <!-- 1. slot="插槽的名称"  -->
          <span slot="center">首页</span>
    
          <!-- 2. v-slot:"插槽的名称",需使用 template 标签包住,固定写法 -->
          <template v-slot:left>
            <span>返回</span>
          </template>
    
          <!-- 2.1 #插槽的名称 简写方式 -->
          <template #right>
            <span>首页</span>
          </template>
        </jumingHeader>
      </div>
    </template>
    
    <script>
    import jumingHeader from './components/jumingHeader.vue';
    export default {
      components: { jumingHeader },
    };
    </script>
    
  • 具名插槽

    <template>
      <!-- 
        具名插槽:
            封装组件时需要预留多个插槽节点,则需要为每个 slot 指定具体的 name 名称
            带有具体名称的插槽就是具名插槽
        注意:没有指定 name 名称的插槽会有一个隐藏的名称 - default
     -->
      <div class="header" :style="'backgroundColor:' + backgroundColor">
        <div>
          <slot name="left"></slot>
        </div>
        <div>
          <slot name="center"></slot>
        </div>
        <div>
          <slot name="right"></slot>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        backgroundColor: {
          type: String,
          default: '#ccc',
        },
      },
    };
    </script>
    
    <style lang="less" scoped>
    .header {
      width: 100%;
      height: 50px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    </style>
    
作用域插槽
  • index.vue

    #list 是 v-slot:list 的简写,绑定的是 插槽的名称

    scope 是传递过来的数据都会包装在内,接收的是数据

    <template>
      <div>
        <h1>作用域插槽的使用</h1>
        <zuoyognyu>
          <!-- 接收数据对应 传递数据时的名称 row length -->
          <!-- 数据传递过来时是个对象, 默认用 scope 接收,使用时也是对象的使用写法 -->
          <!-- <template #list="scope"> {{ scope.row }} --- s{{ scope.length }} </template> -->
    
          <!-- 直接解构的使用写法,更明了 -->
          <template #list="{ row, length }"> {{ row }} --- s{{ length }} </template>
        </zuoyognyu>
      </div>
    </template>
    
    <script>
    import zuoyognyu from './components/zuoyongyu.vue';
    export default {
      components: { zuoyognyu },
    };
    </script>
    
  • 作用域插槽

    <template>
      <div>
        <button>获取数据</button>
        <!-- 
                定义作用域插槽
                为插槽添加数据属性
             -->
    
        <!-- 此处 row 和 length 都是 传递的数据名称,接收时也需对应 -->
        <slot name="list" :row="userList" :length="userList.length"></slot>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          userList: [],
        };
      },
      created() {
        this.userList = [
          { name: '张三', age: 14 },
          { name: '李四', age: 20 },
        ];
      },
    };
    </script>
    

路由

基本实现

  • 下包

    npm i vue-router@3.5.2
    
  • 创建模块文件 - src/router/router.js

    // 当前项目的路由模块文件// 1. 工程化 - 模块化
    import Vue from 'vue';
    import VueRouter from 'vue-router';
    Vue.use(VueRouter);
    ​
    // 引入需要映射的组件
    import Login from '@/components/路由/demo/login.vue';
    import Index from '@/components/路由/demo/index.vue';
    ​
    // 2. 创建路由对象
    const router = new VueRouter({
      /**
       * 添加路由配置
       * 通过 routes 配置路由,主要描述地址和组件的映射关系
       * */
      routes: [
        {
          path: '/login',
          component: Login,
        },
        {
          path: '/index',
          component: Index,
        },
      ],
    });
    ​
    // 3. 暴露
    export default router;
    
  • main.js 中引入并注册路由模块

    import Vue from 'vue';
    import App from './App.vue';
    ​
    // 1. 引入路由模块
    import router from './router/router';
    ​
    Vue.config.productionTip = false;
    ​
    new Vue({
      // 2. 注入路由模块
      router,
      render: h => h(App),
    }).$mount('#app');
    ​
    
  • 在 根组件 添加 router-view

    <template>
      <div id="app">
          
        <!-- 映射组件的展示区域 -->
        <router-view></router-view>
        
      </div>
    </template>
    

设置超链接 - router-link

都要看到的,可在根组件 App.vue 中添加,页面独有的需在相对页面中添加

  • App.vue

    <template>
      <div id="app">
        <router-link to="/home">首页</router-link>
        <router-link to="/login">登录页</router-link>
          
        <!-- 映射组件的展示区域 -->
        <router-view></router-view>
      </div>
    </template>
    

延迟加载 - () => import()

  • router.js

    // 当前项目的路由模块文件// 1. 工程化 - 模块化
    import Vue from 'vue';
    import VueRouter from 'vue-router';
    Vue.use(VueRouter);
    ​
    // 弊端:不管有没有用到,都会加载
    // import Login from '@/components/login.vue';
    // import Home from '@/components/home.vue';// 2. 创建路由对象
    const router = new VueRouter({
      /**
       * 添加路由配置
       * 通过 routes 配置路由,主要描述地址和组件的映射关系
       * */
      routes: [
        {
          path: '/login',
          component: () => import('@/components/login.vue'),
        },
        {
          path: '/home',
          component: () => import('@/components/home.vue'),
        },
      ],
    });
    ​
    // 3. 暴露
    export default router;
    ​
    

动态路由匹配

设置动态路由

  • router/router.js

    const router = new VueRouter({
      routes: [
        {
          // 设置路由的动态参数 参数以 : 作为标识 , name 为形参名字
          // 以下配置说明当前路由需要传递一个参数,如果没有参数路由则无法匹配
          path: '/login/:name',
          component: () => import('@/components/login.vue'),
        },
      ],
    });
    
  • App.vue

    <template>
      <div id="app">
        <!-- 设置路由参数 -->
        <router-link to="/login/login">登录页</router-link>
        <!-- 映射组件的展示区域 -->
        <router-view></router-view>
      </div>
    </template>
    

获取路由参数

参数传给了哪个组件,就在哪个组件中获取参数

mounted 和 watch 的触发时机不一样,存在互补的功能现象

方法一 - 通过 $route.params 获取路由参数

  • login.vue

    <script>
    export default {
      /** 获取路由参数
       * 通过 $route.params 获取路由参数,获取到的是一个对象
       * */
      //   从一个路由跳转到另一个路由时触发
      mounted() {
        const name = this.$route.params.name;
        console.log(name);
      },
      //   通过 watch 监听路由参数的变化
      //   当同一个路由参数变化时触发
      watch: {
        $route(to, from) {
          // 对路由的变化做出响应
          console.log(to, from);
        },
      },
    };
    </script>
    

方法二 - 通过 props 来获取路由参数

  • router/router.js

        {
          path: '/login/:name',
          component: () => import('@/components/login.vue'),
          // 开启 porps 传参  
          props: true,
        },
    
  • login.vue

    <template>
      <div>
        登录页
        <h1>{{ name }}</h1>
      </div>
    </template><script>
    export default {
      // 接口路由参数  
      props: ['name'],
    </script>
    

嵌套路由

  • router/router.js

    const router = new VueRouter({
      routes: [
        {
          path: '/login/:name',
          component: () => import('@/components/login.vue'),
          props: true,
          /** 添加嵌套路由
           * 里面的路由配置类似外层路由配置
           * 根路由就是带 / 的路由,它会被渲染在 App.vue 中的 router-view 结构中
           * */
          children: [
            {
              path: 'userInfo',
              component: () => import('@/components/userInfo.vue'),
            },
          ],
        },
      ],
    });
    ​
    
  • login.vue

    <template>
      <div>
        <!-- 嵌套路由的映射区域 -->  
        <router-view></router-view>
      </div>
    </template>
    

编程式导航

跳转页面并传参 - $router.push

  • 跳转页面

      methods: {
        jump() {
          // 跳转根路由
          //   this.$router.push('/home');
    ​
          // 有参数的情况下跳转嵌套路由
          //   this.$router.push(`/login/${this.name}/userInfo`);
    ​
          // 完整写法
          this.$router.push({ path: `/login/${this.name}/userInfo` });
        },
      },
    
  • 传参

     jump() {
          /** 传递参数
           * 使用 path 跳转时,参数需用 query 传递
           * 使用 name 路由名称跳转时,params 和 query 都可以,推荐都是用 query
           * */
    ​
          //   this.$router.push({
          //     path: `/login/${this.name}/userInfo`,
          //     query: { username: '张三' },
          //   });
    ​
          this.$router.push({
            name: 'userInfo',
            query: { name: '张三', age: 50 },
          });
        },
    
  • 后退

        goBack() {
          // go() 方法 传负数是 后退,0 或不传 为 刷新页面
          //   this.$router.go(-1);
    ​
          // back() 也是后退方法
          this.$router.back();
        },
    

命名路由

  • router/router.js

       {
          path: '/login/:name',
          component: () => import('@/components/login.vue'),
          props: true,
          children: [
            {
              // 配置路由名称 - 需保证名称唯一性,不可重复
              name: 'userInfo',
              path: 'userInfo',
              component: () => import('@/components/userInfo.vue'),
            },
          ],
    
  • 跳转路由时使用

     jump() {
          this.$router.push({ 
            name: 'userInfo',
            query: { name: '张三', age: 50 },
          });
        },
    

路由重定向

  • router/router.js

    const router = new VueRouter({
      routes: [
        // 路由重定向
        // 可以设置与 path 相同的路径
        {
          path: '/',
          // redirect: '/home',
          redirect: { name: 'home' },
        },
        {
          path: '/login/:name',
          component: () => import('@/components/login.vue'),
          props: true,
          // 嵌套路由中 使用 name 更方便  
          redirect: { name: 'userInfo' },
          children: [
            {
              name: 'userInfo',
              path: 'userInfo',
              component: () => import('@/components/userInfo.vue'),
            },
          ],
        },
      ],
    });
    

路由高亮

方法一:使用默认的高亮 class 类 - router-link-active

  • styles/index.css 并 引入 main.js

    // 添加全局的路由高亮显示
    .router-link-active {
      font-size: 20px;
      color: cadetblue;
    }
    

方法二:自定义路由高亮的 class 类

  • router/router.js

    const router = new VueRouter({
      // 自定义路由高亮类名称
      linkActiveClass: 'my-router-link-active',
    });
    
  • styles/index.css

    .my-router-link-active {
      color: turquoise;
      font-size: 22px;
      font-weight: 700;
    }
    

导航前置守卫

  • router/router.js

    const router = new VueRouter({ ... })
    /** 导航前置守卫
     * to:跳转的目标路由
     * from:源路由
     * next:下一步,如果没有 next,那么请求终止
     * */
    router.beforeEach((to, from, next) => {
      // 业务逻辑
      const token = '';
      if (!token) { 
        next({ path: '/login' });
      } else {
        next();
      }
    });
    ​
    // 3. 暴露
    export default router;
    

    \