因为要做一个前后端分离的项目,所以需要学习Vue。基础部分主要按照官方文档的顺序来学习。

安装项目

npm的版本是8.19.2,使用vite安装vue项目的命令如下:

1
2
3
4
npm init vite@latest vue-base -- --template vue
cd vue-base
npm install
npm run dev

这样就在5173端口开启了一个开发服务器。
创建项目的目录如下:

创建Vue应用

先抛开使用vite构建的vue项目,第一种使用Vue的方式就是在单个html中引入Vue的js文件,然后在后面的<script>标签中使用vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- header中引入vue -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

<!-- 与vue关联的标签 -->
<p id="test">{{message}}</p>

<script>
Vue.createApp({
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#test')
</script>

script标签中的Vue变量来自于vue.global.js。

接下来看看之前由vite构建的版本。

根组件

每个Vue应用都是通过createApp函数创建一个新的应用实例。

  • index.html中,有一个idapp的结点。
  • main.jsapp.vue的内容(其中HTML的内容)挂载到index.html的这个div下。
  • app.vue中的内容是html、css和js的集合体,这就是一个组件。

传入createApp的对象实际上是一个组件,向这个函数传入的就是根组件。根组件还可以包含很多子组件,子组件也可以包含子组件——形成复杂的组件树。

挂载应用

使用createApp创建的app对象必须要使用mount挂载到相应的html标签后才能在其中使用vue定义的变量等。

1
2
<div id='app'></div>
app.mount('#app')

实际上,一个项目中的应用示例可以不止一个,可以调用多次createApp函数将组件挂载到不同的标签下:
1
2
app1.mount('#container-1');
app2.mount('#container-2');

在实际的项目中,我们应避免将一个单独的vue应用挂载到整个页面上,而是像第二种方法一样,创建多个小的应用实例,分别挂载到所需的页面元素上去

模板语法

在底层机制中,Vue会将模板编译成高度优化的 JavaScript代码;且响应式系统能只能地推导出需要重新渲染的组件的最少数量,从而减少不必要的DOM操作——Vue的编译时优化做得是很好的。

文本插值

文本插值是数据绑定最基本的形式,它使用Mustache语法在HTML中使用<script>传入的变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
export default {
data() {
return {
str: "Hello, World!",
}
},
};
</script>

<template>
<h1>{{str}}</h1>
</template>

<style></style>

使用原始HTML

如果传入的字符串是一个HTML标签的形式,如<h1>Hello, World!</h1>,那么直接使用Mustache语法后渲染的结果则是:

即Vue会将变量视为整体渲染出来。这往往与预期不符。如果要将传入的变量作为HTML元素进行渲染,则需要使用v-html指令:

1
<span v-html="str"></span>

指令中使用变量不需要添加双大括号。变量将会作为HTML标签放在使用v-html指令的标签下,所以应该使用没有语义的spandiv标签。

属性绑定

Mustache语法不能再HTML属性中使用。如果要在属性中使用变量,则应该使用v-bind指令进行属性的绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
export default {
data() {
return {
id1: 'id-1',
}
},
};
</script>

<template>
<h1 v-bind:id="id1">颜色</h1>
</template>

<style>
#id-1 {
color: red;
}
</style>


v-bind:id='id1'可以简写为: :id='id1'
也可以传入对象动态地绑定多个属性值(对象的格式为{属性1: 属性值1,...}):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<script>
export default {
data() {
return {
attrs: {
id: 'id-1',
class: 'class-1'
}
}
},
};
</script>

<template>
<h1 v-bind="attrs">颜色</h1>
</template>

<style>
#id-1 {
color: red;
}
.class-1 {
background-color: yellow;
}
</style>

需要注意这时候对象的键应该是属性名,不能乱取。

JS表达式

在双大括号和指令中都可以使用JS表达式,如要将str逆序展示在HTML中:

1
{{str.split('').reverse().join('')}}

