Vue3 官方文档速通(上)

简介: Vue3 官方文档速通(上)

一 开始

1. 简介

1.1 什么是 Vue?

一款 JS 框架,并有两个核心功能:声明式渲染、响应性。

1.2 渐进式框架

根据不同的需求场景,使用不同方式的 Vue,比如:

       无需构建步骤,直接引入 vuejs。

       在任何页面中作为 Web Components 嵌入

       使用构建步骤,单页应用 (SPA)

       全栈 / 服务端渲染 (SSR)

       Jamstack / 静态站点生成 (SSG)

       开发桌面端、移动端、WebGL,甚至是命令行终端中的界面

Vue 为什么可以称为“渐进式框架”:它是一个可以与你共同成长、适应你不同需求的框架。

1.3 单文件组件

单文件组件是 Vue 的标志性功能。*.vue、SFC 就是单文件组件:将一个组件的逻辑 (JS),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。

1.4 API 风格

选项式 API(Options API):包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted。

组合式 API(Composition API):使用导入的 API 函数来描述组件逻辑。通常会与  搭配使用。

综上:两种 API 是同一个底层系统构建的。选项式 API 是在组合式 API 的基础上实现的!

2. 快速上手

2.1 创建一个 Vue 应用

// 安装并执行 create-vue,它是 Vue 官方的项目脚手架工具。
npm create vue@latest

2.2 通过 CDN 使用 Vue

可以用于增强静态的 HTML 或与后端框架集成。但将无法使用单文件组件 (SFC) 语法:

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
 
<div id="app">{{ message }}</div>
 
<script>
  const { createApp, ref } = Vue;
 
  createApp({
    setup() {
      const message = ref("Hello vue!");
      return {
        message,
      };
    },
  }).mount("#app");
</script>

通过 CDN 以及原生 ES 模块使用 Vue:

<div id="app">{{ message }}</div>
 
<script type="module">
  import {
    createApp,
    ref,
  } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";
 
  createApp({
    setup() {
      const message = ref("Hello Vue!");
      return {
        message,
      };
    },
  }).mount("#app");
</script>

使用导入映射表 (Import Maps) 来告诉浏览器如何定位到导入的 vue:

<script type="importmap">
  {
    "imports": {
      "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
    }
  }
</script>
 
<div id="app">{{ message }}</div>
 
<script type="module">
  import { createApp, ref } from "vue";
 
  createApp({
    setup() {
      const message = ref("Hello Vue!");
      return {
        message,
      };
    },
  }).mount("#app");
</script>

分割代码成单独的 JS 文件,以便管理。

<!-- index.html -->
<div id="app"></div>
 
<script type="module">
  import { createApp } from "vue";
  import MyComponent from "./my-component.js";
 
  createApp(MyComponent).mount("#app");
</script>
// my-component.js
import { ref } from "vue";
export default {
  setup() {
    const count = ref(0);
    return { count };
  },
  template: `<div>count is {{ count }}</div>`,
};

注意:直接点击 index.html,会抛错误,因为 ES 模块不能通过 file:// 协议工作。只能通过 http:// 协议工作。需要启动一个本地的 HTTP 服务器,通过命令行在 HTML 文件所在文件夹下运行 npx serve。

二 基础

1. 创建一个 Vue 应用

1.1 应用实例

通过 createApp 函数创建 Vue 应用实例:

import { createApp } from "vue";
 
const app = createApp({
  /* 根组件选项 */
});

1.2 根组件

createApp 需要传入一个根组件,其他组件将作为其子组件:

import { createApp } from "vue";
// 从一个单文件组件中导入根组件
import App from "./App.vue";
 
const app = createApp(App);

1.3 挂载应用

调用 .mount() 方法,传入一个 DOM 元素或是 CSS 选择器。它的返回值是根组件实例而非应用实例:

<div id="app"></div>
import { createApp } from "vue";
// 从一个单文件组件中导入根组件
import App from "./App.vue";
 
const app = createApp(App);
app.mount("#app");

1.4 应用配置

