vue3 学习笔记

285 阅读5分钟

🎉 此文章是本人在学习的过程中总结的,如有错的地方欢迎评论区留言指正

响应式数据定义

vue2中在data函数的return中定义响应数据:

data () {
  return {
    name: "zhangsan",
    person: {
      name: 'zhangsan',
      age: 25
    }
  }
}

vue3中我们可以通过refreactive来定义响应数据:

const name = ref("zhangsan");
const person = reactive({
  name: "zhangsan",
  age: 25,
});
const greeting = "Hello " + name.value;
person.age++;

在模板中使用reactive定义的对象都需要通过 对象名[对象属性]的方式使用,写起来比较麻烦,此时可以通过toRefs将响应式对象转为普通对象,转换后的每个属性都指向原始对象对应属性的ref

<template>
  <div class="homePage">
    <p>第 {{ year }} 年</p>
    <p>姓名: {{ person.name }}</p>
    <p>年龄: {{ person.age }}</p>
    <p>动物名称: {{ animalName }}</p>
    <p>科种: {{ type }}</p>
  </div>
</template>

<script>
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
  setup() {
    const year = ref(2022);
    const person = reactive({
      name: "zhangsan",
      age: 25,
    });
    const animal = reactive({
      animalName: "青蛙",
      type: "两栖动物",
    });
    return {
      year,
      person,
      ...toRefs(animal),
    };
  },
});
</script>

ref:

  • 建议定义基本数据的时候使用
  • 在 js 中通过.value方法来获取数据的值

reactive:

  • 建议定义复杂数据的时候使用
  • 可使用toRefs将其解包,在模板中使用他的属性

生命周期钩子

钩子选项式 APIHook inside setup
创建前beforeCreateNot needed*
创建后createdNot needed*
挂载前beforeMountonBeforeMount
挂载后mountedonMounted
更新前beforeUpdateonBeforeUpdate
更新后updatedonUpdated
销毁前beforeDestorybeforeUnmounte
销毁后destoryedunmounted
keepAliveactivatedonActivated
keepAlivedeactivatedonDeactivated

官网是这么解释的 Hook 中没有 beforeCreatecreated的原因:

🏷️TIP

因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们.换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

单文件组件样式特性

  1. 深度选择器

处于 scoped 样式中的选择器如果想要做更“深度”的选择,也即:影响到子组件,使用:deep()这个伪类

.list :deep(.el-button) {
  ...;
}

🏷️TIP

通过 v-html 创建的 DOM 内容不会被作用域样式影响,但你仍然可以使用深度选择器来设置其样式。

  1. vue3 插槽选择器

一般情况下作用域的样式不会作用到插槽<slot/>里面的内容,可以使用:slotted来选择到插槽

:slotted(div) {
  ...;
}
  1. 全局样式
:global(.tab) {
  color: blue;
}
  1. css module

可通过<style module>方式将 css 类作为$style对象暴露出来给组件使用,同时也支持自定义名称:<style module="molly">

<template>
  <p :class="molly1.red">red</p>
</template>

<style module="molly1">
.red {
  color: red;
}
</style>
<style module="molly2">
.red {
  color: red;
}
</style>
  1. style v-bind

vue3.2中增加了一个v-bind的特性,简单来说就是把我们script中的数据可以在style标签中使用,下面我们来写一个最简单的例子:

<template>
  <p :class="molly.red">red</p>
</template>
<script>
import { ref } from "vue";
const color = ref("red");
</script>
<style module="molly">
.red {
  color: v-bind(color);
}
</style>

watch 和 watchEffect

watch:

watch(source, callback, [options]);

参数说明:

  • source: 可以支持 StringObjectFunctionArray; 用于指定要侦听的响应式变量
  • callback: 执行的回调函数
  • options:支持 deepimmediateflush 选项。

1. 侦听单一源

const year = ref(2022);
const person = reactive({
  name: "zhangsan",
  age: 25,
});
watch(year, (newValue, oldValue) => {
  // ...
});
watch(
  () => person.age,
  (newValue, oldValue) => {
    // ...
  },
  {
    deep: true,
    immediate: true,
  }
);

2. 侦听多个源

const year = ref(2022);
const person = reactive({
  name: "zhangsan",
  age: 25,
});
watch([year, () => person.age], ([newYear, newAge], [oldYear, oldAge]) => {
  console.log(newYear, oldYear, newAge, oldAge);
});

watchEffect:

watchEffect(Function, [options]);

watchEffect会自动查找依赖,当副作用函数中的依赖改变时,执行该函数

const person = reactive({
  name: "zhangsan",
  age: 25,
});

watchEffect((onInvalidate) => {
  console.log(person.age);
});

刷新页面,此时控制台输出25

const person = reactive({
  name: "zhangsan",
  age: 25,
});

watchEffect((onInvalidate) => {
  console.log(person.age);
});

setTimeout(() => {
  age.value++;
}, 1000);