但是此处的JS表达式是受限的,只能访问到一些全局对象。

事件监听

可以使用v-on指令来监听发生在该HTML元素上的事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
export default {
data() {
return {
flag: true,
}
},
};
</script>

<template>
<h1 :id="flag?'id-1':'id-2'">颜色</h1>
<button v-on:click="flag=!flag">改变颜色</button>
</template>

<style>
#id-1 {
color: red;
}
#id-2 {
color: green;
}
</style>

点击button即可在红色和绿色之间切换:

v-on:可以简写为@。此外,还可以像v-on传入函数。结果和上面一样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script>
export default {
data() {
return {
flag: true,
}
},
methods: {
changeColor: function() {
this.flag=!this.flag;
}
}
};
</script>

<template>
<h1 :id="flag?'id-1':'id-2'">颜色</h1>
<button v-on:click="changeColor">改变颜色</button>
</template>

<style>
#id-1 {
color: red;
}
#id-2 {
color: green;
}
</style>

函数需要放在method内,访问定义的变量需要使用this

还有更多的事件之后会专门讲到。

计算属性

我们可以在{{}}v-指令中使用JS表达式。但是当表达式比较复杂或需要反复使用时,最好使用计算属性。
计算属性放在computed中进行定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
export default {
data() {
return {
str: 'Hello, World!'
}
},
computed: {
reversedStr() {
console.log("调用计算属性");
return this.str.split('').reverse().join('');
}
}
};
</script>

<template>
<h1>{{reversedStr}}</h1>
<h1>{{reversedStr}}</h1>
</template>

和函数的区别

使用函数也可以达到同样的效果,二者的区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script>
export default {
data() {
return {
str: 'Hello, World!'
}
},
computed: {
reversedStr() {
console.log("调用计算属性");
return this.str.split('').reverse().join('');
}
},
methods: {
reverseStr() {
console.log("调用函数");
return this.str.split('').reverse().join('');
}
}
};
</script>

<template>
<p>使用计算属性</p>
<h1>{{reversedStr}}</h1>
<h1>{{reversedStr}}</h1>

<p>使用函数</p>
<h1>{{reverseStr()}}</h1>
<h1>{{reverseStr()}}</h1>
</template>

查看输出,调用计算属性打印一次,而调用函数打印两次:

这是因为第二次调用计算属性时会使用之前缓存的结果,能提高效率(Vue的优化)。

计算属性是对象

计算属性其实是一个对象,它包含了getset两个函数(set函数可选)。

1
2
3
4
5
6
7
8
9
10
11
12
computed: {
reversedStr: {
set(newValue) {
console.log(newValue);
// 所有值都会更新
this.str = newValue
},
get() {
return this.str.split('').reverse().join('')
}
}
},

当改变计算属性的值时,set函数带的第一个参数可以获取到这个新值。
获取到新值后,修改依赖的变量str的值:
1
2
3
4
5
<template>
<h1>{{str}}</h1><hr>
<h1>{{reversedStr}}</h1>
<button @click="reversedStr='change'">改变,就是好事</button>
</template>

点击button,二者都会变化:

Watch侦听器

基本使用

侦听器和计算属性一样,当依赖值发生变化时,就会调用这个函数。
常常用来执行异步操作或编写复杂逻辑的代码,这是计算属性做不到的(计算属性由副作用)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
export default {
data() {
return {
str: 'Hello, World!'
}
},
watch: {
str: function(newValue, oldValue) {
console.log(oldValue);
console.log(newValue);
if(newValue.length < 5) alert("不能小于5")
}
}
};
</script>

<template>
<h1>{{str}}</h1>
<button @click="str='你好'">改变str</button>
</template>


传入侦听器函数的第一个值是新值(和set一样),第二个参数是旧值。
因为能接收到新值,所以可以进行表单的输入限制,如对邮箱进行正则匹配,确保用户输入的邮箱是正确的格式。

深度监听

