VUE2(穿插VUE3知识点)

监听

watch

  • 当监听数据的改变时需要使用异步操作时必须使用watch(相对于computed),因为watch不需要返回值

  • watch监听的属性应该是data里面的数据,也可以是computed计算的数据,当然$route也可监听

  • watch有两种格式

    • 一种是数据写成函数的方式,这种方式只适用于基本数据类型

    • watch:{
          数据(){
              逻辑
          }
      }
      
    • 一种是数据写成对象的形式,对象内使用函数handler来执行逻辑,这种方式所有类型都可以使用

    • watch:{
          数据:{
              handler(){
                  逻辑
              },
              deep:true,
              immediate:true
          }
      }
      
    • deep是深度监听,当监听的数据是引用数据类型时必须为true

    • immediate是初始化的时候是否执行

  • watch有触发间隔,有类似节流效果

  • watch注重的是过程,监听的是数据发生变化的过程所发生的事

  • 当监听的一个值影响多个值时使用watch

计算属性computed

默认就是初始化执行,可以用于基本数据类型也可以用于引用数据类型

有返回值,因此返回值也代表了一个data数据,不需要在data列表中定义可以直接使用,这个数据是根据写在函数中的多个数据来动态计算到的,此时如果这些数据任意一条发生改变都会被监听到进而重新计算出一个数值

computed: {
    yf() {
      return this.money >= 30 ? 0 : 5;
    },
    doubleAge() {
      return this.obj.age * 2 +this.money
    },
},

例如上例中,doubleAge可以直接作为data数据来使用不需要()调用,而且无论age或者money任何一个改变都会被监听到进而使doubleAge重新得出一个结果

因此computed可以同时监听多个数据来动态计算出一个结果

计算属性会将上一次的计算结果缓存,如果在多个地方多次调用数据则下一次直接从缓存中去取数据而非重新计算,只有监听到结果的数据源发生变化才会重新计算

不能够在computed内执行异步,但是如果使用不相关数据去接受异步结果则可以使用异步,但这样无疑多此一举,且不符合设计理念

两者的面试点

如果监听某个值的改变来确定执行异步的时机必须使用watch

如果一个属性影响多个值使用watch,多个值影响一个结果使用computed

computed和watch根据个人喜欢,给出合适的理由就可以了,同时提一提两者的区别

插槽

组件标签的中间书写的内容不会被渲染,但我们会发现router-link却可以在组件标签中间书写内容且被渲染,这就是因为插槽

普通插槽

插槽的书写方式为

  • 在组件中需要显示内容的地方使用<slot></slot>
  • 此时书写在组件标签中间的内容就会被渲染到事先占位的地方

插槽除了渲染文字,还可以渲染其他组件或者HTML

具名插槽

我们知道的一个具名插槽—命名视图

当我们想在一个组件内多个地方使用不同插槽时就需要名字来区分不同的内容,否则会重复渲染同一内容

在组件内的<slot name=“名字”></slot>标签上添加name属性

然后在组件标签内将不同的插槽内容使用template标签包裹,并且使用#名字来区别不同的插槽内容

  • 组件内部
<template>
  <div><slot name="prev"></slot> <input type="text" /><slot name="next"></slot></div>
</template>
  • 组件标签
<Input>  // 组件标签
      <template #prev>  // 插槽内容
        <select name="" id="">
          <option value="">1年级</option>
          <option value="">2年级</option>
          <option value="">3年级</option>
        </select>
      </template>


      <template #next> <button>提交</button></template>  // 另一个插槽内容
</Input>

如果插槽没有命名实际上是有默认名字的,就是default

作用域插槽

作用于插槽和组件通信的父传子很像

<slot></slot>标签上添加自定义属性就相当于传递参数了

<template>
  <div>
    <slot :list="list" n="10"></slot> <input type="text" /><slot name="append"></slot>
  </div>
</template>

