您现在的位置是:首页 >学无止境 >vue3使用指南网站首页学无止境
vue3使用指南
vue3使用指南
主要介绍vue3
的使用,同时会涉及到vue2
,并会讲解其中的一些差异。
安装
CDN引入
如果想快速体验,可以直接通过cdn
进行引入。
<div id="app">{{msg}}</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script >
Vue.createApp({
data() {
return {
msg: 'Vue3'
}
}
}).mount('#app')
</script>
通过 CDN
引入 Vue
时,由于不涉及到构建步骤,可以使得设置更加简单,并且可以用于增强静态的 HTML
或与后端框架集成。但是这也意味着无法使用SFC
(单文件)语法。
使用es模块构建
现代浏览器大多都已原生支持 ES
模块,可以像这样通过 CDN
以及原生 ES
模块使用 Vue
。
<div id="app">{{msg}}</div>
<script type="module">
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
createApp({
data() {
return {
msg: 'Vue3'
}
}
}).mount('#app')
</script>
使用import maps
<div id="app">{{msg}}</div>
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
<script type="module">
import {createApp} from 'vue'
createApp({
data() {
return {
msg: 'Hello Vue!'
}
}
}).mount('#app')
</script>
工程化
使用vue@latest
方式
Vue
官方的项目脚手架工具,内部使用的是vite
进行处理。
npm init vue@latest
这个指令会安装并执行create-vue
,之后按照提示安装依赖并运行即可。
请注意,生成的项目中的示例组件使用的是组合式 API
和 <script setup>
,而非选项式 API
。
使用@vue/cli
方式
虽然vue
现在已经不推荐使用该方式,内部使用的webpack
进行处理,但是依旧提供了vue3
模板的下载。
安装@vue/cli
npm i @vue/cli -g
# 创建项目
vue create xxx
可以选择官方的vue3
模板,也可以自行配置。
使用vite
创建
# npm 6.x
npm init vite@latest <project-name> --template vue
# npm 7+,需要加上额外的双短横线
npm init vite@latest <project-name> -- --template vue
创建实例
在vue2
中,通过引入vue
文件,进行实例的创建。
import Vue from 'vue'
new Vue({
...
})
而在vue3
中做了改变,被移动到了由新的 createApp
方法所创建的应用实例上。
import { createApp } from 'vue'
const app = createApp({})
组合式API
在vue2.x
中,一个组件中通常包含了data
、computed
、methods
、watch
等组件选项来组织逻辑,当组件变得越来越大时,逻辑关注点的列表也会增长,尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。
这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
为了使得能将同一个逻辑的相关代码单独放在一起,vue3
引入了组合式API
的概念,使用setup
来编写组件式API
来简单看看一个例子理解一下组合式API
。
<template>
<!-- 计数器 -->
<p>{{obj.count}}</p>
<button @click="add">+1</button>
<!-- v-if展示区域 -->
<button @click="show">显示</button>
<button @click="hide">隐藏</button>
<div v-if="showDivFlag">一个被控制显隐的div</div>
</template>
<script>
import { reactive, ref } from 'vue'
// 计数器逻辑
function count() {
const data = {
count: 0
}
const obj = reactive(data);
function add() {
obj.count++;
}
return {
obj,
add
}
}
// v-if展示区域逻辑
function vifDiv() {
const showDivFlag = ref(true)
function show() {
showDivFlag.value = true
}
function hide() {
showDivFlag.value = false
}
return {
showDivFlag,
show,
hide
}
}
export default {
setup() {
const { obj, add } = count();
const {showDivFlag, show, hide} = vifDiv();
return {
obj,
add,
showDivFlag,
show,
hide
}
},
}
</script>
在setup
内对数据进行响应式,之后将data
、methods
返回,这样,模板中就可以使用这些。比起vue2
中选项式API
,使用setup
能将各个模块逻辑单独放在一起,避免在methods
中定义一大堆的方法。
以上例子使用vue2
的选项式API
:
<template>
<!-- 计数器 -->
<p>{{obj.count}}</p>
<button @click="add">+1</button>
<!-- v-if展示区域 -->
<button @click="show">显示</button>
<button @click="hide">隐藏</button>
<div v-if="showDivFlag">一个被控制显隐的div</div>
</template>
<script>
export default {
data() {
return {
obj: {
count: 0
},
showDivFlag: true
}
},
methods: {
add() {
this.obj.count++;
},
show() {
this.showDivFlag = true;
},
hide() {
this.showDivFlag = false;
}
}
}
</script>
组合式 API
的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要开发者对 Vue
的响应式系统有更深的理解才能高效使用。相应的,它的灵活性也使得组织和重用逻辑的模式变得更加强大。
两种 API
风格都能够覆盖大部分的应用场景。它们只是同一个底层系统所提供的两套不同的接口。实际上,选项式 API
是在组合式 API
的基础上实现的。关于 Vue
的基础概念和知识在它们之间都是通用的。
setup()
组件中的一个选项,处于生命周期函数 beforeCreate
钩子函数之前的函数,props
被解析之后执行。同时也是组合式API
的入口。
接受两个参数:
props
: 父组件的传值
如果想在setup
中使用props
,必须在props
对prop
进行声明,未声明的prop
的无法在setup
中的props
中解构出来(可以通过context.attrs
获取)。声明了的prop
即使父组件没有传,在setup
的props
仍然可以获取,但是值为undefined
或者props
中定义的默认值。props: { key: String, value: { type: String, default: 'default' } }, setup(props) { console.log(props); }
setup
函数中的props
是响应式的,当传入新的prop
时,它将被更新。但是,因为props
是响应式的,你不能使用ES6
解构,因为它会消除prop
的响应性。可以使用toRefs
来解构。setup(props) { const {key, value} = toRefs(props); }
context
: 上下文,context
是一个普通的JavaScript
对象,也就是说,它不是响应式的,这意味着你可以安全地对context
使用ES6
解构。
context
有4个属性:attrs
: 非响应式对象,跟vue2
的this.$attrs
一样,用于接收父组件传的但是子组件中未声明的值。emit
: 跟vue2
的this.$emit
一样 用于向父组件触发事件。slots
:非响应式对象,跟vue2
的this.$slots
一样,用于获取插槽的信息。expose
:用于当在setup
中使用渲染函数,而无法将组件中的data
这些暴露给外部组件(外部组件通过ref
获取)。// 子组件 <script> import { h, ref } from 'vue' export default { setup(props, { expose }) { const count = ref(0) const increment = () => ++count.value expose({ increment }) return () => h('div', count.value) } } </script> <!-- 父组件 --> <son ref="son"></son> <script> export default { mounted() { console.log(this.$refs['son'].increment()) } } </script>
执行
setup
时,组件实例尚未被创建(在setup()
内部,this
不会是该活跃实例的引用,即不指向vue
实例,Vue
为了避免开发者错误的使用,直接将setup
函数中的this
修改成了undefined
)
从setup()
中返回的对象上的property
返回并可以在模板中被访问时,它将自动展开为内部值。不需要在模板中追加.value
setup
函数只能是同步的不能是异步的
<script setup>
其实就是 setup
函数的一个语法糖。标签内的代码会被编译成setup()
函数里的内容(这样也能使得组件实例在被创建的时候才会执行,而普通的<script>
在组件引入时就执行)。
下面举几个例子看看语法糖的简便:
1. 变量和方法
<script setup>
import {ref} from 'vue';
import {minusNum} from 'xxx';
const num = ref(1);
function addNum() {
num.value++;
}
</script>
<script>
import {ref} from 'vue';
import {minusNum} from 'xxx';
setup() {
const num = ref(1);
function addNum() {
num.value++;
}
return {
num,
addNum,
minusNum
}
}
</script>
使用语法糖不需要将变量和方法(包括外部文件中的方法)返回即可使用。
2. 组件注册、自定义指令
<template>
<h1 v-direct>directive</h1>
</template>
<script setup>
import { Component1 } from 'xxx';
import { vDirect } from 'xxx';
</script>
<script>
import { Component1 } from 'xxx';
import { direct } from 'xxx';
export default {
components: {
Component1
},
directives: {
direct
}
}
</script>
使用语法糖组件就不需要在 component
注册,在组件中定义指令虽然也不需要在 directives
中注册,但是需要遵循 vNameOfDirective
这样的命名规范,否则并不会注册成指令。
3. 父子组件通信
<script setup>
import { defineProps, defineEmits } from 'vue'
const emit = defineEmits(['addNum']);
const addNumEmit = () => {
emit('addNum', 1);
}
const props = defineProps({
num: {
type: Number,
default: 0
}
})
console.log(props.num);
</script>
<script>
export default {
props: {
num: {
type: Number,
default: 0
}
},
setup (props, { emit }) {
console.log(props.num)
const addNumEmit = () => {
emit('addNum', 1)
}
return {
addNumEmit
}
}
}
</script>
可以与普通script一起使用
<script setup>
可以和普通的 <script>
一起使用。
<script>
export default {
mounted() {
console.log('mounted');
}
}
</script>
<script setup>
import { onMounted } from 'vue';
onMounted(() => {
console.log('setup mounted')
})
</script>
setup
中定义的生命周期钩子比普通script
先执行。
生命周期
在setup
中通过onXXX
来注册生命周期:
选项式 API
的生命周期选项和组合式 API
之间的映射
因为 setup
是围绕 beforeCreate
和 created
生命周期钩子运行的,因此setup
内部的没有beforeCreate
、created
对应的钩子映射。
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeUnmount
->onBeforeUnmount
unmounted
->onUnmounted
errorCaptured
->onErrorCaptured
renderTracked
->onRenderTracked
renderTriggered
->onRenderTriggered
activated
->onActivated
deactivated
->onDeactivated
这些函数接受一个回调函数,当钩子被组件调用时将会被执行:
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
选项式API与组合式API混用
选项式API
与组合式API
是允许混用的(需要Vue3
或vue2.7
后的版本)。如下:
<script>
import { reactive } from 'vue';
export default {
data() {
return {
value: 1
}
},
props: {
num: {
type: Number,
default: 0
}
},
setup() {
let name = reactive('leo');
return {
name
}
},
methods: {
getName() {
console.log(this.name);
}
}
}
</script>
在混用的时候需要注意由于setup
函数里的this
指向undefined
,因此不能使用选项式中定义的data
、methods
这些。
组件
组件注册
全局注册
vue2
中是直接使用vue
的原型中component
进行注册.
import Vue from 'vue';
Vue.component('xxx', xxx);
在vue3
中,则是在实例中进行添加
import { createApp } from 'vue'
const app = createApp({})
app.component('xxx', xxx);
局部注册
前面提到了对于<script setup>
不需要进行声明,直接引入即可使用,否则跟vue2
一样也需要在components
中注册。
<template>
<Component1 />
</template>
<script setup>
import { Component1 } from 'xxx';
</script>
<script>
import { Component1 } from 'xxx';
export default {
components: {
Component1
}
}
</script>
响应式API
reactive
reactive
用于将数据变成响应式数据。调用reactive
后返回的对象是响应式副本而非原始对象。其原理就是将传入的数据包装成一个Proxy
对象。
import { reactive, watchEffect } from 'vue';
const data = {
count: 0
};
const obj = reactive(data);
data === obj // false
watchEffect(() => {
// 用于响应性追踪
console.log(obj.count);
});
setTimeout(() => {
obj.count++;
}, 2000);
响应式转换是“深层”的——它影响所有嵌套
property
。在基于ES2015 Proxy
的实现中,返回的proxy
是不等于原始对象的。建议只使用响应式proxy
,避免依赖原始对象。
reactive
用于复杂数据类型,比如对象和数组等,当传入基础数据类型时,默认情况下修改数据,界面不会自动更新,如果想更新,可以通过重新赋值的方式。
readonly
接受一个对象 (响应式或纯对象) 或 ref
并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property
也是只读的,不能改变值,否则是报错。
import { reactive, watchEffect, readonly } from 'vue';
const data = {
count: 0
};
const obj = reactive(data);
const copy = readonly(obj);
watchEffect(() => {
// 用于响应性追踪
console.log(obj.count)
});
setTimeout(() => {
obj.count++;
copy.count++;
}, 2000);
ref
接受一个内部值并返回一个响应式且可变的 ref
对象。ref
对象仅有一个 .value
值,指向该内部值。跟reactive
类似,也是将数据变成响应式。
import { ref, watchEffect } from 'vue';
const count = ref(0);
const obj = ref({
count: 0
})
console.log(obj)
watchEffect(() => {
console.log(count.value);
})
watchEffect(() => {
console.log('obj.value.count: ', obj.value.count);
})
count.value++;
obj.value.count++;
ref和reactive的区别
ref
是把值类型添加一层包装,使其变成响应式的引用类型的值。ref(0) --> reactive( { value:0 })
reactive
则是引用类型的值变成响应式的值。
两者的区别只是在于是否需要添加一层引用包装,对于对象而言,添加一层包装后会被reactive
处理为深层的响应式对象,在调用unref
后就能看到其实对象是一个Reactive
对象。
像上面的例子,使用ref
同样可以将对象响应化,不过访问的时候需要调用value.
去访问内部属性。所以对于对象而言,最好使用reative
去响应化处理。
watch
vue3
中组合式api
watch
与vue2
中选项式的watch
完全等效。watch
需要监听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在监听源发生变化时被调用。
与 watchEffect
相比,watch
允许我们:
- 惰性地执行副作用;
- 更具体地说明应触发监听器重新运行的状态;
- 访问被监听状态的先前值和当前值。
监听单一源
源数据可以是一个reactive
,也可以直接是一个 ref
语法
watch( name , callback, options ) ;
name
: 需要监听的属性或者返回值的getter
函数callback
: 属性改变后执行的方法,接受两个参数-
newVal
: 新值
-
oldVal
: 旧值
options
: 配置项,可配置如下-
deep
:Boolean
, 是否深度监听
-
immediate
:Boolean
,是否立即执行
// 侦听reactive
const state = reactive({ count: 0, value: 1 })
// 只监听对象中的count
watch(
// 使用getter函数保证只监听了state中的count
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 侦听state中所有属性
watch(state, (newVal, oldVal) => {
// ...
})
// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
监听多个源
使用数组来同时侦听多个源数据,当任一源数据发生改变都会触发watch
const state = reactive({ count: 0, value: 1 });
const count = ref(0)
watch([state, count], ([newState, newCount], [oldState, oldCount]) => {
console.log(newState);
console.log(newCount);
})
state.count++;
count.value++;
深度监听
在watch
的第三个参数中传入deep: true
const state = reactive({ count: 0, value: { status: false } });
watch(() => state, (newVal, oldVal) => {
console.log('不会触发')
})
watch(() => state, (newVal, oldVal) => {
console.log('触发深度监听')
}, {
deep: true
})
state.value.status = true;
由于getter
方法只返回state
,对没有对内部的对象进行监听,因此内部对象的属性发生改变不会触发watch
。
当没有使用getter
方法而是传入state
这个reactive
数据,则不需要设置deep: true
都会进行深度监听。
立即执行
在watch
的第三个参数中传入immediate: true
,当传入数据就会执行一次。
watchEffect
立即执行传入的一个函数,响应式追踪其依赖,在其依赖变更时重新运行该函数。
它会监听引用数据类型的所有属性,不需要具体到某个属性,一旦运行就会立即监听,组件卸载的时候会停止监听
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
停止监听
当 watchEffect
在组件的 setup()
函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
在一些情况下,也可以显式调用返回值以停止侦听
const stop = watchEffect(() => {
/* ... */
})
// later
stop()
清除副作用
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate
函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时
- 侦听器被停止 (如果在
setup()
或生命周期钩子函数中使用了watchEffect
,则在组件卸载时)
watchEffect(onInvalidate => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id has changed or watcher is stopped.
// invalidate previously pending async operation
token.cancel()
})
})
computed
接受一个 getter
函数,并根据 getter
的返回值返回一个不可变的响应式 ref
对象。
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
或者,接受一个具有 get
和 set
函数的对象,用来创建可写的 ref
对象。
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0