如果需要监听的是一个对象,当对象的属性值改变时,并不会执行监听器函数。如果希望此时调用监听器函数,则需要开启深度监听。

侦听器也是对象。

1
2
3
4
5
6
7
8
watch: {
std: {
deep: true, //使用这个选项
handler: function(newValue) {
console.log(newValue.name);
}
}
}

将deep属性设置为true

class与style绑定

数据绑定一个常用的场景是操作元素的CSS样式,这就涉及到class属性和style属性。之前那样使用v-bind可以做,但如果class和style多一点看起来就很复杂。Vue专门为class和style的v-bind提供了特殊的功能增强。

class绑定

绑定对象

绑定的对象的格式为{class名:boolean,...}。后面的布尔值用于确定是否启用这个class——这样可以实现类的开关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script>
export default {
data() {
return {
classObj: {
class1: true,
class2: false,
}
}
},
};
</script>

<template>
<h1 :class="classObj">颜色</h1>
<button @click="classObj.class1=!classObj.class1">改变颜色</button>
<button @click="classObj.class2=!classObj.class2">改变背景</button>
</template>

<style>
.class1 {
color: red;
}
.class2 {
background-color: yellow;
}
</style>

这种使用方法是比较常用的。

绑定数组

数组只有类名,只能控制数组中这些类加入或删除,用得比较少。

style绑定

常用的还是绑定对象,其中对象的格式为:{样式名: 样式值, ...}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
export default {
data() {
return {
styleObj: {
color: 'red',
backgroundColor: 'yellow',
}
}
},
};
</script>

<template>
<h1 :style="styleObj">颜色</h1>
</template>

需要注意的是如果样式名中间有-,那么应该使用驼峰命名法(变量不允许-);样式值应该为字符串

条件渲染

v-if

条件渲染使用v-if指令:如果v-if后面的值为true,则渲染该元素,否则不进行渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
export default {
data() {
return {
flag: true,
}
},
};
</script>

<template>
<h1 v-if="flag">颜色</h1>
<button @click="flag=!flag">Toggle</button>
</template>

按下button后,h1标签消失。

此外,还有v-else-ifv-else的指令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
export default {
data() {
return {
flag: 1
}
},
};
</script>

<template>
<h1 v-if="flag==1">颜色1</h1>
<h1 v-else-if="flag==2">颜色2</h1>
<h1 v-else>颜色3</h1>
<button @click="flag=(flag+1)%3">改变颜色</button>
</template>

点击改变颜色,即可在颜色1~颜色3之间切换。

template+v-if

如果想要同时控制一组元素的消隐,可以在外面套一个div,然后在这个div上使用v-if

1
2
3
4
5
<div v-if="flag">
<h1>测试</h1>
<p>文字测试</p>
</div>
<button @click="flag=!flag">Triggle</button>

但为了规范,最好使用template。因为template不会被渲染出来,意思是使用template不会添加额外的dom结点

v-show

v-show也可以控制元素的消隐,实现和v-if同样的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
export default {
data() {
return {
flag: true
}
},
};
</script>

<template>
<h1 v-show="flag">标题</h1>
<button @click="flag=!flag">Toggle</button>
</template>


点击button同样可以实现h1的消隐。但是使用v-showv-if的原理区别:

v-showfalse时,会给结点添加display: none;添的样式;而v-if是直接出现在dom中。
总结:

  1. 可以说v-if是真正意义上的条件渲染,v-show是伪条件渲染。
  2. 二者都有相应的使用场景:v-if具有更高的切换开销,v-show具有更高的初始渲染开销——如果需要频繁切换,则使用v-show;如果运行时很少改变,则v-if比较好。
    1. 如某些内容是分年龄展示的,则使用v-if。因为年龄只在初始时进行选择。
    2. v-show的例子暂时没想到。。。但应该还是用得比较多。

      列表渲染

      传入列表

      很常用的功能,如果想要一个个展示一个数组(列表)中的内容,则需要使用v-for指令:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      <script>
      export default {
      data() {
      return {
      nums: [11,45,14],
      }
      },
      };
      </script>

      <template>
      <ul>
      <li v-for="num in nums">{{num}}</li>
      </ul>
      </template>

      v-for也可以传入第二个参数来接收列表的下标:
      1
      2
      3
      4
      5
      <template>
      <ul>
      <li v-for="(num,index) in nums">{{num}}-->{{index}}</li>
      </ul>
      </template>

      in也可以换成of,二者在这里没有区别,但是后者更接近js的语法。因为js中使用in得到的是数组元素的下标。