此时组件标签内插槽模版可以在名字后面使用数据对象来接受数据

<Input>
      <!--接受到组件本身传递数据的对象   scope = {list:[],n:'10'}--->
      <template #default="scope">  // 名字可以是任意的,不一定非要是scope
        <select name="" id="">
          <option :value="item" v-for="(item, i) in scope.list" :key="i">
            {{ item }}
          </option>
        </select>
      </template>
      <template #append="sccope"> <button>提交</button></template>
</Input>

此时插槽模板就可以拿到组件内的数据了

动态组件

router-view实际上就是动态组件实现的

动态组件的格式为<component is="组件名"></component>

组件名是哪个就显示哪个组件,给组件设置名字在VUE3选项式中使用name:"名字"

keep-alive

keep-alive必须要和动态组件component is一起用

因为动态组件在切换时是直接销毁重新创建,而keep-alive可以使动态组件切换时保持活动

可以通过将is变成动态属性来实现选项卡效果

<keep-alive>
   <component :is="nameList[index]" />
</keep-alive>

keep-alive使用点在于,有时候我们在切换页面后不希望切换回来时重新发送请求或者清空之前保存的数据,因此可以通过缓存页面来达到效果

有些组件不能使用keep-alive 涉及到安全或者更新的问题

keep-alive默认包裹的组件都缓存,但有两个属性excludeinclude来控制

exclude="不缓存的组件名,组件2",作用是排除不缓存的页面,其余都缓存

include="缓存的组件名1,组件2",作用是除了这些页面其余都不缓存

在使用时,可以根据缓存和不缓存的数量灵活选择,另外两者除了可以填字符串还支持正则表达式

v-if判断的写法 (vue3 不支持)

除了使用include等还可以使用v-if来实现控制路由显示

<div v-if="index == 1">
   <keep-alive>
      <component :is="list[index]" />
   </keep-alive>
</div>

    <component v-else :is="list[index]" />

一个组件里面有一部分内容想要缓存,一部分需要更新

keep-alive提供了两个钩子函数来在需要的时候执行逻辑

activated()当进入组件的时候执行

deactivated当离开组件的时候执行

可以在这两个钩子函数中手动更新需要更新的数据

综合路由使用

动态组件和keep-alive可以配合路由使用

<router-view>
   <template #default="{ Component }">
       <keep-alive>
          <component :is="Component"></component>
       </keep-alive>
   </template>
</router-view>

上例中{Component}是对象解构赋值,因此Component是不能改变的,他代表的是组件的名字

上例中的流程是,template从router-view的插槽拿到了跳转路由的信息,从中解构赋值出路由页面的名字然后将这个名字作为参数传给动态组件,动态组件渲染页面,因为有keep-alive所以所有路由在加载一次后都会被缓存

此时如果想重新刷新页面信息,可以使用钩子函数,也可以使用exclude属性

CSS属性穿透

<style scoped></style>,其中scoped代表的是局部样式,在局部样式内设置的样式只会影响当前组件,而不会影响到子组件,确保样式不会污染

但有时我们确实需要设置子组件的样式,此时可以使用属性穿透

在scss中使用父组件元素 ::deep 子组件元素来实现属性穿透,也可以父元素组件 :deep(子组件元素)

也可以使用:global(class)来将样式应用到全局

ref

给元素加上ref属性,在组件内就可以通过this.$refs.元素名来获取元素

修饰符

键盘@keydown

.enter.键盘按键,实现监听特定按键,可以嵌套实现.enter.ctrl

v-model

.trim,去掉首尾空格

.number,将数字字符串 ‘123’改为数字类型123

.lazy,将v-model的input事件改为 change事件

@click

.stop,阻止冒泡

