Vue3 基础篇

199 阅读7分钟

本篇文章分享来自小伙伴「liuxin」的一次学习总结分享,希望跟社区的同学一起探讨。

本文主要对 Vue3 做一些介绍,通过本文可以快速了解 Vue3,能进行一些简单开发。

快速上手

前提条件

已安装 16.0 或更高版本的 Node.js

创建一个 Vue 应用

npm init vue@latest

根据提示选择想要的支持

image.png

选择完毕后,切换到 vue3-project 目录下,安装依赖并启动开发服务器。

image.png

指南

声明式渲染

Vue 的核心功能是声明式渲染:通过扩展于标准 HTML 的模板语法,我们可以根据 JavaScript 的状态来描述 HTML 应该是什么样子的。当状态改变时,HTML 会自动更新。

能在改变时触发更新的状态被称作是响应式的。

Vue 的 reactive() API 可以用来声明响应式状态。由 reactive()  创建的对象都是 JavaScript Proxy,其行为与普通对象一样:

<script setup>
  import { reactive } from 'vue';

  const counter = reactive({
    count: 0
  });

  console.log(counter.count); // 0
  counter.count++;
  console.log(counter.count); // 1
</script>

reactive 与 ref 的区别:

  • reactive() 只适用于对象(包括数组和内置类型,如 MapSet)。
  • ref() 则可以接受任何类型,ref 会返回一个包裹对象,并在 .value 属性下暴露内部值。 :::
<script setup>
  import { ref } from 'vue';

  const message = ref('Hello world');

  console.log(message);
  message.value = 'Hello Vue';
  console.log(message);
</script>

image.png

在组件的 <script setup> 块中声明的响应式状态,可以直接在模板中使用。

<script setup>
  import { reactive, ref } from 'vue';

  const counter = reactive({
    count: 0
  });
  const message = ref('Hello world');
</script>

<template>
  <h1>{{ message }}</h1>
  <p>{{ counter.count }}</p>
</template>

注意:在模板中访问 message ref 时不需要使用 .value,它会被自动解包。

Attribute 绑定

在 Vue 中,mustache 语法(即双大括号)只能用于文本插值,给 attribute 绑定一个动态的值,需要使用 v-bind 指令(v-bind 可以简写为 :)

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

  const dynamicId = ref('id');
</script>

<template>
  <div v-bind:id="dynamicId"></div>
	<!-- or -->
	<!-- <div :id="dynamicid"></div> -->
</template>

事件监听

使用 v-on 指令(v-on 可以简写为 @)监听 DOM 事件。

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

  const count = ref(0);

  function increment() {
    count.value++;
  }
</script>

<template>
  <button v-on:click="increment">{{ count }}</button>
	<!-- or -->
	<!-- <button @click="increment">{{ count }}</button>-->
</template>

表单绑定

  • 可以同时使用 v-bindv-on 在表单的输入元素上创建双向绑定:
<script setup>
  import { ref } from 'vue';
  const text = ref('');

  function onInput(e) {
    text.value = e.target.value;
  }
</script>

<template>
  <input :value="text" @input="onInput" />
</template>
  • 也可以使用 v-model 指令,v-model 是上述操作的语法糖:
<script setup>
  import { ref } from 'vue';
  const text = ref('');
</script>

<template>
  <input v-model="text" />
</template>

条件渲染

使用 v-if 指令来有条件的渲染元素:

<script setup>
  import { ref } from 'vue';
  const showName = ref(true);
</script>

<template>
  <h1 v-if="showName">Vue</h1>
</template>

使用 v-elsev-else-if 来表示其他分支:

<script setup>
  import { ref } from 'vue';
  const showName = ref(true);
</script>

<template>
  <h1 v-if="showName">Vue3</h1>
  <h1 v-else>Vue2</h1>
</template>

列表渲染

使用 v-for 指令来渲染一个基于源数组的列表:

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

  // 给每个 todo 对象设置了唯一的 id
  let id = 0;

  const todos = ref([
    { id: id++, text: 'lean HTML' },
    { id: id++, text: 'lean JavaScript' },
    { id: id++, text: 'lean Vue' }
  ]);
</script>

<template>
  <ul>
    <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
  </ul>
</template>

这里的 todo 是一个局部变量,表示当前正在迭代的数组元素,它只能在 v-for 所绑定的元素或者其内部访问,就像函数的作用域一样。