传入对象

v-for也可以遍历对象的键值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
export default {
data() {
return {
person: {
name: 'sato',
age: 20,
sex: 'male',
}
}
},
};
</script>

<template>
<ul>
<li v-for="(value,key) in person">{{key}}:{{value}}</li>
</ul>
</template>

这里也是可以接收两个参数,分别为对象每一项的值和键(也可以缺省第二个参数):

使用key

如果不使用key,那么Vue只会就地更新每个元素,确保它们在原本指定的索引的位置渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
export default {
data() {
return {
nums: [1, 2, 3],
}
},
};
</script>

<template>
<template v-for="num in nums">
<input type="checkbox">{{num}}<br>
</template>
<button @click="nums.unshift(nums.length+1)">向首部插入元素</button>
</template>


点击button后,checkbox与后面元素的对应顺序就发生了改变。有时候这不是我们所期望的,这时候就需要使用key

1
2
3
<template v-for="(num,index) in nums" :key="num">
<input type="checkbox">{{num}}-->{{index}}<br>
</template>


key用于唯一地标识一个元素,使用key可以跟踪每个结点的标识,从而重用和重新排序现有元素
特别地,如果将key指定为index,则和不使用key没有区别。

不使用key(默认模式)是高效的,但是只适用于列表渲染出的结果不依赖子组件状态或临时DOM状态的情况。我们得按需选择。

事件处理

之前用了很多@click这样的点击事件,这里详细学习Vue的事件处理。

内联事件和方法事件

内联事件就是直接在事件后面使用js表达式:

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

除了在button上定义事件,事件也可以用于其他标签上:
1
<h1 @click="count++">{{count}}</h1>

现在点击h1标签就会使count加1。
如果要执行的操作比较复杂,那么使用方法事件比较好:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
export default {
data() {
return {
str: 'Hello, World!'
}
},
methods: {
reverseStr() {
this.str=this.str.split('').reverse().join('');
}
}
};
</script>

<template>
<h1>{{str}}</h1>
<button @click="reverseStr">Toggle</button>
</template>

传入参数

使用内联处理器调用函数可以像其中传入参数,这一点比方法事件更灵活:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
export default {
data() {
return {
str: 'Hello'
}
},
methods: {
changeTo(msg) {
this.str = msg;
}
}
};
</script>

<template>
<h1>{{str}}</h1>
<button @click="changeTo('Hello')">hello</button>
<button @click="changeTo('Bye')">bye</button>
</template>

Vue会检查v-on的值是否是合法的js标识符或属性访问路径来区别是内联事件处理器还是方法处理器。

接收事件参数

使用内联事件处理器调用函数时可以在实参中使用$event来向函数中传递事件参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
export default {
data() {
return {
str: 'Hello'
}
},
methods: {
changeTo(e, msg) {
console.log(e);
this.str = msg;
}
}
};
</script>

<template>
<h1>{{str}}</h1>
<button @click="changeTo($event,'Hello')">hello</button>
<button @click="changeTo($event,'Bye')">bye</button>
</template>

打印事件,会发现这是一个一个PointerEvent

这一点目前应该会用得比较少,因为它能做的主要事情已经有专门的事件修饰符去做了。

事件修饰符