此时控制台先输出25,一秒钟后打印26,由此我们可以知道,watchEffect会在页面初始化的时候执行一次,然后当副作用函数中的依赖发生变化时会重新执行该副作用函数。

停止监听:

一般情况下 watchEffect 会在组件卸载的时候自动停止,但是也可以显式调用返回值停止监听:

const person = reactive({
  name: "zhangsan",
  age: 25,
});

const stop = watchEffect((onInvalidate) => {
  console.log(person.age);
});

stop(); // 停止监听

清除副作用:

watchEffect传入的函数可以接收一个 onInvalidate 函数作入参,用来清理上次回调的影响。当满足下述情况时,onInvalidate会被触发

  • 副作用函数重新执行时(可以理解为当依赖值改变后先会执行 onInvalidate, 其次再执行副作用函数)
  • 监听被停止或者组件卸载时

🏷️TIP

依赖值改变,先执行副作用,再更新组件

const person = reactive({
  name: "zhangsan",
  age: 25,
});

const stop = watchEffect((onInvalidate) => {
  console.log(person.age);
  onInvalidate(() => {
    console.log("执行了onInvalidate");
  });
});

stop();

此时控制台依次输出25执行了onInvalidate

const person = reactive({
  name: "zhangsan",
  age: 25,
});

watchEffect((onInvalidate) => {
  console.log(person.age);
  onInvalidate(() => {
    console.log("执行了onInvalidate");
  });
});

此时控制台先输出25,一秒钟后执行了onInvalidate26

副作用刷新时机:

前面提到依赖值改变,先执行副作用,再更新组件,如果需要在组件更新后执行副作用函数,我们可以用到watchEffect的第二个参数

// 在组件更新后触发,这样你就可以访问更新的 DOM。
// 注意:这也将推迟副作用的初始运行,直到组件的首次渲染完成。
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: "post",
  }
);

Vue 3.2+的版本上更新的新的 api 可以替代上述操作,那就是watchPostEffect

watchEffect 的常用场景

ToDo...

<script setup>

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 TypeScript 声明 props 和抛出事件。
  • 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
  • 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。
  1. 在使用<script setup>的时候,任何声明的变量、函数以及 import 的内容都能在模板直接使用
<template>
  <div @click="handleClick">{{ color }}</div>
</template>

<script setup>
import { ref } from "vue";
const color = ref("red");
const handleClick = () => {
  console.log("我点击了");
};
</script>

<style module="molly">
.red {
  color: v-bind(color);
}
</style>
  1. 使用组件
<template>
  <!-- <my-component></my-component> -->
  <!-- 建议这么使用,保持一致性 -->
  <MyComponent />
</template>

<script setup>
import MyComponent from "./MyComponent.vue";
</script>
  1. 递归组件

一个单文件组件可以通过它的文件名被其自己所引用。例如:名为 FooBar.vue 的组件可以在其模板中用 <FooBar/> 引用它自己。 请注意这种方式相比于 import 导入的组件优先级更低。如果有命名的 import 导入和组件的推断名冲突了,可以使用 import 别名导入:

<script setup>
import { FooBar as FooBarChild } from "./components";
</script>
  1. definePropsdefineEmits

通过definePropsdefineEmits来声明 propsemits

<script setup>
const props = defineProps({
  foo: {
    type: String,
    default: '001'
  }
})

console.log(props.foo);

const emit = defineEmits(['change', 'delete']) // emit需要先声明

const handleClick = () => {
  emit('change')
}
</script>
  1. defineExpose

当使用 <script setup> 的时候外部组件是无法获取到组件的实例,该组件是默认关闭的,如果想从外部组件获取该组件实例的方法或者变量时,就会用到defineExpose

<script setup>
  import { ref } from 'vue'

  const a = 1;
  const b = ref(2);
  const test = () => {
    console.log("test")
  }

  defineExpose({
    a,
    b,
    test
  })
</script>
  1. 自定义指令

🏷️TIP

必须以 vNameOfDirective 的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。

<template>
  <div v-my-directive></div>
</template>

<script setup>
const vMyDirective = {
  beforeMount: (el) => {
    // 在元素上做些操作
  },
};
</script>
<script setup>
// 导入的指令同样能够工作,并且能够通过重命名来使其符合命名规范
import { myDirective as vMyDirective } from "./MyDirective.js";
</script>

🪄 teleport 组件

teleport组件可以直接通过to属性将其中的 html 插入到指定的节点上,相比vue2就非常的银杏 😆

<teleport to="body"> </teleport>

vite 相关问题

关于在 vite 项目中使用类似 webpack 中 require.context 方法

import.meta.glob("./zh/*.js") 会转为下述代码:

const modules = {
  ./zh/login.js: () => import("/src/lang/zh/login.js")
}

import.meta.glob 该方法返回一个 promise 来接收

import.meta.globEager 直接.default 来接收