Vue3 - 从 vue2 到 vue3 过渡,这一套就够了(案例 + 效果演示)(二)

目录

一、组合式 API 的使用

1.1、watch 函数

1.2、watchEffect 函数

1.3、toRef 和 toRefs

1.3.1、toRef

1.3.2、toRefs

1.4、vue3  的声明周期

1.5、setup 语法糖

1.5.1、语法糖介绍

1.5.2、新增API:defineProps

1.5.3、新增API:defineEmits

1.5.4、新增API:defineExpose


一、组合式 API 的使用


1.1、watch 函数

与 vue2.x 中的 watch 配置功能一致,但是多了一些坑:

这是我当前的 vue 版本

  1. 监视 reactive 定义的响应式数据时:oldValue 无法正确获取
  2. 监视 reactive 定义的响应式数据中某个属性时:deep 配置有效.

Ps:我目前使用的 vue3 的版本没有以上问题!!!

这里我来列出几种情况:

a)监视 ref 所定义的响应式数据

<template>
  <h2>当前和为: {{ sum }}</h2>
  <button @click="sum++">点我 + 1</button>
</template>

<script>
import { ref, watch } from "vue";
export default {
  name: "App",
  setup() {
    let sum = ref(0);

    watch(sum, (newValue, ordValue) => {
      console.log("sum变了", newValue, ordValue);
    });

    return {
      sum,
    };
  },
};
</script>

监视没有问题,如下:

b)监视 ref 定义的多个响应式数据(只要其中一个变,就会触发监视事件)

<template>
  <h2>当前和为: {{ sum }}</h2>
  <h2>{{ msg }}</h2>
  <button @click="sum++">点我 + 1</button>
  <button @click="msg += '!'">点我 + !</button>
</template>

<script>
import { ref, watch } from "vue";
export default {
  name: "App",
  setup() {
    let sum = ref(0);
    let msg = ref("你好呀");

    //情况二
    watch([sum, msg], (newValue, oldValue) => {
      console.log("sum 或 msg 变了", newValue, oldValue);
    });

    return {
      sum,
      msg,
    };
  },
};
</script>

先点三下 “点我 + 1”、再点三下 “点我 + !”,效果如下:

c)监视 reactive 所定义的一个响应式数据的全部属性,无法正确的获取 oldValue

若没有配置 deep,默认开启深度监视(老版本的 vue3 强制开启深度监视,无法修改,我当前使用的版本已修改该问题).

<template>
  <h2>一个人的信息</h2>
  <div>{{ person.name }}</div>
  <div>{{ person.age }}</div>
  <div>{{ person.a.b.c }}</div>
  <button @click="person.name = 'lyj'">修改姓名</button>
  <button @click="person.age += 1">修改年龄</button>
  <button @click="person.a.b.c += 100">修改a.b.c</button>
</template>

<script>
import { reactive, ref, watch } from "vue";
export default {
  name: "App",
  setup() {
    let person = reactive({
      name: "cyk",
      age: 18,
      a: {
        b: {
          c: 666,
        },
      },
    });

    //情况三
    watch(
      person,
      (newValue, oldValue) => {
        console.log("person变化了", newValue, oldValue);
      },
    );

    return {
      person,
    };
  },
};
</script>

依次点击按钮,效果如下:

若设置 deep 为 false ,则点击 “修改 a.b.c ” 无效,如下:

<template>
  <h2>一个人的信息</h2>
  <div>{{ person.name }}</div>
  <div>{{ person.age }}</div>
  <div>{{ person.a.b.c }}</div>
  <button @click="person.name = 'lyj'">修改姓名</button>
  <button @click="person.age += 1">修改年龄</button>
  <button @click="person.a.b.c += 100">修改a.b.c</button>
</template>

<script>
import { reactive, ref, watch } from "vue";
export default {
  name: "App",
  setup() {
    let person = reactive({
      name: "cyk",
      age: 18,
      a: {
        b: {
          c: 666,
        },
      },
    });

    //情况三
    watch(
      person,
      (newValue, oldValue) => {
        console.log("person变化了", newValue, oldValue);
      },
      { deep: false } //此处 deep 配置有效!!版本更新,修复了之前无效的问题
    );

    return {
      person,
    };
  },
};
</script>

d)监视 reactive 所定义的一个响应式数据中的某个属性,这样可以解决上述请款三无法正确显示 oldValue 的问题

这里写法上和之前有点差异,第一个参数返回的是一个箭头函数.

<template>
  <h2>一个人的信息</h2>
  <div>{{ person.name }}</div>
  <div>{{ person.age }}</div>
  <div>{{ person.a.b.c }}</div>
  <button @click="person.name = 'lyj'">修改姓名</button>
  <button @click="person.age += 1">修改年龄</button>
  <button @click="person.a.b.c += 100">修改a.b.c</button>