Vue为v-on提供了事件修饰符,包括:

  • .stop:阻止事件冒泡,即点击内层标签的同时也会触发外层标签的事件。如果只需要触发内层的事件,则在内层这里的事件添加上.stop

    1
    2
    3
    4
    5
    <div @click="clickDiv">
    <!-- 对比 -->
    <button @click.stop="changeTo($event,'Hello')">hello</button>
    <button @click="changeTo($event,'Bye')">bye</button>
    </div>
  • .prevent:阻止默认的操作,如点击HTML表单的提交按钮后会默认发送get请求。但如果不希望使用这种默认的方式,则需要给事件加上.prevent

    1
    2
    3
    <form method="get">
    <input type="submit" value="提交" @click.prevent="submitForm">
    </form>
  • .self

  • .capture
  • .once:事件只会触发一次回调,可以对某些操作进行限制。
  • .passive

按键修饰符

键盘按键

在监听事件时,还有一种常用的事件就是键盘按键。比如常用的按下enter键提交表单:

1
2
3
4
5
submit() {
console.log("提交成功");
}

<input type="text" @keyup.enter="submit">

Vue给常用的按键起了别名,如enter、tab、space、up、down、ctrl、shift等。字母按键需要去查表。

鼠标按键

Vue也可以监听鼠标事件,包括left、right、middle。

1
<input type="text" @mouseup.middle="submit">

有这么多可以监听的事件甚至都可以做简单点的游戏了。

表单输入绑定

表单输入绑定即将表单的输出值和一个变量绑定,可以使用之前的绑属性定和事件监听来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
export default {
data() {
return {
str:""
}
},
methods: {
model(e) {
this.str = e.target.value;
console.log(this.str);
}
}
};
</script>

<template>
<input :value="str" @input="model($event)">
</template>

:value实现变量到输入框内容的同步,@input实现输入框内容到变量的同步——这样就实现了数据的双向绑定。

使用e.target.value可以访问到输入框的值。

Vue为这样的双向绑定提供了简便的操作:使用v-model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
export default {
data() {
return {
str:""
}
},
};
</script>

<template>
<h1>{{str}}</h1>
<input v-model="str">
</template>

除了最基本的inputv-model还支持绑定其他输入框。

多行文本

绑定<textarea>标签中的多行文本。

复选框

  1. 一个变量:绑定<input type="checkbox">的布尔值。
  2. 一个数组:绑定<input type="checkbox">value值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <script>
    export default {
    data() {
    return {
    fruits: []
    }
    },
    };
    </script>

    <template>
    <p>{{fruits}}</p>
    <input type="checkbox" value="banana" v-model="fruits">banana<br>
    <input type="checkbox" value="apple" v-model="fruits">apple<br>
    <input type="checkbox" value="orange" v-model="fruits">orange<br>
    </template>

    单选按钮

    绑定<input type="radio">value值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <script>
    export default {
    data() {
    return {
    fruit: ""
    }
    },
    };
    </script>

    <template>
    <p>{{fruit}}</p>
    <input type="radio" value="banana" v-model="fruit">banana<br>
    <input type="radio" value="apple" v-model="fruit">apple<br>
    <input type="radio" value="orange" v-model="fruit">orange<br>
    </template>

    单选框和复选框的区别就是是否可以选多个元素。

选择器

绑定<select>下拉菜单的值(<option>里面的值)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
export default {
data() {
return {
fruit: "orange"
}
},
};
</script>

<template>
<p>{{fruit}}</p>
<select v-model="fruit">
<option>apple</option>
<option>orange</option>
<option>banana</option>
</select>
</template>

绑定变量的初始值如果和某个<option>的值一样,那么这个option就是默认选项。

修饰符

lazy

如果不使用.lazy修饰符,那么每次输入都会更新值:

number

将输入的类型自动转换为number,如年龄等。用于下拉菜单比较好。

trim

自动去除输入内容两端的空格再赋值给变量。

组件基础