注意,我们还给每个 todo 对象设置了唯一的 id,并且将它作为特殊的 key attribute 绑定到每个 <li>key 使得 Vue 能够精确的移动每个 <li>,以匹配对应的对象在数组中的位置。

更新列表有两种方式:

  1. 在源数组上调用变更方法
<script setup>
  import { ref } from 'vue';

  // 给每个 todo 对象设置了唯一的 id
  let id = 0;

  const todos = ref([
    { id: id++, text: 'lean HTML' },
    { id: id++, text: 'lean JavaScript' },
    { id: id++, text: 'lean Vue' }
  ]);

  todos.value.push({ id: id++, text: 'lean Vite' });
</script>

<template>
  <ul>
    <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
  </ul>
</template>
  1. 使用新的数组替代原数组:
<script setup>
  import { ref } from 'vue';

  // 给每个 todo 对象设置了唯一的 id
  let id = 0;

  const todos = ref([
    { id: id++, text: 'lean HTML' },
    { id: id++, text: 'lean JavaScript' },
    { id: id++, text: 'lean Vue' }
  ]);

  todos.value = todos.value.filter((todo) => todo.id !== 2);
</script>

<template>
  <ul>
    <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
  </ul>
</template>

注意:

  • Vue2 不可以使用这种形式去改变数组,具体是因为 Vue2 出于性能/体验性价比考虑放弃这种形式,Vue2 改变数组使用 push()、pop()、shift()、unshift()、splice()、sort()、reverse() 可以达到响应式。
  • Vue3 可以使用这种形式去改变数组,因为 Vue3 使用的 Proxy,而 Proxy 作为新标准会吃到性能红利,其劫持的也是整个对象,不需要做特殊处理。
  • 具体可以参考为什么要用Proxy重构

计算属性

computed() 可以创建一个计算属性 ref,这个 ref 会动态的根据其他响应式数据源来计算其 .value

<script setup>
  import { ref, computed } from 'vue';

  // 给每个 todo 对象设置了唯一的 id
  let id = 0;

  const hideCompleted = ref(false);
  const todos = ref([
    { id: id++, text: 'lean HTML', done: false },
    { id: id++, text: 'lean JavaScript', done: true },
    { id: id++, text: 'lean Vue', done: false }
  ]);

  const filteredTodos = computed(() => {
    return todos.value.filter((todo) => !(todo.done && hideCompleted.value));
  });
</script>

<template>
  <ul>
    <li v-for="todo in filteredTodos" :key="todo.id">{{ todo.text }}</li>
  </ul>

  <button @click="hideCompleted = !hideCompleted">
    {{ hideCompleted ? 'Show all' : 'Hide completed' }}
  </button>
</template>

计算属性会自动跟踪其计算中所使用到的其他响应式状态,并将它们收集为自己的依赖,计算结果会被缓存,并只有在其依赖发生改变时才会被自动更新。

生命周期和模板引用

使用模板引用--也就是指向模板中一个 DOM 元素的 ref,然后通过这个特殊的 ref attribute 来实现模板引用,要访问该引用,我们需要声明一个同名的 ref:

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

  const p = ref(null);
</script>

<template>
  <p ref="p">Hello World!</p>
</template>

注意:

  • 这个 ref 使用 null 来初始化,这是因为当 <script setup> 执行时,DOM 元素还不存在,模板引用 ref 只能在组件挂载后访问。

要在挂载之后执行代码,需要使用 onMounted() 函数:

<script setup>
  import { ref, onMounted } from 'vue';

  const p = ref(null);

  onMounted(() => {
    p.value.textContent = 'Hello!';
  });
</script>

<template>
  <p ref="p">Hello World!</p>
</template>

onMounted 被称为生命周期钩子,它允许我们注册一个在组件特定生命周期调用的回调函数。还有其他的钩子,如:onUpdatedonUnmounted 等。更多可以查阅下面生命周期图示。

image.png

侦听器

当需要响应性的执行一些“副作用”,比如,当一个数字改变时将其输入到控制台,可以通过侦听器实现它:

<script setup>
  import { ref, watch } from 'vue';

  const count = ref(0);

  watch(count, (newCount) => {
    console.log(`new count is: ${newCount}`);
  });
</script>

<template>
  <p ref="p">Hello World!</p>
</template>