</template>

<script>
import { reactive, ref, watch } from "vue";
export default {
  name: "App",
  setup() {
    let person = reactive({
      name: "cyk",
      age: 18,
      a: {
        b: {
          c: 666,
        },
      },
    });

    //情况四
    watch(
      () => person.name,
      (newValue, oldValue) => {
        console.log("person 的 name 变化了", newValue, oldValue);
      }
    );

    return {
      person,
    };
  },
};
</script>

点就 修改姓名 按钮,效果如下: 

e)监视 reactive 所定义的一个响应式数据中的某些属性(这种方式可以用来解决监视 reactive 后 oldValue 变化的问题).

<template>
  <h2>一个人的信息</h2>
  <div>{{ person.name }}</div>
  <div>{{ person.age }}</div>
  <div>{{ person.a.b.c }}</div>
  <button @click="person.name = 'lyj'">修改姓名</button>
  <button @click="person.age += 1">修改年龄</button>
  <button @click="person.a.b.c += 100">修改a.b.c</button>
</template>

<script>
import { reactive, ref, watch } from "vue";
export default {
  name: "App",
  setup() {
    let person = reactive({
      name: "cyk",
      age: 18,
      a: {
        b: {
          c: 666,
        },
      },
    });

    //情况五
    watch([() => person.name, () => person.age], (newValue, oldValue) => {
      console.log("person 的 name 或 age 变化了", newValue, oldValue);
    });

    return {
      person,
    };
  },
};
</script>

依次点击 修改姓名 和 修改年龄,效果如下:

f)特殊情况:当监视的是 reactive 定义的对象中的某个属性对象或者当前对象,并且第一个参数返回的是 回调方法,deep也会生效,默认非深层监视.

1.2、watchEffect 函数

watchEffect 的参数就是一个回调.

不用指明监视的是哪个属性,只要监视的回调中用到哪个属性发生变化了,就会重新执行回调(挂载的时候也会执行一次).

<template>
  <h2>一个人的信息</h2>
  <div>{{ person.name }}</div>
  <div>{{ person.age }}</div>
  <div>{{ person.a.b.c }}</div>
  <button @click="person.name = 'lyj'">修改姓名</button>
  <button @click="person.age += 1">修改年龄</button>
  <button @click="person.a.b.c += 100">修改a.b.c</button>
</template>

<script>
import { reactive, watchEffect } from "vue";
export default {
  name: "App",
  setup() {
    let person = reactive({
      name: "cyk",
      age: 18,
      a: {
        b: {
          c: 666,
        },
      },
    });

    watchEffect(() => {
      const test1 = person.name;
      const test2 = person.a.b.c;
      console.log("watchEffect 回调执行了");
    });

    return {
      person,
    };
  },
};
</script>

刚开始组件挂载的时候会执行一次,之后分别点击 修改姓名 和 修改 a.b.c ,又会执行两次,效果如下: 

Ps:默认 deep: false,因此如果 watchEffect 中使用 person.a 的话,修改 person.a.b.c 是不会重新执行回调的.

1.3、toRef 和 toRefs

1.3.1、toRef

将指定的属性通过 toRef 就可以创建一个 ref 对象.

通俗来讲,是对源对象的属性的浅拷贝,并且将源对象对应的属性改成响应式的.

为什么要使用它?例如以下场景:

    let person = reactive({
      name: "cyk",
      age: 18,
      a: {
        b: {
          c: 666,
        },
      },
    });

a)要给模板中展示  person 的所有属性很麻烦,每次都要 person. 的方式才能获取到对应的属性,因此可能就有人会这样操作:

    let person = reactive({
      name: "cyk",
      age: 18,
      a: {
        b: {
          c: 666,
        },
      },
    });
    return {
      person,
      name: person.name, //模板中可以直接使用 name 表示 person.name
      age: person.age, //同理
    };

b)但是上述这种方式返回的数据不是响应式的呀,因此,就有人想到了以下方式

    let person = reactive({
      name: "cyk",
      age: 18,
      a: {
        b: {
          c: 666,
        },
      },
    });
    return {
      person,
      name: ref(person.name), //模板中可以直接使用 name 表示 person.name
      age: ref(person.age), //同理
    };

c)虽然实现了响应式,但是这相当于是重新创建了一个新的 ref 对象,对这些数据的修改不会影响源数据,因此就需要使用 toRef 了~

    let person = reactive({
      name: "cyk",
      age: 18,
      a: {
        b: {
          c: 666,
        },
      },
    });
    return {
      person,
      name: toRef(person, "name"), //模板中可以直接使用 name 表示 person.name
      age: toRef(person, "age"), //同理
    };

1.3.2、toRefs

