Vue
为什么选择Vue & 什么是Vue
Vue是一款渐进式JavaScript框架,易学易用,性能出色,生态丰富。
它基于HTML、CSS、JavaScript构建,并提供了多种声明式、组合式的编程模型,帮助我们高效地开发用户界面。
本笔记主要记录Vue3。
Vue的官网:https://cn.vuejs.org
在网页中嵌入Vue
Vue能够直接嵌入到网页中,像使用JQuery一样,但是比JQuery操作DOM的能力更强。
下面通过两种API风格演示如何在网页中嵌入Vue。有关API风格,请向下翻阅,这里只需要懂基本原理即可。
示例:
<!-- 选项式API -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
</style>
</head>
<body>
<div id="app"> <!-- 为Vue控制的区域创建一个选择器 -->
<span>{{ count }}</span>
<button @click="increment">添加</button>
</div>
<script type="module"> // 在这里导入Vue,添加type="module",这样浏览器就会将js文件当做模块来处理
import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'; // 导入Vue
createApp({ // 创建一个Vue实例
data() { // 在这里定义数据
return {
count: 0 // 初始化数据
}
},
methods: { // 方法定义
increment: function () {
this.count++;
}
},
mounted: function () { // 元素被挂载到页面上后执行
console.log('Vue组件已被创建。');
}
}).mount('#app'); // 在这里用mount方法渲染,将Vue组件挂载到id为app的元素上
</script>
</body>
</html>
<!-- 组合式API -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
</style>
</head>
<body>
<div id="app"> <!-- 为Vue控制的区域创建一个选择器 -->
<span>{{ count }}</span>
<button @click="increment">添加</button>
</div>
<script type="module"> // 在这里导入Vue,添加type="module",这样浏览器就会将js文件当做模块来处理
import {createApp, ref, onMounted} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
createApp({ // 创建一个Vue实例
setup() {
const count = ref(0); // 创建一个响应式变量
function increment() {
count.value++;
}
onMounted(() => { // 相当于选项式API的mounted函数
console.log('Vue组件已经被挂载')
})
return {
count, increment //在这里返回要在页面中出现的方法和变量
}
}
}).mount('#app'); // 在这里用mount方法渲染,将Vue组件挂载到id为app的元素上
</script>
</body>
</html>
创建Vue项目
按顺序依次执行以下命令即可。
cd your_base_project_dir
npm init vue@latest
输入项目名称后,剩下的配置自己选择,这里全选【否】。
cd your_project_dir
npm install
安装完成后可以输入以下命令预览生成的默认页面。
npm run dev
若npm原registry缓慢,可以执行
npm config set registry http://registry.npm.taobao.org/
切换到国内镜像源。
Vue项目的文件夹结构
.vscode # VSCode配置文件夹
|-- extensions.json
public # 公共资源文件夹
|-- favicon.ico
src # 源代码文件夹
|-- assets # 资源文件夹
| |-- base.css
| |-- logo.svg
|-- components # 组件文件夹
| |-- icons
| |-- ...
| |-- ...
|-- App.vue # 全局应用文件
|-- main.js # 创建Vue项目
index.html
.gitignore
第一个Vue文件
上述文件中,删除src/components和src/assets文件夹下的所有文件,保留一个HelloWorld.vue。将HelloWorld.vue的文件内容改成与下面一致。
<template>
</template>
<script>
</script>
然后在App.vue中删掉导入之前已删除的文件的代码,改成和下面一致。
App.vue实际上是一个组件,它一般用于统一引入其他组件,而App这个组件本身是被main.js所使用的,具体知识请查阅【组件】一节。
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld />
</template>
还有main.js,最好检查一下是否有其他导入没有删除。
接下来在HelloWorld.vue中开始写代码。
<script setup>
</script>
<template>
<p>Hello World!</p>
</template>
执行如下命令,在localhost:5173预览界面。
npm run dev
运行结果为:Hello World!
Vue的API分为两种:选项式和组合式,具体看下面的两种代码风格。
API风格
选项式API
它使用含多个选项的对象描述组件的路基,如data、methods、mounted。它的定义方式是卸载export default上。选项定义的属性都会暴露在函数内部的this上,它会指向当前组件的实例。
<Script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log(`The initial count is ${this.count}`)
}
}
// 所有的特性都写在这里面,这就是选项式API
</Script>
<template>
<button @click="increment">
Count is {{ count }}
</button>
</template>
组合式API
它可以使用导入的API函数描述组件逻辑。
<script setup>
import { ref, onMounted } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
onMounted(()=>{
console.log(`The initial count is ${count.value}`)
})
// 使用导入函数实现组件逻辑
</script>
<template>
<button @onclick='increment'>
Count is {{ count }}
</button>
</template>
选择API风格
两种API风格都能覆盖大部分的应用场景,它们之时同一个系统底层提供两套的不同接口。选项式API是在组合式API的基础上实现的,关于Vue的基础概念和知识在它们之间都是通用的。在生产项目中,若我们不打算使用构建工具,或在低复杂度的场景使用Vue,推荐使用选项式API。若使用Vue构建完整的单页应用,则可以采用组合式API。
关于导入的知识
在Vue或者任何使用ES6模块导入(import
)语法的JavaScript项目中,大括号的使用与否取决于导入的成员类型。主要有两种导入方式:默认导入(Default Imports)和命名导入(Named Imports)。
默认导入(Default Import): 默认导入是指模块导出时使用
export default
语法导出的成员。默认导出每个模块只能有一个,而且导入时不使用大括号。例如,如果一个模块使用export default
导出了一个函数或一个对象,那么在导入这个默认导出的成员时,你可以给它起任何名字,并且不需要大括号。// 假设有个模块defaultExport.js export default function() { ... } // 导入时 import anyName from './defaultExport.js';
这里的
anyName
可以是任何有效的变量名,它会被赋予defaultExport.js
模块默认导出的成员。命名导入(Named Imports): 命名导入用于导入模块中通过
export
(而非export default
)导出的成员。模块可以导出多个命名成员,导入时需要使用大括号{}
来指定想要导入的成员名。这要求导入时的名称与模块内部导出的名称完全匹配。// 假设有个模块namedExports.js export const name1 = ...; export function name2() { ... } // 导入时 import { name1, name2 } from './namedExports.js';
在这个例子中,
name1
和name2
必须与namedExports.js
中导出的名称完全相同。
总结:当你看到导入语句使用大括号时,它表示这是一个或多个命名导入的情形,必须与导出时的名称完全匹配。如果没有大括号,那么它是一个默认导入,表示导入的是模块的默认导出成员,可以自由命名。在Vue3中,如ref
和onMounted
等都是Vue提供的API,需要通过命名导入的方式从vue
包中导入,因为它们是作为命名导出提供的。
模板语法
文本
数据绑定最常见的形式就是使用“Mustache” (双大括号) 语法的文本插值,和Jinja2的语法相同。
<span>Message: {{ msg }}</span> <!--`{{ msg }}`就是文本插值,类似于Python的Jinja2-->
在data()中可以定义变量,然后就可以使用文本插值在文档中渲染了。
export default {
name: 'HelloWorld',
data(){
return{
msg: "消息提示" //这里的msg是一个变量
}
}
}
在上方介绍了一个Vue组件包含的基本标签,包含style、template、script三个标签,有时script会包含一个setup属性,这是组合式API,使用时要注意。
原始 HTML
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,我们需要使用v-html
指令。
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p> <!--在v-html内部使用一个变量名称来让这个值渲染成html-->
data(){
return{
rawHtml:"<a href='https://www.itbaizhan.com'>百战</a>"
}
}
带有v-html的标签内部不能有元素。如下方的语法是错误的。
<template>
<p v-html="html">123</p>
</template>
<script>
export default {
data() {
return {
html: '<a href="https://google.com">超链接</a>'
}
}
}
</script>
属性 Attribute
Mustache 语法不能在 HTML 属性中使用,然而,可以使用 v-bind
指令,让属性也随变量动态地变化。
<div v-bind:id="dynamicId"></div> <!--所有标签的属性若要动态的使用双花括号语法表示,就要使用v-bind动态绑定-->
data(){
return{
dynamicId:1001
}
}
温馨提示
v-bind:
可以简写成 :
,如:
<template>
<p :id="id">你好</p>
</template>
<script>
export default {
data() {
return {
id: 'id-attribute'
}
}
}
</script>
由于通过Class属性操作元素的方式非常常见,因此Vue给v-bind做了一个增强使用。(更详细的语法请参考https://cn.vuejs.org/guide/essentials/class-and-style.html#binding-html-classes)
我们可以给:class提供一个对象,类似于:
<div :class="{ active: isActive }"></div>
上面的语法表示 active
是否存在取决于数据属性 isActive
的真假值。
:class也可以与一般的class属性值共存,如:
data() {
return {
isActive: true,
hasError: false
}
}
配合下面的模板语法:
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
渲染出的将是<div class="static active"></div>
。
:class也可以绑定一个数组来渲染多个class,但这种方式有些冗余。
使用 JavaScript 表达式
在我们的模板中,我们一直都只绑定简单的 property 键值,Vue.js 提供了完全的 JavaScript 表达式支持,文本插值语法可以计算一个正确的单表达式并渲染出来,但不能使用多个逻辑。
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
// 它们只能是单个表达式,类似于Jinja2
这些表达式会在当前活动实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。
<!-- 这是语句,不是表达式:-->
{{ var a = 1 }}
<!-- 流程控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}
例:
<template>
<p>number+1={{number+1}}</p>
<p>str's words: {{str.split(' ')}}</p>
<p>bool's value is {{ bool ? 'TRUE' : 'FALSE'}}</p>
</template>
<script>
export default {
data() {
return {
number: 1,
str: 'This is a String.',
bool: true
}
}
}
</script>
条件渲染
v-if
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true
值的时候被渲染。
<p v-if="flag">我是孙猴子</p> <!--会被渲染-->
data() {
return {
flag: true
}
}
v-else
我们可以使用 v-else
指令来表示 v-if
的“else 块”。
<p v-if="flag">我是孙猴子</p>
<p v-else>你是傻猴子</p> <!--会被渲染-->
data() {
return {
flag: false
}
}
v-show
另一个用于条件性展示元素的选项是 v-show
指令,但它的本质是设置display属性,而不是选择是否渲染。
<h1 v-show="ok">Hello!</h1> <!--当ok的值为真时它的属性display为block,否则为none,和v-if的区别是它已被渲染-->
v-if
vs v-show
的区别
v-if
是“真正”的条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好
列表渲染
用 v-for
把一个数组映射为一组元素
我们可以用 v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用 item in items
形式的特殊语法,其中 items 是源数据数组,而 item
则是被迭代的数组元素的别名。
<ul>
<li v-for="item in items">{{ item.message }}</li> <!--类似于Js的for in 语法-->
</ul>
data() {
return {
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
in关键字可以使用of关键字替代,效果是一样的。
维护状态
当 Vue 正在更新使用 v-for
渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一的 key
attribute:
<div v-for="(item,index) in items" :key="item.id|index">
<!-- 内容 -->
</div>
事件处理
监听事件
我们可以使用 v-on
指令 (通常缩写为 @
符号) 来监听 DOM 事件,并在触发事件时执行一些 JavaScript。用法为 v-on:click="methodName"
或使用快捷方式 @click="methodName"
。
<button @click="counter += 1">Add 1</button>
data() {
return {
counter: 0
}
}
如:
<template>
<button @click="onclick">+1</button>
<p>{{ number }}</p>
</template>
<script>
export default {
data() {
return {
number: 0
}
},
methods: {
onclick() {
this.number += 1
}
}
}
</script>
事件处理方法
然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
还可以接收一个需要调用的方法名称。
<button @click="greet">Greet</button> <!--若事件处理方法只有一个参数,而@click没有参数,则这个参数代表事件对象-->
methods: {
greet(event) {
// `event` 是原生 DOM event
if (event) {
alert(event.target.tagName)
}
}
}
内联处理器中的方法
这是官方的翻译称呼,其实我们可以直接叫他 "事件传递参数"。
<button @click="say('hi')">Say hi</button>
<button @click="say('what')">Say what</button> <!--若显式传递参数了则函数参数就是普通参数-->
methods: {
say(message) {
alert(message)
}
}
就是给方法传了个参数。
表单输入绑定
你可以用 v-model
指令在表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model
本质上不过是语法糖。它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理。
<input v-model="message" placeholder="edit me" /> <!--该语法的意思是此input与变量message绑定了,它们的值始终一致-->
<p>Message is: {{ message }}</p>
data() {
return {
message:""
}
}
如下面用一个简单的滑块改变数字显示:
<template>
<input type="range" v-model="number">
<p>{{ number }}</p>
</template>
<script>
export default {
data() {
return {
number: 0
}
}
}
</script>
修饰符
.lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步 。我们可以添加 lazy
修饰符,从而转为在 change
事件之后进行同步。
<input v-model.lazy="message" /> <!--这种情况下只有每次此input失去焦点后message才会与其同步-->
<p>Message is: {{ message }}</p>
data() {
return {
message:""
}
}
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model
添加 trim
修饰符。
<input v-model.trim="message" /> <!--去除首尾空格-->
data() {
return {
message:""
}
}
模板引用
为了更方便地操作DOM元素,Vue为元素提供了一个特殊的属性:ref。它的属性值是一个对象,此对象要在组件的script中声明。
<template>
<input type="text" ref="input" @blur="print"/>
</template>
<script setup>
import {ref} from 'vue';
const input = ref(null);
function print() {
console.log("输入框的内容为:" + input.value.value)
}
</script>
<template>
<input type="text" ref="input" @blur="print"/>
</template>
<script>
export default {
methods: {
print: function () {
console.log("输入框的内容为:" + this.$refs.input.value)
}
}
}
</script>
注意,你只可以在组件挂载后才能访问模板引用。如果你想在模板中的表达式上访问 $refs.input
,在初次渲染时会是 undefined
。这是因为在初次渲染前这个元素还不存在呢!
在v-for使用ref时,对象将会是一个数组。
除了和一个对象绑定,它还可以绑定一个函数,这个函数可以有一个参数,代表这个元素本身。这个函数将在组件更新后被调用。
<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">
若ref属性被应用在一个组件上,则对应的引用对象将是这个组件本身。
组件基础
单文件组件
Vue 单文件组件(又名 *.vue
文件,缩写为 SFC)是一种特殊的文件格式,它允许将 Vue 组件的模板、逻辑 与 样式封装在单个文件中。
<template>
<h3>单文件组件</h3>
</template>
<script>
export default {
name:"MyComponent"
}
</script>
<style scoped>
h3{
color: red;
}
</style>
style标签添加scoped属性指此文件内的CSS样式只在此组件内生效,因此它里面的选择器可以和其它组件内的选择器重复。
加载组件
第一步:引入组件 import MyComponentVue from './components/MyComponent.vue'
第二步:挂载组件 ,在export default中填写components: { MyComponentVue }
第三步:显示组件,在template中使用 <my-componentVue />
例:在components文件夹下创建MyComponent1.vue和MyComponent2.vue两个文件,然后更改App.vue内容:
<script setup>
import MyComponent1 from './components/MyComponent1.vue'
</script>
<template>
<MyComponent1 />
</template>
MyComponent1.vue:
<script>
import MyComponent2 from "@/components/MyComponent2.vue"; // 导入子组件,组件名称一般与文件名相同
export default {
components: {
MyComponent2 // 子组件名称,与上方导入的一致
},
data() {
return {
msg: '这是自定义组件MyComponent1的msg内容'
}
}
}
</script>
<template>
<MyComponent2 /> // 然后就可以在这里使用components变量声明过的组建了
<p>{{ msg }}</p>
</template>
<style scoped>
</style>
MyComponent2.vue:
<script>
export default {
data() {
return {
msg: `这个是MyComponent2组件内的msg变量`
}
},
}
</script>
<template>
<h2>这个是MyComponent2组件内的内容</h2>
{{ msg }}
</template>
<style scoped>
h2 {
color: red;
font-size: 20px;
}
</style>
<!--此组件由于没有引入任何组件,因此就像普通组件那样写就可以-->
原理是在App.vue中指注册MyComponent1组件,然后在MyComponent1组件中注册MyComponent2组件,就达到了组件嵌套的效果。
组件的具体位置中,可以使用类似于大写字母开头的组件名称,如MyComponent,也可以使用连字符形式,如my-component。
组件交互
使用props属性进行组件交互
组件与组件之间是需要存在交互的,否则完全没关系,组件的意义就很小了。
我们可以在porps中定义可能由其他组件传递过来的变量列表,可以是一个数组,也可以是一个列表。
<my-componentVue title="标题"/>
<template>
<h3>单文件组件</h3>
<p>{{ title }}</p>
</template>
<script>
export default {
name:"MyComponent",
props:{
title:{
type:String,
default:""
},
//第二种写法:props: ['title', 'subtitle', ...]
}
}
</script>
如:
还是上面的例子,MyComponent1.vue:
<script>
import MyComponent2 from "@/components/MyComponent2.vue";
export default {
components: {
MyComponent2
}
}
</script>
<template>
<h1>MyComponent1的h1标签</h1>
<MyComponent2 msg="组件传递信息" />
</template>
<style scoped>
</style>
MyComponent2.vue:
<script>
export default {
props: ['msg']
}
</script>
<template>
<h1>MyComponent2的h1标签</h1>
<p>{{ msg }}</p>
</template>
<style scoped>
</style>
原理是在MyComponent1中在使用MyComponent2时传递了一个参数msg,然后在MyComponent2中使用props引出这个变量,最后输出就可以。
温馨提示:组件传递信息时,信息只能从父级组件传递到子级组件,反向传递的方法在下面有介绍。
若使用组合式API,则props需要单独声明。
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
若没有<script setup>
,props需要作为setup的参数。
export default {
props: ['foo'],
setup(props) {
// setup() 接收 props 作为第一个参数
console.log(props.foo)
}
}
Prop 类型
Prop传递参数其实是没有类型限制的,可以传递数组、字符串、数字、对象。
Prop常用的可传递的参数类型都是JavaScript的内置类型,有:Number、String、Array、Object等。
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function
}
例(MyComponent2.vue):
<script>
export default {
props: {
msg: {
type: String,
default: "默认值"
}
}
}
</script>
<template>
<h1>MyComponent2的h1标签</h1>
<p>{{msg}}</p>
</template>
<style scoped>
</style>
上方msg设置了类型和默认值,对于类型,若其他地方使用了这个组件时传递了错误的参数类型,控制台会警告但不会报错;对于默认值,若组件在引用时没有传递这个参数,那么这个参数在被渲染时会显示默认值。
温馨提示:
- 传递对象时,参数需要使用v-bind类型,如MyComponent1.vue:
<script>
import MyComponent2 from "@/components/MyComponent2.vue";
export default {
components: {
MyComponent2
},
data(){
return {
info: {
id: 1,
name: '张三' // 这里使用对象类型
}
}
}
}
</script>
<template>
<MyComponent2 :info=info /> <!--这里使用v-bind类型-->
</template>
<style scoped>
</style>
MyComponent2.vue:
<script>
export default {
props: ['info']
}
</script>
<template>
<p>{{ info.id }}</p>
<p>{{ info.name }}</p>
</template>
<style scoped>
</style>
- 数据类型为数组或者对象的时候,默认值需要返回工厂模式,如:
<script>
export default {
props: {
info : {
type: Object,
default: () => {
return {
id: 1,
name: '张三' //默认值使用工厂模式,使用匿名函数
}
}
}
}
}
</script>
<template>
<p>{{ info.id }}</p>
<p>{{ info.name }}</p>
</template>
<style scoped>
</style>
自定义事件组件交互
上述使用props方法在组件间传递数据时数据只能由父组件传递至子组件,不能反向传递。若要实现反向传递,有一种方法就是利用自定义组件交互。具体实现方法就是在子组件中为一个元素(如按钮)添加事件监听,监听后触发的函数内部调用this.$emit函数。第一个参数为父组件使用子组件时使用v-on绑定的自定义名称,第二个参数为函数参数。在父组件中,实现了子组件触发了事件监听的函数后,在调用子组件时,用v-on将子组件中用this.$emit函数的第一个参数定义的自定义名称传递到子组件,当子组件触发了事件监听时,emit就会执行父组件的函数。
<template>
<h3>单文件组件</h3>
<button @click="sendHandle">发送数据</button> <!--这里写函数名称,函数内部要有this.$emit-->
</template>
<script>
export default {
name: "MyComponent",
methods:{
sendHandle(){
this.$emit("onCustom","数据") //这里调用$emit
}
}
}
</script>
<style scoped>
h3 {
color: red;
}
</style>
<template>
<my-componentVue @onCustom="getData" /> <!--这里使用子组件$emit调用时传递的第一个参数,值写要调用的函数-->
</template>
<script>
import MyComponentVue from './components/MyComponent.vue'
export default {
name: 'App',
components: {
MyComponentVue
},
methods: {
getData(data) {
console.log(data);
}
}
}
</script>
props只读
上方演示了在不同组件之间传递变量,这些变量实际上值都是不能改变的,即在一个组件传入另一个组件的值是不能改变的,如下方的语法是错误的。
<!--MyComponent1-->
<template>
<MyComponent2 msg='msg'/>
</template>
<script>
import MyComponent2 from '@/components/MyComponent2.vue'
export default {
components: {
MyComponent2
},
data(){
return {
msg: '值1'
}
}
}
</script>
<!--MyComponent2-->
<template>
<button @click='clickMethod'>点击更改msg的值</button>
<p>msg = {{ msg }}</p>
</template>
<script>
export default {
props: ['msg'],
methods: {
clickMethod(){
this.msg = '值2' //错误的语法
}
}
}
</script>
Prop组件与v-model一起使用
子组件内,我们可以用v-model与一个变量绑定(在data中声明),然后在watch中添加一个和这个变量同名的方法,如下。
<!--MyComponent2-->
<template>
<input v-model="number" type="range">
</template>
<script>
export default {
data() {
return {
number: 0
}
},
watch: {
number(newValue, oldValue) {
this.$emit('emitEvent', newValue);
}
}
}
</script>
父组件内,定义一个用于显示的元素,并实现一个设置此元素的方法。使用上面的方法导入并显示这个组件后,传递一个v-on参数,参数的名称是子组件内要改变的那个值,值写设置父组件值的函数。
<!--MyComponent1-->
<template>
<MyComponent2 @emitEvent="setData"/>
<p>msg = {{ data }}</p>
</template>
<script>
import MyComponent2 from '@/components/MyComponent2.vue'
export default {
components: {
MyComponent2
},
data() {
return {
data: 1
}
},
methods: {
setData(value) {
this.data = value
}
}
}
</script>
这样子组件的变量会改变,并在父组件中显示。
组件生命周期
每个组件在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
为了方便记忆,我们可以将他们分类:
创建时:beforeCreate
、created
渲染时:beforeMount
、mounted
更新时:beforeUpdate
、updated
卸载时:beforeUnmount
、unmounted
生命周期函数是和data()函数绑定在一起的,这八个函数要写在它里面。
比如mounted函数可以存放与网络请求有关的代码,unmounted可以存放与销毁组件有关的代码,可以销毁吃性能的组件。
插槽Slots
插槽是一个可以向组件中添加HTML标签的语法,具体看下面的例子。
在子组件中,添加一个没有任何文本的空slot标签,在父组件引用子组件时,使用闭合标签,里面添加的HTML标签,会替换子组件的slot标签。
<!--MyComponent1-->
<template>
<MyComponent2>
<h3>在MyComponent1中添加的h3</h3>
</MyComponent2>
</template>
<script>
import MyComponent2 from '@/components/MyComponent2.vue'
export default {
components: {
MyComponent2
}
}
</script>
<!--MyComponent2-->
<template>
<h3>MyComponent2</h3>
<slot></slot>
</template>
<script>
export default {
}
</script>
Vue引入第三方——以Swiper轮播图为例
Vue之所以是一个强大的Web框架,原因之一就是它有着强大的第三方插件支持。这里以Swiper为例,介绍一下如何安装第三方插件以及如何使用它们。
Swiper
开源、免费、强大的触摸滑动插件。
Swiper
是纯javascript打造的滑动特效插件,面向手机、平板电脑等移动终端。
Swiper
能实现触屏焦点图、触屏Tab切换、触屏轮播图切换等常用效果。
温馨提示
由于Swiper也是一个框架,它也会不断地更新。有时官方更新之后较以前的版本有较大的改动,有许多基于以前的版本实现的代码就需要重构,因此安装最新版本时不是最佳选择,这时我们就可以选择安装指定版本。
官方文档:https://swiperjs.com/vue
安装指定版本:
npm install --save swiper@8.1.6
基础实现
<template>
<div class="hello">
<swiper class="mySwiper"> <!--使用组件-->
<swiper-slide>Slide 1</swiper-slide>
<swiper-slide>Slide 2</swiper-slide>
<swiper-slide>Slide 3</swiper-slide>
</swiper>
</div>
</template>
<script>
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css'; // 导入CSS,不导入则无法显示样式
export default {
name: 'HelloWorld',
components: {
Swiper, // 设置组件
SwiperSlide,
}
}
</script>
为轮播图添加指示器
<template>
<div class="hello">
<swiper class="mySwiper" :modules="modules" :pagination="{ clickable: true }"> <!--这里也需要做一个改动-->
<swiper-slide>
<img src="../assets/logo.png" alt="">
</swiper-slide>
<swiper-slide>
<img src="../assets/logo.png" alt="">
</swiper-slide>
<swiper-slide>
<img src="../assets/logo.png" alt="">
</swiper-slide>
</swiper>
</div>
</template>
<script>
import { Pagination } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';
import 'swiper/css/pagination'; //导入CSS
export default {
name: 'HelloWorld',
data(){
return{
modules: [ Pagination ] // 这里加一个modules
}
},
components: {
Swiper,
SwiperSlide,
}
}
</script>
Axios网络请求
Axios 是一个基于 promise 的网络请求库
安装
Axios的应用是需要单独安装的 npm install --save axios
引入
组件中引入: import axios from "axios"
全局引用:
import axios from "axios"
const app = createApp(App);
app.config.globalProperties.$axios = axios
app.mount('#app')
// 在组件中调用
this.$axios
网络请求基本示例
get请求
axios({
method: "get",
url: "http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php"
}).then(res => {
console.log(res.data);
})
post请求
温馨提示
post请求参数是需要额外处理的
- 安装依赖:
npm install --save querystring
- 转换参数格式:
qs.stringify({})
axios({
method:"post",
url:"http://iwenwiki.com/api/blueberrypai/login.php",
data:qs.stringify({
user_id:"iwen@qq.com",
password:"iwen123",
verification_code:"crfvw"
})
}).then(res =>{
console.log(res.data);
})
快捷方案
get请求
axios.get("http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php")
.then(res =>{
console.log(res.data);
})
post请求
axios.post("http://iwenwiki.com/api/blueberrypai/login.php", qs.stringify({
user_id: "iwen@qq.com",
password: "iwen123",
verification_code: "crfvw"
}))
.then(res => {
console.log(res.data);
})
Axios网络请求封装
在日常应用过程中,一个项目中的网络请求会很多,此时一般采取的方案是将网络请求封装起来
在src
目录下创建文件夹utils
,并创建文件request
,用来存储网络请求对象 axios
import axios from "axios"
import qs from "querystring"
const errorHandle = (status,info) => {
switch(status){
case 400:
console.log("语义有误");
break;
case 401:
console.log("服务器认证失败");
break;
case 403:
console.log("服务器拒绝访问");
break;
case 404:
console.log("地址错误");
break;
case 500:
console.log("服务器遇到意外");
break;
case 502:
console.log("服务器无响应");
break;
default:
console.log(info);
break;
}
}
const instance = axios.create({
timeout:5000
})
instance.interceptors.request.use(
config =>{
if(config.method === "post"){
config.data = qs.stringify(config.data)
}
return config;
},
error => Promise.reject(error)
)
instance.interceptors.response.use(
response => response.status === 200 ? Promise.resolve(response) : Promise.reject(response),
error =>{
const { response } = error;
errorHandle(response.status,response.info)
}
)
export default instance;
在src
目录下创建文件夹api
,并创建文件index
和path
分别用来存放网络请求方法和请求路径
// path.js
const base = {
baseUrl:"http://iwenwiki.com",
chengpin:"/api/blueberrypai/getChengpinDetails.php"
}
export default base
// index.js
import path from "./path"
import axios from "../utils/request"
export default {
getChengpin(){
return axios.get(path.baseUrl + path.chengpin)
}
}
在组件中直接调用网络请求
import api from "../api/index"
api.getChengpin().then(res =>{
console.log(res.data);
})
网络请求跨域解决方案
JS采取的是同源策略。
同源策略是浏览器的一项安全策略,浏览器只允许js 代码请求和当前所在服务器域名,端口,协议相同的数据接口上的数据,这就是同源策略。
也就是说,当协议、域名、端口任意一个不相同时,都会产生跨域问题,所以又应该如何解决跨域问题呢。
跨域错误提示信息
目前主流的跨域解决方案有两种:
- 后台解决:cors
- 前台解决:proxy
devServer: {
proxy: {
'/api': {
target: '<url>',
changeOrigin: true
}
}
}
温馨提示
解决完跨域配置之后,要记得重启服务器才行哦!
Vue引入路由配置
在Vue中,我们可以通过vue-router
路由管理页面之间的关系
Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。
在Vue中引入路由
第一步:安装路由 npm install --save vue-router
第二步:配置独立的路由文件
// index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
第三步:引入路由到项目
// main.js
import router from './router'
app.use(router)
第四步:指定路由显示入口 <router-view/>
第五步:指定路由跳转
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
路由传递参数
页面跳转过程中,是可以携带参数的,这也是很常见的业务
例如:在一个列表项,点击进入查看每个列表项的详情
第一步:在路由配置中指定参数的key
{
path:"/list/:name",
name:"list",
component:() => import("../views/ListView.vue")
}
第二步:在跳转过程中携带参数
<li><router-link to="/list/内蒙">内蒙旅游十大景区</router-link></li>
<li><router-link to="/list/北京">北京旅游十大景区</router-link></li>
<li><router-link to="/list/四川">四川旅游十大景区</router-link></li>
第三步:在详情页面读取路由携带的参数
<p>{{ $route.params.name }}城市旅游景区详情</p>
嵌套路由配置
路由嵌套是非常常见的需求
第一步:创建子路由要加载显示的页面
第二步:在路由配置文件中添加子路由配置
{
path:"/news",
name:"news",
redirect:"/news/baidu",
component:() => import("../views/NewsView.vue"),
children:[
{
path:"baidu",
component:() => import("../views/NewsList/BaiduNews.vue"),
},
{
path:"wangyi",
component:() => import("../views/NewsList/WangyiNews.vue"),
}
]
}
第三步:指定子路由显示位置<router-view></router-view>
第四步:添加子路由跳转链接
<router-link to="/news/baidu">百度新闻</router-link> |
<router-link to="/news/wangyi">网易新闻</router-link>
第五步:重定向配置 redirect:"/news/baidu"
Vue状态管理(Vuex)
组件中的信息传递需要在被引用的组件中声明参数,在引用组件中传参。若组件的层级过深则需要频繁声明变量和传参,比较繁琐。Vue官方提供了VueX,可以集中地管理这些参数/变量。我们只需要在VueX中声明需要在各个组件中使用的变量和函数,在其他需要的组件中引用这个变量就可以。并且这些变量是全局的,在一个组件中改变了它的值后,其他组件内的值也会跟着改变。
引入Vuex的步骤
第一步:安装Vuex npm install --save vuex
第二步:配置Vuex文件
在src文件夹下创建一个文件夹store,在store文件夹下创建一个index.js,里面写上如下代码。
import { createStore } from 'vuex'
export default createStore({
state: {
counter:0
}
})
第三步:在主文件中引入Vuex
在main.js中添加如下代码,表示在全局应用对象中使用vuex。
import store from './store'
app.use(store)
第四步:在组件中读取状态
在组件的template中添加如下内容渲染文档。
<p>counter:{{ $store.state.counter }}</p>
也可以是:
<script>
import { mapState } from 'vuex';
computed:{
...mapState(["counter"])
}
</script>
<template>
<p>{{ counter }}</p>
</template>
Vue状态管理核心(Vuex)
最常用的核心概念包含: State
、Getter
、Mutation
、Action
Getter
对Vuex中的数据进行过滤
import { createStore } from 'vuex'
export default createStore({
state: {
counter: 0
},
getters: {
getCount(state){
return state.counter > 0 ? state.counter : "counter小于0,不符合要求"
}
}
})
import { mapState,mapGetters } from 'vuex';
computed:{
...mapGetters(["getCount"])
}
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数
import { createStore } from 'vuex'
export default createStore({
state: {
counter: 0
},
getters: {
},
mutations: {
setCounter(state, num) {
state.counter += num
}
}
})
import { mapState,mapMutations } from 'vuex';
methods:{
...mapMutations(["setCounter"]),
clickHandler(){
// this.$store.commit("setCounter",20)
// 或者
// this.setCounter(10)
}
}
Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态
- Action 可以包含任意异步操作
import { createStore } from 'vuex'
import axios from "axios"
export default createStore({
state: {
counter: 0
},
getters: {
getCount(state){
return state.counter > 0 ? state.counter : "counter小于0,不符合要求"
}
},
mutations: {
setCounter(state, num) {
state.counter += num
}
},
actions: {
asyncSetCount({ commit }){
axios.get("http://iwenwiki.com/api/generator/list.php")
.then(res =>{
commit("setCounter",res.data[0])
})
}
}
})
import { mapState,mapMutations,mapGetters,mapActions } from 'vuex';
methods:{
...mapActions(["asyncSetCount"]),
clickAsyncHandler(){
// this.$store.dispatch("asyncSetCount")
// 或者
// this.asyncSetCount()
}
}
Vue3新特性1
Vue3是目前Vue的最新版本,自然也是新增了很多新特性
六大亮点
- Performance:性能更比Vue 2.0强。
- Tree shaking support:可以将无用模块“剪辑”,仅打包需要的。
- Composition API:组合API
- Fragment, Teleport, Suspense:“碎片”,Teleport即Protal传送门,“悬念”
- Better TypeScript support:更优秀的Ts支持
- Custom Renderer API:暴露了自定义渲染API
ref或者reactive
在2.x中通过组件data的方法来定义一些当前组件的数据
data() {
return {
name: 'iwen',
list: [],
}
}
在3.x中通过ref或者reactive创建响应式对象
import { ref,reactive } from "vue"
export default {
name: 'HelloWorld',
setup(){
const name = ref("iwen")
const state = reactive({
list:[]
})
return{
name,
state
}
}
}
methods中定义的方法写在setup()
在2.x中methods来定义一些当前组件内部方法
methods:{
http(){}
}
在3.x中直接在setup方法中定义并return
setup() {
const http = ()=>{
// do something
}
return {
http
};
}
setup()中使用props和context
在2.x中,组件的方法中可以通过this获取到当前组件的实例,并执行data变量的修改,方法的调用,组件的通信等等,但是在3.x中,setup()在beforeCreate和created时机就已调用,无法使用和2.x一样的this,但是可以通过接收setup(props,ctx)的方法,获取到当前组件的实例和props
export default {
props: {
name: String,
},
setup(props,ctx) {
console.log(props.name)
ctx.emit('event')
},
}
Vue3新特性2
在setup中使用生命周期函数
你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
下表包含如何在 setup () 内部调用生命周期钩子
Options API | Hook inside setup |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
Provide / Inject
- provide() 和 inject() 可以实现嵌套组件之间的数据传递。
- 这两个函数只能在 setup() 函数中使用。
- 父级组件中使用 provide() 函数向下传递数据。
- 子级组件中使用 inject() 获取上层传递过来的数据。
- 不限层级
// 父组件
import { provide } from "vue"
setup() {
provide("customVal", "我是父组件向子组件传递的值");
}
// 子组件
import { inject } from "vue"
setup() {
const customVal = inject("customVal");
return {
customVal
}
}
Fragment
Fragment翻译为:“碎片”
- 不再限于模板中的单个根节点
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App" />
</template>
Vue3加载Element-plus
Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0
的桌面端组件库
Element Plus 基于 Vue 3
,面向设计师和开发者的组件库
安装Element-Plus
npm install element-plus --save
完整引用
如果你对打包后的文件大小不是很在乎,那么使用完整导入会更方便
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
按需导入
按需导入才是我们的最爱,毕竟在真实的应用场景中并不是每个组件都会用到,这会造成不小的浪费
首先你需要安装unplugin-vue-components
和 unplugin-auto-import
这两款插件
npm install -D unplugin-vue-components unplugin-auto-import
然后修改vue.config.ts
配置文件
const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
]
}
})
最后,可以直接在组件中使用
<template>
<el-button>Default</el-button>
<el-button type="primary">Primary</el-button>
</template>
Vue3加载Element-plus的字体图标
Element-plus
不仅仅是提供了各种组件,同时还提供了一整套的字体图标方便开发者使用
安装icons
字体图标
npm install @element-plus/icons-vue
全局注册
在项目根目录下,创建plugins
文件夹,在文件夹下创建文件icons.js
文件
import * as components from "@element-plus/icons-vue";
export default {
install: (app) => {
for (const key in components) {
const componentConfig = components[key];
app.component(componentConfig.name, componentConfig);
}
},
};
引入文件
在main.js
中引入icons.js
文件
import elementIcon from "./plugins/icons";
app.use(elementIcon)
使用方式
接下来就可以直接在组件中引入使用了
<el-icon class="expand" color="#409EFC" :size="30">
<expand />
</el-icon>
组合式API相关
组合式API组件的相关变量是在beforeCreate事件前执行的。
<script>
export default {
setup() {
console.log('setup.')
},
beforeCreate() {
console.log('beforeCreate.')
}
}
</script>
执行结果是先输出setup后输出beforeCreate。
定义变量
在默认导出时若在setup函数中定义变量,则需要返回这些变量以便于外部使用。
<script>
export default {
setup() {
const message = 'Hello World';
function print() {
console.log(message)
}
return {
message,
print
}
}
}
</script>
<template>
<span> Message is: {{ message }} </span>
<button @click="print"> Print Message </button>
</template>
可以给script标签添加setup标签,添加了setup标签的script标签为我们省去了上述定义和返回的步骤,我们可以直接在组件中使用这些变量。
<script setup>
const message = 'Hello World'
function print() {
console.log(message)
}
</script>
<template>
<span> Message is: {{ message }} </span>
<button @click="print"> Print Message </button>
</template>
这里带setup的script标签就是语法糖。
若要定义可变值的数据,不能简单地将它赋值给一个变量,而是要使用vue为我们提供的响应式函数。定义对象使用reactive函数,定义普通变量则使用ref函数。使用这两个函数定义响应式变量之后,只要这两个变量的内容被改变,包括对象的键值和普通变量的值,Vue都能检测到并更改每一个引用这个变量的组件中所有值。
<script setup>
import {reactive, ref} from "vue"
const person = reactive({
name: "张三",
age: 18
})
const count = ref(0)
</script>
<template>
<span> {{ person.name }} 的年龄为 {{ person.age }} </span>
<button @click="person.age ++">增加person的年龄</button>
<span>{{ count }}</span>
<button @click="count++"> count+1 </button>
</template>
在脚本区域更改count的值时则必须通过.value属性来更改。
import {reactive, ref} from "vue"
const count = ref(0)
count++ // 错误
count.value++ // 正确
计算属性
由于响应式数据在更新后需要在其他组件内也实时更新,还是需要像上方那样使用vue提供的响应式函数来处理数据。计算变量的属性时需要使用vue提供的conputed函数,它接收一个函数作为参数,返回运算后的结果。
<script setup>
import {computed, ref} from "vue"
const arr = ref([1, 2, 3, 4])
const computedArr = computed(function () {
return arr.value.filter(e => e > 2)
})
setTimeout(function () {
arr.value.push(5, 6)
}, 3000)
</script>
<template>
<span>原始响应式数组: {{ arr }}</span>
<span>更新后的数组:{{ computedArr }}</span>
</template>
刚开始时输出的两个数组分别是1234和34,三秒之后数组元素被更新,两者就分别是123456和3456。
监听数据变化
组合式API的响应式数据更新时,使用watch函数侦听响应式数据源,并在数据更新后执行回调函数。watch函数需要导入,它接收两个参数。第一个参数为响应式对象,即ref对象,第二个参数为一个回调函数,可以是匿名函数。这个匿名函数可以包含两个参数,第一个参数为更新后的值,第二个参数为更新前的值。
<script setup>
import {ref, watch} from "vue"
const count = ref(0)
watch(count, function (newVal, oldVal) {
console.log(`count被更新,更新前的值:${oldVal},更新后的值:${newVal}`)
})
</script>
<template>
<button @click="count++"> {{ count }} + 1 </button>
</template>
这里的watch只侦听一个变量的变化,可以同时侦听多个变化,但这些变量中有一个变化后都会触发这个侦听器,这样会判断不出来哪个发生了变化。
<script setup>
import {ref, watch} from "vue"
const count1 = ref(0)
const count2 = ref(0)
watch([count1, count2], function (
[newCount1, newCount2],
[oldCount1, oldCount2]
) {
console.log(`count1和count2有一个值发生改变,这两个数的新值:${newCount1}, ${newCount2},这两个数的旧值:${oldCount1}, ${oldCount2}`)
})
</script>
<template>
<button @click="count1++"> {{ count1 }} + 1</button>
<button @click="count2++"> {{ count2 }} + 1</button>
</template>
watch函数此时接受的第一个参数为一个列表,这个列表中要侦听的变量具有顺序,第二个参数还是回调函数,这个回调函数内的第一个参数仍然是新值,第二个参数仍然是旧值,这两个值也是列表,且和watch函数的第一个参数列表的变量声明顺序一致。
除watch函数还接受第三个参数,它是一个对象,它可以带有一个immediate属性,如果它为真,则在侦听器床架初期时会执行一次回调函数,在响应式数据改变之后还会执行回调函数。
watch(count, function(newVal, oldVal){ console.log(newVal, oldVal) }, {immediate: true})
watch函数侦听ref对象默认是浅层监听,直接修改对象的属性不会触发回调。watch的第三个参数可以加一个deep属性,若它为true,则代表开启深度监听,若响应式对象的属性值发生改变则会触发回调函数。
<script setup>
import {ref, watch} from 'vue'
const obj = ref({count: 0})
watch(obj, function () {
console.log('obj变化了')
},
{
deep: true // 若注释掉这行,在obj更改后不会触发回调函数
}
)
</script>
<template>
<button @click="obj.count++"> {{ obj.count }} + 1 </button>
</template>
deep深度监听会产生性能损耗,可以只监听对象的某一个属性。这时候watch的第一个参数就是一个返回此属性的回调函数。
<script setup>
import {ref, watch} from 'vue'
const obj = ref({count: 0, age: 0})
watch(function () {
return obj.value.count
}, function () {
console.log('obj变化了')
},
{
deep: true // 若注释掉这行,在obj更改后不会触发回调函数
}
)
</script>
<template>
<button @click="obj.count++"> {{ obj.count }} + 1</button>
</template>
组件生命周期
以组件渲染后为例,选项式API中使用mounted函数来在组件渲染完毕后执行回调函数。
<script>
export default {
mounted: ()=> {
console.log("组件被渲染完成")
}
}
</script>
<template>
</template>
在组合式API中,它对应的代码如下。
<script setup>
import {onMounted} from 'vue'
onMounted(function () {
console.log("组件被渲染完成")
})
</script>
<template>
</template>
除beforeCreate和created,其他生命周期都是对应的名称前面加on,如beforeMounted在组合式API中的生命周期名称为onBeforeMounted。
beforeCreate和created对应的组合式API的生命周期则被封装进了setup中。
组件数据传递 - 父传子
组合式API中,组件间的信息传递和选项式API一样,都是通过属性名="属性值"
传递的,不同的地方还是在脚本代码上。得益于setup提供的语法糖,在父组件中导入子组件后,可以直接使用。
<script setup>
import ChildComponent from "./ChildComponent.vue";
</script>
<template>
<ChildComponent message="Hello World"/>
</template>
在子组件中,使用defineProps宏定义组件内变量名称。
<script setup>
const props = defineProps({
message: String
})
</script>
<template>
{{ message }}
</template>
若要传递变量,即响应式数据,需要在引用组件时使用v-bind,其他都和原来的一致。
<!--父组件-->
<script setup>
import ChildComponent from "./ChildComponent.vue";
import {ref} from "vue";
const number = ref(0)
setTimeout(() => {
number.value ++
}, 3000)
</script>
<template>
<ChildComponent :number="number"/>
</template>
<!--子组件-->
<script setup>
const props = defineProps({
number: String
})
</script>
<template>
{{ number }}
</template>
组件数据传递 - 子传父
即子组件调用父组件中的函数。
在选项式API中,子传父是通过以下方法实现的。
父组件:
- 导入并引入子组件,在子组件用v-on绑定一个自定义事件,事件名假如为customEvent,值为本组件内使用methods定义的countPlus。
- 在data()中定义一个count,初始值为0,在本组件内输出count的值。
- countPlus定义一个参数,调用时将这个组件内的count加上参数的值。
子组件
- 在emits中声明上方声明的自定义事件名,在本例中为customEvent。
- 在子组件中定义一个按钮,按钮点击后触发f函数,f函数使用
this.$emit("customEvent", number)
,customEvent为自定义事件,number为在本组件中使用data()定义的一个变量,它与一个input使用v-model绑定。
在组合式API中,子传父是通过以下方式实现的:
父组件(使用setup语法糖)
- 直接导入子组件,可直接使用;
- 定义一个函数,这个函数要被子组件调用。
- 在引用组件时使用v-on传递一个自定义事件,事件名称这里假如为customEvent,值为上方定义的函数。
子组件(使用setup语法糖)
- 使用defineProps宏定义上方传入的自定义事件名,该方法返回一个此组件内可用的emit对象,和组合式API的
this.$emit
很像; - 使用emit(customEvent, args)调用即可。
<!--父组件-->
<script setup>
import ChildComponent from "@/ChildComponent.vue";
import {ref} from "vue";
const count = ref(0)
function func(number) {
count.value += number
}
</script>
<template>
count = {{ count }}
<ChildComponent @custom-event="func" />
</template>
<!--子组件-->
<script setup>
import {ref} from "vue";
const number = ref(0)
const emit = defineEmits([
"customEvent"
])
function clickFunc() {
emit("customEvent", number.value)
}
</script>
<template>
<h2>子组件</h2>
<input v-model="number" type="number">
<button @click="clickFunc">点击</button>
</template>
模板引用
组合式API中,在初始声明响应式变量时,将初始值设为null,然后在被引用的组件上使用ref引用这个变量即可。
<script setup>
import {ref, onMounted} from "vue";
const span = ref(null);
onMounted(function(){
console.log(span) //组件渲染完成才能获取
})
</script>
<template>
<span ref="span">span元素</span>
<button @click="console.log(span)">输出span元素</button>
</template>
若在组件上使用ref属性,变量引用的将是组件本身。
默认情况下,setup语法糖内部的属性和方法是不开放给父组件使用的,可以通过defineExpose宏指定哪些属性和方法能被访问。
import {ref} from "vue";
const count = ref(0);
const f = function() {
console.log('f函数');
}
defineExpose([
count, f
])
跨层传递数据
在多个具有嵌套关系的组件中,更顶层的组件可以用provide函数定义数据,它接受两个参数,第一个参数为键类型,第二个参数为一个普通数据或一个响应式对象。底层的组件使用inject接受组件,它接受一个参数,为键名,返回一个对象,可用const承接。
<!--父组件-->
<script setup>
import {provide, ref} from "vue";
import ChildComponent from "./SonComponent.vue";
const constant = '这是一个常量';
const count = ref(0);
const countPlus = () => {
count.value++
}
provide('constant-key', constant);
provide('count-key', count);
provide('countPlus-key', countPlus)
</script>
<template>
<h2>父组件</h2>
<ChildComponent />
</template>
<!--子组件-->
<script setup>
import GrandComponent from './GrandComponent.vue';
</script>
<template>
<h2>子组件</h2>
<GrandComponent/>
</template>
<!--孙子组件-->
<script setup>
import {inject, ref} from "vue";
const constant = inject('constant-key');
const count = inject('count-key');
const countPlus = inject('countPlus-key');
</script>
<template>
<span>{{ constant }}</span>
<span>{{ count }}</span>
<button @click="countPlus"> + 1 </button>
</template>