## 一、Vue方法、计算属性及监听器 #### 1、计算属性 计算属性是`Vue.createApp()`参数对象中的一个属性`computed`,其传入的是一个方法数组,计算属性中的方法都可以直接像`data()`属性中一样直接使用。 ##### 反序输出字符串 ```html Title {{msg}} {{reversedMsg}} ``` **注意**:计算属性具有自动缓存的特点,当`msg`并未发生变化时,`reversedMsg()`并不会再次被执行,而是直接调用缓存里的结果返回,所以计算属性可以提高运行效率。 ##### 计算商品总价 ```html Title 商品名称:小米手机 数量:-{{num}}+ 总价:{{total}} ``` ##### 数据过滤处理 ```html Title {{moneyInfo}} ``` #### 2、方法 方法是`Vue.createApp()`参数对象中的一个属性`methods`,其传入的是一个方法数组,可以将方法绑定到事件中,若要像计算属性一样调用,需要加`()`。 ##### 方法声明与调用 ```html Title {{money}} {{moneyInfo}} {{moneyInfo2()}} ``` ##### 方法与计算属性的区别 | **对比项** | **computed** | **methods** | | -------------- | ---------------------------------------------------------- | ---------------------------------------------------- | | **定义方式** | 在`computed`对象中定义计算属性 | 在`methods` 对象中定义方法 | | **缓存机制** | **有缓存**:依赖数据未变化时直接返回缓存结果,避免重复计算 | **无缓存**:每次调用都会重新执行函数 | | **调用方式** | 在模板中作为**属性**使用(无需括号) | 在模板中作为**方法**调用(需加括号) | | **响应式依赖** | **自动追踪依赖**:仅在其依赖的响应式数据变化时重新计算 | 不自动追踪依赖,每次调用都执行完整逻辑 | | **适用场景** | 适用于**派生数据**(如过滤、计算属性) | 适用于**事件处理**或需要主动触发的逻辑 | | **同步/异步** | 必须是**同步操作**,不支持异步逻辑 | 可包含**异步操作**(如 `await`) | | **返回值** | 必须返回一个值(用于模板渲染或逻辑依赖) | 可以无返回值(如事件处理函数) | | **性能优化** | 高频使用时因缓存机制更高效 | 无缓存,频繁调用可能影响性能(需配合事件节流等优化) | #### 3、监听器 监听器(`watch`) 的主要作用是观察和响应Vue实例中数据的变化,当特定的数据发生变化时执行异步操作或复杂的逻辑处理。 监听器的声明要在`watch`属性中以函数数组的方式声明,其参数有两个返回值,一个为改变后的新值,一个为改变前的旧值。 ##### 基本数据类型监听 ```html Title 商品名称:小米手机 数量: - {{num}} + 总价:{{total}} ``` **注意**:监听器声明时的函数名必须与所监听的变量名一致。 ##### 引用数据类型深度监听 **模板** ```js watch: { varName: { handler() { // 处理代码 }, deep: true } } ``` **示例** ```html Title 商品名称:{{goods.name}} 数量: - {{goods.num}} + 总价:{{total}} ``` **注意**:此处虽然实现了深度监听,但无法获取前后变化的值,因为对象为引用数据类型,前后地址并无变化,所以取出的属性值都为一致的。 ##### 引用数据类型限制属性改变 核心思想:在计算属性中使用内置`JSON`工具将对象转换(`.stringify`)成Json格式的文本,并且监听该计算属性(文本)改变,并在监听器里将Json格式的文本转化(`.parse`)为对象,通过新旧两个对象的属性值来判断值变化是否合法。 ```html Title 商品名称:{{goods.name}} 数量: - {{goods.num}} + 总价:{{total}} ``` ## 二、表单数据绑定 #### 1、注册表单示例 ```html Title 注册 用户名: 密码: 再次输入密码: 性别: 男 女 爱好: 读书 体育 旅游 国籍: 中国 美国 俄罗斯 个人介绍: 注册 ``` **注意**:在表单中复选框、单选框以及下拉列表必须给`value`属性一个值,要不然会出现错误,且复选框不能在同一个``容器下包裹。 #### 2、`v-model`修饰符 ##### `v-model.lazy` 只有在input输入框发生一个blur时才会触发,也就是延迟同步到失去焦点时。 ```html Title 数值: 延迟加载数值: ``` ##### `v-model.trim` 将用户输入内容的前后空格去掉。 ```html Title 文本头::文本尾 去空格输入: 不去空格输入: ``` **注意**:``标签可以将使多个连续的空格不折叠。 ##### `v-model.number` 将用户输入的字符串自动转换成数值。 ```html Title 数值: 普通输入框: 自动转换数字: ``` ## 三、Vue组件 #### 1、什么是Vue组件 组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成一个层层嵌套的树状结构:  Vue组件大致可以分为三种:根组件、全局组件、局部组件。 一个Vue组件通常包含三个部分: - 模板(Template): 定义组件的HTML结构和布局。 - 逻辑(Script): 定义组件的数据、方法和生命周期。 - 样式(Style): 定义组件的CSS样式(可选,且可以设置作用域)。 #### 2、根组件 ```html Title ``` **注意**:当组件挂载到`Vue实例`上,那么他就是一个全局组件,当组件挂载到`根组件`上,那么他就是一个局部组件 #### 3、全局组件 全局组件可以应用在任何Vue实例(根组件)上,如下Vue实例挂载在`app`与`app2`任意一个上都起作用,全局组件一旦定义了即使不使用也会消耗系统资源。 ```html Title app app2 ``` **注意**:组件标签命名必须全是`小写`,单词需用`-`分割,否则不识别。 #### 4、组件中的内容 组件可以实现Vue实例中所有的操作,例如数据绑定、计算属性、方法、监听器等。 ```html Title ``` #### 5、局部组件 与全局组件不同的是,跟组件只能应用在声明的根组件中,而且局部组件只有在使用时才会消耗系统资源。 ```html Title ``` **注意**:当前定义局部组件方式要求组件名称不能出现符号以及大写字母。 #### 6、组件模板标签 组件具有复用性,局部组件与局部组件之间属性作用域是独立的。 ```html Title 我是局部组件,内容: 改变文本 ``` ## 四、父子组件 父子组件简单来说就是组件之间的嵌套,组件内有组件。 ```html Title 我是父组件 我是子组件 ``` ## 五、组件之间的通讯 #### 1、子组件获取父组件数据(单向) 组件与组件之间的作用域是孤立的,默认情况下父子组件的数据是不能共享的,若需要共享需要使用数据传递属性`props`。 ```html Title 我是父组件,文本: 输入文本: 我是子组件,文本: ``` **注意**:首先要在父组件``调用子组件时给子组件标签添加一个自定义属性,并且让Vue绑定他,在子组件定义属性时添加`props`属性,属性值为一个字符串数组,里面字符串需要填写子组件上的自定义属性名。 #### 2、子组件传值校验 使用`prop`对传值校验时,需要将`prop`值声明为对象,属性名为传入的标签属性名,使用其参数`type`来校验数据类型 ```html Title 我是父组件,文本:,数字是: 输入文本: 输入数字: 我是子组件,文本+1后是:,数字+1后是: ``` **注意**:若对引用组件时传的自定义标签属性是否存在进行校验,则将该属性在`prop`中添加`required:true`,这样当传入属性时没有该属性时,会抛出缺失警告。 #### 3、单向数据流 父组件向子组件传递数据是单向的,也就是说:子组件可以接受并使用父组件传递过来的数据,单子组件不能修改此数据。 这样做是为了防止子组件意外的改变了父组件的状态,从而导致父组件的行为异常。 ##### 子组件修改值测试 ```html Title 我是父组件,文本:,数字是: 我是子组件,文本+1后是:,数字+1后是: 输入文本: 输入数字: ``` 会出现如下图警告  #### 4、父组件获取子组件数据(单向) 父组件要获取到子组件的数据需要在引用子组件时绑定自定义方法,例如`@childmsg=""`,并在父组件`methods`中写一个处理子组件方法返回值的方法,例如`getMsg(value)`,然后让自定义方法去调用这个处理返回值的方法,例如`@childmsg="getMsg"`,之后在子组件中用`emits`属性去声明自定义方法,例如`emits:['childmsg']`,并在子组件中`methods`编写一个调用`$emit()`方法的方法,例如`updateTxt`用来调用父组件传来的自定义方法,在`$emit()`中第一个参数为自定义方法名,之后的参数为父组件处理数据方法的参数值。 ##### 基础写法 ```html Title 我是父组件,文本: 我是子组件,文本: 输入文本: ``` **注意**:若不用`emits`属性声明父组件在子组件引用时的自定义方法,那么将会抛出警告:`[Vue warn]: Extraneous non-emits event listeners (childmsg) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option. ` 若是``表单组件,可以简化`` ##### 进阶写法 ```html Vue组件多参数传递 Vue组件多参数传递示例 这个示例展示了如何将父组件的多个数据(txt和num)传递给子组件。我们使用props将父组件中的数据传递给子组件。 在子组件中,我们通过props接收数据,并使用自定义事件将更改传递回父组件。 父组件 (mycomponent) 文本数据: 数值数据: 数据摘要: 文本是"",数值是 更新文本: 更新数值: 数值+1 数值-1 重置数据 子组件 (mycomponent2) 从父组件接收的文本: 从父组件接收的数值: 更新文本: 更新数值: 操作数值: 选择操作 加倍 减半 平方 ``` #### 5、多级组件通讯 ##### 基础写法 父组件 -> 子组件 ->孙子组件 ```html Title 我是父组件,文本: 输入文本: 我是子组件,文本是: 我是孙子组件,文本是: ``` ##### 进阶写法 使用Vue提供的`provide/inject`来实现,`provide`需要传入一个函数对象,所以直接用`provide(){}`来声明,返回值为需要向后代组件提供的属性对象,`Vue.computed(() => this.txt)`定义了一个匿名的计算属性并且赋给了`msg`属性,以实现当父组件`txt`的值改变时,也会改变`msg`的值。 后代组件通过属性`inject`来接收并声明父组件传递过来的属性。 ```html Title 我是父组件,文本: 输入文本: 我是子组件 我是孙子组件,文本是: ``` ## 六、slot插槽 Vue官方实现了一套内容分发的API,将slot元素作为承载分发内容的出口。 #### 1、用法 引用子标签时在子标签内部写入HTML代码,在子标签的``中将``标签添加到需要插入的位置上。 ```html Title 我是父组件 插槽的内容 我是子组件 ``` #### 2、插槽的作用域 父级模板中的所有内容都是在父级作用域中编译,子级模板里的所有内容都是在子作用域中编译。 所以在父组件给出插槽中的数据时就已经在父级作用域中编译完成了,子组件获取到的只是编译的结果,例如传入一个`num`属性,在父级作用域内可以对其进行修改,子组件插槽内容也会进行改变,但子组件获取到的只是`num`的值,并不是该属性本身。 ```html Title 我是父组件 插槽的内容{{num}}父组件+1 我是 子组件 子组件+1 ``` **注意**:此处`子组件+1`修改父组件中的`num`通过传入自定义方法并且用`emits:[]`声明后使用`$emit()`调用达到的效果,因为在父组件中定义了`父组件+1`按钮,所以按钮可以直接调用父组件中的方法。 #### 3、插槽的默认值 在子组件``标签内即可定义插槽的默认值,当父组件传入内容时会显示传入的内容,当父组件没有传入内容时将显示插槽的默认内容。 ```html 我是 [我是插槽的默认值] 子组件 子组件+1 ``` #### 4、具名插槽(多个插槽) 可以在父组件传入内容时使用``标签属性`v-slot:插槽名`来定义具名插槽,在子组件中只需使用``即可调用相应的部分,当然每个部分插槽都可以有其默认值。 ```html Title 我是父组件 头部内容 插槽的内容{{num}} 父组件+1 尾部内容 我是子组件 [我是头部插槽的默认值] [我是插槽的默认值] 子组件+1 [我是尾部插槽的默认值] ``` ## 七、动态组件 #### 1、`v-if`实现 ```html Title 我是父组件 切换 我是子组件1 我是子组件2 ``` #### 2、通用组件实现 Vue中有一个通用组件标签``,可以代表所有的组件,使用属性`is`来决定具体组件,所以可以通过变量来控制组件的名称以达到动态组件的效果。 ```html Title 我是父组件 切换 我是子组件1 我是子组件2 ``` #### 3、状态保持 当两个子组件中有表单等需要用户输入数据的组件时,直接切换会导致这些组件值重置,并不会保留用户之前输入的数据,Vue为解决这种现象提供了数据状态保持标签``,只需将切换的``组件放入状态保持标签中即可在切换时也不会清空数据。 ```html Title 我是父组件切换 我是子组件1 我是子组件2 ``` ## 八、异步组件 在实际开发中,一个网页由很多组件组成,若在网页启动时就一次性加载完,会导致网页需要渲染时间过长,效率低下,因此通过异步组件来实现按需加载。 Vue提供了一个工厂函数的方式来定义组件,这个工厂函数会异步解析组件定义,Vue只有在这个组件需要被渲染的时候才会触发该工厂函数,并且将结果缓存起来以便未来重新渲染。 #### 1、定义格式 ```js components : { asynccpt : Vue.defineAsyncComponent(() => { return new Promise((resolve,reject) => { // 处理的条件 // 符合条件 resolve({ template:'定义组件' }); // 不符合条件 reject({ template:'定义组件' }); }); }), } ``` #### 2、例子 ```html Title 我是父组件 我是子组件1 我是子组件2,3秒后才会显示我 Loading... ## 一、Vue方法、计算属性及监听器 #### 1、计算属性 计算属性是`Vue.createApp()`参数对象中的一个属性`computed`,其传入的是一个方法数组,计算属性中的方法都可以直接像`data()`属性中一样直接使用。 ##### 反序输出字符串 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> {{msg}}<br> {{reversedMsg}} </div> <script> Vue.createApp({ data() { return { msg:'hello world!' } }, computed:{ reversedMsg(){ // 根据''分割 将数组倒序 将数组拼接称String return this.msg.split('').reverse().join(''); } } }).mount('#app'); </script> </body> </html> ``` **注意**:计算属性具有自动缓存的特点,当`msg`并未发生变化时,`reversedMsg()`并不会再次被执行,而是直接调用缓存里的结果返回,所以计算属性可以提高运行效率。 ##### 计算商品总价 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> 商品名称:小米手机<br> 数量:<button @click="sub">-</button>{{num}}<button @click="add">+</button><br> 总价:{{total}} </div> <script> Vue.createApp({ data() { return { num: 0 } }, methods: { add() { this.num++; }, sub() { this.num--; } }, computed: { total() { return this.num * 100; } } }).mount('#app'); </script> </body> </html> ``` ##### 数据过滤处理 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> {{moneyInfo}} </div> <script> Vue.createApp({ data() { return { money: 1000 } }, methods: {}, computed:{ moneyInfo(){ return this.money + '$'; } } }).mount('#app'); </script> </body> </html> ``` #### 2、方法 方法是`Vue.createApp()`参数对象中的一个属性`methods`,其传入的是一个方法数组,可以将方法绑定到事件中,若要像计算属性一样调用,需要加`()`。 ##### 方法声明与调用 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> {{money}} {{moneyInfo}} {{moneyInfo2()}} </div> <script> Vue.createApp({ data() { return { money: 1000 } }, methods: { moneyInfo2() { return this.money + '$'; } }, computed: { moneyInfo() { return this.money + '$'; } } }).mount('#app'); </script> </body> </html> ``` ##### 方法与计算属性的区别 | **对比项** | **computed** | **methods** | | -------------- | ---------------------------------------------------------- | ---------------------------------------------------- | | **定义方式** | 在`computed`对象中定义计算属性 | 在`methods` 对象中定义方法 | | **缓存机制** | **有缓存**:依赖数据未变化时直接返回缓存结果,避免重复计算 | **无缓存**:每次调用都会重新执行函数 | | **调用方式** | 在模板中作为**属性**使用(无需括号) | 在模板中作为**方法**调用(需加括号) | | **响应式依赖** | **自动追踪依赖**:仅在其依赖的响应式数据变化时重新计算 | 不自动追踪依赖,每次调用都执行完整逻辑 | | **适用场景** | 适用于**派生数据**(如过滤、计算属性) | 适用于**事件处理**或需要主动触发的逻辑 | | **同步/异步** | 必须是**同步操作**,不支持异步逻辑 | 可包含**异步操作**(如 `await`) | | **返回值** | 必须返回一个值(用于模板渲染或逻辑依赖) | 可以无返回值(如事件处理函数) | | **性能优化** | 高频使用时因缓存机制更高效 | 无缓存,频繁调用可能影响性能(需配合事件节流等优化) | #### 3、监听器 监听器(`watch`) 的主要作用是观察和响应Vue实例中数据的变化,当特定的数据发生变化时执行异步操作或复杂的逻辑处理。 监听器的声明要在`watch`属性中以函数数组的方式声明,其参数有两个返回值,一个为改变后的新值,一个为改变前的旧值。 ##### 基本数据类型监听 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> 商品名称:小米手机<br> 数量: <button @click="sub">-</button> {{num}} <button @click="add">+</button> <br> 总价:{{total}} </div> <script> Vue.createApp({ data() { return { num: 0 } }, methods: { add() { this.num++; }, sub() { this.num--; } }, computed: { total() { return this.num * 100; } }, watch: { num(newValue, oldValue) { this.num = newValue < 0 ? oldValue : newValue; } } }).mount('#app'); </script> </body> </html> ``` **注意**:监听器声明时的函数名必须与所监听的变量名一致。 ##### 引用数据类型深度监听 **模板** ```js watch: { varName: { handler() { // 处理代码 }, deep: true } } ``` **示例** ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> 商品名称:{{goods.name}}<br> 数量: <button @click="sub">-</button> {{goods.num}} <button @click="add">+</button> <br> 总价:{{total}} </div> <script> Vue.createApp({ data() { return { goods: { name: '小米手机', num: 0 } } }, methods: { add() { this.goods.num++; }, sub() { this.goods.num--; } }, computed: { total() { return this.goods.num * 100; } }, watch: { goods: { handler(newValue, oldValue) { this.goods.num = newValue.num < 0 ? oldValue.num : newValue.num; }, deep: true } } }).mount('#app'); </script> </body> </html> ``` **注意**:此处虽然实现了深度监听,但无法获取前后变化的值,因为对象为引用数据类型,前后地址并无变化,所以取出的属性值都为一致的。 ##### 引用数据类型限制属性改变 核心思想:在计算属性中使用内置`JSON`工具将对象转换(`.stringify`)成Json格式的文本,并且监听该计算属性(文本)改变,并在监听器里将Json格式的文本转化(`.parse`)为对象,通过新旧两个对象的属性值来判断值变化是否合法。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> 商品名称:{{goods.name}}<br> 数量: <button @click="sub">-</button> {{goods.num}} <button @click="add">+</button> <br> 总价:{{total}} </div> <script> Vue.createApp({ data() { return { goods: { name: '小米手机', num: 0 } } }, methods: { add() { this.goods.num++; }, sub() { this.goods.num--; } }, computed: { total() { return this.goods.num * 100; }, goodsStr() { return JSON.stringify(this.goods); } }, watch: { goodsStr(newValue, oldValue) { const n = JSON.parse(newValue); const o = JSON.parse(oldValue); return this.goods.num = n.num < 0 ? o.num : n.num; } } }).mount('#app'); </script> </body> </html> ``` ## 二、表单数据绑定 #### 1、注册表单示例 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> <h3>注册</h3> <div> <label>用户名: <input type="text" v-model="user.userName"> </label><br> <label>密码: <input type="password" v-model="user.password"> </label><br> <label>再次输入密码: <input type="password" v-model="user.beginPass"> </label><br> <label>性别: <input type="radio" v-model="user.sex" value="0">男 <input type="radio" v-model="user.sex" value="1">女 </label><br> 爱好: <label> <input type="checkbox" v-model="user.like" value="read">读书 </label> <label> <input type="checkbox" v-model="user.like" value="pe">体育 </label> <label> <input type="checkbox" v-model="user.like" value="trip">旅游 </label> <br> <label>国籍: <select v-model="user.nationality"> <option value="0">中国</option> <option value="1">美国</option> <option value="2">俄罗斯</option> </select> </label><br> <label>个人介绍: <!-- 这里写textarea会使文章页面布局出错,若要复制使用请自行还原 --> <textare cols="30" rows="10" v-model="user.brief"></textare> </label><br> <button @click="reg">注册</button> </div> </div> <script> Vue.createApp({ data() { return { user: { userName: '', password: '', beginPass: '', sex: 0, like: [], nationality: 0, brief: '' } } }, methods: { reg(){ console.log(this.user); } } }).mount('#app'); </script> </body> </html> ``` **注意**:在表单中复选框、单选框以及下拉列表必须给`value`属性一个值,要不然会出现错误,且复选框不能在同一个`<lable>`容器下包裹。 #### 2、`v-model`修饰符 ##### `v-model.lazy` 只有在input输入框发生一个blur时才会触发,也就是延迟同步到失去焦点时。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> <label>数值:<span v-text="num"></span><br> <input v-model="num"> </label><br> <label>延迟加载数值:<span v-text="num"></span><br> <input v-model.lazy="num"> </label> </div> <script> Vue.createApp({ data() { return { num: 10 } }, methods: {} }).mount('#app'); </script> </body> </html> ``` ##### `v-model.trim` 将用户输入内容的前后空格去掉。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> <label><pre>文本头:<span v-text="msg"></span>:文本尾<br></pre> 去空格输入:<input v-model.lazy.trim="msg" type="text"><br> 不去空格输入:<input v-model.lazy="msg" type="text"> </label> </div> <script> Vue.createApp({ data() { return { msg: '' } }, methods: {} }).mount('#app'); </script> </body> </html> ``` **注意**:`<pre>`标签可以将使多个连续的空格不折叠。 ##### `v-model.number` 将用户输入的字符串自动转换成数值。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./js/vue.global.js"></script> </head> <body> <div id="app"> <label>数值:<span v-text="num+1"></span><br> 普通输入框:<input type="text" v-model="num"><br> 自动转换数字:<input type="text" v-model.number="num"> </label> </div> <script> Vue.createApp({ data() { return { num: '' } }, methods: {} }).mount('#app'); </script> </body> </html> ``` ## 三、Vue组件 #### 1、什么是Vue组件 组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成一个层层嵌套的树状结构:  Vue组件大致可以分为三种:根组件、全局组件、局部组件。 一个Vue组件通常包含三个部分: - 模板(Template): 定义组件的HTML结构和布局。 - 逻辑(Script): 定义组件的数据、方法和生命周期。 - 样式(Style): 定义组件的CSS样式(可选,且可以设置作用域)。 #### 2、根组件 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> </div> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {} }); // 获取根组件 const vm = app.mount('#app'); </script> </body> </html> ``` **注意**:当组件挂载到`Vue实例`上,那么他就是一个全局组件,当组件挂载到`根组件`上,那么他就是一个局部组件 #### 3、全局组件 全局组件可以应用在任何Vue实例(根组件)上,如下Vue实例挂载在`app`与`app2`任意一个上都起作用,全局组件一旦定义了即使不使用也会消耗系统资源。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <h3>app</h3> <my-component></my-component> <my-component></my-component> <my-component></my-component> </div> <div id="app2"> <h3>app2</h3> <my-component></my-component> <my-component></my-component> <my-component></my-component> </div> <script> // 创建Vue实例 let app = Vue.createApp({ data() { return {} }, methods: {} }); // 注册全局组件 app.component('my-component',{ template:'<p>我是全局组件</p>' }); // 获取根组件 let vm = app.mount('#app'); // 当组件挂载到Vue实例上,那么他就是一个全局组件 // 当组件挂载到根组件上,那么他就是一个局部组件 </script> </body> </html> ``` **注意**:组件标签命名必须全是`小写`,单词需用`-`分割,否则不识别。 #### 4、组件中的内容 组件可以实现Vue实例中所有的操作,例如数据绑定、计算属性、方法、监听器等。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <my-component></my-component> <my-component></my-component> </div> <script> // 创建Vue实例 let app = Vue.createApp({ data() { return {} }, methods: {} }); // 注册全局组件 // 注意:组件标签命名必须全是小写,单词需用-分割,否则不识别。 app.component('my-component', { template: ` <div> <p>我是全局组件,数值:<span v-text="num"></span></p> <button @click="change(1)">加</button> <button @click="change(-1)">减</button> </div>`, data() { return { num: 10 } }, methods: { change(value) { this.num += value; } } }); // 获取根组件 let vm = app.mount('#app'); // 当组件挂载到Vue实例上,那么他就是一个全局组件 // 当组件挂载到根组件上,那么他就是一个局部组件 </script> </body> </html> ``` #### 5、局部组件 与全局组件不同的是,跟组件只能应用在声明的根组件中,而且局部组件只有在使用时才会消耗系统资源。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <script> // 创建Vue实例 const vm = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, // 局部组件 components: { mycomponent: { template: ` <p>我是局部组件,内容:<span v-text="txt"></span></p> <input type="text" v-model="txt"> `, data() { return { txt: 'test' }; } }, } }).mount('#app'); </script> </body> </html> ``` **注意**:当前定义局部组件方式要求组件名称不能出现符号以及大写字母。 #### 6、组件模板标签 组件具有复用性,局部组件与局部组件之间属性作用域是独立的。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="mytemplate"> <p>我是局部组件,内容:<span v-text="txt"></span></p> <input type="text" v-model="txt"> <button @click="changeText">改变文本</button> </template> <script> // 创建Vue实例 const vm = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { mycomponent: { template: '#mytemplate', data() { return { txt: 'test' }; }, methods: { changeText() { this.txt = 'test2'; } } } } }).mount('#app'); </script> </body> </html> ``` ## 四、父子组件 父子组件简单来说就是组件之间的嵌套,组件内有组件。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <p>我是父组件</p> <mycomponent2></mycomponent2> </template> <template id="t2"> <p>我是子组件</p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { mycomponent: { template: '#t1', components: { mycomponent2: { template: '#t2' } } } } }).mount('#app'); </script> </body> </html> ``` ## 五、组件之间的通讯 #### 1、子组件获取父组件数据(单向) 组件与组件之间的作用域是孤立的,默认情况下父子组件的数据是不能共享的,若需要共享需要使用数据传递属性`props`。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <p>我是父组件,文本:<span v-text="txt"></span></p> <label>输入文本: <input type="text" v-model="txt"> </label> <mycomponent2 :msg="txt"></mycomponent2> </template> <template id="t2"> <p>我是子组件,文本:<span v-text="msg"></span></p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { mycomponent: { template: '#t1', data() { return { txt: 'test' }; }, components: { mycomponent2: { template: '#t2', props: [ 'msg' ] } } } } }).mount('#app'); </script> </body> </html> ``` **注意**:首先要在父组件`<template>`调用子组件时给子组件标签添加一个自定义属性,并且让Vue绑定他,在子组件定义属性时添加`props`属性,属性值为一个字符串数组,里面字符串需要填写子组件上的自定义属性名。 #### 2、子组件传值校验 使用`prop`对传值校验时,需要将`prop`值声明为对象,属性名为传入的标签属性名,使用其参数`type`来校验数据类型 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <p>我是父组件,文本:<span v-text="txt"></span>,数字是:<span v-text="num"></span></p> <label>输入文本: <input type="text" v-model="txt"> </label> <label>输入数字: <input type="text" v-model.number="num"> </label> <mycomponent2 :msg="txt" :number="num"></mycomponent2> </template> <template id="t2"> <p>我是子组件,文本+1后是:<span v-text="msg+1"></span>,数字+1后是:<span v-text="number+1"></span></p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { mycomponent: { template: '#t1', data() { return { txt: '2', num: 2 }; }, methods: {}, components: { mycomponent2: { template: '#t2', props: { msg: { type: String, required: true }, number: { type: Number, required: true } } } } } } }).mount('#app'); </script> </body> </html> ``` **注意**:若对引用组件时传的自定义标签属性是否存在进行校验,则将该属性在`prop`中添加`required:true`,这样当传入属性时没有该属性时,会抛出缺失警告。 #### 3、单向数据流 父组件向子组件传递数据是单向的,也就是说:子组件可以接受并使用父组件传递过来的数据,单子组件不能修改此数据。 这样做是为了防止子组件意外的改变了父组件的状态,从而导致父组件的行为异常。 ##### 子组件修改值测试 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <p>我是父组件,文本:<span v-text="txt"></span>,数字是:<span v-text="num"></span></p> <mycomponent2 :msg="txt" :number="num"></mycomponent2> </template> <template id="t2"> <p>我是子组件,文本+1后是:<span v-text="msg+1"></span>,数字+1后是:<span v-text="number+1"></span></p> <label>输入文本: <input type="text" v-model="msg"> </label> <label>输入数字: <input type="text" v-model.number="number"> </label> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { mycomponent: { template: '#t1', data() { return { txt: '2', num: 2 }; }, methods: {}, components: { mycomponent2: { template: '#t2', props: { msg: { type: String, required: true }, number: { type: Number, required: true } } } } } } }).mount('#app'); </script> </body> </html> ``` 会出现如下图警告  #### 4、父组件获取子组件数据(单向) 父组件要获取到子组件的数据需要在引用子组件时绑定自定义方法,例如`@childmsg=""`,并在父组件`methods`中写一个处理子组件方法返回值的方法,例如`getMsg(value)`,然后让自定义方法去调用这个处理返回值的方法,例如`@childmsg="getMsg"`,之后在子组件中用`emits`属性去声明自定义方法,例如`emits:['childmsg']`,并在子组件中`methods`编写一个调用`$emit()`方法的方法,例如`updateTxt`用来调用父组件传来的自定义方法,在`$emit()`中第一个参数为自定义方法名,之后的参数为父组件处理数据方法的参数值。 ##### 基础写法 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <p>我是父组件,文本:<span v-text="txt"></span></p> <mycomponent2 :msg="txt" @childmsg="getMsg"></mycomponent2> </template> <template id="t2"> <p>我是子组件,文本:<span v-text="msg"></span></p> <label>输入文本: <input type="text" v-model="msg_"> </label> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { mycomponent: { template: '#t1', data() { return { txt: 'hello world' }; }, methods: { getMsg(value) { this.txt = value; } }, components: { mycomponent2: { template: '#t2', data() { return { msg_: this.msg, } }, props: { msg: { type: String, required: true } }, emits:['childmsg'], watch: { msg_(){ this.updateTxt(); } }, methods: { updateTxt() { this.$emit('childmsg',this.msg_); } } } } } } }).mount('#app'); </script> </body> </html> ``` **注意**:若不用`emits`属性声明父组件在子组件引用时的自定义方法,那么将会抛出警告:`[Vue warn]: Extraneous non-emits event listeners (childmsg) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option. ` 若是`<input>`表单组件,可以简化`<input type="text" :value="msg" @input="$emit('childmsg', $event.target.value)">` ##### 进阶写法 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue组件多参数传递</title> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; background-color: #f5f7fa; color: #333; line-height: 1.6; } .container { background: white; border-radius: 10px; padding: 25px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); margin-bottom: 20px; } .parent-component { border: 2px solid #3498db; padding: 20px; margin-bottom: 15px; border-radius: 8px; background-color: #e8f4fc; } .child-component { border: 2px solid #2ecc71; padding: 20px; border-radius: 8px; background-color: #eafaf1; margin-top: 15px; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 10px; } h2 { color: #3498db; margin-top: 0; } h3 { color: #2ecc71; margin-top: 0; } .explanation { background-color: #f9f9f9; padding: 15px; border-left: 4px solid #3498db; margin: 20px 0; border-radius: 4px; } code { background-color: #f1f1f1; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; color: #c7254e; } input, select { padding: 8px; border: 1px solid #ddd; border-radius: 4px; width: 200px; margin: 5px 0; } .value-display { font-weight: bold; color: #e74c3c; background-color: #f9f9f9; padding: 3px 8px; border-radius: 4px; display: inline-block; margin: 0 5px; } .data-item { margin: 10px 0; padding: 10px; background-color: #f8f9fa; border-radius: 5px; } .controls { display: flex; flex-wrap: wrap; gap: 15px; margin-top: 15px; } .control-group { flex: 1; min-width: 200px; } button { background-color: #3498db; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; margin: 5px; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } .method-buttons { display: flex; gap: 10px; margin-top: 10px; } .data-summary { background-color: #fff3cd; padding: 10px; border-radius: 5px; margin-top: 15px; border-left: 4px solid #ffc107; } </style> </head> <body> <h1>Vue组件多参数传递示例</h1> <div class="explanation"> <p>这个示例展示了如何将父组件的多个数据(txt和num)传递给子组件。我们使用<code>props</code>将父组件中的数据传递给子组件。</p> <p>在子组件中,我们通过<code>props</code>接收数据,并使用自定义事件将更改传递回父组件。</p> </div> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <div class="parent-component"> <h2>父组件 (mycomponent)</h2> <div class="data-item"> <p>文本数据: <span class="value-display" v-text="txt"></span></p> <p>数值数据: <span class="value-display" v-text="num"></span></p> </div> <div class="data-summary"> <p>数据摘要: 文本是"<span class="value-display" v-text="txt"></span>",数值是<span class="value-display" v-text="num"></span></p> </div> <div class="controls"> <div class="control-group"> <label>更新文本: <input type="text" v-model="txt"> </label> </div> <div class="control-group"> <label>更新数值: <input type="number" v-model.number="num"> </label> </div> <div class="control-group"> <div class="method-buttons"> <button @click="incrementNum">数值+1</button> <button @click="decrementNum">数值-1</button> <button @click="resetData">重置数据</button> </div> </div> </div> <mycomponent2 :parent-txt="txt" :parent-num="num" @update:txt="txt = $event" @update:num="num = $event"> </mycomponent2> </div> </template> <template id="t2"> <div class="child-component"> <h3>子组件 (mycomponent2)</h3> <div class="data-item"> <p>从父组件接收的文本: <span class="value-display" v-text="parentTxt"></span></p> <p>从父组件接收的数值: <span class="value-display" v-text="parentNum"></span></p> </div> <div class="controls"> <div class="control-group"> <label>更新文本: <input type="text" :value="parentTxt" @input="$emit('update:txt', $event.target.value)"> </label> </div> <div class="control-group"> <label>更新数值: <input type="number" :value="parentNum" @input="$emit('update:num', parseInt($event.target.value) || 0)"> </label> </div> <div class="control-group"> <label>操作数值: <select @change="handleNumChange($event)"> <option value="">选择操作</option> <option value="double">加倍</option> <option value="half">减半</option> <option value="square">平方</option> </select> </label> </div> </div> </div> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, components: { mycomponent: { template: '#t1', data() { return { txt: 'Hello Vue!', num: 42 }; }, methods: { incrementNum() { this.num++; }, decrementNum() { this.num--; }, resetData() { this.txt = 'Hello Vue!'; this.num = 42; } }, components: { mycomponent2: { template: '#t2', props: { parentTxt: String, parentNum: Number }, emits: ['update:txt', 'update:num'], methods: { handleNumChange(event) { const value = event.target.value; let newNum = this.parentNum; switch(value) { case 'double': newNum = this.parentNum * 2; break; case 'half': newNum = this.parentNum / 2; break; case 'square': newNum = this.parentNum * this.parentNum; break; } this.$emit('update:num', newNum); event.target.value = ''; // 重置选择 } } } } } } }).mount('#app'); </script> </body> </html> ``` #### 5、多级组件通讯 ##### 基础写法 父组件 -> 子组件 ->孙子组件 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <p>我是父组件,文本:<span v-text="txt"></span></p> <label>输入文本: <input type="text" v-model="txt"> </label> <mycomponent2 :msg="txt"></mycomponent2> </template> <template id="t2"> <p>我是子组件,文本是:<span v-text="msg"></span></p> <mycomponent3 :msg="msg"></mycomponent3> </template> <template id="t3"> <p>我是孙子组件,文本是:<span v-text="msg"></span></p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ components: { mycomponent: { template: '#t1', data() { return { txt: '2' }; }, components: { mycomponent2: { template: '#t2', props: ['msg'], components: { mycomponent3: { template: '#t3', props: ['msg'] } } }, } } } }).mount('#app'); </script> </body> </html> ``` ##### 进阶写法 使用Vue提供的`provide/inject`来实现,`provide`需要传入一个函数对象,所以直接用`provide(){}`来声明,返回值为需要向后代组件提供的属性对象,`Vue.computed(() => this.txt)`定义了一个匿名的计算属性并且赋给了`msg`属性,以实现当父组件`txt`的值改变时,也会改变`msg`的值。 后代组件通过属性`inject`来接收并声明父组件传递过来的属性。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <p>我是父组件,文本:<span v-text="txt"></span></p> <label>输入文本: <input type="text" v-model="txt"> </label> <mycomponent2></mycomponent2> </template> <template id="t2"> <p>我是子组件</p> <mycomponent3></mycomponent3> </template> <template id="t3"> <p>我是孙子组件,文本是:<span v-text="msg"></span></p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ components: { mycomponent: { template: '#t1', data() { return { txt: '2' }; }, provide() { return { msg: Vue.computed(() => this.txt), }; }, components: { mycomponent2: { template: '#t2', components: { mycomponent3: { template: '#t3', inject: ['msg'] } } }, } } } }).mount('#app'); </script> </body> </html> ``` ## 六、slot插槽 Vue官方实现了一套内容分发的API,将slot元素作为承载分发内容的出口。 #### 1、用法 引用子标签时在子标签内部写入HTML代码,在子标签的`<template>`中将`<slot>`标签添加到需要插入的位置上。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <p>我是父组件</p> <mycomponent2><p>插槽的内容</p></mycomponent2> </template> <template id="t2"> <p>我是<slot></slot>子组件</p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { mycomponent: { template: '#t1', components: { mycomponent2: { template: '#t2', } } } } }).mount('#app'); </script> </body> </html> ``` #### 2、插槽的作用域 父级模板中的所有内容都是在父级作用域中编译,子级模板里的所有内容都是在子作用域中编译。 所以在父组件给出插槽中的数据时就已经在父级作用域中编译完成了,子组件获取到的只是编译的结果,例如传入一个`num`属性,在父级作用域内可以对其进行修改,子组件插槽内容也会进行改变,但子组件获取到的只是`num`的值,并不是该属性本身。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <p>我是父组件</p> <mycomponent2 @childadd="add"><p>插槽的内容{{num}}</p><button @click="add">父组件+1</button></mycomponent2> </template> <template id="t2"> <p>我是 <slot></slot> 子组件 <button @click="$emit('childadd')">子组件+1</button> </p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { mycomponent: { template: '#t1', data() { return { num: 100 }; }, methods: { add() { this.num++; } }, components: { mycomponent2: { template: '#t2', emits:['childadd'] } } } } }).mount('#app'); </script> </body> </html> ``` **注意**:此处`子组件+1`修改父组件中的`num`通过传入自定义方法并且用`emits:[]`声明后使用`$emit()`调用达到的效果,因为在父组件中定义了`父组件+1`按钮,所以按钮可以直接调用父组件中的方法。 #### 3、插槽的默认值 在子组件`<slot>`标签内即可定义插槽的默认值,当父组件传入内容时会显示传入的内容,当父组件没有传入内容时将显示插槽的默认内容。 ```html <template id="t2"> <p>我是 <slot>[我是插槽的默认值]</slot> 子组件 <button @click="$emit('childadd')">子组件+1</button> </p> </template> ``` #### 4、具名插槽(多个插槽) 可以在父组件传入内容时使用`<template>`标签属性`v-slot:插槽名`来定义具名插槽,在子组件中只需使用`<slot name="插槽名">`即可调用相应的部分,当然每个部分插槽都可以有其默认值。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <mycomponent></mycomponent> </div> <template id="t1"> <p>我是父组件</p> <mycomponent2 @childadd="add"> <template v-slot:header> <div>头部内容</div> </template> <template v-slot:body> <p>插槽的内容{{num}}</p> <button @click="add">父组件+1</button> </template> <template v-slot:footer> <div>尾部内容</div> </template> </mycomponent2> </template> <template id="t2"> <p>我是子组件 <slot name="header">[我是头部插槽的默认值]</slot> <slot name="body">[我是插槽的默认值]</slot> <button @click="$emit('childadd')">子组件+1</button> <slot name="footer">[我是尾部插槽的默认值]</slot> </p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { mycomponent: { template: '#t1', data() { return { num: 100 }; }, methods: { add() { this.num++; } }, components: { mycomponent2: { template: '#t2', emits: ['childadd'] } } } } }).mount('#app'); </script> </body> </html> ``` ## 七、动态组件 #### 1、`v-if`实现 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <cpt_father></cpt_father> </div> <template id="t1"> <p>我是父组件</p> <button @click="switch_child">切换</button> <cpt_child1 v-if="!status1"></cpt_child1> <cpt_child2 v-if="status1"></cpt_child2> </template> <template id="t2"> <p>我是子组件1</p> </template> <template id="t3"> <p>我是子组件2</p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { cpt_father: { template: '#t1', data() { return { status1: false }; }, methods: { switch_child() { this.status1 = !this.status1; } }, components: { cpt_child1: { template: '#t2', }, cpt_child2: { template: '#t3', } } } } }).mount('#app'); </script> </body> </html> ``` #### 2、通用组件实现 Vue中有一个通用组件标签`<component>`,可以代表所有的组件,使用属性`is`来决定具体组件,所以可以通过变量来控制组件的名称以达到动态组件的效果。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <cpt_father></cpt_father> </div> <template id="t1"> <p>我是父组件</p> <button @click="switch_child">切换</button> <component :is="name"></component> </template> <template id="t2"> <p>我是子组件1</p> </template> <template id="t3"> <p>我是子组件2</p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { cpt_father: { template: '#t1', data() { return { name: 'cpt_child1' }; }, methods: { switch_child() { if (this.name === 'cpt_child1') this.name = 'cpt_child2'; else this.name = 'cpt_child1'; } }, components: { cpt_child1: { template: '#t2', }, cpt_child2: { template: '#t3', } } } } }).mount('#app'); </script> </body> </html> ``` #### 3、状态保持 当两个子组件中有表单等需要用户输入数据的组件时,直接切换会导致这些组件值重置,并不会保留用户之前输入的数据,Vue为解决这种现象提供了数据状态保持标签`<keep-alive>`,只需将切换的`<component>`组件放入状态保持标签中即可在切换时也不会清空数据。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <cpt_father></cpt_father> </div> <template id="t1"> <p>我是父组件<button @click="switch_child">切换</button></p> <keep-alive> <component :is="name"></component> </keep-alive> </template> <template id="t2"> <label>我是子组件1 <input type="text"> </label> </template> <template id="t3"> <label>我是子组件2 <!-- 这里写textarea会导致文章页面出现混乱,若需复制使用请自行补全 --> <textare></textare> </label> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { cpt_father: { template: '#t1', data() { return { name: 'cpt_child1' }; }, methods: { switch_child() { if (this.name === 'cpt_child1') this.name = 'cpt_child2'; else this.name = 'cpt_child1'; } }, components: { cpt_child1: { template: '#t2', }, cpt_child2: { template: '#t3', } } } } }).mount('#app'); </script> </body> </html> ``` ## 八、异步组件 在实际开发中,一个网页由很多组件组成,若在网页启动时就一次性加载完,会导致网页需要渲染时间过长,效率低下,因此通过异步组件来实现按需加载。 Vue提供了一个工厂函数的方式来定义组件,这个工厂函数会异步解析组件定义,Vue只有在这个组件需要被渲染的时候才会触发该工厂函数,并且将结果缓存起来以便未来重新渲染。 #### 1、定义格式 ```js components : { asynccpt : Vue.defineAsyncComponent(() => { return new Promise((resolve,reject) => { // 处理的条件 // 符合条件 resolve({ template:'定义组件' }); // 不符合条件 reject({ template:'定义组件' }); }); }), } ``` #### 2、例子 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="assets/js/vue.global.js"></script> </head> <body> <div id="app"> <cpt_father></cpt_father> </div> <template id="t1"> <p>我是父组件</p> <cpt_child1></cpt_child1> <cpt_child2></cpt_child2> </template> <template id="t2"> <p>我是子组件1</p> </template> <template id="t3"> <p>我是子组件2,3秒后才会显示我</p> </template> <script> // 创建Vue实例 const app = Vue.createApp({ data() { return {} }, methods: {}, watch: {}, components: { cpt_father: { template: '#t1', data() { return {}; }, methods: {}, components: { cpt_child1: { template: '#t2', }, cpt_child2: Vue.defineAsyncComponent(() => { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ template: '#t3' }) }, 3000) }) }) } } } }).mount('#app'); </script> </body> </html ``` 最后修改:2025 年 12 月 19 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 👆请简单粗暴地爱我。