和 toRef 的功能一致,但是它可以直接将一个对象中的所有属性都变成响应式的,语法:toRefs(person).

    return {
      ...toRefs(person),
    };

1.4、vue3  的声明周期

a)vue3 中可以继续使用 vue2 中的生命周期钩子,但有两个被更名.

  • beforeDestory 改名为 beforeUnmount.
  • destroyed 改名为 unmounted.

b)vue3 中也提供了 Composition API 形式的生命周期钩子,与 vue2 中的钩子对应关系如下:

案例如下:

App 父组件定义如下

<template>
  <button @click="isShowUser = !isShowUser">切换隐藏/显示</button>
  <User v-if="isShowUser" />
</template>

<script>
import { ref } from "vue";
import User from "./views/User.vue";
export default {
  name: "App",
  components: { User },
  setup() {
    let isShowUser = ref(true);
    return {
      isShowUser,
    };
  },
};
</script>

 User 子组件如下:

<template>
  <h2>求和: {{ sum }}</h2>
  <button @click="sum++">点我 + 1</button>
</template>

<script>
import {
  ref,
  onBeforeMount,
  onBeforeUnmount,
  onBeforeUpdate,
  onMounted,
  onUnmounted,
  onUpdated,
} from "vue";
export default {
  name: "User",
  setup() {
    let sum = ref(0);

    // vue3 中可以通过组合式 API 的形式取使用声明周期的钩子
    onBeforeMount(() => {
      console.log("-------onBeforeMount-------");
    });
    onMounted(() => {
      console.log("-------onMounted-------");
    });
    onBeforeUpdate(() => {
      console.log("-------onBeforeUpdate-------");
    });
    onUpdated(() => {
      console.log("-------onUpdated-------");
    });
    onBeforeUnmount(() => {
      console.log("--------onBeforeUnmount------");
    });
    onUnmounted(() => {
      console.log("--------onUnmounted------");
    });

    return {
      sum,
    };
  },
};
</script>

使用效果如下: 

1.5、setup 语法糖

1.5.1、语法糖介绍

直接在 script 标签中添加 setup 属性就可以直接使用 setup 语法糖了.

使用setup 语法糖之后:

  • 不用写 setup 函数.
  • 组件只需要引入不需要注册.
  • 属性和方法也不需要返回,可以直接在 template 模板中使用.
<script setup>
  const count = ref(1)
  const countInc = () => count.value++
</script>


<template>
  <div>计数器</div>
  <el-button @click="countInc">{{ count }}</el-button>
</template>

1.5.2、新增API:defineProps

子组件接收父组件中传来的 props.

a)父组件

<template>
  <div>计数器</div>
  <el-button @click="countInc">{{ count }}</el-button>

  <Children :count="count"></Children>
</template>

<script setup>
  import Children from "./Children.vue";

  const count = ref(1)
  const countInc = () => count.value++
</script>

b)子组件

<template>
  <div>父组件传入的数据: {{count}}</div>

</template>

<script setup>
  defineProps({
    count: {
      type: Number,
      default: NaN
    }
  })
</script>

1.5.3、新增API:defineEmits

子组件调用父组件中的方法.

a)父组件

<template>
  <div>计数器</div>
  <el-button @click="countInc">{{ count }}</el-button>

  <Children @countIncr="countInc" :count="count"></Children>
</template>

<script setup>
  import Children from "./Children.vue";

  const count = ref(1)
  const countInc = () => count.value++
</script>

b)子组件

<template>
  <div>父组件传入的数据: {{count}}</div>
  <n-button @click="incr">{{count}}</n-button>

</template>

<script setup>
  defineProps({
    count: {
      type: Number,
      default: NaN
    }
  })

  const emit = defineEmits(['countIncr'])
  const incr = () => {
    //emit(父组件中的自定义方法, 参数1, 参数2....)
    emit("countIncr")
  }

</script>

1.5.4、新增API:defineExpose

子组件暴露属性,可以在父组件中拿到.

a)父组件

<template>
  <Children ref="children"></Children>
  <el-button :plain="true" @click="getChildrenNum">获取子组件中暴露的值</el-button>
</template>

<script setup>
  import Children from "./Children.vue";
  import {ElMessage} from "element-plus";

  //注册 ref,获取组件(这里的变量名必须和 上面引入子组件时的 ref 中的值一样)
  let children = ref()
  const getChildrenNum = () => {
    ElMessage({
      message: `子组件的值为: ${children.value.num}`,
      type: 'success',
    })
  }


</script>

b)子组件

<template>
  <n-button @click="childrenIncr">点我 +1: {{num}}</n-button>

</template>

<script setup>
  let num = ref(100)
  function childrenIncr() {
    num.value++
  }
  //暴露子组件中的属性
  defineExpose({
    num
  })
</script>

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值