个人博客:Vue3快速入门(一)
实习进入公司后,简单安装vscode、node、git等必备工具后,导师直接问有没有学过vue3,说了只学过vue2后,就给我分享了两篇文章,以及让我查看文档,快速从vue2加速到vue3。本文将参考到很多文章、文档,部分借用的可能没有备注出来,侵权请联系。(不过就算是参考,例子我很多按自己的理解弄成自己以后看更容易理解的了,虽然也差不了多少)
顺带附上以前的笔记:Vue2
开始的第一步,当然就是构建项目啦。这里就有一个重大的区别了,vue3使用web开发构建工具vite,而不是webpack
vite优点:
另外,vite开启的服务器端口默认是3000,而不是8080
npm init vite@latest
# 使用yarn
yarn create vite
按需选择
项目启动的方式不再是npm run serve
了,而是npm run dev
Vue2
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
Vue3
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
Vue3引入的不再是Vue构造函数,而是createApp
工厂函数。
setup函数是Composition API(组合API)的入口
在setup函数中定义的变量和方法需要return出去,才能在模板中使用
实际上和vue2类似,只不过在vue2中数据在data函数中,方法在methods节点中,而vue3则是"更有人情味了",和原生js更相似,数据和方法就是普通的变量和方法,只需要return出去,就能在模板中使用。
Vue3
<template>
<button @click="alertName">alert namebutton>
<p>
{{ nickname }}
p>
template>
<script>
export default {
name: "App",
setup() {
let nickname = "赤蓝紫"
function alertName() {
alert(`我是${nickname}`)
}
return {
nickname,
alertName
}
}
}
script>
template里不再必须要一个唯一的根标签了,这个改进个人感觉很舒服。查了一下资料,发现是虽然看似没有根节点,但是只是vue3的根节点是一个虚拟节点,不会映射到一个具体节点。因为一棵树必须有一个根节点,所以使用虚拟节点作为根节点非常有用。
Vue2
<template>
<div id="app">
<button @click="alertName">alert nicknamebutton>
<p>
{{ nickname }}
p>
div>
template>
<script>
export default {
name: "App",
data() {
return {
nickname: "赤蓝紫"
}
},
methods: {
alertName() {
alert(`我是${this.nickname}`)
}
}
}
script>
先看一下,下面这个例子
<template>
<button @click="changeName">change namebutton>
<p>
{{ nickname }}
p>
template>
<script>
export default {
name: "App",
setup() {
let nickname = "赤蓝紫"
function changeName() {
nickname = "clz"
console.log(nickname)
}
return {
nickname,
changeName
}
}
}
script>
点击后,发现数据改变了,但是p标签中的名字却没变,难道是vue的数据驱动视图失效了。
当然,并不是,只是vue3中多出了响应式数据和普通数据的区别,只有响应式数据才能驱动视图的改变。而上面的nickname只是字符串,不是响应式数据,试图自然也不会发生改变。
而将字符串变成响应式数据也非常简单,只需要引入并使用ref
即可
<template>
<button @click="changeName">change namebutton>
<p>
{{ nickname }}
p>
template>
<script>
import { ref } from "vue"
export default {
name: "App",
setup() {
let nickname = ref("赤蓝紫")
function changeName() {
nickname = "clz"
console.log(nickname)
}
return {
nickname,
changeName
}
}
}
script>
然而,还是不行,这是为什么呢?原因就是ref把nickname变成RefImpl的实例对象了,修改的时候要.value
去修改,底层还是用的get和set去操作。
把上面changeName方法中的nickname = "clz"
注释掉后,再点击按钮,就能知道变成RefImpl的实例对象了
最终版本:
<template>
<button @click="changeName">change namebutton>
<p>
{{ nickname }}
p>
template>
<script>
import { ref } from "vue"
export default {
name: "App",
setup() {
let nickname = ref("赤蓝紫")
function changeName() {
nickname.value = "clz"
// console.log(nickname)
}
return {
nickname,
changeName
}
}
}
script>
修改要用.value
来修改,为什么显示时不用{{ nickname.value }}
来显示呢?这是因为vue3检测到是ref对象后,直接给你nickname.value了(还挺人性化)
<template>
<button @click="changeName">change namebutton>
<p>
name: {{ people.name }}
p>
<p>
age: {{ people.age }}
p>
template>
<script>
import { ref } from "vue"
export default {
name: "App",
setup() {
let people = ref({
name: "赤蓝紫",
age: 21
})
function changeName() {
people.value.name = "clz"
}
return {
people,
changeName
}
}
}
script>
乍一看和上面的ref中一样,但是实际上如果是将对象类型转换成响应式数据是应该使用函数reactive
的,只是如果ref中是对象的话,会自动调用reactive
而已。打印people.value
可以发现不再是RefImpl
对象了,而是Proxy
对象。
Object.defineProperty
里的get
和set
进行数据劫持来实现响应式Proxy
来实现响应式<template>
<button @click="changeName">change namebutton>
<p>
name: {{ people.name }}
p>
<p>
age: {{ people.age }}
p>
<p>hobby: {{hobbys[0]}}, {{hobbys[1]}}p>
template>
<script>
import { reactive } from "vue"
export default {
name: "App",
setup() {
let people = reactive({
name: "赤蓝紫",
age: 21
})
let hobbys = reactive(["音乐", "动漫"])
// let hobbys = ["音乐", "动漫"]
function changeName() {
people.name = "clz"
hobbys[0] = "学习"
}
return {
people,
changeName,
hobbys
}
}
}
script>
上面有个问题:数组形式的不通过reactive()转换成响应式也是响应式数据,暂不知道原因
也可以按vue2中data的形式来写
<template>
<button @click="changeName">change namebutton>
<p>
name: {{ data.people.name }}
p>
<p>
age: {{ data.people.age }}
p>
<p>hobby: {{data.hobbys[0]}}, {{data.hobbys[1]}}p>
template>
<script>
import { reactive } from "vue"
export default {
name: "App",
setup() {
let data = reactive({
people: {
name: "赤蓝紫",
age: 21
},
hobbys: ["音乐", "动漫"]
})
function changeName() {
data.people.name = "clz"
data.hobbys[0] = "学习"
}
return {
data,
changeName
}
}
}
script>
ref和reactive的区别:
ref | reactive |
---|---|
定义基本类型数据 | 定义对象或数组类型数据 |
通过Object.defineProperty() 的get 和set 来实现响应式(数据劫持) |
通过Proxy 实现响应式(数据劫持),通过Reflect 操作源代码内部数据 |
操作数据需要.value ,读取不需要 |
操作个读取数据都不需要.value |
计算属性,和vue2差不多
<template>
r: <input type="text" v-model.number="color.r"><br>
g: <input type="text" v-model.number="color.g"><br>
b: <input type="text" v-model.number="color.b"><br>
rgb: <input type="text" v-model="color.rgb" :style="{color: color.rgb}">
template>
<script>
import { reactive, computed } from "vue"
export default {
name: "App",
setup() {
let color = reactive({
r: 255,
g: 0,
b: 0
})
color.rgb = computed(() => {
return `rgb(${color.r}, ${color.g}, ${color.b})`
})
return {
color
}
}
}
script>
如果修改计算出来的东西,会报警告,因为计算属性是只读属性
实现可修改:
<template>
r: <input type="text" v-model="color.r"><br>
g: <input type="text" v-model="color.g"><br>
b: <input type="text" v-model="color.b"><br>
rgb: <input type="text" v-model="color.rgb" :style="{color: color.rgb}">
template>
<script>
import { reactive, computed } from "vue"
export default {
name: "App",
setup() {
let color = reactive({
r: 255,
g: 0,
b: 0
})
color.rgb = computed({
get() {
console.log(color.r)
return `rgb(${color.r},${color.g},${color.b})`
},
set(value) {
let rgbList = value.split(",")
color.r = rgbList[0].slice(4)
color.g = rgbList[1]
color.b = rgbList[2].slice(0, -1)
}
})
return {
color
}
}
}
script>
实现计算属性可修改的关键:computed()参数为一个对象,对象中有一个get方法用来获取值,set方法用来修改值
监听器
<template>
<div class="home">
<h1>当前数字为:{{num}}h1>
<button @click="num++">+1button>
div>
template>
<script>
import { ref, watch } from "vue"
export default {
name: "Home",
setup() {
let num = ref(0)
watch(
num, // 如果想监听多个数据,则第一个参数是要监听的数据数组,其中newValue、oldValue也是数组,第一个元素就是监听的第一个参数
(newValue, oldValue) => {
console.log(`数字增加了,现在的值${newValue}, 原值${oldValue}`)
}
// {
// immediate: true,
// deep: true
// } // 第三个参数就是选项设置,可设置立即监听、深度监听
)
return {
num
}
}
}
script>
<template>
<div class="home">
<h1>age:{{people.age}}h1>
<button @click="people.age++">年龄加1button>
<h1>salary:{{people.job.salary}}h1>
<button @click="people.job.salary+=100">薪水加100button>
div>
template>
<script>
import { reactive, watch } from "vue"
export default {
name: "Home",
setup() {
let people = reactive({
name: "赤蓝紫",
age: 21,
job: {
salary: -10
}
})
watch(
people,
(newValue, oldValue) => {
console.log(`信息改变了`, newValue, oldValue) // 直接监听reactive所定义的一个响应式数据,会出现oldValue也是更新后的值,且默认开启深度监听,还无法关闭
},
{
deep: false
}
)
// 监听reactive所定义的一个响应式数据中的某个属性
// 如果是对象还需要开启深度监听,才能监听到变化。
// 或者变为监听people.job.salary(直接手动直接指出最终的目标)
// watch(
// () => people.job,
// (newValue, oldValue) => {
// console.log(`工作信息改变了`, newValue, oldValue)
// },
// {
// deep: true
// }
// )
return {
people
}
}
}
script>
如果监听器用来监听reactive定义的响应式数据,那么无法获取到旧数据,而且默认开启深度监听,无法关闭深度监听
watchEffect:
immediate: true
)<template>
<div class="home">
<h1>age:{{people.age}}h1>
<button @click="people.age++">年龄加1button>
<h1>salary:{{people.job.salary}}h1>
<button @click="people.job.salary+=100">薪水加100button>
div>
template>
<script>
import { reactive, watchEffect } from "vue"
export default {
name: "Home",
setup() {
let people = reactive({
name: "赤蓝紫",
age: 21,
job: {
salary: -10
}
})
watchEffect(() => {
const salary = people.job.salary
console.log("工资变更")
})
return {
people
}
}
}
script>
vue3中,beforeDestroy改为beforeUnmount,destroyed改为unmounte
beforeCreatd和created没有API,因为setup实际上就相当于这两个生命周期函数
使用示例:
<template>
<h2>{{num}}h2>
<button @click="num++">+1button>
template>
<script>
import { onMounted, onUpdated } from "vue"
import { ref } from "vue"
export default {
name: "Home",
setup() {
onMounted(() => {
console.log("onMounted")
}),
onUpdated(() => {
console.log("数据更新啦")
})
let num = ref(0)
return {
num
}
}
}
script>
toRef就是把数据变成ref类型的数据,toRefs
就是将多个数转换成响应式数据
先引用一下之前的例子:
<template>
<div class="home">
<h1>age:{{people.age}}h1>
<button @click="people.age++">年龄加1button>
<h1>salary:{{people.job.salary}}h1>
<button @click="people.job.salary+=100">薪水加100button>
div>
template>
<script>
import { reactive, watchEffect } from "vue"
export default {
name: "Home",
setup() {
let people = reactive({
name: "赤蓝紫",
age: 21,
job: {
salary: -10
}
})
watchEffect(() => {
const salary = people.job.salary
console.log("工资变更")
})
return {
people
}
}
}
script>
仔细观察,可以发现template
中,使用了很多次people.
,于是想偷一下懒,return的时候耍点小聪明
return {
age: people.age,
salary: people.job.salary
}
哦豁,点击按钮不再能改变数据了,原因就是因为return出去的数据不是响应式,而是number,自然不能改变。验证也很简单,只要在watchEffect()
中顺便打印出people.age
就行了。
通过toRef
就可以实现自动修改people
里的数据,不要忘记引入toRef
了
return {
age: toRef(people, 'age'),
salary: toRef(people.job, 'salary')
}
这种时候,有可能会想到使用ref就可以了,即以下形式
return {
age: ref(people.age), // ref()不会改变people里的数据,而toRef可以修改
salary: ref(people.job.salary)
}
这样子,乍一看,效果确实一样,但是,实际上的数据并没有发现改变,通过监听器就可以发现
为什么呢?实际上使用ref的话,有类似于new出来一个对象,new出来的对象自然和原来的数据没有什么实质上的联系
使用toRefs
就可以稍微偷一下懒
return {
...toRefs(people), // 解构,只能解构一层,所以深层的还是要写
...toRefs(people.job)
}
移除keyCode
作为v-on
的修饰符
<input v-on:keyup.13="submit" />
<input v-on:keyup.enter="submit" />
移除native
作为v-on
的修饰符
移除filter
过滤器
Vue 3 迁移策略笔记
app.vue
<template>
<router-view>router-view>
template>
<script>
import { useRoute, useRouter } from "vue-router"
export default {
setup() {
const route = useRoute()
const router = useRouter()
console.log(route.path)
router.push("/home")
}
}
script>
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
router \ index.js
import { createRouter, createWebHashHistory } from "vue-router"
const routes = [
{
path: "/home",
name: "home",
component: () => import("../components/home.vue")
},
{
path: "/login",
name: "login",
component: () => import("../components/login.vue")
}
]
export default createRouter({
history: createWebHashHistory(),
routes
})
注意:
$router
和 $route
,所以不需要在 setup
中返回 router
或 route
vue-router
中引入的useRoute
,useRouter
相当于vue2的 this.$route
,this.$router
.vue
后缀编程式导航传参
<template>
<router-view>router-view>
template>
<script>
import { useRoute, useRouter } from "vue-router"
export default {
setup() {
const route = useRoute()
const router = useRouter()
// query编程式导航传参
// router.push({
// path: "/home",
// query: {
// id: 666
// }
// })
// params编程式导航传参
router.push({
name: "login", // 需要使用命名路由
params: {
// 路由规则那里也要配成path: "/login/:id",
id: 666
}
})
}
}
script>
home.vue
<template>
home
template>
<script>
import { onBeforeRouteLeave } from "vue-router"
export default {
setup() {
onBeforeRouteLeave((to, from) => { // 默认可前往,可通过return false禁止前往
console.log("去", to)
console.log("来自", from)
})
}
}
script>
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
router.beforeEach((to, from) => { // next是可选参数,可写可不写,return false是取消导航,否则意味着通过验证
console.log('去', to)
console.log('来自', from)
// return false
})
createApp(App)
.use(router)
.mount('#app')
<template>
<button @click="getValue" ref="btn">点击button>
template>
<script>
import { getCurrentInstance, nextTick, reactive, ref } from "vue"
export default {
setup() {
const ci = getCurrentInstance()
const { proxy } = getCurrentInstance()
// console.log(getCurrentInstance()) //只有在setup()这一直接块才可以,如果在函数中,则会得到null
function getValue() {
console.log(ci.refs.btn)
console.log(proxy.$refs.btn) // 也可以通过proxy.$refs来获取
}
return {
getValue
}
}
}
script>
nextTick():在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
<template>
<button @click="change" ref="btn">{{msg}}button>
template>
<script>
import { getCurrentInstance, nextTick, ref } from "vue"
export default {
setup() {
let { proxy } = getCurrentInstance()
let msg = ref("Hi")
function change() {
const btn = proxy.$refs.btn
msg.value = "Hello"
console.log("直接打印:", btn.innerText)
nextTick(() => {
console.log("nextTick:", btn.innerText)
})
}
return {
msg,
change
}
}
}
script>
使用
,可以通过to
将teleport下的html传送到指定位置(如传送到body
中)。
<template>
<div class="one">
<div class="two">
<teleport to="body">
<div class="three">
<div class="four">div>
div>
teleport>
div>
div>
template>
<script>
export default {
name: "App"
}
script>
Vue3.0通过setup()函数,需要把数据和方法return出去才能使用,但是Vue3.2中,只需要在srcipt标签上加上setup
属性,这样子就无需return,template就可以直接使用了
<template>
<button @click="alertName">alert namebutton>
<p>{{ nickname }}p>
template>
<script setup>
let nickname = "赤蓝紫"
function alertName() {
alert(`我是${nickname}`)
}
script>
参考:
vue3保姆级教程
官方文档