wacth 可以直接侦听一个 ref,并且只要 count 的值改变就会触发回调,也可以其他类型的数据源。

组件

要是用子组件,需要先导入它:

<script setup>
  import ChildComp from './ChildComp.vue';
</script>

<template>
  <ChildComp />
</template>

Props

子组件可以通过 props 从父组件接受动态数据,首先,需要声明它所接受的 props:

<script setup>
  const props = defineProps({
    msg: String
  });
  console.log(props.msg)
</script>

注意:

  • defineProps() 是一个编译时宏,并不需要导入。一旦声明 msg prop 就可以在子组件的模板中使用,它也可以通过 defineProps() 所返回的对象在 JavaScript 中访问。

父组件可以像声明式 HTML attributes 一样传递 props,若要传递动态数据,可以使用 v-bind 语法:

<script setup>
  import { ref } from 'vue';
  import ChildComp from './ChildComp.vue';

  const msg = ref('from parent');
</script>
<template>
  <ChildComp :msg="msg" />
</template>

Emits

子组件还可以向父组件触发事件:

<script setup>
// 声明触发的事件
const emit = defineEmits(['response']);

// 带参数触发
emit('response', 'from child');
</script>

emit() 的第一参数是事件的名称,其他所有参数都将传递给事件监听器。

父组件可以使用 v-on 监听子组件触发的事件,这里的处理函数接收了子组件触发事件时的额外参数并将它赋值给了本地状态:

<script setup>
  import { ref } from 'vue';
  import ChildComp from './ChildComp.vue';

  const msg = ref('from parent');
</script>
<template>
  <ChildComp :msg="msg" @response="(param) => msg = param" />
</template>

插槽

除了通过 props 传递数据外,父组件还可以通过插槽 (slots) 将模板片段传递给子组件:

<script setup>
  import ChildComp from './ChildComp.vue';
</script>
<template>
  <ChildComp>
    This is some slot content!
  </ChildComp>
</template>

在子组件中,可以使用 <slot> 元素作为插槽出口 (slot outlet) 渲染父组件中的插槽内容 (slot content),<slot> 插口中的内容将被当作“默认”内容:它会在父组件没有传递任何插槽内容时显示:

<template>
  <!-- 在子组件的模板中 -->
	<slot/>
	<!-- or 设置默认内容,在父组件没有传递插槽内容时会显示 -->
  <slot>Fallback content</slot>
</template>

总结

  1. 声明响应式状态可以使用 reactive()ref(),组合式 API 需要在 <script> 设置 setup attribute 后使用,如果是选项式的使用方式和 Vue2 类似。

    reactive 与 ref 的区别:

    • reactive() 只适用于对象(包括数组和内置类型,如 MapSet)。
    • ref() 则可以接受任何类型,ref 会返回一个包裹对象,并在 .value 属性下暴露内部值。
  2. 给 attribute 绑定一个动态的值,需要使用 v-bind 指令(v-bind 可以简写为 :)。

  3. 使用 v-on 指令(v-on 可以简写为 @)监听 DOM 事件。

  4. 可以同时使用 v-bindv-on 在表单地输入元素上创建双向绑定,也可以使用 v-model 指令,v-model 是同时使用 v-bindv-on 操作的语法糖。

  5. 使用 v-ifv-elsev-else-if 来有条件的渲染元素,这些指令是通过添加移除 DOM 来控制显示隐藏的。

  6. 使用 v-for 指令来渲染一个基于源数组的列表,设置特殊的 key attribute 使得 Vue 能够精确的移动每个 <li>,以匹配对应的对象在数组中的位置。

  7. computed() 可以创建一个计算属性 ref,这个 ref 会动态的根据其他响应式数据源来计算其 .value

  8. 可以使用 ref attribute 进行模板引用,要访问该引用,我们需要声明一个同名的 ref,这个 ref 使用 null 来初始化,这是因为当 <script setup> 执行时,DOM 元素还不存在,模板引用 ref 只能在组件挂载后访问。生命周期钩子使用 onMounted() 等。

  9. wacth 可以直接侦听一个 ref,并且只要 count 的值改变就会触发回调。

  10. defineProps() 是一个编译时宏,并不需要导入。一旦声明 msg prop 就可以在子组件的模板中使用,它也可以通过 defineProps() 所返回的对象在 JavaScript 中访问。

  11. 使用 defineEmits 声明要触发的事件。