`.prevent,阻止默认

.self,只有它自己触发的时候才执行

.capture,改为捕获事件

.navtive触发原生事件修饰符 (组件触发原生事件) (vue3不需要了,因为VUE3中组件默认就有原生事件)

鼠标事件

.left,左键

.right,右键

.middle,中键

Element-plus

根据文档使用element-plus组件实现ui效果

其中重要的有el-fromElmessageel-buttonel-tableel-input

VUE3

vue2和vue3的响应式区别

vue2

vue2使用的是Object.defineProperty(对象,属性,{getter,setter})来实现响应式,他提供了gettersetter方法

setter方法在属性改变时被激活,因此渲染函数可以在这里执行,就能实现数据改变自动更新视图,而getter方法在获取属性时激活,可以在这里执行数据更新

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue2和vue3的响应式原理</title>
</head>
<body>
    <div id="box"></div>
    <button onclick="changeName()">切换名字</button>
    <script>
        // 面试题 
        // Vue2
        let data = {
            name: "zhangsan", //
            age: 20,
        }
        // 渲染视图
        function render(str) {
            let box = document.querySelector("#box")
            box.innerHTML = str
        }
        //
        render(data.name)
        let val;
        // 监听数据更新视图
        Object.defineProperty(data, 'name', {
            // 使用调用属性的时候执行,作用是同步(data)数据
            get() {
                console.log('get函数执行了')
                return val
            },
            // 修改数据的时候自动执行
            set(newVal) {
                console.log(newVal, 'set函数执行了')
                //更新渲染视图
                render(newVal)
                val = newVal
            },
        })
        function changeName() {
            data.name = 'lisi'
            // render(data.name)
            console.log(data, 'data')
        }
    </script>
</body>

</html>

但他有缺点,因此vue3中并未使用这种方法

  • 他只能监听对象的基本类型的属性,如果是引用数据类型的属性就需要循环遍历去监听
  • 他是对象的方法,因此数组无法监听,vue2中的数组方法是重写过的,在原方法上添加了视图更新,其中this.list[0]=1=this.list.length = 0就没重写,因此无法实现响应式
  • 它只能监听已经存在的属性,后添加的属性和删除的属性不能够监听到

vue3

vue3中采用的是proxy代理对象实现响应式

他也提供gettersetter方法,并且功能上也是相同的

但是在细节上就不同

  • proxy代理的是整个对象,因此就不需要遍历每个属性,但是如果属性是对象还是需要遍历一次
  • 可以直接监听数组
  • 添加的元素和删除的属性都可以背动态添加到
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue2和vue3的响应式原理</title>
</head>
<body>
    <div id="box"></div>
    <button onclick="changeName()">切换名字</button>
    <script>
        // 面试题 
        // Vue2
        let data = {
            name: "zhangsan", //
            age: 20,
            sex: "",
        }
        // 渲染视图
        function render(str) {
            let box = document.querySelector("#box")
            box.innerHTML = str
        }
        render(data.name)

        let proxy = new Proxy(data, {
            //数据改变的时候执行
            set(target, attr, value) {
                console.log(target, 'target-set执行了')
                console.log(attr, 'attr-set执行了')
                console.log(value, 'value-set执行了')
                // 目标对象
                target[attr] = value
                render(value)
                // 更新视图
            },
            // 数据调用的时候执行
            get(target, attr) {
                // console.log(target[attr], 'get执行了')
                // 同步数据
                return target[attr]
            }
        })
        function changeName() {
            // proxy.name = 'lisi'
            // render(data.name)
            proxy.age = 222
            console.log(data, 'data')
        }
    </script>
</body>
</html>

ref

proxy虽然可以监听数组了,但他还是无法监听基本数据类型,因此如果是基本数据类型,vue3的ref属性就配上用场了

ref会自动在数据外层套一层对象来方便代理

因此在script标签内使用时往往需要使用对象.value来获取到真正的数据

而在template模版标签时vue3会自动解包,因此在模版内使用数据不需要使用.value

ref使用时需要引入import {ref} from 'vue'

reactive

因为ref会自动包装,而对象也会被包装一层,因此对象这类引用数据类型可以使用reactive

注意,基本数据类型不能使用

reactive使用时需要引入import {reactive} from 'vue'

Vue3 setup语法糖

setup函数

Vue3有两种写法,一种是setup(){}函数,这种方式可以和datamethods等一起使用

导入组件后仍需要在setup函数外注册

在函数内定义的数据、方法需要使用return暴漏在外面

在组件通信的时候因为setup函数内没有this因此无法直接使用this.$emit等,因此就需要先外部引入

setup(props, context){},其中context中有$emit

setup语法糖

另一种是setup语法糖,在script标签上书写setup标记,此时导入组件后就不需要注册,vue会自动注册

也不需要使用return将属性和方法暴漏出去

生命周期

生命周期的阶段还是那些阶段

但是阶段的钩子函数名有一些变化

onBeforeMount

onMounted(),{挂载后 获取真实DOM. 请求数据}

onBeforeUpdate

onUpdated

onUnmounted() ,销毁阶段,不是destroyed

等,基本是在原来的函数前加on,除了销毁阶段

同时在使用生命周期和ref一样需要从vue导入

格式为import {onUpdated,onMounted} from 'vue'

监听

功能上没变化

写法上有点变化

watch(
  [n,user,a,b],
  () => {
    //执行代码
    console.log("watch");
  },
  { 
    deep: true,  //深度监听
    immediate: true, //初始化的时候执行
  }
);
const 计算属性 = computed(() => {return 逻辑});

组件v-model

如果我们自己写组件的时候我们不希望一个需要双向绑定数据去手写父传子子传父,那样太麻烦了,调用子组件的时候需要传入一个参数还需要接受一个事件,尽管v-model底层也是这样做的

因此我们可以使用v-model的更深用法,在子组件中我们使用modelValue作为props接受的参数名,然后使用update:modelValue作为事件名,这样在父组件中我们就可以直接使用v-model来实现双向数据绑定

当然如果我们希望使用其他的props参数名,事件名就也要跟着改变,将modelValue换成参数名,比如update:自定义参数,在父组件绑定v-model时就需要加上参数名v-model:自定义参数名

子组件

const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false,
  },
});
const update = () => {
  emit("update:modelValue", false);
};

父组件

<MyDialog v-model="visitable"></MyDialog>

defineModel

vue3.4之后出现了v-model语法糖,子组件中也不需要写propsemits

可以写const visible = defineModel("modelValue");,如果是自定义参数名格式则为

const visible = defineModel("自定义参数名",{type:boolean,default:false});

provide和inject

依赖注入,父辈和子辈之间可以通过两者传递参数

父辈可以使用provide("key", value数据);来提供数据

子辈可以使用const user: any = inject("key");来消费数据

provide和inject共享的数据在引用数据类型下是响应式的,但是如果是基础数据类型则不是响应式的,因此常用于不需要改变的数据或者公共方法

当然两者也可以变成响应式的,原理就是将基础数据类型变成引用数据类型

provide提供数据时提供一个函数provide("user", ()=>10);

inject接收数据时正常接收const user: any = inject("user");,但是在使用时因为实际上是个函数因此可以直接调用,这样数据就是响应式得了,当然更进一步是使用计算属性再包装一层const n = computed(() => user());

自定义hooks函数

hooks函数要求以use开头,将需要复用的方法抽离成一个公共函数

import { ref } from "vue";

export function useCount() {
  const count = ref(0);
  const add = (payload: number = 1): void => {
    count.value += payload;
  };
  const dec = (payload: number = 1): void => {
    count.value -= payload;
  };
  return {
    count,
    add,
    dec,
  };
}

toRef和toRefs

两者都是将解构出来的数据变成响应式数据

toRef是一次只能转换一条const name = toRef(obj.value, "name");

toRefs则是可以一次转换多个属性const { name, age } = toRefs(obj.value);

pinia

vuex的替代,操作比vuex简单

参考资料