注意:确保在挂载应用实例之前完成所有应用配置!

import { createApp } from "vue";
// 从一个单文件组件中导入根组件
import App from "./App.vue";
 
const app = createApp(App);
 
// 应用实例的 .config 对象可以进行一些配置,例如配置错误处理器:用来捕获所有子组件上的错误:
app.config.errorHandler = (err) => {
  /* 处理错误 */
};
 
// 全局挂载组件
app.component("TodoDeleteButton", TodoDeleteButton);
 
// 全局属性的对象。
app.config.globalProperties.msg = "hello";
 
app.mount("#app");

1.5 多个应用实例

每个应用都拥有自己的用于配置和全局资源的作用域:

const app1 = createApp({
  /* ... */
});
app1.mount("#container-1");
 
const app2 = createApp({
  /* ... */
});
app2.mount("#container-2");

2. 模板语法

2.1 文本插值

最基本的数据绑定是文本插值,使用“Mustache”语法 (即双大括号):

<span>Message: {{ msg }}</span>

2.2 原始 HTML

双大括号会将数据解释为纯文本,若想插入 HTML,需要使用 v-html 指令。

安全警告:动态渲染 HTML 是很危险的,容易造成 XSS 漏洞。仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容。

<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

2.3 Attribute 绑定

绑定 attribute,使用 v-bind 指令:

<div v-bind:id="dynamicId"></div>
<!-- 简写 -->
<div :id="dynamicId"></div>

绑定多个值,通过不带参数的 v-bind。

const objectOfAttrs = {
  id: "container",
  class: "wrapper",
};
<div v-bind="objectOfAttrs"></div>

2.4 使用 JS 表达式

数据绑定都支持完整的 JS 表达式,也就是一段能够被求值的 JS 代码。一个简单的判断方法是是否可以合法地写在 return 后面:

{{ number + 1 }}
 
{{ ok ? 'YES' : 'NO' }}
 
{{ message.split('').reverse().join('') }}
 
<div :id="`list-${id}`"></div>

可以在绑定的表达式中使用一个组件暴露的方法:

<time :title="toTitleDate(date)" :datetime="date">
  {{ formatDate(date) }}
</time>

2.5 指令 Directives

指 v- 前缀的特殊 attribute。它的值为 JS 表达式(v-for、v-on、v-slot 除外),指令值变化时响应式的更新 DOM,比如 v-if:

<p v-if="seen">Now you see me</p>

带参数的指令,用':'隔开:

<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a>

指令的参数,也可以动态绑定,用 '[ ]' 包裹:

<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>

带修饰符的指令,用 '.' 隔开:

<!-- 触发的事件调用 event.preventDefault() -->
<form @submit.prevent="onSubmit">...</form>

3. 响应式基础

3.1 声明响应式状态

官方推荐使用 ref() 函数来声明响应式状态:

import { ref } from "vue";
 
const count = ref(0);

ref() 接收参数,并返回一个带有 .value 属性的 ref 对象:

const count = ref(0);
 
console.log(count); // { value: 0 }
console.log(count.value); // 0
 
count.value++;
console.log(count.value); // 1

在模板中使用 ref 变量,不需要添加 .value。ref 会自动解包。也可以直接在事件监听器中改变一个 ref:

<button @click="count++">{{ count }}</button>

通过单文件组件(SFC),使用 <script setup> 来大幅度地简化代码:

<script setup>
import { ref } from "vue";
 
const count = ref(0);
 
function increment() {
  count.value++;
}
</script>
 
<template>
  <button @click="increment">
    {{ count }}
  </button>
</template>

为什么使用 ref,而不是普通的变量。这是因为 Vue 需要通过.value 属性来实现状态响应性。基础原理是在 getter 中追踪,在 setter 中触发:

// 伪代码,不是真正的实现
const myRef = {
  _value: 0,
  get value() {
    track();
    return this._value;
  },
  set value(newValue) {
    this._value = newValue;
    trigger();
  },
};

要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API:

1. import { nextTick } from "vue";
2. 
3. async function increment() {
4.   count.value++;
5. await nextTick();
6. // 现在 DOM 已经更新了
7. }

3.2 reactive()

reactive(),参数只能是对象类型,返回的是一个原始对象的 Proxy,它和原始对象是不相等的:

import { nextTick } from "vue";
 
async function increment() {
  count.value++;
  await nextTick();
  // 现在 DOM 已经更新了
}

reactive() 局限性包括:只能用于对象类型(对象,数组,Map,Set)、不能替换整个对象、对结构操作不友好:

let state = reactive({ count: 0 });
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 });
 
// 对解构不友好
const state = reactive({ count: 0 });
// 当解构时,count 已经与 state.count 断开连接
let { count } = state;
// 不会影响原始的 state
count++;
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count);

reactive() API 有一些局限性,官方建议使用 ref() 作为声明响应式状态的主要 API。博主个人还是喜欢 ref,reactive 混着用,注意那些局限性就可以了。

3.3 额外的 ref 解包细节

一个 ref 会在作为响应式对象的属性被访问或修改时自动解包:

const count = ref(0);
const state = reactive({
  count,
});
 
console.log(state.count); // 0
 
state.count = 1;
console.log(count.value); // 1

4. 计算属性

4.1 基础示例

computed() 方法期望接收一个 getter 函数,返回为一个计算属性 ref:

<script setup>
import { reactive, computed } from "vue";
 
const author = reactive({
  name: "John Doe",
  books: [
    "Vue 2 - Advanced Guide",
    "Vue 3 - Basic Guide",
    "Vue 4 - The Mystery",
  ],
});
 
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? "Yes" : "No";
});
</script>
 
<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

4.2 计算属性缓存 vs 方法

计算属性值会基于其响应式依赖被缓存。方法调用则总会在重新渲染时再次执行。

4.3 可写计算属性

计算属性默认是只读的。但可以通过设置 get 和 set 函数变成可读可写:

<script setup>
import { ref, computed } from "vue";
 
const firstName = ref("John");
const lastName = ref("Doe");
 
const fullName = computed({
  // getter
  get() {
    return firstName.value + " " + lastName.value;
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(" ");
  },
});
</script>

4.4 最佳实践

使用计算属性,不要在里面做异步请求和修改 DOM。并且尽量保持只读。

5. 类与样式绑定

5.1 绑定 HTML class

通过对象来动态切换 class:

<div :class="{ active: isActive }"></div>

可以直接绑定一个对象:

const classObject = reactive({
  active: true,
  "text-danger": false,
});
<div :class="classObject"></div>

通过数组渲染多个 class:

const activeClass = ref("active");
const errorClass = ref("text-danger");
<div :class="[activeClass, errorClass]"></div>

数组中也可以使用 JS 表达式:

1. <div :class="[isActive ? activeClass : '', errorClass]"></div>
2. <!-- 等于 -->
3. <div :class="[{ activeClass: isActive }, errorClass]"></div>

如果组件有多个根元素,透传的 class 需要通过组件的 $attrs 属性来实现指定:

<MyComponent class="baz" />
<!-- MyComponent 模板使用 $attrs 时 -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>
<p class="baz">Hi!</p>
<span>This is a child component</span>

5.2 绑定内联样式

值为对象,对应的是 style 属性:

const activeColor = ref("red");
const fontSize = ref(30);
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

直接绑定一个样式对象使模板更加简洁:

const styleObject = reactive({
  color: "red",
  fontSize: "13px",
});
<div :style="styleObject"></div>

还可以绑定一个包含多个样式对象的数组:

<div :style="[baseStyles, overridingStyles]"></div>

6. 条件渲染

6.1 v-if、v-else、v-else-if

v-if 指令用于条件性地渲染内容。当值为真时才被渲染:

<h1 v-if="awesome">Vue is awesome!</h1>

v-else 为 v-if 添加一个“else 区块”。并且必须跟在一个 v-if 或者 v-else-if 元素后面:

<button @click="awesome = !awesome">Toggle</button>
 
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

v-else-if 提供的是相应于 v-if 的“else if 区块”。可以连续多次使用。并且必须跟在一个 v-if 或者 v-else-if 元素后面:

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

可以使用 <template> 包裹想要一起切换的元素块,渲染的结果并不会包含这个 <template> 元素。v-else、v-else-if 同理:

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

6.2 v-show

v-show 仅切换了元素上 display 的 CSS 属性。且不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。

6.3 v-if vs v-show

v-if 切换的组件都会被销毁与重建。但是如果初始条件为 false,则不会做任何事,有更高的切换开销。

v-show 切换的组件只有 display 属性被修改,但初始化都会渲染。有更高的渲染开销。

综上:如果切换频繁用 v-show,反之用 v-if。

6.4 v-if 和 v-for

v-if 和 v-for 不推荐同时使用,因为这样二者的优先级不明显。如果二者同时存在一个元素上,v-if 优先执行。

7. 列表渲染

7.1 v-for

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

const parentMessage = ref("Parent");
const items = ref([{ message: "Foo" }, { message: "Bar" }]);
<li v-for="(item, index) in items">
  {{ parentMessage }} - {{ index }} - {{ item.message }}
</li>

可以使用 of 替代 in:

<div v-for="item of items"></div>

7.2 v-for 与对象

v-for 可以遍历对象属性:

const myObject = reactive({
  title: "How to do lists in Vue",
  author: "Jane Doe",
  publishedAt: "2016-04-10",
});
<!-- 第二个参数表示属性名,第三个参数表示位置索引 -->
<li v-for="(value, key, index) in myObject">
  {{ index }}. {{ key }}: {{ value }}
</li>

7.3 在 v-for 里使用范围值

v-for 可以接受一个整数。从 1~n 开始遍历:

<span v-for="n in 10">{{ n }}</span>

7.4 <template> 上的 v-for

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

7.5 v-for 与 v-if

在同一节点上,v-if 比 v-for 优先级更高。意味着 v-if 的条件无法访问到 v-for 中的变量:

<!-- 会抛出一个错误,因为v-if访问不到属性 todo -->
<li v-for="todo in todos" v-if="!todo.isComplete">{{ todo.name }}</li>
 
<!-- 解决方法:包裹一层template -->
<template v-for="todo in todos">
  <li v-if="!todo.isComplete">{{ todo.name }}</li>
</template>

7.6 通过 key 管理状态

Vue 默认的 “就地更新” 策略是高效的,但当数据源的顺序改变时,Vue 不会随之移动 DOM 顺序,而是就地更新每个元素。解决这一问题需要给每个元素添加一个 Key 值,官方推荐使用 v-for 都添加 key 值:

<div v-for="item in items" :key="item.id">
  <!-- 内容 -->
</div>

7.7 数组变化侦测

Vue 能侦听响应式数组的变化。改变原数组的方法:push(),pop(),shift(),unshift(),splice(),sort(),reverse()。不改变元素组:filter(),concat(),slice()

7.8 展示过滤或排序后的结果

使用 computed,在不修改数据源的前提下,展示过滤或排序后的数据:

const numbers = ref([1, 2, 3, 4, 5]);
 
const evenNumbers = computed(() => {
  return numbers.value.filter((n) => n % 2 === 0);
});
<li v-for="n in evenNumbers">{{ n }}</li>

8. 事件处理

8.1 监听事件

使用 v-on 指令 (简写 @) 来监听 DOM 事件

8.2 内联事件处理器

const count = ref(0);
<button @click="count++">Add 1</button>

8.3 方法事件处理器

const name = ref("Vue.js");
 
function greet(event) {
  alert(`Hello ${name.value}!`);
  // `event` 是 DOM 原生事件
  if (event) {
    alert(event.target.tagName);
  }
}
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>

8.4 事件传参

function say(message) {
  alert(message);
}
<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>

8.5 在方法中访问原生 DOM

通过 $event 变量访问原生 DOM,或使用内联箭头函数中的 event 形参:

<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">Submit</button>
 
<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
  Submit