组件允许我们将UI划分为独立的、可重用的部分,让我们可以对每个部分进行单独的思考。
在实际的引用中,组件之间的关系体现为层层嵌套的树状结构:

定义组件

一个.vue文件就是一个组件,组件内包含了HTML、CSS和JS代码,称为单文件组件。
我们创建的组件应该放在/src/components目录下。比如定义一个简单的组件Counter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<h1>Count: {{count}}</h1>
<button @click="count++">+1</button>
</template>

<script>
export default {
data() {
return {
count: 0,
}
}
}
</script>

使用组件

在js代码中使用import xxx from 'xxx.vue'的方式导入组件,然后使用components声明导入的组件,最后就可以在HTML中以标签的形式使用组件了:<xxx></xxx>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
// 第一步:导入组件
import Counter from './components/Counter.vue'

export default {
// 第二步:注册组件
components: {
Counter
}
};
</script>

<template>
// 第三步:使用组件
<Counter></Counter>
<Counter></Counter>
</template>

可以发现,多次调用的组件之间的数据是独立的:

父组件➡子组件

组件是可复用的,为了提高组件的复用性,应该把组件中的不同文本等内容和组件分离,父组件使用子组件时将这些内容以参数的形式传入。
如现在想要展示博客文章的列表(标题+预览),使用父组件向子组件传值就是比较好的。

  1. 在组件的props列表中定义需要父组件传入的参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <template>
    <h3>{{title}}</h3>
    <p>{{preview}}</p>
    </template>

    <script>
    export default {
    props: ['title', 'preview']
    }
    </script>
  2. 父组件以关键字参数的形式向子组件传参(同时使用列表渲染):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <script>
    import PageList from './components/PageList.vue'

    export default {
    data() {
    return{
    pages: [
    {
    id: 1,
    title: "Vue基础",
    preview: "主要学习Vue的基础语法"
    },
    {
    id: 2,
    title: "计算机网络编程",
    preview: "本篇文章先复习计算机网络的基础知识"
    },
    ]
    }
    },
    components: {
    PageList
    }
    };
    </script>

    <template>
    <PageList v-for="page in pages" :key="page.id" v-bind="page"/>
    </template>

监听事件

主要的作用是子组件向父组件传值。感觉用得比较少,先跳过吧。

插槽

使用props可以实现父组件向子组件传递数据,而使用插槽可以实现父组件向子组件传递内容(比如HTML片段),进一步提高了组件的可复用性。
举个例子,hexo博客侧边栏的公告,我们可以在其中使用HTML语法自定义自己想要写入的内容。接下来研究如何使用插槽来实现这功能。

  1. 子组件使用<slot></slot>定义插槽:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <div class="box">
    <h3>公告</h3>
    <slot></slot>
    </div>
    </template>

    <style>
    .box {
    border: 5px solid pink;
    width: 300px;
    }
    </style>
  2. 父组件导入子组件并在子组件的双标签内部定义用于替换slot的模板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <script>
    import Announcement from './components/Announcement.vue'

    export default {
    data() {
    return{

    }
    },
    components: {
    Announcement
    }
    };
    </script>

    <template>
    <Announcement>
    <h3>
    <u style="border-color:#bcbcbc;color:#bcbcbc;border-width:3px;">莱莎的炼金工房</u>
    </h3>
    <p class="sideb">十年炼金无人问,一朝肉腿天下知。<br><br>住在村裡的萊莎有如鄰家女孩,是一位“再普通不過”的少女。<br>某日,萊莎一行下定決心,前往禁止進入的「浮島對岸」,展開首次探險活動。<br>于是,僅限今夏的冒險,从此开始。</p>
    <a target="_blank" style="display:block;margin:0;padding:0;" href="https://music.fullcomb.top/">
    <img src="https://cloud.fullcomb.top/private/source/image/build/Ryza/sidebar.png" style="display:block;width:250px">
    </a>
    </Announcement>
    </template>

    大功告成: