毓君 發表於 2026-5-5 22:37:25

Vue3 + TypeScript 开发指南

<h1 id="0x00-概述">0x00 概述</h1>
<ul>
<li>阅读以下内容需要具备一定的 Vue2 基础</li>
<li>代码采用规范为:<strong>TypeScript</strong> + <strong>组合式 API</strong> + <strong>setup 语法糖</strong></li>
</ul>
<h2 id="1vue3-简介">(1)Vue3 简介</h2>
<ul>
<li>Vue3 第一个正式版发布于 2020 年 9 月 18 日</li>
<li>Vue3 中文官网</li>
<li>Vue3 相比 Vue2 的优势:
<ol>
<li>性能提升:打包体积减小,初次渲染和更新渲染都更快,内存使用减少</li>
<li>源码升级:使用 <em>Proxy</em> 实现响应式,重写虚拟 DOM 的实现和 <code>Tree-Shaking</code></li>
<li>支持 TypeScript</li>
<li>新增特性:组合式 API、新内置组件、新生命周期钩子等</li>
</ol>
</li>
</ul>
<h2 id="2typescript-概述">(2)TypeScript 概述</h2>
<ul>
<li>TypeScript 入门:学习 TypeScript | 稀土掘金-SRIGT</li>
</ul>
<h1 id="0x01-第一个项目">0x01 第一个项目</h1>
<h2 id="1创建项目">(1)创建项目</h2>
<blockquote>
<p>创建项目共有两种方法</p>
</blockquote>
<ol>
<li>
<p>使用 vue-cli 创建</p>
<ol>
<li>
<p>在<strong>命令提示符</strong>中使用命令 <code>npm install -g @vue/cli</code> 下载并全局安装 vue-cli</p>
<blockquote>
<p>如果已经安装过,则可以使用命令 <code>vue -V</code> 查看当前 vue-cli 的版本,版本要求在 4.5.0 以上</p>
</blockquote>
<blockquote>
<p>如果需要多版本 vue-cli 共存,则可以参考文章:安装多版本Vue-CLI的实现方法 | 脚本之家-webgiser</p>
</blockquote>
</li>
<li>
<p>使用命令 <code>vue create project_name</code> 开始创建项目</p>
</li>
<li>
<p>使用方向键选择 <code>Default ()</code></p>
</li>
<li>
<p>等待创建完成后,使用命令 <code>cd project_name</code> 进入项目目录</p>
</li>
<li>
<p>使用命令 <code>npm serve</code> 启动项目</p>
</li>
</ol>
</li>
<li>
<p>使用 vite 创建</p>
<blockquote>
<p>相比使用 vue-cli 创建,使用 vite 的优势在于</p>
<ul>
<li>轻量快速的热重载,实现极速的项目启动</li>
<li>对 TypeScript、JSX、CSS 等支持开箱即用</li>
<li>按需编译,减少等待编译的时间</li>
</ul>
</blockquote>
<ol>
<li>使用命令 <code>npm create vue@latest</code> 创建 Vue3 项目</li>
<li>输入项目名称</li>
<li>添加 TypeScript 支持</li>
<li>不添加 JSX 支持、Vue Router、Pinia、Vitest、E2E 测试、ESLint 语法检查、Prettier 代码格式化</li>
<li>使用命令 <code>cd [项目名称]</code> 进入项目目录</li>
<li>使用命令 <code>npm install</code> 添加依赖</li>
<li>使用命令 <code>npm run dev</code> 启动项目</li>
</ol>
</li>
</ol>
<h2 id="2项目结构">(2)项目结构</h2>
<ul>
<li>
<p>node_modules:项目依赖</p>
</li>
<li>
<p>public:项目公共资源</p>
</li>
<li>
<p>src:项目资源</p>
<ul>
<li>
<p>assets:资源目录</p>
</li>
<li>
<p>components:组件目录</p>
</li>
<li>
<p>main.ts:项目资源入口文件</p>
<pre><code class="language-typescript">import './assets/main.css'                // 引入样式文件

import { createApp } from 'vue'        // 引入创建 Vue 应用方法
import App from './App.vue'                // 引入 App 组件

createApp(App).mount('#app')        // 创建以 App 为根组件的应用实例,并挂载到 id 为 app 的容器中(该容器为 index.html)
</code></pre>
</li>
<li>
<p>App.vue:应用根组件</p>
<pre><code class="language-vue">&lt;template&gt;
        &lt;!-- 模型 --&gt;
&lt;/template&gt;

&lt;script lang="ts"&gt;
    /* 控制 */
&lt;/script&gt;

&lt;style&gt;
    /* 样式 */
&lt;/style&gt;
</code></pre>
</li>
</ul>
</li>
<li>
<p>env.d.ts:TypeScript 环境配置文件,包括文件类型声明等</p>
</li>
<li>
<p>index.html:项目入口文件</p>
</li>
<li>
<p>vite.config.ts:项目配置文件</p>
</li>
</ul>
<h1 id="0x02-核心语法">0x02 核心语法</h1>
<h2 id="1组合式-api">(1)组合式 API</h2>
<ul>
<li>
<p>首先使用 Vue2 语法完成一个组件</p>
<ul>
<li>
<p>项目结构</p>
<div class="mermaid">graph TB
src--&gt;components--&gt;Person.vue
src--&gt;App.vue &amp; main.ts
</div></li>
<li>
<p>详细代码</p>
<ul>
<li>
<p>main.ts</p>
<pre><code class="language-typescript">import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
</code></pre>
</li>
<li>
<p>App.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h1&gt;App&lt;/h1&gt;
    &lt;Person /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script lang="ts"&gt;
import Person from './components/Person.vue';
export default {
name: 'App',// 当前组件名
components: { // 注册组件
    Person
}
}
&lt;/script&gt;

&lt;style scoped&gt;
.app {
padding: 20px;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ age }}&lt;/h2&gt;
&lt;button @click="showDetail"&gt;Detail&lt;/button&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;button @click="changeName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script lang="ts"&gt;
export default {
name: "Person",
data() {// 数据配置
    return {// 包含组件的所有数据
      name: "John",
      age: 18,
      telephone: "1234567890",
      email: "john@gmail.com"
    }
},
methods: {// 方法配置
    showDetail() {// 包含组件的全部方法
      alert(`Detail: \ntelephone: ${this.telephone}\n email: ${this.email}`)
    },
    changeName() {
      this.name = "Jane";
    },
    changeAge() {
      this.age += 1;
    }
}
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
</ul>
</li>
<li>
<p>目前,使用 Vue2 语法完成的组件 Person 中使用了<strong>选项式 API</strong>(Options API),其中 <code>name</code>、<code>data</code>、<code>methods</code> 均称为选项(或称配置项)</p>
</li>
<li>
<p>选项式 API 的问题在于:数据、方法、计算属性等分散在 <code>data</code>、<code>methods</code>、<code>computed</code> 等选项中,当需要新增或修改需求时,就需要分别修改各个选项,不便于维护和复用</p>
</li>
</ul>
</li>
<li>
<p>Vue3 采用<strong>组合式 API</strong></p>
<ul>
<li>
<p>组合式 API(Composition API)优势在于:使用函数的方式组织代码,使相关代码更加有序的组织在一起,便于新增或修改需求</p>
</li>
<li>
<p>选项式 API 与 组合式 API 对比(下图来自《做了一夜动画,就为让大家更好的理解Vue3的Composition Api | 稀土掘金-大帅老猿》)</p>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d05799744a6341fd908ec03e5916d7b6~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp" alt="" loading="lazy"></p>
</li>
</ul>
</li>
</ul>
<h2 id="2setup">(2)<code>setup</code></h2>
<h3 id="a-概述">a. 概述</h3>
<ul>
<li><code>setup</code> 是 Vue3 中一个新的配置项,值是一个函数,其中包括组合式 API,组件中所用到的数据、方法、计算属性等均配置在 <code>setup</code> 中</li>
<li>特点
<ol>
<li><code>setup</code> 函数返回的可以是一个对象,其中的内容可以直接在模板中使用;也可以返回一个渲染函数</li>
<li><code>setup</code> 中访问 <code>this</code> 是 <code>undefined</code>,表明 Vue3 已经弱化 <code>this</code> 的作用</li>
<li><code>setup</code> 函数会在方法 <code>beforeCreate</code> 之前调用,在所有钩子函数中最优先执行</li>
</ol>
</li>
</ul>
<h3 id="b-使用-setup">b. 使用 <code>setup</code></h3>
<blockquote>
<p>修改本章第(1)小节采用 Vue2 语法的项目</p>
</blockquote>
<ul>
<li>
<p>App.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;Person /&gt;
&lt;/template&gt;

&lt;script lang="ts"&gt;
import Person from './components/Person.vue';
export default {
name: 'App',
components: {
    Person
}
}
&lt;/script&gt;

&lt;style scoped&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>Person.vue</p>
<ol>
<li>
<p>引入 <code>setup</code></p>
<pre><code class="language-vue">&lt;script lang="ts"&gt;
export default {
name: "Person",
setup() {
}
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>在 <code>setup</code> 中声明数据</p>
<pre><code class="language-vue">&lt;script lang="ts"&gt;
export default {
name: "Person",
setup() {
    let name = "John"
    let age = 18
    let telephone = "1234567890"
    let email = "john@gmail.com"
}
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>将需要使用的数据返回到模板中</p>
<blockquote>
<p>可以为返回的变量设置别名:如 <code>n: name</code></p>
</blockquote>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ n }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ age }}&lt;/h2&gt;
&lt;/template&gt;

&lt;script lang="ts"&gt;
export default {
name: "Person",
setup() {
    let name = "John"
    let age = 18
    let telephone = "1234567890"
    let email = "john@gmail.com"
   
    return {
      n: name,
      age
    }
}
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>在 <code>setup</code> 中声明方法并返回</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ n }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ age }}&lt;/h2&gt;
&lt;button @click="showDetail"&gt;Detail&lt;/button&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;button @click="changeName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script lang="ts"&gt;
export default {
name: "Person",
setup() {
    // let ...

    function showDetail() {
      alert(`Detail: \ntelephone: ${telephone}\n email: ${email}`)
    }
    function changeName() {
      name = "Jane";
    }
    function changeAge() {
      age += 1;
    }

    return {
      n: name,
      age,
      showDetail,
      changeName,
      changeAge
    }
}
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
<p>此时可以发现,点击“姓名修改”或“年龄修改”按钮后,页面并未发生改变,这是因为之前使用 <code>let</code> 声明的数据<strong>不是响应式</strong>的,具体方法参考本章第 x 小节</p>
</li>
</ol>
</li>
<li>
<p><code>setup</code> 可以与 <code>data</code> 和 <code>methods</code> 同时存在</p>
<ul>
<li>
<p>在 <code>data</code> 或 <code>methods</code> 中使用 <code>this</code> 均可以访问到在 <code>setup</code> 中声明的数据与函数</p>
<ul>
<li>因为<code>setup</code> 的执行早于 <code>data</code> 和 <code>methods</code></li>
</ul>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Age: {{ a }}&lt;/h2&gt;
&lt;/template&gt;

&lt;script lang="ts"&gt;
export default {
name: "Person",
data() {
    return { a: this.age }
},
setup() {
    let name = "John"
    let age = 18
    return { n: name, age }
}
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>在 <code>setup</code> 中无法使用 <code>data</code> 或 <code>methods</code> 中声明的数据或函数</p>
</li>
</ul>
</li>
</ul>
<h3 id="c-语法糖">c. 语法糖</h3>
<ul>
<li>
<p>在上述项目中,每次在 <code>setup</code> 中声明新数据时,都需要在 <code>return</code> 中进行“注册”,之后才能在模板中使用该数据,为解决此问题,需要使用 <code>setup</code> <strong>语法糖</strong></p>
</li>
<li>
<p><code>&lt;script setup lang="ts"&gt;&lt;/script&gt;</code> 相当于如下代码:</p>
<pre><code class="language-vue">&lt;script lang="ts"&gt;
export default {
setup() {
    return {}
}
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;script setup lang="ts"&gt;
let name = "John";
let age = 18;
let telephone = "1234567890";
let email = "john@gmail.com";

function showDetail() {
alert(`Detail: \ntelephone: ${telephone}\n email: ${email}`);
}
function changeName() {
name = "Jane";
}
function changeAge() {
age += 1;
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>在 <code>&lt;script setup lang="ts"&gt;&lt;/script&gt;</code> 中无法直接设置组件的 <code>name</code> 属性,因此存在以下两种方式进行设置:</p>
<ol>
<li>
<p>使用两个 <code>&lt;script&gt;</code> 标签</p>
<pre><code class="language-vue">&lt;script lang="ts"&gt;
export default {
name: "Person"
}
&lt;/script&gt;
&lt;script setup lang="ts"&gt;
// ...
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>(推荐)基于插件实现</p>
<blockquote>
<ol>
<li>
<p>使用命令 <code>npm install -D vite-plugin-vue-setup-extend</code> 安装需要的插件</p>
</li>
<li>
<p>在 vite.config.ts 中引入并调用这个插件</p>
<pre><code class="language-typescript">// import ...
import vpvse from 'vite-plugin-vue-setup-extend'

export default defineConfig({
plugins: [
    vue(),
    vpvse(),
],
// ...
})
</code></pre>
</li>
<li>
<p>重新启动项目</p>
</li>
</ol>
</blockquote>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...
&lt;/script&gt;
</code></pre>
</li>
</ol>
</li>
<li>
<p>将 App.vue 中的 Vue2 语法修改为 Vue3</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;Person /&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Person from './components/Person.vue';
&lt;/script&gt;

&lt;style scoped&gt;
&lt;/style&gt;
</code></pre>
</li>
</ul>
<h2 id="3创建基本类型的响应式数据">(3)创建基本类型的响应式数据</h2>
<blockquote>
<p>使用 <code>ref</code> 创建</p>
</blockquote>
<ol>
<li>
<p>引入 <code>ref</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person123"&gt;
import {ref} from 'vue'
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>对需要成为响应式的数据使用 <code>ref</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person123"&gt;
import {ref} from 'vue'

let name = ref("John");
let age = ref(18);
let telephone = "1234567890";
let email = "john@gmail.com";
&lt;/script&gt;
</code></pre>
<ul>
<li>
<p>使用 <code>console.log()</code> 可以发现使用 <code>ref</code> 的 <code>name</code> 变成了</p>
<pre><code class="language-json">// RefImpl
{
    "__v_isShallow": false,
    "dep": {},
    "__v_isRef": true,
    "_rawValue": "John",
    "_value": "John"
}
</code></pre>
<p>未使用 <code>ref</code> 的 <code>telephone</code> 依然是字符串 <code>'1234567890'</code></p>
</li>
</ul>
</li>
<li>
<p>修改方法</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person123"&gt;
import {ref} from 'vue'

let name = ref("John");
let age = ref(18);
let telephone = "1234567890";
let email = "john@gmail.com";

function showDetail() {
alert(`Detail: \ntelephone: ${telephone}\n email: ${email}`);
}
function changeName() {
name.value = "Jane";
}
function changeAge() {
age.value += 1;
}
&lt;/script&gt;
</code></pre>
<ul>
<li>
<p>在函数方法中使用 <code>name</code> 之类被响应式的数据,需要在属性 <code>value</code> 中获取或修改值,而在模板中不需要</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ age }}&lt;/h2&gt;
&lt;button @click="showDetail"&gt;Detail&lt;/button&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;button @click="changeName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;
</code></pre>
</li>
</ul>
</li>
</ol>
<h2 id="4创建对象类型的响应式数据">(4)创建对象类型的响应式数据</h2>
<h3 id="a-使用-reactive-创建">a. 使用 <code>reactive</code> 创建</h3>
<ol>
<li>
<p>创建对象以及函数方法,并在模板中使用</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ Teacher.name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ Teacher.age }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeTeacherAge"&gt;Change Teacher Age&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
let Teacher = { name: "John", age: 18 }

function changeTeacherAge() {
Teacher.age += 1;
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>引入 <code>reactive</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import {reactive} from 'vue'

// ...
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>对需要成为响应式的对象使用 <code>reactive</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import {reactive} from 'vue'

let Teacher = reactive({ name: "John", age: 18 })

// ...
&lt;/script&gt;
</code></pre>
<ul>
<li>使用 <code>console.log()</code> 可以发现 <code>John</code> 变成了 <em>Proxy(Object)</em> 类型的对象</li>
</ul>
</li>
<li>
<p>按钮方法不做调整:<code>Teacher.age += 1;</code></p>
</li>
<li>
<p>添加对象数组以及函数方法,并在模板中遍历</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;!-- ... --&gt;
&lt;hr /&gt;
&lt;ul&gt;
    &lt;li v-for="student in Student" :key="student.id"&gt;{{ student.name }}&lt;/li&gt;
&lt;/ul&gt;
&lt;button @click="changeFirstStudentName"&gt;Change First Student Name&lt;/button&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
// ...
let Student = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
]

// ...
function changeFirstStudentName() {
Student.name = "Alex";
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>将对象数组变为响应式</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...
let Student = reactive([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
])

// ...
&lt;/script&gt;
</code></pre>
<p>此时点击按钮即可修改第一个学生的名字为 Alex</p>
</li>
</ol>
<h3 id="b-使用-ref-创建">b. 使用 <code>ref</code> 创建</h3>
<ol>
<li>
<p>修改上述使用 <code>reactive</code> 的组件内容</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ Teacher.name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ Teacher.age }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeTeacherAge"&gt;Change Teacher Age&lt;/button&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;ul&gt;
    &lt;li v-for="student in Student" :key="student.id"&gt;{{ student.name }}&lt;/li&gt;
&lt;/ul&gt;
&lt;button @click="changeFirstStudentName"&gt;Change First Student Name&lt;/button&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
let Teacher = { name: "John", age: 18 }
let Student = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
]

function changeTeacherAge() {}
function changeFirstStudentName() {}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>引入 <code>ref</code> 并为需要成为响应式的对象使用</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import {ref} from 'vue'

let Teacher = ref({ name: "John", age: 18 })
let Student = ref([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
])

// ...
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改函数方法</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

function changeTeacherAge() {
Teacher.value.age += 1;
}
function changeFirstStudentName() {
Student.value.name = "Alex";
}
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h3 id="c-对比-ref-与-reactive">c. 对比 <code>ref</code> 与 <code>reactive</code></h3>
<ul>
<li>
<p>功能上:</p>
<ul>
<li><code>ref</code> 可以用来定义<em>基本类型数据</em>和<em>对象类型数据</em>
<ul>
<li>在使用 <code>ref</code> 创建响应式对象过程中,<code>ref</code> 底层实际借用了 <code>reactive</code> 的方法</li>
</ul>
</li>
<li><code>reactive</code> 可以用来定义<em>对象类型数据</em></li>
</ul>
</li>
<li>
<p>区别在于:</p>
<ul>
<li>
<p>使用 <code>ref</code> 创建的数据必须使用 <code>.value</code></p>
<ul>
<li>
<p>解决方法:使用插件 <em>volar</em> 自动添加</p>
<blockquote>
<p>插件设置方法:</p>
<ol>
<li>点击左下角齿轮图标(管理),并选择“设置”</li>
<li>在左侧选项列表中依次选择“扩展”-“Volar”</li>
<li>将功能“Auto Insert: Dot Value”打勾即可</li>
</ol>
</blockquote>
</li>
</ul>
</li>
<li>
<p>使用 <code>reactive</code> 重新分配一个新对象会失去响应式(<code>reactive</code> 的<strong>局限性</strong>)</p>
<ul>
<li>
<p>解决方法:使用 <code>Object.assign</code> 整体替换</p>
<p>举例:修改 <code>Teacher</code> 对象</p>
<ul>
<li>
<p><code>reactive</code></p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ Teacher.name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ Teacher.age }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeTeacher"&gt;Change Teacher&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import {reactive} from 'vue'

let Teacher = reactive({ name: "John", age: 18 })

function changeTeacher() {
Object.assign(Teacher, { name: "Mary", age: 19 })
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p><code>ref</code></p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ Teacher.name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ Teacher.age }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeTeacher"&gt;Change Teacher&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import {ref} from 'vue'

let Teacher = ref({ name: "John", age: 18 })

function changeTeacher() {
Teacher.value = { name: "Mary", age: 19 }
}
&lt;/script&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>使用原则:</p>
<ol>
<li>若需要一个基本类型的响应式数据,则只能选择 <code>ref</code></li>
<li>若需要一个对象类型、层级较浅的响应式数据,则选择任何一个都可以</li>
<li>若需要一个对象类型、层级较<strong>深</strong>的响应式数据,则推荐选择 <code>reactive</code></li>
</ol>
</li>
</ul>
<h2 id="5torefs-和-toref">(5)<code>toRefs</code> 和 <code>toRef</code></h2>
<ul>
<li>作用:将一个响应式对象中的每一个属性转换为 <code>ref</code> 对象</li>
<li><code>toRefs</code> 与 <code>toRef</code> 功能相同,<code>toRefs</code> 可以批量转换</li>
</ul>
<ol>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ John.name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ John.age }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import {reactive} from 'vue'

let John = reactive({ name: "John", age: 18 })

function changeName() {
John.name += "~"
}
function changeAge() {
John.age += 1
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>声明两个新变量,并使用 <code>Person</code> 对其进行赋值,之后修改模板</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ age }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
// ...

let John = reactive({ name: "John", age: 18 })
let { name, age } = John

// ...
&lt;/script&gt;
</code></pre>
<p>此时,新变量 <code>name</code> 和 <code>age</code> 并非响应式,点击按钮无法在页面上修改(实际上已发生修改)</p>
</li>
<li>
<p>引入 <code>toRefs</code>,将变量 <code>name</code> 和 <code>age</code> 变为响应式</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import {reactive, toRefs} from 'vue'

let John = reactive({ name: "John", age: 18 })
let { name, age } = toRefs(John)

// ...
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改函数方法</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

function changeName() {
name.value += "~"
}
function changeAge() {
age.value += 1
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>引入 <code>toRef</code> 替代 <code>toRefs</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import {reactive, toRef} from 'vue'

let John = reactive({ name: "John", age: 18 })
let name = toRef(John, 'name')
let age = toRef(John, 'age')

function changeName() {
name.value += "~"
}
function changeAge() {
age.value += 1
}
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="6computed">(6)<code>computed</code></h2>
<ul>
<li><code>computed</code> 是计算属性,具有<strong>缓存</strong>,当计算方法相同时,<code>computed</code> 会调用缓存,从而优化性能</li>
</ul>
<ol>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;First Name: &lt;input type="text" v-model="firstName" /&gt;&lt;/h2&gt;
&lt;h2&gt;Last Name: &lt;input type="text" v-model="lastName" /&gt;&lt;/h2&gt;
&lt;h2&gt;Full Name: &lt;span&gt;{{ firstName }} {{ lastName }}&lt;/span&gt;&lt;/h2&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import {ref} from 'vue'

let firstName = ref("john")
let lastName = ref("Smith")
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>引入计算属性 <code>computed</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import {ref, computed} from 'vue'

// ...
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>将姓与名的首字母大写,修改模板与控制器</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;!-- ... --&gt;
&lt;h2&gt;Full Name: &lt;span&gt;{{ fullName }}&lt;/span&gt;&lt;/h2&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
// ...

let fullName = computed(() =&gt; {
return firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1) + " " + lastName.value.slice(0, 1).toUpperCase() + lastName.value.slice(1)
})
&lt;/script&gt;
</code></pre>
<p>此时的 <code>fullName</code> 是<strong>只读</strong>的,无法修改,是一个使用 <code>ref</code> 创建的响应式对象</p>
</li>
<li>
<p>修改 <code>fullName</code>,实现可读可写</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

let fullName = computed({
get() {
    return firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1) + " " + lastName.value.slice(0, 1).toUpperCase() + lastName.value.slice(1)
},
set() {}
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>在模板中添加按钮,用于修改 <code>fullName</code>,并在控制器中添加相应的函数方法</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;!-- ... --&gt;
&lt;button @click="changeFullName"&gt;Change Full Name&lt;/button&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
// ...

function changeFullName() {
fullName.value = "bob jackson"
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改计算属性中的 <code>set()</code> 方法</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

let fullName = computed({
get() {
    return firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1) + " " + lastName.value.slice(0, 1).toUpperCase() + lastName.value.slice(1)
},
set(value) {
    const = value.split(" ")
    firstName.value = newFirstName
    lastName.value = newLastName
}
})

// ...
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="7watch">(7)<code>watch</code></h2>
<ul>
<li><code>watch</code> 是监视属性</li>
<li>作用:监视数据变化(与 Vue2 中的 <code>watch</code> 作用一致)</li>
<li>特点:Vue3 中的 <code>watch</code> 只监视以下数据:
<ol>
<li><code>ref</code> 定义的数据</li>
<li><code>reactive</code> 定义的数据</li>
<li>函数返回一个值</li>
<li>一个包含上述内容的数组</li>
</ol>
</li>
</ul>
<h3 id="a-情况一监视-ref-定义的基本类型数据">a. 情况一:监视 <code>ref</code> 定义的基本类型数据</h3>
<blockquote>
<p>直接写数据名即可,监视目标是 <code>value</code> 值的改变</p>
</blockquote>
<ol>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Sum: {{ sum }}&lt;/h2&gt;
&lt;button @click="changeSum"&gt; +1 &lt;/button&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import {ref} from 'vue'

let sum = ref(0)

function changeSum() {
sum.value += 1
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>引入 <code>watch</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import {ref, watch} from 'vue'

// ...
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>使用 <code>watch</code>,一般传入两个参数,依次是<em>监视目标</em>与<em>相应的回调函数</em></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import {ref, watch} from 'vue'

// ...

watch(sum, (newValue, oldValue) =&gt; {
console.log("Sum changed from " + oldValue + " to " + newValue)
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 <code>watch</code>,设置“停止监视”</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

const stopWatch = watch(sum, (newValue, oldValue) =&gt; {
console.log("Sum changed from " + oldValue + " to " + newValue)
if(newValue &gt;= 10) {
    stopWatch()
}
})
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h3 id="b-情况二监视-ref-定义的对象类型数据">b. 情况二:监视 <code>ref</code> 定义的对象类型数据</h3>
<blockquote>
<ul>
<li>直接写数据名即可,监视目标是对象的地址值的改变</li>
<li>如需监视对象内部的数据,需要手动开启深度监视</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>若修改的是 <code>ref</code> 定义的对象中的属性,则 <code>newValue</code> 和 <code>newValue</code> 都是新值,因为它们是同一个对象</li>
<li>若修改整个 <code>ref</code> 定义的对象,则 <code>newValue</code> 是新值,<code>oldValue</code> 是旧值,因为它们不是同一个对象</li>
</ul>
</blockquote>
<ol>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ person.name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ person.age }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;button @click="changeAll"&gt;Change All&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import { ref, watch } from "vue"

let person = ref({
name: "John",
age: 18
})

function changeName() {
person.value.name += '~'
}
function changeAge() {
person.value.age += 1
}
function changeAll() {
person.value = {
    name: "Mary",
    age: 19
}
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>使用 <code>watch</code> 监视整个对象的地址值变化</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(person, (newValue, oldValue) =&gt; {
console.log(newValue, oldValue)
})
&lt;/script&gt;
</code></pre>
<p>此时,只有点击“Change All” 按钮才会触发监视,<code>newValue</code> 与 <code>oldValue</code> 不同</p>
</li>
<li>
<p>手动开启深度监视,监视对象内部的数据</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(
person,
(newValue, oldValue) =&gt; {
    console.log(newValue, oldValue)
},
{
    deep: true,
}
)
&lt;/script&gt;
</code></pre>
<p>此时,点击“Change Name”或“Change Age”也能触发监视,<code>newValue</code> 与 <code>oldValue</code> 相同,但是点击“Change All”时,<code>newValue</code> 与 <code>oldValue</code> 依旧不同</p>
</li>
</ol>
<h3 id="c-情况三监视-reactive-定义的对象类型数据">c. 情况三:监视 <code>reactive</code> 定义的对象类型数据</h3>
<blockquote>
<p>该情况下,默认开启了深度监视且无法关闭</p>
</blockquote>
<ol>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ person.name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ person.age }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;button @click="changeAll"&gt;Change All&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import { reactive, watch } from "vue"

let person = reactive({
name: "John",
age: 18,
})

function changeName() {
person.name += "~"
}
function changeAge() {
person.age += 1
}
function changeAll() {
Object.assign(person, {
    name: "Mary",
    age: 19,
})
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>使用 <code>watch</code> 监视对象</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(person, (newValue, oldValue) =&gt; {
console.log(newValue, oldValue)
})
&lt;/script&gt;
</code></pre>
<p>对于使用 <code>reactive</code> 创建的对象,在使用 <code>Object.assign()</code> 时,仅修改了对象里的内容(覆盖原来的内容),并非创建了新对象,故无法监视对象地址值的变化(因为没有变化)</p>
</li>
</ol>
<h3 id="d-情况四监视-ref-或-reactive-定义的对象类型数据中的某个属性">d. 情况四:监视 <code>ref</code> 或 <code>reactive</code> 定义的对象类型数据中的某个属性</h3>
<blockquote>
<p>若该属性值不是对象类型,则需写成函数形式</p>
<p>若该属性值是对象类型,则建议写成函数形式</p>
</blockquote>
<ol>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ person.name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ person.age }}&lt;/h2&gt;
&lt;h2&gt;Nickname: {{ person.nickname.n1 }}/{{ person.nickname.n2 }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;button @click="changeNickname1"&gt;Change Nickname 1&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeNickname2"&gt;Change Nickname 2&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeNickname"&gt;Change Nickname&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import { reactive, watch } from "vue"

let person = reactive({
name: "John",
age: 18,
nickname: {
    n1: "J",
    n2: "Jack"
}
})

function changeName() {
person.name += "~"
}
function changeAge() {
person.age += 1
}
function changeNickname1() {
person.nickname.n1 = "Big J"
}
function changeNickname2() {
person.nickname.n2 = "Joker"
}
function changeNickname() {
person.nickname = {
    n1: "Joker",
    n2: "Big J"
}
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>使用 <code>watch</code> 监视全部的变化</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(person, (newValue, oldValue) =&gt; {
console.log(newValue, oldValue)
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 <code>watch</code>,设置监视<strong>基本类型</strong>数据 <code>person.name</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(
() =&gt; {
    return person.name;
},
(newValue, oldValue) =&gt; {
    console.log(newValue, oldValue);
}
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 <code>watch</code>,设置监视<strong>对象类型</strong>数据 <code>person.nickname</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(
() =&gt; person.nickname,
(newValue, oldValue) =&gt; {
    console.log(newValue, oldValue);
},
{ deep: true }
})
&lt;/script&gt;
</code></pre>
<ul>
<li>
<p>以下写法仅能监视对象类型内部属性数据变化</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(
person.nickname,
(newValue, oldValue) =&gt; {
    console.log(newValue, oldValue);
}
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>以下写法仅能监视对象类型整体地址值变化</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(
() =&gt; person.nickname,
(newValue, oldValue) =&gt; {
    console.log(newValue, oldValue);
}
})
&lt;/script&gt;
</code></pre>
</li>
</ul>
</li>
</ol>
<h3 id="e-情况五监视多个数据">e. 情况五:监视多个数据</h3>
<ol>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Name: {{ person.name }}&lt;/h2&gt;
&lt;h2&gt;Age: {{ person.age }}&lt;/h2&gt;
&lt;h2&gt;Nickname: {{ person.nickname.n1 }}/{{ person.nickname.n2 }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;button @click="changeNickname1"&gt;Change Nickname 1&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeNickname2"&gt;Change Nickname 2&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeNickname"&gt;Change Nickname&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import { reactive, watch } from "vue"

let person = reactive({
name: "John",
age: 18,
nickname: {
    n1: "J",
    n2: "Jack"
}
})

function changeName() {
person.name += "~"
}
function changeAge() {
person.age += 1
}
function changeNickname1() {
person.nickname.n1 = "Big J"
}
function changeNickname2() {
person.nickname.n2 = "Joker"
}
function changeNickname() {
person.nickname = {
    n1: "Joker",
    n2: "Big J"
}
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>使用 <code>watch</code> 监视 <code>person.name</code> 和 <code>person.nickname.n1</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(
[() =&gt; person.name, () =&gt; person.nickname.n1],
(newValue, oldValue) =&gt; {
    console.log(newValue, oldValue);
},
{ deep: true }
)
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="8watcheffect">(8)<code>watchEffect</code></h2>
<ul>
<li>作用:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数</li>
<li><code>watchEffect</code> 与 <code>watch</code> 对比,两者都能监视响应式数据,但是监视数据变化的方式不同
<ul>
<li><code>watch</code> 需要指明监视的数据</li>
<li><code>watchEffect</code> 则不用指明,函数中用到哪些属性,就监视哪些属性</li>
</ul>
</li>
</ul>
<ol>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Sum1: {{ sum1 }}&lt;/h2&gt;
&lt;h2&gt;Sum2: {{ sum2 }}&lt;/h2&gt;
&lt;p&gt;&lt;button @click="changeSum1"&gt;Sum1 +1&lt;/button&gt;&lt;/p&gt;
&lt;p&gt;&lt;button @click="changeSum2"&gt;Sum2 +3&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import { ref, watch } from 'vue'

let sum1 = ref(0)
let sum2 = ref(0)

function changeSum1() {
sum1.value += 1
}
function changeSum2() {
sum2.value += 3
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>使用 <code>watch</code> 监视 <code>sum1</code> 和 <code>sum2</code>,获取最新的值</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(, (value) =&gt; {
console.log(value)
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>对 <code>sum1</code> 和 <code>sum2</code> 进行条件判断</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watch(, (value) =&gt; {
let = value
if(newSum1 &gt;= 10 || newSum2 &gt;= 30) {
    console.log('WARNING: Sum1 or Sum2 is too high')
}
})
&lt;/script&gt;
</code></pre>
<p>此时,仅对 <code>sum1</code> 和 <code>sum2</code> 进行监视,当需要监视的目标更多时,建议使用 <code>watchEffect</code></p>
</li>
<li>
<p>引入 <code>watchEffect</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import { ref, watch, watchEffect } from 'vue'

// ...
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>使用 <code>watchEffect</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
// ...

watchEffect(() =&gt; {
if(sum1.value &gt;= 10 || sum2.value &gt;= 30) {
    console.log('WARNING: Sum1 or Sum2 is too high')
}
})
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="9标签的-ref-属性">(9)标签的 <code>ref</code> 属性</h2>
<ul>
<li>作用:用于注册模板引用
<ul>
<li>用在普通 DOM 标签上,获取到 DOM 节点</li>
<li>用在组件标签上,获取到组件实例对象</li>
</ul>
</li>
</ul>
<ol>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Vue 3&lt;/h2&gt;
&lt;button @click="showH2"&gt;Show Element H2&lt;/button&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
function showH2() {
console.log()
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>引入标签的 <code>ref</code> 属性</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2 ref="title"&gt;Vue 3&lt;/h2&gt;
&lt;button @click="showH2"&gt;Show Element H2&lt;/button&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import { ref } from 'vue'

// ...
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>创建一个容器,用于存储 <code>ref</code> 标记的内容</p>
<p>容器名称与标签中 <code>ref</code> 属性值相同</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import { ref } from 'vue'

let title = ref()

// ...
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改函数方法 <code>showH2()</code>,输出标签</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import { ref } from 'vue'

let title = ref()

function showH2() {
console.log(title.value)
}
&lt;/script&gt;
</code></pre>
<p>此时,<code>ref</code> 属性用在普通 DOM 标签上,获取到 DOM 节点</p>
</li>
<li>
<p>在 App.vue 中,对组件 Person 设置 <code>ref</code> 属性</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;Person ref="person" /&gt;
&lt;hr /&gt;
&lt;button @click="showPerson"&gt;Show Element Person&lt;/button&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Person from './components/Person.vue';
import { ref } from 'vue'

let person = ref()

function showPerson() {
console.log(person.value)
}
&lt;/script&gt;

&lt;style scoped&gt;
&lt;/style&gt;
</code></pre>
<p>此时,<code>ref</code> 属性用在组件标签上,获取到组件实例对象</p>
</li>
<li>
<p>在 Person.vue 中引入 <code>defineExpose</code> 实现父子组件通信</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import { ref, defineExpose } from 'vue'

let title = ref()
let number = ref(12345)

function showH2() {
console.log(title.value)
}

defineExpose({ title, number })
&lt;/script&gt;
</code></pre>
<p>此时,再次点击按钮“Show Element Person”便可在控制台中看到来自 Person.vue 组件中的 <code>title</code> 和 <code>number</code></p>
</li>
<li>
<p>重置 App.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;Person /&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Person from './components/Person.vue'
&lt;/script&gt;

&lt;style scoped&gt;
&lt;/style&gt;
</code></pre>
</li>
</ol>
<h2 id="95typescript-回顾">(9.5)TypeScript 回顾</h2>
<blockquote>
<p>TypeScript 基本概述:学习 TypeScript | 稀土掘金-SRIGT</p>
</blockquote>
<blockquote>
<p>在 Vue3 项目中,TS 接口等位于 <em>~/src/types/index.ts</em> 中</p>
</blockquote>
<ol>
<li>
<p>定义接口,用于限制对象的具体属性</p>
<pre><code class="language-typescript">interface IPerson {
id: string,
name: string,
age: number
}
</code></pre>
</li>
<li>
<p>暴露接口</p>
<blockquote>
<p>暴露接口有三种方法:默认暴露、分别暴露、统一暴露,以下采用分别暴露方法</p>
</blockquote>
<pre><code class="language-typescript">export interface IPerson {
id: string,
name: string,
age: number
}
</code></pre>
</li>
<li>
<p>修改 Person.vue,声明新变量,引入接口对新变量进行限制</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import { type IPerson } from '@/types'

let person:IPerson = {
id: "dpb7e82nlh",
name: "John",
age: 18
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
<p>此时,仅声明了一个变量,对于同类型的数组型变量,需要使用泛型</p>
</li>
<li>
<p>修改 Perosn.vue,声明一个数组,使用泛型</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import { type IPerson } from '@/types'

let personList:Array&lt;IPerson&gt; = [
{ id: "dpb7e82nlh", name: "John", age: 18 },
{ id: "u55dyu86gh", name: "Mary", age: 19 },
{ id: "ad3d882dse", name: "Niko", age: 17 }
]
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 index.ts,定义一个自定义类型,简化对数组限制的使用</p>
<pre><code class="language-typescript">export interface IPerson {
id: string,
name: string,
age: number,
}

// 自定义类型
export type Persons = Array&lt;IPerson&gt;
</code></pre>
<p>或:<code>export type Persons = IPerson[]</code></p>
</li>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import { type Persons } from '@/types'

let personList:Persons = [
{ id: "dpb7e82nlh", name: "John", age: 18 },
{ id: "u55dyu86gh", name: "Mary", age: 19 },
{ id: "ad3d882dse", name: "Niko", age: 17 }
]
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="10props">(10)props</h2>
<ol>
<li>
<p>修改 App.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;Person /&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Person from './components/Person.vue'

import { reactive } from 'vue'

let personList = reactive([
{ id: "dpb7e82nlh", name: "John", age: 18 },
{ id: "u55dyu86gh", name: "Mary", age: 19 },
{ id: "ad3d882dse", name: "Niko", age: 17 }
])
&lt;/script&gt;

&lt;style scoped&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>引入接口并使用</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
// ...
import { type Persons } from '@/types';

let personList = reactive&lt;Persons&gt;([
{ id: "dpb7e82nlh", name: "John", age: 18 },
{ id: "u55dyu86gh", name: "Mary", age: 19 },
{ id: "ad3d882dse", name: "Niko", age: 17 }
])
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改父组件 App.vue 的模板内容,向子组件 Person.vue 发送数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;Person note="This is a note" :list="personList" /&gt;
&lt;/template&gt;
</code></pre>
</li>
<li>
<p>修改子组件 Person.vue,从父组件 App.vue 中接收数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;{{ note }}&lt;/h2&gt;
&lt;ul&gt;
    &lt;li v-for="person in list" :key="person.id"&gt;{{ person.name }}-{{ person.age }}&lt;/li&gt;
&lt;/ul&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import { defineProps } from 'vue'

// 只接收
// defineProps(['note', 'list'])

// 接收并保存
let personList = defineProps(['note', 'list'])
console.log(personList)
console.log(personList.note)
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
<ul>
<li>
<p>接收限制类型</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import { defineProps } from 'vue'
import type { Persons } from '@/types'

defineProps&lt;{ list:Persons }&gt;()
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>接收限制类型并指定默认值</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import { defineProps } from 'vue'
import type { Persons } from '@/types'

withDefaults(defineProps&lt;{ list?: Persons }&gt;(), {
list: () =&gt; [{ id: "dpb7e82nlh", name: "John", age: 18 }],
})
&lt;/script&gt;
</code></pre>
</li>
</ul>
</li>
<li>
<p>重置 App.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;Person /&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Person from './components/Person.vue'
&lt;/script&gt;

&lt;style scoped&gt;
&lt;/style&gt;
</code></pre>
</li>
</ol>
<h2 id="11生命周期">(11)生命周期</h2>
<ul>
<li>组件的生命周期包括:创建、挂载、更新、销毁/卸载</li>
<li>组件在特定的生命周期需要调用特定的生命周期钩子(生命周期函数)</li>
</ul>
<h3 id="a-vue2-的生命周期">a. Vue2 的生命周期</h3>
<ul>
<li>
<p>Vue2 生命周期包括八个生命周期钩子</p>
<table>
    <tbody><tr>
      <th colspan="2">生命周期</th>
      <th>生命周期钩子</th>
    </tr>
    <tr>
      <td rowspan="2">创建</td>
      <td>创建前</td>
      <td><code>beforeCreate</code></td>
    </tr>
    <tr>
      <td>创建完成后</td>
      <td><code>created</code></td>
    </tr>
    <tr>
      <td rowspan="2">挂载</td>
      <td>挂载前</td>
      <td><code>beforeMount</code></td>
    </tr>
    <tr>
      <td>挂载完成后</td>
      <td><code>mounted</code></td>
    </tr>
    <tr>
      <td rowspan="2">更新</td>
      <td>更新前</td>
      <td><code>beforeUpdate</code></td>
    </tr>
    <tr>
      <td>更新完成后</td>
      <td><code>updated</code></td>
    </tr>
    <tr>
      <td rowspan="2">销毁</td>
      <td>销毁前</td>
      <td><code>beforeDestroy</code></td>
    </tr>
    <tr>
      <td>销毁完成后</td>
      <td><code>destroyed</code></td>
    </tr>
</tbody></table>
</li>
</ul>
<ol>
<li>
<p>使用命令 <code>vue create vue2_test</code> 或 <code>vue init webpack vue2_test</code> 创建一个 Vue2 项目</p>
</li>
<li>
<p>重置 ~/src/App.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
name: 'App',
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>在 ~/src/components 中新建 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div&gt;
    &lt;h2&gt;Sum: {{ sum }}&lt;/h2&gt;
    &lt;button @click="changeSum"&gt;+1&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
// eslint-disable-next-line
name: 'Person',
data() {
    return {
      sum: 0
    }
},
methods: {
    changeSum() {
      this.sum += 1
    }
}
}
&lt;/script&gt;

&lt;style scoped&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>在 App.vue 引入 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;PersonVue /&gt;
&lt;/template&gt;

&lt;script&gt;
import PersonVue from './components/Person.vue'

export default {
name: 'App',
components: {
    PersonVue
}
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>使用生命周期钩子</p>
<pre><code class="language-vue">&lt;script&gt;
export default {
// eslint-disable-next-line
name: 'Person',
data() {
    // ...
},
methods: {
    // ...
},

// 创建前
beforeCreate() {
    console.log("beforeCreate")
},
// 创建完成后
created() {
    console.log("created")
},

// 挂载前
beforeMount() {
    console.log("beforeMount")
},
// 挂载完成后
mounted() {
    console.log("mounted")
},

// 更新前
beforeUpdate() {
    console.log("beforeUpdate")
},
// 更新完成后
updated() {
    console.log("updated")
},

// 销毁前
beforeDestroy() {
    console.log("beforeDestroy")
},
// 销毁完成后
destroyed() {
    console.log("destroyed")
}
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>使用命令 <code>npm run serve</code> 启动项目,在开发者工具中查看生命周期过程</p>
</li>
</ol>
<h3 id="b-vue3-的生命周期">b. Vue3 的生命周期</h3>
<ul>
<li>
<p>Vue3 生命周期包括七个生命周期钩子</p>
<table>
    <tbody><tr>
      <th colspan="2">生命周期</th>
      <th>生命周期钩子</th>
    </tr>
    <tr>
      <td colspan="2">创建</td>
      <td><code>setup</code></td>
    </tr>
    <tr>
      <td rowspan="2">挂载</td>
      <td>挂载前</td>
      <td><code>onBeforeMount</code></td>
    </tr>
    <tr>
      <td>挂载完成后</td>
      <td><code>onMounted</code></td>
    </tr>
    <tr>
      <td rowspan="2">更新</td>
      <td>更新前</td>
      <td><code>onBeforeUpdate</code></td>
    </tr>
    <tr>
      <td>更新完成后</td>
      <td><code>onUpdated</code></td>
    </tr>
   <tr>
      <td rowspan="2">卸载</td>
      <td>卸载前</td>
      <td><code>onBeforeUnmount</code></td>
    </tr>
    <tr>
      <td>卸载完成后</td>
      <td><code>onUnmounted</code></td>
    </tr>
</tbody></table>
</li>
</ul>
<ol>
<li>
<p>在之前的 Vue3 项目中,修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Sum: {{ sum }}&lt;/h2&gt;
&lt;button @click="changeSum"&gt;+1&lt;/button&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import { ref } from 'vue'

let sum = ref(0)

function changeSum() {
sum.value += 1
}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>引入并使用生命周期钩子</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import {
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onMounted,
onUnmounted,
onUpdated,
ref
} from 'vue'

// ...

// 创建
console.log("setup")

// 挂载前
onBeforeMount(() =&gt; {
console.log("onBeforeMount")
})
// 挂载完成后
onMounted(() =&gt; {
console.log("onMounted")
})

// 更新前
onBeforeUpdate(() =&gt; {
console.log("onBeforeUpdate")
})
// 更新完成后
onUpdated(() =&gt; {
console.log("onUpdated")
})

// 卸载前
onBeforeUnmount(() =&gt; {
console.log("onBeforeUnmount")
})
// 卸载完成后
onUnmounted(() =&gt; {
console.log("onUnmounted")
})
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="12自定义hooks">(12)自定义Hooks</h2>
<blockquote>
<p>使用命令 <code>npm install -S axios</code> 安装 Axios,用于网络请求</p>
</blockquote>
<ol>
<li>
<p>修改 Person.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Sum: {{ sum }}&lt;/h2&gt;
&lt;button @click="changeSum"&gt;+1&lt;/button&gt;
&lt;hr /&gt;
&lt;img
    v-for="(img, index) in imgList"
    :src="img"
    :key="index"
    style="height: 100px"
/&gt;
&lt;p&gt;&lt;button @click="changeImg"&gt;Next&lt;/button&gt;&lt;/p&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Person"&gt;
import { reactive, ref } from "vue";

let sum = ref(0);
let imgList = reactive([""]);

function changeSum() {
sum.value += 1;
}
function changeImg() {}
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>引入 Axios</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import axios from 'axios'
// ...
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>使用 Axios 获取图片地址</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import axios from 'axios'
// ...
async function changeImg() {
try {
    let result = await axios.get(
      "https://dog.ceo/api/breed/pembroke/images/random"
    );
    imgList.push(result.data.message);
} catch (error) {
    alert("Error! Please try again.");
    console.log(error);
}
}
&lt;/script&gt;
</code></pre>
<p>此时,多个功能(求和、请求图片)同时在组件中互相交织,可以使用 Hooks 重新组织代码</p>
</li>
<li>
<p>在 src 目录下新建目录 hooks,其中分别新建 useSum.ts、useImg.ts</p>
<ul>
<li>
<p>useSum.ts</p>
<pre><code class="language-typescript">import { ref } from 'vue'

export default function () {
let sum = ref(0)

function changeSum() {
    sum.value += 1
}

return {
    sum,
    changeSum
}
}
</code></pre>
</li>
<li>
<p>useImg.ts</p>
<pre><code class="language-typescript">import axios from 'axios'
import { reactive } from 'vue'

export default function () {
let imgList = reactive([""])

async function changeImg() {
    try {
      let result = await axios.get(
      "https://dog.ceo/api/breed/pembroke/images/random"
      )
      imgList.push(result.data.message)
    } catch (error) {
      alert("Error! Please try again.")
      console.log(error)
    }
}

return {
    imgList,
    changeImg
}
}
</code></pre>
</li>
</ul>
</li>
<li>
<p>在 Person.vue 中引入并使用两个 Hooks</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Person"&gt;
import useSum from '@/hooks/useSum'
import useImg from '@/hooks/useImg'

const { sum, changeSum } = useSum()
const { imgList, changeImg } = useImg()
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h1 id="0x03-路由">0x03 路由</h1>
<h2 id="1概述">(1)概述</h2>
<ul>
<li>此处的路由是指前后端交互的路由</li>
<li>路由(route)就是一组键值的对应关系</li>
<li>多个路由需要经过路由器(router)的管理</li>
<li>路由组件通常存放在 pages 或 views 文件夹,一般组件通常存放在 components 文件夹</li>
</ul>
<h2 id="2路由切换">(2)路由切换</h2>
<ol>
<li>
<p>重置 <em>~/src</em> 目录结构</p>
<div class="mermaid">graph TB
src--&gt;components &amp; pages &amp; App.vue &amp; main.ts
components--&gt;Header.vue
pages--&gt;Home.vue &amp; Blog.vue &amp; About.vue
</div></li>
<li>
<p>修改 App.vue(样式可忽略)</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;Header /&gt;
    &lt;div class="navigation"&gt;
      &lt;a href="/home" class="active"&gt;Home&lt;/a&gt;
      &lt;a href="/blog"&gt;Blog&lt;/a&gt;
      &lt;a href="/about"&gt;About&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="main-content"&gt;
      Content
    &lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Header from './components/Header.vue'
&lt;/script&gt;

&lt;style scoped&gt;
.app {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.navigation {
width: 15%;
height: 506px;
float: left;
background: #03deff;
}
.navigation a {
display: block;
text-decoration-line: none;
color: black;
text-align: center;
font-size: 28px;
padding-top: 10px;
padding-bottom: 10px;
border-bottom: 3px solid #fff;
}
.navigation a.active {
background: rgb(1, 120, 144);
color: white;
}
.main-content {
display: inline;
width: 80%;
height: 500px;
float: left;
margin-left: 10px;
font-size: 26px;
border: 3px solid #000;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>修改 Header.vue、Home.vue、Blog.vue、About.vue</p>
<ul>
<li>
<p>Header.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2 class="title"&gt;Route Test&lt;/h2&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Header"&gt;
&lt;/script&gt;

&lt;style scope&gt;
.title {
width: 100%;
text-align: center;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>Home.vue、Blog.vue、About.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Home&lt;/h2&gt;
&lt;!-- &lt;h2&gt;Blog&lt;/h2&gt; --&gt;
&lt;!-- &lt;h2&gt;About&lt;/h2&gt; --&gt;
&lt;/template&gt;

&lt;script setup lang="ts"&gt;
&lt;/script&gt;

&lt;style scope&gt;
&lt;/style&gt;
</code></pre>
</li>
</ul>
</li>
<li>
<p>创建路由器</p>
<ol>
<li>
<p>使用命令 <code>npm install vue-router -S</code> 安装路由器</p>
</li>
<li>
<p>在 <em>~/src</em> 目录下新建目录 router,其中新建 index.ts,用于创建一个路由器并暴露</p>
</li>
<li>
<p>引入 <code>createRouter</code> 和 <code>createWebHistory</code></p>
<pre><code class="language-typescript">import {
    createRouter,
    createWebHistory
} from 'vue-router'
</code></pre>
<p>其中 <code>createWebHistory</code> 是路由器的工作模式,在本章第(3)小节有详细介绍</p>
</li>
<li>
<p>引入子组件</p>
<pre><code class="language-typescript">// ...

import Home from '@/pages/Home.vue'
import Blog from '@/pages/Blog.vue'
import About from '@/pages/About.vue'
</code></pre>
</li>
<li>
<p>创建路由器</p>
<pre><code class="language-typescript">// ...

const router = createRouter({
history: createWebHistory(),
routes: []
})
</code></pre>
</li>
</ol>
</li>
<li>
<p>制定路由规则</p>
<pre><code class="language-typescript">// ...
routes: [
{
    path: '/home',
    component: Home
},
{
    path: '/blog',
    component: Blog
},
{
    path: '/about',
    component: About
}
]
// ...
</code></pre>
</li>
<li>
<p>暴露路由</p>
<pre><code class="language-typescript">// ...

export default router
</code></pre>
</li>
<li>
<p>修改 main.ts,引入并使用路由器</p>
<pre><code class="language-typescript">// ...
import router from './router'

createApp(App).use(router).mount('#app')
</code></pre>
</li>
<li>
<p>修改 App.vue,引入路由器视图,修改模板中的超链接</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;Header /&gt;
    &lt;div class="navigation"&gt;
      &lt;RouterLink to="/home" active-class="active"&gt;Home&lt;/RouterLink&gt;
      &lt;RouterLink to="/blog" active-class="active"&gt;Blog&lt;/RouterLink&gt;
      &lt;RouterLink to="/about" active-class="active"&gt;About&lt;/RouterLink&gt;
    &lt;/div&gt;
    &lt;div class="main-content"&gt;
      &lt;RouterView&gt;&lt;/RouterView&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Header from './components/Header.vue'
import { RouterLink, RouterView } from 'vue-router'
&lt;/script&gt;

&lt;style scoped&gt;
/* ... */
&lt;/style&gt;
</code></pre>
<ul>
<li>此时,点击导航后,“消失”的路由组件默认是被<strong>卸载</strong>的,需要的时候再重新挂载
<ul>
<li>可以在组件中使用生命周期钩子验证</li>
</ul>
</li>
<li><code>&lt;RouterLink&gt;</code> 中的 <code>to</code> 属性有两种写法
<ol>
<li><code>to="/home"</code></li>
<li><code>:to="{path:'/home'}"</code>
<ul>
<li>此写法的优点在本章第()小节介绍</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ol>
<h2 id="3路由器的工作模式">(3)路由器的工作模式</h2>
<h3 id="a-history-模式">a. history 模式</h3>
<ul>
<li>
<p>优点:URL 更美观,更接近于传统网站的 URL</p>
</li>
<li>
<p>缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有 404 报错</p>
</li>
<li>
<p>使用方法:</p>
<ul>
<li>
<p>Vue2:<code>mode: 'history'</code></p>
</li>
<li>
<p>Vue3:</p>
<pre><code class="language-typescript">// router/index.ts
import {
createRouter,
createWebHistory
} from 'vue-router'

const router = createRouter({
    history: createWebHistory(),
    // ...
})
</code></pre>
</li>
<li>
<p>React:<code>BrowserRouter</code></p>
</li>
</ul>
</li>
</ul>
<h3 id="b-hash-模式">b. hash 模式</h3>
<ul>
<li>
<p>优点:兼容性更好,不需要服务端处理路径</p>
</li>
<li>
<p>缺点:URL 中带有 <code>#</code> 号,在 SEO 优化方面相对较差</p>
</li>
<li>
<p>使用方法:</p>
<ul>
<li>
<p>Vue3:</p>
<pre><code class="language-typescript">// router/index.ts
import {
createRouter,
createWebHashHistory
} from 'vue-router'

const router = createRouter({
    history: createWebHashHistory(),
    // ...
})
</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="4命名路由">(4)命名路由</h2>
<ul>
<li>作用:简化路由跳转与传参</li>
</ul>
<ol>
<li>
<p>在 router/index.ts 中为路由命名</p>
<pre><code class="language-typescript">// ...
routes: [
{
    name: 'zy',
    path: '/home',
    component: Home
},
{
    name: 'bk',
    path: '/blog',
    component: Blog
},
{
    name: 'gy',
    path: '/about',
    component: About
}
]
// ...
</code></pre>
</li>
<li>
<p>修改 App.vue,使用命名路由的方法进行跳转</p>
<pre><code class="language-vue">&lt;template&gt;
        &lt;!-- ... --&gt;
    &lt;div class="navigation"&gt;
      &lt;RouterLink :to="{ name: 'zy' }" active-class="active"&gt;Home&lt;/RouterLink&gt;
      &lt;RouterLink to="/blog" active-class="active"&gt;Blog&lt;/RouterLink&gt;
      &lt;RouterLink to="/about" active-class="active"&gt;About&lt;/RouterLink&gt;
    &lt;/div&gt;
    &lt;!-- ... --&gt;
&lt;/template&gt;
</code></pre>
<p>此时,共有两类方式三种方法实现路由跳转:</p>
<table>
    <tbody><tr>
      <td colspan="2">字符串写法</td>
      <td><code>to="/home"</code></td>
    </tr>
    <tr>
      <td rowspan="2">对象写法</td>
      <td>命名跳转</td>
      <td><code>:to="{ name: 'zy' }"</code></td>
    </tr>
    <tr>
      <td>路径跳转</td>
      <td><code>:to="{ path: '/home' }"</code></td>
    </tr>
</tbody></table>
</li>
</ol>
<h2 id="5嵌套路由">(5)嵌套路由</h2>
<blockquote>
<p>在博客页面实现路由的嵌套,实现博客内容根据选择进行动态展示</p>
</blockquote>
<ol>
<li>
<p>在 pages 目录下新建 Detail.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;ul&gt;
    &lt;li&gt;id: id&lt;/li&gt;
    &lt;li&gt;title: title&lt;/li&gt;
    &lt;li&gt;content: content&lt;/li&gt;
&lt;/ul&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="detail"&gt;
&lt;/script&gt;

&lt;style scope&gt;
ul {
list-style: none;
padding-left: 20px;
}
ul&gt;li {
line-height: 30px;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>修改 router/index.ts,引入 Detail.vue</p>
<pre><code class="language-typescript">// ...
import Detail from '@/pages/Detail.vue'

const router = createRouter({
// ...
routes: [
    // ...
    {
      path: '/blog',
      component: Blog,
      children: [
      {
          path: 'detail',
          component: Detail
      }
      ]
    },
    // ...
]
})

export default router
</code></pre>
</li>
<li>
<p>修改 Blog.vue,添加博客导航列表、博客内容展示区、相关数据以及样式</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Blog&lt;/h2&gt;
&lt;ul&gt;
    &lt;li v-for="blog in blogList" :key="blog.id"&gt;
      &lt;RouterLink to="/blog/detail"&gt;{{ blog.title }}&lt;/RouterLink&gt;
    &lt;/li&gt;
&lt;/ul&gt;
&lt;div class="blog-content"&gt;
    &lt;RouterView&gt;&lt;/RouterView&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Blog"&gt;
import { reactive } from 'vue'

const blogList = reactive([
{ id: 'fhi27df4sda', title: 'Blog01', content: 'Content01' },
{ id: 'opdcd2871cb', title: 'Blog02', content: 'Content02' },
{ id: 'adi267f4hp5', title: 'Blog03', content: 'Content03' }
])
&lt;/script&gt;

&lt;style scope&gt;
ul {
float: left;
}
ul li {
display: block;
}
ul li a {
text-decoration-line: none;
}
.blog-content {
float: left;
margin-left: 200px;
width: 70%;
height: 300px;
border: 3px solid black;
}
&lt;/style&gt;
</code></pre>
</li>
</ol>
<h2 id="6路由传参">(6)路由传参</h2>
<ul>
<li>在 Vue 中,路由用两种参数:query 和 params</li>
</ul>
<h3 id="a-query">a. query</h3>
<ol>
<li>
<p>修改 Blog.vue,发送参数</p>
<p>修改 <code>to</code> 属性为 <code>:to</code>,使用模板字符串,在路由后添加 <code>?</code>,之后使用格式 <code>key1=val1&amp;key2=val2</code> 的方式传递参数</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;!-- ... --&gt;
    &lt;li v-for="blog in blogList" :key="blog.id"&gt;
      &lt;RouterLink :to="`/blog/detail?id=${blog.id}&amp;title=${blog.title}&amp;content=${blog.content}`"&gt;
      {{ blog.title }}
      &lt;/RouterLink&gt;
    &lt;/li&gt;
&lt;!-- ... --&gt;
&lt;/template&gt;
</code></pre>
</li>
<li>
<p>简化传参</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;!-- ... --&gt;
&lt;RouterLink
:to="{
    path: '/blog/detail',
    query: {
      id: blog.id,
      title: blog.title,
      content: blog.content
    }
}"
&gt;
{{ blog.title }}
&lt;/RouterLink&gt;
&lt;!-- ... --&gt;
&lt;/template&gt;
</code></pre>
</li>
<li>
<p>修改 Detail.vue,接收参数</p>
<p>使用 Hooks <code>useRoute</code> 接收</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;ul&gt;
    &lt;li&gt;id: {{ route.query.id }}&lt;/li&gt;
    &lt;li&gt;title: {{ route.query.title }}&lt;/li&gt;
    &lt;li&gt;content: {{ route.query.content }}&lt;/li&gt;
&lt;/ul&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="detail"&gt;
import { useRoute } from 'vue-router'
const route = useRoute()
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>简化数据展示</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;ul&gt;
    &lt;li&gt;id: {{ query.id }}&lt;/li&gt;
    &lt;li&gt;title: {{ query.title }}&lt;/li&gt;
    &lt;li&gt;content: {{ query.content }}&lt;/li&gt;
&lt;/ul&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="detail"&gt;
import { toRefs } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
let { query } = toRefs(route)
&lt;/script&gt;
</code></pre>
<p>此时,模板中仍旧使用了很多 <code>query.xxx</code> 的语法,为省略 <code>query.</code>,本章第(7)小节有相关处理方法</p>
</li>
</ol>
<h3 id="b-params">b. params</h3>
<ol>
<li>
<p>修改 router/index.ts</p>
<pre><code class="language-typescript">// ...
children: [
{
    path: 'detail/:id/:title/:content',
    component: Detail
}
]
// ...
</code></pre>
<p>可以在相关参数后添加 <code>?</code> 设置参数的必要性,如:<code>path: 'detail/:id/:title/:content?'</code></p>
</li>
<li>
<p>修改 Blog.vue,发送参数</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;!-- ... --&gt;
&lt;RouterLink :to="`/blog/detail/${blog.id}/${blog.title}/${blog.content}`"&gt;
{{ blog.title }}
&lt;/RouterLink&gt;
&lt;!-- ... --&gt;
&lt;/template&gt;
</code></pre>
</li>
<li>
<p>简化传参</p>
<ol>
<li>
<p>修改 router/index.ts,为 <code>/detail</code> 路由命名</p>
<pre><code class="language-typescript">// ...
children: [
{
    name: 'detail',
    path: 'detail/:id/:title/:content',
    component: Detail
}
]
// ...
</code></pre>
</li>
<li>
<p>修改 Blog.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;!-- ... --&gt;
&lt;RouterLink
:to="{
    name: 'detail',
    params: {
      id: blog.id,
      title: blog.title,
      content: blog.content
    }
}"
&gt;
{{ blog.title }}
&lt;/RouterLink&gt;
&lt;!-- ... --&gt;
&lt;/template&gt;
</code></pre>
</li>
</ol>
</li>
<li>
<p>修改 Detail.vue,接收参数</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;ul&gt;
    &lt;li&gt;id: {{ route.params.id }}&lt;/li&gt;
    &lt;li&gt;title: {{ route.params.title }}&lt;/li&gt;
    &lt;li&gt;content: {{ route.params.content }}&lt;/li&gt;
&lt;/ul&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="detail"&gt;
import { useRoute } from 'vue-router'
const route = useRoute()
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>简化数据展示</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;ul&gt;
    &lt;li&gt;id: {{ params.id }}&lt;/li&gt;
    &lt;li&gt;title: {{ params.title }}&lt;/li&gt;
    &lt;li&gt;content: {{ params.content }}&lt;/li&gt;
&lt;/ul&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="detail"&gt;
import { toRefs } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
let { params } = toRefs(route)
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="7路由的-props-配置">(7)路由的 props 配置</h2>
<ul>
<li>作用:让路由组件更方便地接收参数</li>
<li>原理:将路由参数作为 <code>props</code> 传递给组件</li>
</ul>
<h3 id="a-对象写法">a. 对象写法</h3>
<ol>
<li>
<p>修改 Blog.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;!-- ... --&gt;
&lt;RouterLink
:to="{
    name: 'detail',
    query: {
      id: blog.id,
      title: blog.title,
      content: blog.content
    }
}"
&gt;
{{ blog.title }}
&lt;/RouterLink&gt;
&lt;!-- ... --&gt;
&lt;/template&gt;
</code></pre>
</li>
<li>
<p>修改 router/index.ts</p>
<pre><code class="language-typescript">{
name: 'detail',
path: 'detail',
component: Detail,
props(route) {
    return route.query
}
}
</code></pre>
<p>此时相当于将 <code>&lt;Detail /&gt;</code> 修改为 <code>&lt;Detail id=xxx title=xxx content=xxx /&gt;</code>,可以按照第二章第(10)小节的方法将参数从 props 中取出使用</p>
</li>
<li>
<p>修改 Detail.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;ul&gt;
    &lt;li&gt;id: {{ id }}&lt;/li&gt;
    &lt;li&gt;title: {{ title }}&lt;/li&gt;
    &lt;li&gt;content: {{ content }}&lt;/li&gt;
&lt;/ul&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="detail"&gt;
defineProps(['id', 'title', 'content'])
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h3 id="b-布尔值写法">b. 布尔值写法</h3>
<ol>
<li>
<p>修改 Blog.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;!-- ... --&gt;
&lt;RouterLink
:to="{
    name: 'detail',
    params: {
      id: blog.id,
      title: blog.title,
      content: blog.content
    }
}"
&gt;
{{ blog.title }}
&lt;/RouterLink&gt;
&lt;!-- ... --&gt;
&lt;/template&gt;
</code></pre>
</li>
<li>
<p>修改 router/index.ts</p>
<pre><code class="language-typescript">// ...
{
name: 'detail',
path: 'detail/:id/:title/:content',
component: Detail,
props: true
}
// ...
</code></pre>
</li>
<li>
<p>修改 Detail.vue</p>
</li>
</ol>
<h3 id="c-对象写法">c. 对象写法</h3>
<blockquote>
<p>不常用</p>
</blockquote>
<ol>
<li>
<p>修改 router/index.ts</p>
<pre><code class="language-typescript">// ...
{
name: 'detail',
path: 'detail/:id/:title/:content',
component: Detail,
props: {
    id: xxx,
    title: yyy,
    content: zzz
}
}
// ...
</code></pre>
</li>
<li>
<p>修改 Detail.vue</p>
</li>
</ol>
<h2 id="8replace">(8)<code>replace</code></h2>
<ul>
<li>
<p>默认情况下,采用 push 模式,即记录浏览先后顺序,允许前进和回退</p>
</li>
<li>
<p>replace 模式不允许前进和回退</p>
</li>
<li>
<p>修改 App.vue,在 <code>RouterLink</code> 标签中添加 <code>replace</code> 属性</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;!-- ... --&gt;
      &lt;RouterLink replace to="/home" active-class="active"&gt;Home&lt;/RouterLink&gt;
      &lt;RouterLink replace to="/blog" active-class="active"&gt;Blog&lt;/RouterLink&gt;
      &lt;RouterLink replace to="/about" active-class="active"&gt;About&lt;/RouterLink&gt;
&lt;!-- ... --&gt;
&lt;/template&gt;
</code></pre>
</li>
</ul>
<h2 id="9编程式导航">(9)编程式导航</h2>
<ul>
<li>
<p><code>RouterLink</code> 标签的本质是 <code>a</code> 标签</p>
</li>
<li>
<p>编程式导航的目的是脱离 <code>RouterLink</code> 标签,实现路由跳转</p>
<ul>
<li>
<p>修改 Home.vue,实现进入该页面 3 秒后,以 push 的模式跳转到 <em>/blog</em></p>
<pre><code class="language-vue">&lt;script setup lang="ts"&gt;
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()

onMounted(() =&gt; {
setTimeout(() =&gt; {
    router.push('/blog')
}, 3000)
})
&lt;/script&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
<ol>
<li>
<p>重置 Home.vue</p>
</li>
<li>
<p>修改 Blog.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;h2&gt;Blog&lt;/h2&gt;
&lt;ul&gt;
    &lt;li v-for="blog in blogList" :key="blog.id"&gt;
      &lt;button @click="showDetail(blog)"&gt;More&lt;/button&gt;
      &lt;!-- ... --&gt;
    &lt;/li&gt;
   &lt;!-- ... --&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Blog"&gt;
import { useRouter } from "vue-router";

// ...

const router = useRouter();

interface IBlog {
id: string;
title: string;
content: string;
}

function showDetail(blog: IBlog) {
router.push({
    name: "detail",
    query: {
      id: blog.id,
      title: blog.title,
      content: blog.content,
    },
});
}
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="10重定向">(10)重定向</h2>
<ul>
<li>
<p>修改 router/index.ts,将路径 <em>/</em> 重定向到 <em>/home</em></p>
<pre><code class="language-typescript">routes: [
{
    path: '/',
    redirect: '/home'
},
// ...
]
</code></pre>
<p>此时,访问 http://localhost:5173/ 时,会重定向到 http://localhost:5173/home</p>
</li>
</ul>
<h1 id="0x04-pinia">0x04 pinia</h1>
<h2 id="1概述-1">(1)概述</h2>
<ul>
<li>
<p>pinia 是 Vue3 中的集中式状态管理工具</p>
<ul>
<li>类似的工具有:redux(React)、vuex(Vue2)等</li>
<li>集中式:将所有需要管理的数据集中存放在一个容器中,相对的称为<strong>分布式</strong></li>
<li></li>
</ul>
</li>
<li>
<p>pinia 官网</p>
<img src="https://pinia.vuejs.org/logo.svg" style="zoom: 20%">
</li>
</ul>
<h2 id="2准备">(2)准备</h2>
<ol>
<li>
<p>重置 <em>~/src</em> 目录结构</p>
<div class="mermaid">graph TB
src--&gt;components &amp; App.vue &amp; main.ts
components--&gt;Count.vue &amp; Text.vue
</div></li>
<li>
<p>Count.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="count"&gt;
    &lt;h2&gt;Sum: {{ sum }}&lt;/h2&gt;
    &lt;select v-model.number="number"&gt;
      &lt;option value="1" selected&gt;1&lt;/option&gt;
      &lt;option value="2"&gt;2&lt;/option&gt;
      &lt;option value="3"&gt;3&lt;/option&gt;
    &lt;/select&gt;
    &lt;button @click="add"&gt;Add&lt;/button&gt;
    &lt;button @click="sub"&gt;Sub&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Count"&gt;
import { ref } from 'vue'

let sum = ref(0)
let number = ref(1)

function add() {
sum.value += number.value
}
function sub() {
sum.value -= number.value
}
&lt;/script&gt;

&lt;style scope&gt;
.count {
text-align: center;
width: 50%;
height: 120px;
background: #03deff;
border: 3px solid black;
}
select, button {
margin: 10px;
}
&lt;/style&gt;
</code></pre>
<p>当在 <code>select</code> 标签中进行选择时,为向变量 <code>number</code> 中传入数字,可以使用以下方法之一:</p>
<ol>
<li>修改 <code>select</code> 标签中的 <code>v-model</code> 为 <code>v-model.number</code>(上面使用的方法)</li>
<li>修改 <code>option</code> 标签中的 <code>value</code> 为 <code>:value</code></li>
</ol>
</li>
<li>
<p>Text.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="text"&gt;
    &lt;button @click="getText"&gt;Get Text&lt;/button&gt;
    &lt;ul&gt;
      &lt;li v-for="text in textList" :key="text.id"&gt;
      {{ text.content }}
      &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Text"&gt;
import { reactive } from 'vue'
import axios from 'axios'

let textList = reactive([
{ id: '01', content: "Text01" },
{ id: '02', content: "Text02" },
{ id: '03', content: "Text03" }
])

async function getText() {
let { data:{result:{content}} } = await axios.get('https://api.oioweb.cn/api/common/OneDayEnglish')
textList.unshift({ id: Date.now().toString(), content})
console.log(content)
}
&lt;/script&gt;

&lt;style scope&gt;
.text {
width: 50%;
height: 150px;
background: #fbff03;
border: 3px solid black;
padding-left: 10px;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>App.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;Count /&gt;
&lt;br /&gt;
&lt;Text /&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Count from './components/Count.vue'
import Text from './components/Text.vue'
&lt;/script&gt;

&lt;style scope&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>main.ts</p>
<pre><code class="language-typescript">import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
</code></pre>
</li>
</ol>
<h2 id="3搭建环境">(3)搭建环境</h2>
<ul>
<li>
<p>使用命令 <code>npm install -S pinia</code> 安装 pinia</p>
</li>
<li>
<p>在 main.ts 中引入并安装 pinia</p>
<pre><code class="language-typescript">import { createApp } from 'vue'
import App from './App.vue'

import { createPinia } from 'pinia'

createApp(App).use(createPinia()).mount('#app')
</code></pre>
</li>
</ul>
<h2 id="4存储与读取数据">(4)存储与读取数据</h2>
<ul>
<li>
<p>store 是一个保存<strong>状态</strong>、<strong>业务逻辑</strong>的实体,每个组件都可以读写它</p>
</li>
<li>
<p>store 中有三个概念,与组件中的一些属性类似</p>
<table>
<thead>
<tr>
<th style="text-align: center">store</th>
<th style="text-align: center">属性</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><code>state</code></td>
<td style="text-align: center"><code>data</code></td>
</tr>
<tr>
<td style="text-align: center"><code>getter</code></td>
<td style="text-align: center"><code>computed</code></td>
</tr>
<tr>
<td style="text-align: center"><code>actions</code></td>
<td style="text-align: center"><code>methods</code></td>
</tr>
</tbody>
</table>
</li>
<li>
<p>store 目录下的 ts 文件命名与组件的相同</p>
</li>
</ul>
<blockquote>
<p>以下涉及对文件、变量等命名的格式均符合 pinia 官方文档规范</p>
</blockquote>
<ol>
<li>
<p>在 <em>~/src</em> 目录下,新建 store 目录,其中新建 count.ts 和 text.ts</p>
<ul>
<li>
<p>count.ts</p>
<pre><code class="language-typescript">import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
state() {
    return {
      sum: 10
    }
}
})
</code></pre>
</li>
<li>
<p>text.ts</p>
<pre><code class="language-typescript">import { defineStore } from 'pinia'

export const useTextStore = defineStore('text', {
state() {
    return {
      textList: [
      { id: '01', content: "Text01" },
      { id: '02', content: "Text02" },
      { id: '03', content: "Text03" }
      ]
    }
}
})
</code></pre>
</li>
</ul>
</li>
<li>
<p>修改 Count.vue 和 Text.vue</p>
<ul>
<li>
<p>Count.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="count"&gt;
    &lt;h2&gt;Sum: {{ countStore.sum }}&lt;/h2&gt;
    &lt;select v-model.number="number"&gt;
      &lt;option value="1" selected&gt;1&lt;/option&gt;
      &lt;option value="2"&gt;2&lt;/option&gt;
      &lt;option value="3"&gt;3&lt;/option&gt;
    &lt;/select&gt;
    &lt;button @click="add"&gt;Add&lt;/button&gt;
    &lt;button @click="sub"&gt;Sub&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Count"&gt;
import { ref } from 'vue'
import { useCountStore } from '@/store/count'

const countStore = useCountStore()

let number = ref(1)

function add() {}
function sub() {}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>Text.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="text"&gt;
    &lt;button @click="getText"&gt;Get Text&lt;/button&gt;
    &lt;ul&gt;
      &lt;li v-for="text in textStore.textList" :key="text.id"&gt;
      {{ text.content }}
      &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Text"&gt;
import { useTextStore } from '@/store/text'

const textStore = useTextStore()

async function getText() {}
&lt;/script&gt;
</code></pre>
</li>
</ul>
</li>
</ol>
<h2 id="5修改数据">(5)修改数据</h2>
<ol>
<li>
<p>方式一:直接手动修改</p>
<p>修改 Count.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="count"&gt;
    &lt;!-- ... --&gt;
    &lt;br /&gt;
    &lt;button @click="change"&gt;Change&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Count"&gt;
// ...
import { useCountStore } from '@/store/count'

const countStore = useCountStore()
// ...
function change() {
countStore.sum = 100
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>方式二:手动批量修改</p>
<p>修改 Count.vue</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Count"&gt;
// ...
function change() {
countStore.$patch({
    sum: 100,
    num: 1
})
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>方式三:使用 <code>actions</code> 修改</p>
<ol>
<li>
<p>修改 count.ts</p>
<pre><code class="language-typescript">import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
// ...
actions: {
    increment(value:number) {
      if(this.sum &lt; 20) {
      this.sum += value
      }
    }
}
})
</code></pre>
</li>
<li>
<p>修改 Count.vue</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Count"&gt;
// ...

function add() {
countStore.increment(number.value)
}
// ...
&lt;/script&gt;
</code></pre>
</li>
</ol>
</li>
</ol>
<h2 id="6storetorefs">(6)<code>storeToRefs</code></h2>
<ol>
<li>
<p>对于从 store 中获得的数据,可以按以下方法在模板中展示</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="count"&gt;
    &lt;h2&gt;Sum: {{ countStore.sum }}&lt;/h2&gt;
    &lt;!-- ... --&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Count"&gt;
// ...
import { useCountStore } from '@/store/count'
const countStore = useCountStore()
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>为使模板更加简洁,可以使用 <code>toRefs</code> 将 <code>sum</code> 从 store 中解构出来</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="count"&gt;
    &lt;h2&gt;Sum: {{ sum }}&lt;/h2&gt;
    &lt;!-- ... --&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Count"&gt;
// ...
import { toRefs } from 'vue'
const { sum } = toRefs(countStore)
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>直接使用 <code>toRefs</code> 会将 store 中携带的方法也变为响应式数据,因此可以使用 pinia 提供的 <code>storeToRefs</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Count"&gt;
// ...
import { storeToRefs } from 'pinia'
const { sum } = storeToRefs(countStore)
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="7getters">(7)<code>getters</code></h2>
<ul>
<li>在 <code>state</code> 中的数据需要经过处理后再使用时,可以使用 <code>getters</code> 配置</li>
</ul>
<ol>
<li>
<p>修改 store/count.ts</p>
<pre><code class="language-typescript">export const useCountStore = defineStore('count', {
state() {
    return {
      sum: 10
    }
},
getters: {
    bigSum(state) {
      return state.sum * 100
    }
},
// ...
})
</code></pre>
</li>
<li>
<p>使用 <code>this</code> 可以替代参数 <code>state</code> 的传入</p>
<pre><code class="language-typescript">getters: {
bigSum(): number {
    return this.sum * 100
}
},
</code></pre>
</li>
<li>
<p>如果不使用 <code>this</code> 则可以使用箭头函数</p>
<pre><code class="language-typescript">getters: {
bigSum: state =&gt; state.sum * 100
},
</code></pre>
</li>
<li>
<p>修改 Count.vue,使用 <code>bigSum</code></p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="count"&gt;
    &lt;h2&gt;Sum: {{ sum }}, BigSum: {{ bigSum }}&lt;/h2&gt;
    &lt;!-- ... --&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Count"&gt;
// ...
const { sum, bigSum } = storeToRefs(countStore)
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="8subscribe">(8)<code>$subscribe</code></h2>
<ul>
<li>作用:订阅,监听 <code>state</code> 及其变化</li>
</ul>
<ol>
<li>
<p>修改 Text.vue</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Text"&gt;
// ...
textStore.$subscribe(() =&gt; {
console.log('state.textList changed')
})
&lt;/script&gt;
</code></pre>
<p>此时,点击按钮后,在开发者工具中可以看到输出了“state.textList changed”</p>
</li>
<li>
<p><code>$subscribe</code> 中可以添加参数</p>
<ul>
<li><code>mutate</code>:本次修改的数据</li>
<li><code>state</code>:当前的数据</li>
</ul>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Text"&gt;
// ...
textStore.$subscribe((mutate, state) =&gt; {
console.log(mutate, state)
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>使用 <code>state</code>,借助浏览器本地存储,实现数据变化后,不会在刷新后初始化</p>
<ol>
<li>
<p>修改 Text.vue</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Text"&gt;
import { useTextStore } from '@/store/text'

const textStore = useTextStore()
textStore.$subscribe((mutate, state) =&gt; {
localStorage.setItem('textList', JSON.stringify(state.textList))
})

function getText() {
textStore.getText()
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 store/text.ts</p>
<pre><code class="language-typescript">import axios from 'axios'
import { defineStore } from 'pinia'

export const useTextStore = defineStore('text', {
state() {
    return {
      textList: JSON.parse(localStorage.getItem('textList') as string) || []
    }
},
actions: {
    async getText() {
      let { data:{result:{content}} } = await axios.get('https://api.oioweb.cn/api/common/OneDayEnglish')
      this.textList.unshift({ id: Date.now().toString(), content})
      console.log(content)
    }
}
})
</code></pre>
</li>
</ol>
</li>
</ol>
<h2 id="9store-组合式写法">(9)store 组合式写法</h2>
<ul>
<li>
<p>之前的内容使用了类似 Vue2 语法的选项式写法</p>
</li>
<li>
<p>修改 Text.ts</p>
<pre><code class="language-typescript">import axios from 'axios'
import { defineStore } from 'pinia'
import { reactive } from 'vue'

export const useTextStore = defineStore('text', () =&gt; {
const textList = reactive(JSON.parse(localStorage.getItem('textList') as string) || [])

async function getText() {
    let { data: { result: { content } } } = await axios.get('https://api.oioweb.cn/api/common/OneDayEnglish')
    textList.unshift({ id: Date.now().toString(), content })
    console.log(content)
}

return {
    textList,
    getText
}
})
</code></pre>
</li>
</ul>
<h1 id="0x05-组件通信">0x05 组件通信</h1>
<h2 id="0概述">(0)概述</h2>
<ul>
<li>
<p>Vue3 中移出了事件总线,可以使用 <code>pubsub</code> 代替</p>
<ul>
<li>Vuex 换成了 pinia</li>
<li><code>.sync</code> 优化到 <code>v-model</code> 中</li>
<li><code>$listeners</code> 合并到 <code>$attrs</code> 中</li>
</ul>
</li>
<li>
<p>重置 <em>~/src</em> 目录结构</p>
<div class="mermaid">graph TB
src--&gt;components &amp; App.vue &amp; main.ts
</div><ul>
<li>
<p>Parent.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;Child /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import Child from './Child.vue'
&lt;/script&gt;

&lt;style scope&gt;
.parent {
width: 50%;
padding: 20px;
background: #03deff;
border: 3px solid black;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>Child.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child&lt;/h2&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
&lt;/script&gt;

&lt;style scope&gt;
.child {
width: 50%;
padding: 20px;
background: #fbff03;
border: 3px solid black;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>App.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;Parent /&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Parent from './components/Parent.vue'
&lt;/script&gt;

&lt;style scope&gt;
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>main.ts</p>
<pre><code class="language-typescript">import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
</code></pre>
</li>
</ul>
</li>
<li>
<p>共有 <span class="math inline">\(7+2\)</span> 种通信方式</p>
</li>
</ul>
<h2 id="1props">(1)props</h2>
<h3 id="a-父组件向子组件传递数据">a. 父组件向子组件传递数据</h3>
<ol>
<li>
<p>修改 Parent.vue,在控制器中创建数据,在模板标签中发送数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;h4&gt;Parent's number: {{ numberParent }}&lt;/h4&gt;
    &lt;Child :number="numberParent" /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import Child from './Child.vue'
import { ref } from 'vue'

let numberParent = ref(12345)
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Child.vue,接收数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child&lt;/h2&gt;
    &lt;h4&gt;Child's number: {{ numberChild }}&lt;/h4&gt;
    &lt;h4&gt;Number from Parent: {{ number }}&lt;/h4&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
import { ref } from 'vue'

let numberChild = ref(56789)

defineProps(['number'])
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h3 id="b-子组件向父组件传递数据">b. 子组件向父组件传递数据</h3>
<ol>
<li>
<p>修改 Parent.vue,创建用于接收子组件发送数据的方法,将方法发送给子组件</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;h4&gt;Parent's number: {{ numberParent }}&lt;/h4&gt;
    &lt;h4 v-show="number"&gt;Number from Child: {{ number }}&lt;/h4&gt;
    &lt;Child :number="numberParent" :sendNumber="getNumber" /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import Child from './Child.vue'
import { ref } from 'vue'

let numberParent = ref(12345)
let number = ref(null)

function getNumber(value: Number) {
number.value = value
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Child.vue,接收父组件发送的方法,并添加按钮使用该方法</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child&lt;/h2&gt;
    &lt;h4&gt;Child's number: {{ numberChild }}&lt;/h4&gt;
    &lt;h4&gt;Number from Parent: {{ number }}&lt;/h4&gt;
    &lt;button @click="sendNumber(numberChild)"&gt;Send Child's number to Parent&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
import { ref } from 'vue'

let numberChild = ref(56789)

defineProps(['number', 'sendNumber'])
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="2自定义事件">(2)自定义事件</h2>
<ul>
<li>
<p>获取事件,即使用 <code>$event</code></p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div&gt;
    &lt;button @click="getEvent($event)"&gt;Event&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts"&gt;
function getEvent(event: Event) {
console.log(event)
}
&lt;/script&gt;
</code></pre>
</li>
</ul>
<ol>
<li>
<p>修改 Child.vue,声明自定义事件</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child&lt;/h2&gt;
    &lt;h4&gt;Child's number: {{ numberChild }}&lt;/h4&gt;
    &lt;button @click="emit('custom-event', numberChild)"&gt;Send Child's number to Parent&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
import { ref } from 'vue'

let numberChild = ref(12345)

const emit = defineEmits(['custom-event'])
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Parent.vue,绑定自定义事件</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;h4 v-show="number"&gt;Number from Child: {{ number }}&lt;/h4&gt;
    &lt;Child @custom-event="getNumber" /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import Child from './Child.vue'
import { ref } from 'vue'

let number = ref(null)

function getNumber(value: Number) {
number.value = value
}
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="3mitt">(3)mitt</h2>
<h3 id="a-概述与准备">a. 概述与准备</h3>
<ul>
<li>可以实现任意组件通信</li>
<li>与 pubsub、$bus 相类似,都是消息订阅与发布
<ul>
<li>接收方:提前绑定事件,即订阅消息</li>
<li>发送方:适时触发事件,即发布消息</li>
</ul>
</li>
</ul>
<ol>
<li>
<p>使用命令 <code>npm install -S mitt</code> 安装 mitt</p>
</li>
<li>
<p>在 <em>~/src</em> 目录下新建 utils 目录,其中新建 emitter.ts,引入、调用、暴露 mitt</p>
<pre><code class="language-typescript">import mitt from 'mitt'

const emitter = mitt()

export default emitter
</code></pre>
<ul>
<li><code>emitter.all()</code>:获取所有绑定事件</li>
<li><code>emitter.emit()</code>:触发指定事件</li>
<li><code>emitter.off()</code>:解绑指定事件</li>
<li><code>emitter.on()</code>:绑定指定事件</li>
</ul>
</li>
</ol>
<h3 id="b-基本使用方法">b. 基本使用方法</h3>
<ol>
<li>
<p>在 main.ts 中引入 emitter.ts</p>
<pre><code class="language-typescript">// ...
import emitter from './utils/emitter'
</code></pre>
</li>
<li>
<p>修改 utils/emitter.ts,绑定事件</p>
<pre><code class="language-typescript">import mitt from 'mitt'

const emitter = mitt()

emitter.on('event1', () =&gt; {
console.log('event1')
})
emitter.on('event2', () =&gt; {
console.log('event2')
})

export default emitter
</code></pre>
</li>
<li>
<p>触发事件</p>
<pre><code class="language-typescript">// ...
setInterval(() =&gt; {
emitter.emit('event1')
emitter.emit('event2')
}, 1000)

export default emitter
</code></pre>
</li>
<li>
<p>解绑事件</p>
<pre><code class="language-typescript">// ...
setTimeout(() =&gt; {
emitter.off('event1')
console.log('event1 off')
}, 3000)

export default emitter
</code></pre>
<ul>
<li>
<p>解绑所有事件</p>
<pre><code class="language-typescript">setTimeout(() =&gt; {
emitter.all.clear()
console.log('All clear')
}, 3000)
</code></pre>
</li>
</ul>
</li>
</ol>
<h3 id="c-实际应用">c. 实际应用</h3>
<blockquote>
<p>目录结构:</p>
<div class="mermaid">graph TB
src--&gt;components &amp; utils &amp; App.vue &amp; main.ts
components--&gt;Child1.vue &amp; Child2.vue &amp; Parent.vue
utils--&gt;emitter.ts
</div><ul>
<li>
<p>Child1.vue(Child2.vue)</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child1&lt;/h2&gt;
    &lt;h4&gt;Name: {{ name }}&lt;/h4&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child1"&gt;
import { ref } from 'vue'

// Child1.vue
let name = ref('Alex')
// Child2.vue
// let name = ref('Bob')
&lt;/script&gt;

&lt;style scope&gt;
.child {
width: 50%;
padding: 20px;
background: #fbff03;
border: 3px solid black;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>Parent.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;Child1 /&gt;
    &lt;br /&gt;
    &lt;Child2 /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
&lt;/script&gt;

&lt;style scope&gt;
.parent {
width: 50%;
padding: 20px;
background: #03deff;
border: 3px solid black;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>emitter.ts</p>
<pre><code class="language-typescript">import mitt from 'mitt'

const emitter = mitt()

export default emitter
</code></pre>
</li>
</ul>
</blockquote>
<ol>
<li>
<p>修改 Child2.vue,绑定事件</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;!-- ... --&gt;
    &lt;h4 v-show="brotherName"&gt;Brother's name: {{ brotherName }}&lt;/h4&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child2"&gt;
// ...
import emitter from '@/utils/emitter'

let brotherName = ref('')

emitter.on('send-name', (value: string) =&gt; {
brotherName.value = value
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Child1.vue,发送数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;!-- ... --&gt;
    &lt;button @click="emitter.emit('send-name', name)"&gt;Send Name&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child1"&gt;
// ...
import emitter from '@/utils/emitter'
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Child2.vue,卸载组件时解绑事件</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Child2"&gt;
// ...
import { ref, onUnmounted } from 'vue'

onUnmounted(() =&gt; {
emitter.off('send-name')
})
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="4v-model">(4)<code>v-model</code></h2>
<ul>
<li>实际开发中不常用,常见于 UI 组件库</li>
</ul>
<h3 id="a-html-标签">a. HTML 标签</h3>
<ul>
<li>
<p>修改 Parent.vue,将 <code>v-model</code> 用在 <code>input</code> 标签上</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;input type="text" v-model="name" placeholder="Enter your name" /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import { ref } from 'vue'

let name = ref("John")
&lt;/script&gt;

&lt;style scope&gt;
.parent {
width: 50%;
padding: 20px;
background: #03deff;
border: 3px solid black;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>在 <code>input</code> 标签中使用 <code>v-model</code> 相当于:</p>
<pre><code class="language-html">&lt;input type="text" :value="name" @input="name=(&lt;HTMLInputElement&gt;$event.target).value" /&gt;
</code></pre>
<ul>
<li><code>(&lt;HTMLInputElement&gt;$event.target)</code>:TypeScript 断言检查</li>
</ul>
</li>
</ul>
<h3 id="b-组件标签">b. 组件标签</h3>
<ol>
<li>
<p>在 components 中新建 CustomInput.vue</p>
</li>
<li>
<p>修改 Parent.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;CustomInput :modelValue="name" @update:modelValue="name = $event" /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'

let name = ref("John")
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 CustomInput.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;input
    type="text"
    :value="modelValue"
    @input="emit('update:modelValue', (&lt;HTMLInputElement&gt;$event.target).value)"
/&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="CustomInput"&gt;
defineProps(["modelValue"])
const emit = defineEmits(["update:modelValue"])
&lt;/script&gt;

&lt;style scope&gt;
&lt;/style&gt;
</code></pre>
<ul>
<li>对于 <code>$event</code> 的 <code>target</code> 判定:
<ul>
<li>当触发的是原生事件时,<code>$event</code> 是事件对象,需要 <code>.target</code></li>
<li>当触发的是自定义事件时,<code>$event</code> 是触发事件数据,不需要 <code>.target</code></li>
</ul>
</li>
</ul>
</li>
<li>
<p>修改 Parent.vue,使用 <code>v-model</code></p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;!-- &lt;CustomInput :modelValue="name" @update:modelValue="name = $event" /&gt; --&gt;
    &lt;CustomInput v-model="name" /&gt;
&lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>此时,可以通过设置 <code>v-model</code>,实现修改 <code>modelValue</code> 以及添加多个 <code>v-model</code></p>
</li>
<li>
<p>修改 Parent.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;!-- ... --&gt;
    &lt;CustomInput v-model:m1="name1" v-model:m2="name2" /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
// ...
let name1 = ref("John")
let name2 = ref("Mary")
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 CustomInput.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;input
    type="text"
    :value="m1"
    @input="emit('update:m1', (&lt;HTMLInputElement&gt;$event.target).value)"
/&gt;

&lt;input
    type="text"
    :value="m2"
    @input="emit('update:m2', (&lt;HTMLInputElement&gt;$event.target).value)"
/&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="CustomInput"&gt;
defineProps(["m1", "m2"])
const emit = defineEmits(["update:m1", "update:m2"])
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="5attrs">(5)<code>$attrs</code></h2>
<ul>
<li><code>$attrs</code> 是一个对象,包含所有父组件传入的标签属性,用于实现祖父组件向孙子组件通信</li>
</ul>
<blockquote>
<p>目录结构:</p>
<div class="mermaid">graph TB
components--&gt;Grand.vue &amp; Parent.vue &amp; Child.vue
</div></blockquote>
<ol>
<li>
<p>修改 Grand.vue,创建数据,并通过 props 发送</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="grand"&gt;
    &lt;h2&gt;Grand&lt;/h2&gt;
    &lt;h4&gt;Number: {{ number }}&lt;/h4&gt;
    &lt;Parent :number="number" /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Grand"&gt;
import Parent from './Parent.vue'
import { ref } from 'vue'

let number = ref(123)
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Parent.vue,使用 <code>$attrs</code> 将来自 Grand.vue 的数据以 props 的方式转发给 Child.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;Child v-bind="$attrs"/&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import Child from './Child.vue'
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Child.vue,接收数据并展示</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child&lt;/h2&gt;
    &lt;h4&gt;Number from Grand: {{ number }}&lt;/h4&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
defineProps(['number'])
&lt;/script&gt;
</code></pre>
<p>此时,祖父组件 Grand.vue 也可以向孙子组件 Child.vue 传递方法,从而可以在孙子组件中修改祖父组件的数据</p>
</li>
</ol>
<h2 id="6refs--parent">(6)<code>$refs</code> &amp; <code>$parent</code></h2>
<ul>
<li>
<p>父子组件通信</p>
<table>
    <tbody><tr>
      <th>属性</th>
      <th>作用</th>
      <th colspan="2">说明</th>
    </tr>
    <tr>
      <td><code>$refs</code></td>
      <td>父组件向子组件通信</td>
      <td rowspan="2">值<br>为<br>对<br>象</td>
      <td>包含所有被 <code>ref</code> 属性标识的 DOM 元素或组件实例</td>
    </tr>
    <tr>
      <td><code>$parent</code></td>
      <td>子组件向父组件通信</td>
      <td>值为对象,当前组件的父组件实例对象</td>
    </tr>
</tbody></table>
</li>
</ul>
<blockquote>
<p>目录结构:</p>
<div class="mermaid">graph TB
components--&gt;Parent.vue &amp; Child1.vue &amp; Child2.vue
</div></blockquote>
<h3 id="a-refs">a. <code>$refs</code></h3>
<ol>
<li>
<p>修改 Child1.vue 和 Child2.vue,创建数据并允许访问</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child1&lt;/h2&gt;
    &lt;!--
    Child2.vue
    &lt;h2&gt;Child2&lt;/h2&gt;
    --&gt;
    &lt;h4&gt;Number: {{ number }}&lt;/h4&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child1"&gt;
import { ref } from 'vue'

let number = ref(456)
// Child2.vue
// let number = ref(789)

defineExpose({ number })
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Parent.vue,设置按钮使其能够修改子组件中的 <code>number</code></p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;button @click="changeNumber"&gt;Change Child1's number&lt;/button&gt;
    &lt;hr /&gt;
    &lt;Child1 ref="c1" /&gt;
    &lt;br /&gt;
    &lt;Child2 ref="c2" /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import { ref } from 'vue'

let c1 = ref()
let c2 = ref()

function changeNumber() {
c1.value.number = 123
c2.value.number = 123
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>使用 <code>$refs</code>,使其能够批量修改</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;!-- ... --&gt;
    &lt;button @click="getAll($refs)"&gt;Get All Child's number&lt;/button&gt;
    &lt;!-- ... --&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
// ...
function getAll(refs: any) {
for (let key in refs) {
    refs.number = 123
}
}
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h3 id="b-parent">b. <code>$parent</code></h3>
<ol>
<li>
<p>修改 Parent.vue,创建数据并允许访问</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;h4&gt;Number: {{ number }}&lt;/h4&gt;
    &lt;hr /&gt;
    &lt;Child1 /&gt;
    &lt;br /&gt;
    &lt;Child2 /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import { ref } from 'vue'

let number = ref(123)

defineExpose({ number })
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Child1.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child1&lt;/h2&gt;
    &lt;button @click="changeNumber($parent)"&gt;Change Number&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child1"&gt;
function changeNumber(parent: any) {
parent.number = 456
}
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="7provide--inject">(7)<code>provide</code> &amp; <code>inject</code></h2>
<ul>
<li>
<p>祖孙组件通信</p>
</li>
<li>
<p>使用方法:</p>
<div class="mermaid">graph LR
A(祖父组件)--provide 发送数据--&gt;B(孙子组件)--inject 接收数据--&gt;A
</div></li>
</ul>
<blockquote>
<p>目录结构:</p>
<div class="mermaid">graph TB
components--&gt;Grand.vue &amp; Parent.vue &amp; Child.vue
</div></blockquote>
<ol>
<li>
<p>修改 Grand.vue,创建数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="grand"&gt;
    &lt;h2&gt;Grand&lt;/h2&gt;
    &lt;h4&gt;Number: {{ number }}&lt;/h4&gt;
    &lt;Parent /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Grand"&gt;
import Parent from './Parent.vue'
import { ref } from 'vue'

let number = ref(123)
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>使用 <code>provide</code> 发送数据</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Grand"&gt;
// ...
import { ref, provide } from 'vue'

provide('number', number)
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Child.vue,接收并展示数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child&lt;/h2&gt;
    &lt;h4&gt;Number from Grand: {{ number }}&lt;/h4&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
import { inject } from 'vue'
let number = inject('number')
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>当 <code>inject</code> 的数据不存在时,可以展示默认值</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child&lt;/h2&gt;
    &lt;h4&gt;Number from Grand: {{ number }}&lt;/h4&gt;
    &lt;h4&gt;Number from Parent: {{ numberP }}&lt;/h4&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
import { inject } from 'vue'
let number = inject('number')
let numberP = inject('numberP', 456)
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Grand.vue,提供方法用于修改祖父组件中的数据</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Grand"&gt;
// ...
function changeNumber() {
number.value = 456
}

provide('number', {
number, changeNumber
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Child.vue,接收方法并使用按钮触发</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;Child&lt;/h2&gt;
    &lt;h4&gt;Number from Grand: {{ number }}&lt;/h4&gt;
    &lt;button @click="changeNumber"&gt;Change Number&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
import { inject } from 'vue'
let { number, changeNumber } = inject('number')
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="8pinia">(8)pinia</h2>
<blockquote>
<p>参考第四章内容</p>
</blockquote>
<h2 id="9slot">(9)<code>slot</code></h2>
<ul>
<li>slot 翻译为“插槽”,分为默认插槽、具名插槽、作用域插槽</li>
</ul>
<h3 id="a-默认插槽">a. 默认插槽</h3>
<ol>
<li>
<p>修改 Parent.vue,创建数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;div class="category"&gt;
      &lt;Child title="C1"&gt;&lt;/Child&gt;
      &lt;Child title="C2"&gt;&lt;/Child&gt;
      &lt;Child title="C3"&gt;&lt;/Child&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import Child from './Child.vue'
import { reactive } from 'vue'

let c1List = reactive([
{ id: "01", Text: "C1-Text1" },
{ id: "02", Text: "C1-Text2" },
{ id: "03", Text: "C1-Text3" }
])
let c2List = reactive([
{ id: "01", Text: "C2-Text1" },
{ id: "02", Text: "C2-Text2" },
{ id: "03", Text: "C2-Text3" }
])
let c3List = reactive([
{ id: "01", Text: "C3-Text1" },
{ id: "02", Text: "C3-Text2" },
{ id: "03", Text: "C3-Text3" }
])
&lt;/script&gt;

&lt;style scope&gt;
.parent {
width: 80%;
padding: 20px;
background: #03deff;
border: 3px solid black;
}
.category {
display: flex;
justify-content: space-around;
}
.category div {
margin: 10px;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>修改 Child.vue,接收数据并展示</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;{{ title }}&lt;/h2&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
defineProps(['title'])
&lt;/script&gt;

&lt;style scope&gt;
.child {
width: 50%;
padding: 20px;
background: #fbff03;
border: 3px solid black;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>修改 Parent.vue 中的模板内容</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;div class="category"&gt;
      &lt;Child title="C1"&gt;
      &lt;ul&gt;
          &lt;li v-for="item in c1List" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
      &lt;/ul&gt;
      &lt;/Child&gt;
      &lt;Child title="C2"&gt;
      &lt;ul&gt;
          &lt;li v-for="item in c2List" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
      &lt;/ul&gt;
      &lt;/Child&gt;
      &lt;Child title="C3"&gt;
      &lt;!-- &lt;ul&gt;
          &lt;li v-for="item in c3List" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
      &lt;/ul&gt; --&gt;
      &lt;/Child&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;
</code></pre>
</li>
<li>
<p>修改 Child.vue,引入插槽并设置默认值</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;{{ title }}&lt;/h2&gt;
    &lt;slot&gt;Default&lt;/slot&gt;
&lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>此时,C1 和 C2 的内容均正常展示;C3 的内容无法展示,而是显示默认值</p>
</li>
</ol>
<h3 id="b-具名插槽">b. 具名插槽</h3>
<blockquote>
<p>具有名字的插槽</p>
</blockquote>
<ol>
<li>
<p>修改 Child.vue,为插槽添加名字</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;h2&gt;{{ title }}&lt;/h2&gt;
    &lt;slot name="list"&gt;Default&lt;/slot&gt;
&lt;/div&gt;
&lt;/template&gt;
</code></pre>
</li>
<li>
<p>修改 Paren.vue,为引用的组件标签添加 <code>v-slot:</code> 属性</p>
<blockquote>
<p>使用 <code>#</code> 简便写法可以替代 <code>v-slot:</code> 写法</p>
</blockquote>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;div class="category"&gt;
      &lt;Child title="C1" #list&gt;
      &lt;ul&gt;
          &lt;li v-for="item in c1List" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
      &lt;/ul&gt;
      &lt;/Child&gt;
      &lt;Child title="C2" v-slot:List&gt;
      &lt;ul&gt;
          &lt;li v-for="item in c2List" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
      &lt;/ul&gt;
      &lt;/Child&gt;
      &lt;Child title="C3"&gt;
      &lt;ul&gt;
          &lt;li v-for="item in c3List" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
      &lt;/ul&gt;
      &lt;/Child&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>此时,C1 的内容正常展示;C2 和 C3 的内容均无法展示,而是显示默认值</p>
</li>
<li>
<p>修改 Child.vue,设置更多的具名插槽,使 <code>title</code> 通过插槽进行传递</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;slot name="title"&gt;&lt;h2&gt;No Title&lt;/h2&gt;&lt;/slot&gt;
    &lt;slot name="slot"&gt;Default&lt;/slot&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Parent.vue,使用 <code>template</code> 标签</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;div class="category"&gt;
      &lt;Child&gt;
      &lt;template v-slot:title&gt;
          &lt;h2&gt;C1&lt;/h2&gt;
      &lt;/template&gt;
      &lt;template v-slot:list&gt;
          &lt;ul&gt;
            &lt;li v-for="item in c1List" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
          &lt;/ul&gt;
      &lt;/template&gt;
      &lt;/Child&gt;
      &lt;Child&gt;
      &lt;template v-slot:list&gt;
          &lt;ul&gt;
            &lt;li v-for="item in c2List" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
          &lt;/ul&gt;
      &lt;/template&gt;
      &lt;template v-slot:title&gt;
          &lt;h2&gt;C2&lt;/h2&gt;
      &lt;/template&gt;
      &lt;/Child&gt;
      &lt;Child&gt;
      &lt;template v-slot:list&gt;
          &lt;ul&gt;
            &lt;li v-for="item in c3List" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
          &lt;/ul&gt;
          &lt;h2&gt;C3&lt;/h2&gt;
      &lt;/template&gt;
      &lt;/Child&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>此时,C1 和 C2 的内容均正常展示;C3 的内容无法展示,而是显示默认值</p>
</li>
</ol>
<h3 id="c-作用域插槽">c. 作用域插槽</h3>
<ol>
<li>
<p>修改 Child.vue,原始数据在子组件中,使用 slot 将数据传递到父组件中</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;
    &lt;slot :cl="cList"&gt;Default&lt;/slot&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
import { reactive } from 'vue'

let cList = reactive([
{ id: "01", text: "C-Text1" },
{ id: "02", text: "C-Text2" },
{ id: "03", text: "C-Text3" }
])
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>修改 Parent.vue,接收并使用子组件传递来的数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="parent"&gt;
    &lt;h2&gt;Parent&lt;/h2&gt;
    &lt;div class="category"&gt;
      &lt;Child&gt;
      &lt;template v-slot="params"&gt; &lt;!-- 接收传递来的数据 --&gt;
          &lt;ul&gt;
            &lt;li v-for="item in params.cl" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
          &lt;/ul&gt;
      &lt;/template&gt;
      &lt;/Child&gt;
      &lt;Child&gt;
      &lt;template v-slot="{ cl }"&gt; &lt;!-- 解构传递来的数据 --&gt;
          &lt;ol&gt;
            &lt;li v-for="item in cl" :key="item.id"&gt;{{ item.text }}&lt;/li&gt;
          &lt;/ol&gt;
      &lt;/template&gt;
      &lt;/Child&gt;
      &lt;Child&gt;
      &lt;template v-slot:default="{ cl }"&gt; &lt;!-- 具名作用域插槽(default是插槽默认名) --&gt;
          &lt;h4 v-for="item in cl" :key="item.id"&gt;{{ item.text }}&lt;/h4&gt;
      &lt;/template&gt;
      &lt;/Child&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Parent"&gt;
import Child from './Child.vue'
&lt;/script&gt;
</code></pre>
</li>
</ol>
<h2 id="10总结">(10)总结</h2>
<table>
    <tbody><tr>
      <th>组件关系</th>
      <th>通信方式</th>
    </tr>
    <tr>
      <td rowspan="4">父传子</td>
      <td><code>props</code></td>
    </tr>
    <tr>
      <td><code>v-model</code></td>
    </tr>
    <tr>
      <td><code>$refs</code></td>
    </tr>
    <tr>
      <td>默认插槽 / 具名插槽</td>
    </tr>
    <tr>
      <td rowspan="5">子传父</td>
      <td><code>props</code></td>
    </tr>
    <tr>
      <td>自定义事件</td>
    </tr>
    <tr>
      <td><code>v-model</code></td>
    </tr>
    <tr>
      <td><code>$parent</code></td>
    </tr>
    <tr>
      <td>作用域插槽</td>
    </tr>
    <tr>
      <td rowspan="2">祖孙互传</td>
      <td><code>$attrs</code></td>
    </tr>
    <tr>
      <td><code>provide</code> &amp; <code>inject</code></td>
    </tr>
    <tr>
      <td rowspan="2">任意组件</td>
      <td>mitt</td>
    </tr>
    <tr>
      <td>pinia</td>
    </tr>
</tbody></table>
<h1 id="0x06-其他-api">0x06 其他 API</h1>
<ul>
<li>以下 API 使用场景不多,但需要了解</li>
</ul>
<h2 id="1shallowref--shallowreactive">(1)<code>shallowRef</code> &amp; <code>shallowReactive</code></h2>
<ul>
<li>两种方法一般用于绕开深度响应,提高性能,加快访问速度</li>
</ul>
<h3 id="a-shallowref">a. <code>shallowRef</code></h3>
<ul>
<li>
<p>作用:创建一个响应式数据,只对顶层属性进行响应式处理</p>
</li>
<li>
<p>特点:只跟踪引用值的变化,不跟踪值内部的属性的变化</p>
</li>
<li>
<p>用法</p>
<ol>
<li>
<p>修改 App.vue,创建数据与方法,并在模板中展示</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;Name: {{ person.name }}&lt;/h2&gt;
    &lt;h2&gt;Age: {{ person.age }}&lt;/h2&gt;
    &lt;p&gt;&lt;button @click="changerName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
    &lt;p&gt;&lt;button @click="changerAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
    &lt;p&gt;&lt;button @click="changerAll"&gt;Change All&lt;/button&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
let person = {
name: 'John',
age: 18
}

function changerName() {
person.value.name = 'Mary'
}
function changerAge() {
person.value.age = 19
}
function changerAll() {
person.value = {
    name: 'Mary',
    age: 19
}
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>引入 <code>shallowRef</code> 并使用</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
import { shallowRef } from 'vue'

let person = shallowRef({
name: 'John',
age: 18
})
// ...
&lt;/script&gt;
</code></pre>
<p>此时,姓名和年龄均不可修改,而所有信息可以通过自定义的 <code>changeAll()</code> 方法修改</p>
</li>
</ol>
</li>
</ul>
<h3 id="b-shallowreactive">b. <code>shallowReactive</code></h3>
<ul>
<li>
<p>作用:创建一个浅层响应式对象,只将对象的最顶层属性变成响应式,其他属性不变</p>
</li>
<li>
<p>特点:对象的顶层属性是响应式的,嵌套对象的属性不是</p>
</li>
<li>
<p>用法</p>
<ol>
<li>
<p>修改 App.vue,创建数据与方法,并在模板中展示</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;Name: {{ person.name }}&lt;/h2&gt;
    &lt;h2&gt;Age: {{ person.detail.age }}&lt;/h2&gt;
    &lt;p&gt;&lt;button @click="changerName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
    &lt;p&gt;&lt;button @click="changerAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
    &lt;p&gt;&lt;button @click="changerAll"&gt;Change All&lt;/button&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
let person = {
name: 'John',
detail: {
    age: 18
}
}

function changerName() {
person.name = 'Mary'
}
function changerAge() {
person.detail.age = 19
}
function changerAll() {
Object.assign(person, {
    name: 'Mary',
    detail: {
      age: 19
    }
})
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>引入 <code>shallowReactive</code> 并使用</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
import { shallowReactive } from 'vue'

let person = shallowReactive({
name: 'John',
detail: {
    age: 18
}
})
// ...
&lt;/script&gt;
</code></pre>
<p>此时,姓名可以修改,但年龄不可修改,而所有信息可以通过自定义的 <code>changeAll()</code> 方法修改</p>
</li>
</ol>
</li>
</ul>
<h2 id="2readonly--shallowreadonly">(2)<code>readonly</code> &amp; <code>shallowReadonly</code></h2>
<h3 id="a-readonly">a. <code>readonly</code></h3>
<ul>
<li>
<p>作用:创建一个对象的深只读副本</p>
</li>
<li>
<p>特点:</p>
<ul>
<li>对象的所有嵌套属性变为只读</li>
<li>阻止对象被修改</li>
</ul>
</li>
<li>
<p>应用场景:</p>
<ul>
<li>创建不可变的状态快照</li>
<li>保护全局状态/配置不可修改</li>
</ul>
</li>
<li>
<p>用法</p>
<ol>
<li>
<p>修改 App.vue,创建数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;Sum: {{ sum1 }}&lt;/h2&gt;
    &lt;h2&gt;Sum readonly: {{ sum2 }}&lt;/h2&gt;
    &lt;p&gt;&lt;button @click="changeSum1"&gt;Change Sum&lt;/button&gt;&lt;/p&gt;
    &lt;p&gt;&lt;button @click="changeSum2"&gt;Change Sum readonly&lt;/button&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import { ref } from 'vue'

let sum1 = ref(0)
let sum2 = ref(0)

function changeSum1() {
sum1.value += 1
}
function changeSum2() {
sum2.value += 1
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>引入 <code>readonly</code> 并使用</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
import { ref, readonly } from 'vue'
// ...
let sum2 = readonly(sum1)
// ...
&lt;/script&gt;
</code></pre>
<p>此时,<code>sum2</code> 会随着 <code>sum1</code> 改变而改变,但点击按钮“Change Sum readonly”不会对 <code>sum</code> 修改</p>
</li>
</ol>
</li>
</ul>
<h3 id="b-shallowreadonly">b. <code>shallowReadonly</code></h3>
<ul>
<li>
<p>作用:创建一个对象的浅只读副本</p>
</li>
<li>
<p>特点:只将对象的顶层属性设置为只读,对象内部的嵌套属性依旧可读可写</p>
</li>
<li>
<p>应用场景:仅需对对象顶层属性保护时使用</p>
</li>
<li>
<p>用法</p>
<ol>
<li>
<p>修改 App.vue,创建数据</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;Name: {{ personRO.name }}&lt;/h2&gt;
    &lt;h2&gt;Age: {{ personRO.detail.age }}&lt;/h2&gt;
    &lt;p&gt;&lt;button @click="changerName"&gt;Change Name&lt;/button&gt;&lt;/p&gt;
    &lt;p&gt;&lt;button @click="changerAge"&gt;Change Age&lt;/button&gt;&lt;/p&gt;
    &lt;p&gt;&lt;button @click="changerAll"&gt;Change All&lt;/button&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import { reactive } from 'vue'

let person = reactive({
name: 'John',
detail: {
    age: 18
}
})
let personRO = person

function changerName() {
personRO.name = 'Mary'
}
function changerAge() {
personRO.detail.age = 19
}
function changerAll() {
Object.assign(personRO, {
    name: 'Mary',
    detail: {
      age: 19
    }
})
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>引入 <code>shallowReadonly</code> 并使用</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
import { reactive, shallowReadonly } from 'vue'
// ...
let personRO = shallowReadonly(person)
// ...
&lt;/script&gt;
</code></pre>
<p>此时,姓名不可以修改,年龄可以修改,所有信息可以通过自定义的 <code>changeAll()</code> 方法修改</p>
</li>
</ol>
</li>
</ul>
<h2 id="3toraw--markraw">(3)<code>toRaw</code> &amp; <code>markRaw</code></h2>
<h3 id="a-toraw">a. <code>toRaw</code></h3>
<ul>
<li>
<p>作用:获取一个响应式对象的原始对象</p>
</li>
<li>
<p>特点:返回的对象不是响应式的,不会触发视图更新</p>
</li>
<li>
<p>应用场景:将响应式对象传递到非 Vue 的库或外部系统时使用</p>
</li>
<li>
<p>用法</p>
<ol>
<li>
<p>修改 App.vue,创建数据</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
import { reactive } from 'vue'

let person = reactive({
name: 'John',
age: 18
})

console.log(person)
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>引入 <code>toRaw</code> 并使用</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
import { reactive, toRaw } from 'vue'

let person = reactive({
name: 'John',
age: 18
})

console.log(person)
console.log(toRaw(person))
&lt;/script&gt;
</code></pre>
</li>
</ol>
</li>
</ul>
<h3 id="b-markraw">b. <code>markRaw</code></h3>
<ul>
<li>
<p>作用:标记一个对象,使其永远不会变成响应式对象</p>
</li>
<li>
<p>应用场景:例如使用 Mock.js 插件时,为防止误把 <code>mockjs </code>变成响应式对象而使用</p>
<blockquote>
<p>Mock.js 官网</p>
</blockquote>
</li>
<li>
<p>用法</p>
<ol>
<li>
<p>修改 App.vue,创建数据</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
let person = {
name: 'John',
age: 18
}
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>引入 <code>markRaw</code> 并使用</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
import { reactive, markRaw } from 'vue'

let person = markRaw({
name: 'John',
age: 18
})

person = reactive(person)
console.log(person)
&lt;/script&gt;
</code></pre>
<p>此时,<code>person</code> 并未变成响应式对象</p>
</li>
</ol>
</li>
</ul>
<h2 id="4customref">(4)<code>customRef</code></h2>
<ul>
<li>
<p>作用:创建一个自定义的 <code>ref</code>,并对其<em>依赖项跟踪</em>和<em>更新触发</em>进行逻辑控制</p>
</li>
<li>
<p>用法</p>
<ol>
<li>
<p>修改 App.vue,引入 <code>customRef</code></p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;Message: {{ msg }}&lt;/h2&gt;
    &lt;input type="text" v-model="msg" /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import { customRef } from 'vue'

let msg = customRef(() =&gt; {
return {
    get() {},
    set() {}
}
})
&lt;/script&gt;
</code></pre>
<p>其中,<code>get()</code> 在变量 <code>msg</code> 被读取时调用;<code>set()</code> 在变量 <code>msg</code> 被修改时调用</p>
</li>
<li>
<p>声明新变量 <code>initMsg</code> 作为默认值在 <code>get()</code> 中返回</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
import { customRef } from 'vue'

let initMsg = "Default"
let msg = customRef(() =&gt; {
return {
    get() {
      return initMsg
    },
    set() {}
}
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>对 <code>msg</code> 的修改结果通过 <code>set()</code> 接收</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
// ...
let msg = customRef(() =&gt; {
return {
    // ...
    set(value) {
      console.log('set', value)
    }
}
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>在 <code>set()</code> 中修改 <code>initMsg</code></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
// ...
let msg = customRef(() =&gt; {
return {
    // ...
    set(value) {
      // console.log('set', value)
      initMsg = value
    }
}
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p><font color="red">【核心内容】</font>在 <code>customRef</code> 的回调函数中,接收两个参数:<code>track</code>(跟踪)、<code>trigger</code>(触发)</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
// ...
let msg = customRef((track, trigger) =&gt; {
return {
    get() {
      track()
      return initMsg
    },
    set(value) {
      initMsg = value
      trigger()
    }
}
})
&lt;/script&gt;
</code></pre>
<ul>
<li><code>track()</code>:依赖项跟踪,告知 Vue 需要对变量 <code>msg</code> 持续关注,一旦变化立即更新</li>
<li><code>trigger()</code>:更新触发,告知 Vue 变量 <code>msg</code> 发生了变化</li>
</ul>
</li>
<li>
<p>可以在 <code>set()</code> 通过设置延时函数实现指定时间后触发更新</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
// ...
let msg = customRef((track, trigger) =&gt; {
return {
    // ...
    set(value) {
      setTimeout(() =&gt; {
      initMsg = value
      trigger()
      }, 1000)
    }
}
})
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>但是当输入过快时,会导致输入的数据被覆盖丢失,此时引入<strong>防抖方法</strong></p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
// ...
let timer: number
let msg = customRef((track, trigger) =&gt; {
return {
    // ...
    set(value) {
      clearTimeout(timer)
      timer = setTimeout(() =&gt; {
      initMsg = value
      trigger()
      }, 1000)
    }
}
})
&lt;/script&gt;
</code></pre>
<p>防抖原理:清除上一次 <code>set()</code> 中生产的定时器,并以最新定时器为准</p>
</li>
<li>
<p>在 src 目录下新建 hooks 目录,其中新建 useMsgRef.ts,用于将上述自定义 ref 封装成 Hooks</p>
<pre><code class="language-typescript">import { customRef } from 'vue'

export default function(initMsg: string, delaySecond: number) {
let timer: number
let msg = customRef((track, trigger) =&gt; {
    return {
      get() {
      track()
      return initMsg
      },
      set(value) {
      clearTimeout(timer)
      timer = setTimeout(() =&gt; {
          initMsg = value
          trigger()
      }, delaySecond * 1000)
      }
    }
})
return { msg }
}
</code></pre>
</li>
<li>
<p>修改 App.vue,使用上述 Hooks</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="App"&gt;
import useMsgRef from '@/hooks/useMsgRef'

let { msg } = useMsgRef('Default', 1)
&lt;/script&gt;
</code></pre>
</li>
</ol>
</li>
</ul>
<h1 id="0x07-新组件">0x07 新组件</h1>
<h2 id="1teleport">(1)Teleport</h2>
<ul>
<li>
<p>是一种能够将组件 HTML 结构移动到指定位置的技术</p>
</li>
<li>
<p>举例:在页面上封装一个弹窗组件</p>
<blockquote>
<p>目录结构:</p>
<div class="mermaid">graph TB
src--&gt;components &amp; App.vue &amp; main.ts
components--&gt;ModelDialog.vue
</div></blockquote>
<ol>
<li>
<p>修改 App.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="outer"&gt;
    &lt;h2&gt;App Component&lt;/h2&gt;
    &lt;ModelDialog /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import ModelDialog from './components/ModelDialog.vue'
&lt;/script&gt;

&lt;style scope&gt;
.outer {
width: 80%;
height: 500px;
padding: 20px;
background: #03deff;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>修改 ModelDialog.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;button&gt;Show Dialog&lt;/button&gt;
&lt;div class="md"&gt;
    &lt;h2&gt;Dialog Title&lt;/h2&gt;
    &lt;p&gt;Content Here&lt;/p&gt;
    &lt;button&gt;Close&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="ModelDialog"&gt;
&lt;/script&gt;

&lt;style scope&gt;
.md {
text-align: center;
width: 50%;
height: 30%;
background: #fbff03;
border: 3px solid black;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>设置弹窗的显示与隐藏</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;button @click="isShowDialog = true"&gt;Show Dialog&lt;/button&gt;
&lt;div class="md" v-show="isShowDialog"&gt;
    &lt;h2&gt;Dialog Title&lt;/h2&gt;
    &lt;p&gt;Content Here&lt;/p&gt;
    &lt;button @click="isShowDialog = false"&gt;Close&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="ModelDialog"&gt;
import { ref } from 'vue'
let isShowDialog = ref(false)
&lt;/script&gt;
</code></pre>
</li>
<li>
<p>通过 CSS 调整弹窗位于视口正中央</p>
<pre><code class="language-vue">&lt;style scope&gt;
.md {
// ...
position: fixed;
top: 5%;
left: 50%;
margin-left: -100px;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>修改 App.vue,通过 CSS 设置变灰</p>
<pre><code class="language-vue">&lt;style scope&gt;
.outer {
// ...
filter: saturate(0%);
}
&lt;/style&gt;
</code></pre>
<p>此时,弹窗并未按照第 4 步的设置显示在视口正中央,可以通过 Teleport 解决此问题</p>
</li>
<li>
<p>修改 ModelDialog.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;button @click="isShowDialog = true"&gt;Show Dialog&lt;/button&gt;
&lt;Teleport to="body"&gt;
    &lt;div class="md" v-show="isShowDialog"&gt;
    &lt;h2&gt;Dialog Title&lt;/h2&gt;
    &lt;p&gt;Content Here&lt;/p&gt;
    &lt;button @click="isShowDialog = false"&gt;Close&lt;/button&gt;
&lt;/div&gt;
&lt;/Teleport&gt;
&lt;/template&gt;
</code></pre>
<ul>
<li>
<p><code>to</code> 属性用于指定移动的目的地,上述代码将弹窗内容移动到 <code>body</code> 标签中</p>
</li>
<li>
<p>此时,网页渲染后的 DOM 结构如下:</p>
<pre><code class="language-html">&lt;!-- ... --&gt;
&lt;body&gt;
    &lt;div id="app" data-v-app&gt;&lt;!-- ... --&gt;&lt;/div&gt;
    &lt;!-- ... --&gt;
    &lt;div id="md"&gt;&lt;!-- ... --&gt;&lt;/div&gt;
&lt;/body&gt;
</code></pre>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2 id="2suspense">(2)Suspense</h2>
<ul>
<li>等待异步组件时,渲染一些额外的内容,改善用户体验</li>
</ul>
<blockquote>
<p>目录结构:</p>
<div class="mermaid">graph TB
src--&gt;components &amp; App.vue &amp; main.ts
components--&gt;Child.vue
</div></blockquote>
<ol>
<li>
<p>修改 App.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;App&lt;/h2&gt;
    &lt;Child /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Child from './components/Child.vue'
&lt;/script&gt;

&lt;style scope&gt;
.app {
width: 80%;
height: 500px;
padding: 20px;
background: #03deff;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>修改 Child.vue</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="child"&gt;&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="Child"&gt;
&lt;/script&gt;

&lt;style scope&gt;
.child {
width: 200px;
height: 150px;
background: #fbff03;
}
&lt;/style&gt;
</code></pre>
</li>
<li>
<p>添加异步任务</p>
<pre><code class="language-vue">&lt;script setup lang="ts" name="Child"&gt;
import axios from 'axios'

let { data: { result: { content } } } = await axios.get("https://api.oioweb.cn/api/common/OneDayEnglish")

console.log(content)
&lt;/script&gt;
</code></pre>
<p>此时,子组件会在页面上“消失”,可以在 App.vue 中使用 Suspense 解决此问题</p>
</li>
<li>
<p>修改 App.vue,引入 Suspense 并使用</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;App&lt;/h2&gt;
    &lt;Suspense&gt;
      &lt;template #default&gt;
      &lt;Child /&gt;
      &lt;/template&gt;
    &lt;/Suspense&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
import Child from './components/Child.vue'
import { Suspense } from 'vue'
&lt;/script&gt;
</code></pre>
<ul>
<li>Suspense 中预设了两个插槽,<code>default</code> 插槽用于展示异步完成的内容,<code>fallback</code> 插槽用于展示异步进行中的内容</li>
<li>当网络状态不是很好时,子组件不会立即渲染完成,因此可以借助 Suspense 添加加载提示</li>
</ul>
</li>
<li>
<p>设置加载内容:Loading...</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;App&lt;/h2&gt;
    &lt;Suspense&gt;
      &lt;template #default&gt;
      &lt;Child /&gt;
      &lt;/template&gt;
      &lt;template #fallback&gt;
      &lt;h2&gt;Loading...&lt;/h2&gt;
      &lt;/template&gt;
    &lt;/Suspense&gt;
&lt;/div&gt;
&lt;/template&gt;
</code></pre>
</li>
</ol>
<h2 id="3全局-api-转移到应用对象">(3)全局 API 转移到应用对象</h2>
<blockquote>
<div class="mermaid">graph LR
A(Vue2 全局API&lt;br/&gt;Vue.xxx) --&gt; B(Vue3 应用对象API&lt;br/&gt;app.xxx)
</div></blockquote>
<ol>
<li>
<p>app.component</p>
<ol>
<li>
<p>修改 main.ts,注册全局组件</p>
<pre><code class="language-typescript">import { createApp } from 'vue'
import App from './App.vue'
import Child from './components/Child.vue'

const app = createApp(App)
app.component('Child', Child)
app.mount('#app')
</code></pre>
</li>
<li>
<p>修改 App.vue,使用全局组件</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;App&lt;/h2&gt;
    &lt;Child /&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
&lt;/script&gt;
</code></pre>
</li>
</ol>
</li>
<li>
<p>app.config</p>
<ol>
<li>
<p>修改 main.ts,注册全局属性</p>
<pre><code class="language-typescript">import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.config.globalProperties.x = 12345

declare module 'vue' {
interface ComponentCustomProperties {
    x: number;
}
}

app.mount('#app')
</code></pre>
</li>
<li>
<p>修改 App.vue,使用全局属性</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;App&lt;/h2&gt;
    &lt;h4&gt;{{ x }}&lt;/h4&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
&lt;/script&gt;
</code></pre>
<p>实际开发中,不建议该使用方法,容易污染全局</p>
</li>
</ol>
</li>
<li>
<p>app.directive</p>
<ol>
<li>
<p>修改 main.ts,注册全局指令</p>
<pre><code class="language-typescript">import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.directive('custom', (element, {value}) =&gt; {
element.innerText += value
element.style.fontSize = '50px';
})
app.mount('#app')
</code></pre>
</li>
<li>
<p>修改 App.vue,使用全局指令</p>
<pre><code class="language-vue">&lt;template&gt;
&lt;div class="app"&gt;
    &lt;h2&gt;App&lt;/h2&gt;
    &lt;p v-custom="value"&gt;Hello,&lt;/p&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts" name="App"&gt;
let value = "world!"
&lt;/script&gt;
</code></pre>
</li>
</ol>
</li>
<li>
<p>app.mount</p>
<ul>
<li>
<p>在 main.ts 中挂载应用</p>
<pre><code class="language-typescript">import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')
</code></pre>
</li>
</ul>
</li>
<li>
<p>app.unmount</p>
<ul>
<li>
<p>在 main.ts 中卸载应用</p>
<pre><code class="language-typescript">import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

setTimeout(() =&gt; {
app.unmount()
}, 3000)
</code></pre>
</li>
</ul>
</li>
<li>
<p>app.use</p>
<ul>
<li>
<p>在 main.ts 中安装插件,如 pinia</p>
<pre><code class="language-typescript">import { createApp } from 'vue'
import App from './App.vue'
import { PiniaVuePlugin } from 'pinia'

const app = createApp(App)
app.use(PiniaVuePlugin)
app.mount('#app')
</code></pre>
</li>
</ul>
</li>
</ol>
<h2 id="4非兼容性改变">(4)非兼容性改变</h2>
<blockquote>
<p>详见官方文档</p>
</blockquote>
<p>-End-</p>
<blockquote>
<p>2024-11-11 浏览量 1000 次达成纪念<br>
2026-02-08 浏览量 2000 次达成纪念</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/SRIGT/p/18062140
頁: [1]
查看完整版本: Vue3 + TypeScript 开发指南