</button>
function warn(message, event) {
  // 这里可以访问原生事件
  if (event) {
    event.preventDefault();
  }
  alert(message);
}

8.6 事件修饰符

修饰符是用 . 表示的指令后缀,包含:.stop,.prevent,.self,.capture,.once,.passive:

<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
 
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
 
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
 
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
 
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>

.capture、.once 和 .passive 修饰符与原生 addEventListener 事件相对应:

<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
 
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
 
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<!-- .passive 修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。 -->
<div @scroll.passive="onScroll">...</div>

8.7 按键修饰符

.enter,.tab,.delete (捕获“Delete”和“Backspace”两个按键),.esc,.space,.up,.down,.left,.right

<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />

9.  表单输入绑定

v-model 数据双向绑定

<input v-model="text" />
<!-- 等价于 -->
<input :value="text" @input="event => text = event.target.value" />

9.1 基本用法

<!-- 文本 -->
<input v-model="message" placeholder="edit me" />
 
<!-- 多行文本 -->
<textarea v-model="message" placeholder="add multiple lines"></textarea>
 
<!-- 复选框 -->
<input type="checkbox" id="checkbox" v-model="checked" />
 
<!-- 单选按钮 -->
<input type="radio" id="one" value="One" v-model="picked" />
 
<!-- 选择器 -->
<select v-model="selected">
  <option disabled value="">Please select one</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

9.2 值绑定

<!-- 复选框 -->
<!-- true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。 -->
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
 
<!-- 单选按钮 -->
<!-- pick 会在第一个按钮选中时被设为 first,在第二个按钮选中时被设为 second。 -->
<input type="radio" v-model="pick" :value="first" />
<input type="radio" v-model="pick" :value="second" />
 
<!-- 选择器选项 -->
<!-- v-model 同样也支持非字符串类型的值绑定!
在上面这个例子中,当某个选项被选中,selected 会被设为该对象字面量值 { number: 123 }。 -->
<select v-model="selected">
  <!-- 内联对象字面量 -->
  <option :value="{ number: 123 }">123</option>
</select>

9.3 修饰符

<!-- .lazy -->
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
 
<!-- .number -->
<!-- 让用户输入自动转换为数字 -->
<input v-model.number="age" />
 
<!-- .trim -->
<!-- 默认自动去除用户输入内容中两端的空格 -->
<input v-model.trim="msg" />

10. 生命周期

Vue 组件实例的创建到销毁,一些列的生命周期钩子函数,可以让我们特定阶段运行自己的代码。最常用的是 onMounted、onUpdated 和 onUnmounted:

<script setup>
  import { onMounted, onUpdated, onUnmounted } from "vue";
 
  // 初始渲染、创建 DOM 节点后
  onMounted(() => {
    console.log(`the component is now mounted.`);
  });
 
  // 组件更新 DOM 树之后。
  onUpdated(() => {
    // 文本内容应该与当前的 `count.value` 一致
    console.log(document.getElementById("count").textContent);
  });
 
  // 组件实例被卸载之后。
  let intervalId;
  onMounted(() => {
    intervalId = setInterval(() => {
      // ...
    });
  });
  onUnmounted(() => clearInterval(intervalId));
</script>

11. 侦听器

11.1 基本示例

<script setup>
  import { ref, watch } from "vue";
 
  const question = ref("");
  const answer = ref("Questions usually contain a question mark. ;-)");
  const loading = ref(false);
 
  // 可以直接侦听一个 ref
  watch(question, async (newQuestion, oldQuestion) => {
    if (newQuestion.includes("?")) {
      loading.value = true;
      answer.value = "Thinking...";
      try {
        const res = await fetch("https://yesno.wtf/api");
        answer.value = (await res.json()).answer;
      } catch (error) {
        answer.value = "Error! Could not reach the API. " + error;
      } finally {
        loading.value = false;
      }
    }
  });
</script>
 
<template>
  <p>
    Ask a yes/no question:
    <input v-model="question" :disabled="loading" />
  </p>
  <p>{{ answer }}</p>
</template>

watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:

const x = ref(0);
const y = ref(0);
 
// 单个 ref
watch(x, (newX) => {
  console.log(`x is ${newX}`);
});
 
// getter 函数
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`);
  }
);
 
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`);
});

不能直接监听对象的属性:

const obj = reactive({ count: 0 });
// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
  console.log(`count is: ${count}`);
});
 
// 解决:提供一个 getter 函数
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`);
  }
);

11.2 深层侦听器

给 watch() 传入一个响应式对象,会监听对象的所有属性:

const obj = reactive({ count: 0 });
 
watch(obj, (newValue, oldValue) => {
  // 在嵌套的属性变更时触发
  // 注意:`newValue` 此处和 `oldValue` 是相等的
  // 因为它们是同一个对象!
});
 
obj.count++;

11.3 即时回调的侦听器

watch 默认是懒执行的:仅当数据源变化时,才会执行回调。可以通过设置 immediate:true,立即执行一遍回调:

watch(
  source,
  (newValue, oldValue) => {
    // 立即执行,且当 `source` 改变时再次执行
  },
  { immediate: true }
);

11.4 watchEffect()

侦听器的回调与源完全相同的响应式状态是很常见的。可以用 watchEffect 函数 来简化:

const todoId = ref(1);
const data = ref(null);
 
watch(
  todoId,
  async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
    );
    data.value = await response.json();
  },
  { immediate: true }
);
 
// 简化
watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  );
  data.value = await response.json();
});

11.5 回调的触发时机

修改了监听的响应状态后,默认先触发监听函数,后更新组件。意味着在监听函数中访问的 DOM 是更新前的状态,如果要访问更新后的 DOM,可以通过 fulsh:'post'配置:

watch(source, callback, {
  flush: "post",
});
watchEffect(callback, {
  flush: "post",
});
 
// 或者
import { watchPostEffect } from "vue";
watchPostEffect(() => {
  /* 在 Vue 更新后执行 */
});

12. 模板引用

使用 ref attribute 访问 DOM 元素:

<input ref="input" />

12.1 访问模板引用

声明一个同名的 ref:

<script setup>
  import { ref, onMounted } from "vue";
 
  // 声明一个 ref 来存放该元素的引用
  // 必须和模板里的 ref 同名
  const input = ref(null);
 
  onMounted(() => {
    input.value.focus();
  });
</script>
 
<template>
  <input ref="input" />
</template>

侦听模板引用 ref 的变化,需要考虑值为 null 的情况:

watchEffect(() => {
  if (input.value) {
    input.value.focus();
  } else {
    // 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
  }
});

12.2 v-for 中的模板引用

v-for 使用模板引用时,对应的 ref 包含的是一个数组:

<script setup>
  import { ref, onMounted } from "vue";
 
  const list = ref([
    /* ... */
  ]);
 
  const itemRefs = ref([]);
 
  onMounted(() => console.log(itemRefs.value));
</script>
 
<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">{{ item }}</li>
  </ul>
</template>

12.3 函数模板引用

ref attribute 还可以绑定为一个函数,每次组件更新时都被调用。收到的第一个参数是元素引用:

<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }" />

13. 组件基础

组件允许我们将 UI 划分为独立的、可重用的部分,并且对每个部分进行单独的思考。

13.1 定义一个组件

.vue 文件 (简称 SFC):

<script setup>
  import { ref } from "vue";
 
  const count = ref(0);
</script>
 
<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>

13.2 使用组件

官方推荐使用 PascalCase 标签名用于和原生 HTML 标签区分:

<script setup>
import ButtonCounter from "./ButtonCounter.vue";
</script>
 
<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

13.3 传递 props

通过 defineProps 宏,声明可以向组件传递的状态:

<!-- BlogPost.vue -->
<script setup>
defineProps(["title"]);
</script>
 
<template>
  <h4>{{ title }}</h4>
</template>

13.4 监听事件

通过 defineEmits 宏,声明需要抛出的事件。子组件可以通过调用内置的 $emit 方法,通过传入事件名称来抛出一个事件:

<!-- BlogPost.vue -->
<script setup>
defineProps(["title"]);
const emit = defineEmits(["enlarge-text"]);
 
const handleClick = () => {
  emit("enlarge-text");
};
</script>
 
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="handleClick">Enlarge text</button>
  </div>
</template>

13.5 通过插槽来分配内容

通过 <slot> 元素来实现:

<!-- AlertBox.vue -->
<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>
 
<style scoped>
.alert-box {
  /* ... */
}
</style>
<AlertBox> Something bad happened. </AlertBox>

13.6 动态组件

通过 <component :is="..."> 来在多个组件间作切换,可以使用 <KeepAlive> 保持被切换的组件“存活”的状态:

<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>

Vue3 官方文档速通(中)https://developer.aliyun.com/article/1511952?spm=a2c6h.13148508.setting.33.f8774f0e6djMAY

目录
相关文章
|
3天前
|
JSON JavaScript 前端开发
在Vue3项目中,Vite起到的作用及其可以做的事情
Vite是Vue 3项目的关键快速开发服务器和高效的热模块替换(HMR)。它的亮点包括:使用ES模块实现快速启动和动态加载,模块级别HMR提升开发效率,Rollup打包优化生产构建。此外,Vite还支持插件系统、自定义配置、静态资源处理和现代JS特性。通过`npm create vite`可快速搭建Vue项目,配置文件`vite.config.js`可定制行为,`.env`文件管理环境变量。Vite优化了开发与构建流程,提高开发者效率并保证构建性能。
|
11天前
|
JavaScript 前端开发 开发者
vue3+ts配置跨域报错问题解决:> newpro2@0.1.0 serve > vue-cli-service serve ERROR Invalid options in vue.
【6月更文挑战第3天】在 Vue CLI 项目中遇到 &quot;ERROR Invalid options in vue.config.js: ‘server’ is not allowed&quot; 错误是因为尝试在 `vue.config.js` 中使用不被支持的 `server` 选项。正确配置开发服务器(如代理)应使用 `devServer` 对象,例如设置代理到 `http://xxx.com/`: ```javascript module.exports = { devServer: {
24 1
|
11天前
|
JavaScript 前端开发 数据安全/隐私保护
vue3+ts+elementplus写一个登录页面教程
【6月更文挑战第3天】本文介绍了如何使用 Vue 3 和 TypeScript 创建一个登录页面。首先,需安装 Vue CLI,然后创建新项目并启用 TypeScript 支持。接着,创建 `Login.vue` 组件,设计登录表单,包括用户账号、密码和验证码字段,并实现相关验证规则。页面样式包括背景、登录框和按钮等元素的布局与样式。最后,展示了`&lt;script&gt;`部分的代码,包括表单验证逻辑、生成验证码的函数以及登录提交处理。文章还提供了一个登录页面的截图和完整代码示例。
47 1
|
11天前
|
JavaScript API
vue3+element实现一个公告面板
vue3+element实现一个公告面板
36 0
vue3+element plus图片预览点击按钮直接显示图片的预览形式
vue3+element plus图片预览点击按钮直接显示图片的预览形式
|
11天前
|
JavaScript
|
1天前
|
JavaScript 前端开发 测试技术
使用 Vue CLI 脚手架生成 Vue 项目
通过 Vue CLI 创建 Vue 项目可以极大地提高开发效率。它不仅提供了一整套标准化的项目结构,还集成了常用的开发工具和配置,使得开发者可以专注于业务逻辑的实现,而不需要花费大量时间在项目配置上。
54 7
使用 Vue CLI 脚手架生成 Vue 项目
|
3天前
|
JavaScript 算法
“Error: error:0308010C:digital envelope routines::unsupported”启动vue项目遇到一个错误【已解决
“Error: error:0308010C:digital envelope routines::unsupported”启动vue项目遇到一个错误【已解决
8 1
|
3天前
|
JavaScript
error Component name “Login“ should always be multi-word vue/multi-word-component-names【已解决】
error Component name “Login“ should always be multi-word vue/multi-word-component-names【已解决】
9 1