Vue3
写在前面
本博文仅作为个人学习过程的记录,可能存在诸多错误,希望各位看官不吝赐教,支持错误所在,帮助小白成长!
一、Vite初体验
这里我们只是初次体验一下使用vite创建一个Vue3项目,听说这个玩意儿贼快!!
因为尤大说长期下,会推动vite作为Vue的主要项目构建工具!短期下vue-cli和vite并存!后续我们会对vite进行更深入的学习。vite官网:Home | Vite (vitejs.dev)
-
npm初始化
npm init @vitejs/app <project_name>
project_name 为可选参数,可以在配置选项中进行设置,然后针对项目进行配置选择。
-
到项目目录下,依赖安装
cd xxxx npm install
-
启动项目
npm run dev
说时迟那时快,啪的一下,很快啊,一下就启动起来了。嗯~~ 有bear来!
这是Vue3官方文档中贴出的Vue3中新特性一览:
二、Composition(组合式) API入门
2.0、关于
什么是Composition API? 为什么使用?
推荐阅读博文:vue3 composition(组合式)API 是什么?我为什么要使用它?_LGD_Sunday的专栏-CSDN博客
官方给出了一段引言,反复阅读:
通过创建 Vue 组件,我们可以将界面中重复的部分连同其功能一起提取为可重用的代码段。仅此一项就可以使我们的应用在可维护性和灵活性方面走得更远。然而,我们的经验已经证明,光靠这一点可能是不够的,尤其是当你的应用变得非常大的时候——想想几百个组件。当处理这样的大型应用时,共享和重用代码变得尤为重要。
目前我们使用SFC( single-file component,单页面组件)写法来管理组件,其初始目的就是将页面上重复的内容抽离出来形成模块进行管理和重用。可是当单个文件中的内容变得十分庞大,逻辑变得更加复杂时,有暴露出两个问题:
- 代码的可读性下降
- 存在大量重复逻辑
官方的例子中,给出了一个视图组件,这一个组件有好几项职责,这固然会使得代码的逻辑复杂度激增!并且一个视图组件中只有一套组件选项(data
、methods
、computed
、watch
),这一套组件选项就要同时管理这么多功能逻辑,如果代码中每一种颜色就表示一个逻辑关注点时,那么在“超大”组件中代码就变成这样的五颜六色。这就是我们在Vue2中使用的选项式API
这种代码维护起来简直就是噩梦,一个逻辑关注点的相关代码分散在文件的各个角落,我们在进行维护的时候,总是在上下翻寻代码,那么有没有可以将这些逻辑关注点的相关代码整理成块的方式?!
就像这样,即使代码逻辑变得复杂,开发者进行维护和优化的难度也大大降低!那么接下来就是Composition API大放异彩的时候了!选项式API 与 组合式API 的较量拉开序幕
2.1、setup
组合式API依然是作用于组件,所以我们需要一个位置来使用组合式API,它就是setup
。
setup是一个接受props
和context
参数的函数,它在组件被创建之前被调用,一旦props被解析后,setup就成为组合式API的入口。
由于setup执行时机的问题,组件还未被创建,所以组件中其他选项是无法在setup函数中使用的!当然this也是无法使用的!只能使用
props
(即外部传给组件的数据)
当setup执行完成后,其返回值中的内容将暴露给组件的其他部分:计算属性、方法、甚至是生命周期钩子函数以及组件的template。
废话不多说我们先来一个简单的案例:
App.vue
<template>
<h1>Example</h1>
<h2>Counter: {
{counter}}</h2>
<button @click="incr">+1</button>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
export default defineComponent({
name: 'App',
// ===================================
setup() {
return {
counter: 0
}
},
// ===================================
methods: {
incr() {
this.counter ++
console.log(this.counter)
}
}
})
</script>
我们使用setup函数返回一个counter,值为0,然后我们可以直接在template中引用这个counter!并且我们可以在methods中使用this.counter
去访问和使用 setup返回的counter。
但是,会发现一个问题:虽然counter的数值在增加,但是页面并没有变化?!
这是咋回事?!
因为setup()返回的counter,并不是响应式数据!
响应式:页面会响应数据的变化进行重新渲染,也就是我们的数据和页面是绑定的。
官网上有一张图,说明了这个问题的解决方案:
我们出现问题的原因是:template中的counter和代码逻辑中的counter只是主副本关系,两者相互独立。那么解决办法就是:创建一个counter的引用!如何创建呢?请看下节
2.2、ref
ref
是一个接收参数并将其包装成一个带value
的property的对象(Ref)并返回的函数,这样我们就拿到了一个数据对象的引用(即一个响应式数据!)我们可以通过对象的value
property对数据值进行修改。
那么我们修改一下上面的代码,让setup返回一个响应式数据:
<script lang="ts">
import {defineComponent, ref} from 'vue'
export default defineComponent({
name: 'App',
setup() {
let counter = ref(0)
return {
counter
}
},
methods: {
incr() {
this.counter ++
}
}
})
</script>
// 不过这里好像并没有使用.value
来操作响应式数据的值(可能是因为我们包装的原数据就是number),但是效果实现了响应式!
let counter = ref(2021)
console.log(counter.value)
console.log(counter)
// 2021
// App.vue:23 RefImpl {_rawValue: 2021, _shallow: false, __v_isRef: true, _value: 2021}
来看看声明文件中的ref函数,以及其返回值类型Ref:
总结:
ref()
为我们创建了一个响应式的引用,通过这个引用我们就可以获取到响应式数据,并进行操作!在组合API中我们会经常提到引用!
其实响应丢失的问题,使用ref一般只针对基本类型的数据(number、string)因为JavaScript中Number和String都是进行值传递的!。但是如果setup函数返回了一个对象,好像就没有这种顾虑,因为返回的本身就是一个对象的引用:
<template>
<h1>Example</h1>
<h2>Counter: {
{counter}}</h2>
<h4>{
{person}}</h4>
<button @click="incr"> +1 </button>
</template>
<script lang="ts">
import {defineComponent, ref} from 'vue'
export default defineComponent({
name: 'App',
setup() {
let counter = ref(2021)
return {
counter,
person: {
name: 'sakura',
age: 21
}
}
},
methods: {
incr() {
this.person.age ++
this.counter ++
}
}
})
</script>
2.3、生命周期钩子
为了使组合式API更加完整,还提供了生命周期钩子函数的创建,这都要归功于Vue中几个新导出的函数,组合式API中钩子的名字与选项式API相似,不过都以on
作为前缀。(例如:选项式中的mounted
,在组合式中为onMounted
)
setup() {
let counter = ref(0)
onMounted(() => {
console.log("`mounted` hook was triggered!")
counter.value = 2021
})
return {
counter,
person: {
name: 'sakura',
age: 21
}
}
},
好像和我们之前写钩子函数的方式不太一样:
mounted() {
console.log('选项式API的钩子被触发!')
}
// 或者这样
mounted: function() {
// .....
}
我们现在直接将钩子的回调函数作为参数传入!onMounted(hook: () => any)
2.4、watch监视
在组合式API中,我们可以使用vue导出的watch()
函数像在选项式API中那样对property的修改做出对应的响应操作。
watch函数接收三个参数:
- 要监视数据的响应式引用或者getter方法
- 一个回调函数
- 可选参数
下面是一个简单的watch函数使用:
setup() {
let counter = ref(0)
onMounted(() => {
console.log('`mounted` hook was triggered!')
counter.value = 2021
})
// 侦听counter
watch(counter,(newValue, oldValue) => {
console.log(`counter was changed from ${
oldValue} to ${
newValue}`)
})
return {
counter,
person: {
name: 'sakura',
age: 21
}
}
},
watch()
函数不仅可以在Vue实例的setup函数中使用,还可以在vue组件外使用!
import {
defineComponent, onMounted, ref, watch,} from 'vue'
// vue实例外部使用
let num = ref(0);
watch(num, (newValue, oldValue) => {
console.log('num was changed!!' + oldValue + '->' + newValue)
})
export default defineComponent({
name: 'App',
setup() {
let counter = num
onMounted(() => {
console.log('`mounted` hook was triggered!')
counter.value = 2021
})
watch(counter,(newValue, oldValue) => {
console.log(`counter was changed from ${
oldValue} to ${
newValue}`)
})
return {
counter,
person: {
name: 'sakura',
age: 21
}
}
},
methods: {
incr() {
this.person.age ++
this.counter ++
}
},
mounted() {
console.log('选项式API的钩子被触发!')
}
})
现在我们点击按钮,就会改变counter的数值,而counter保存的是num的引用,所以num的数值随之变化,两个侦听的回调函数都会被调用!
后面我们会详细看watch的高阶使用:响应式计算和侦听 | Vue.js (vuejs.org)
2.5、独立的computed属性
与watch一样,你可以从vue导入computed
函数,它可以让你在组件外部创建一个独立计算属性!
计算属性必须是以响应式数据为基础的!并且computed函数返回的也是一个响应式引用!
import {
computed, defineComponent, ref,} from 'vue'
let year = ref(2021);
let age = computed(() => {
return year.value - 2000
})
export default defineComponent({
name: 'App',
setup() {
let counter = year
return {
counter,
person: {
name: 'sakura',
age: age
}
}
},
methods: {
incr() {
this.counter ++
}
}
})
当页面上的按钮按下后,counter的数值变化带动其引用的响应式数据year的数值变化,year数值变化又触发了computed回调函数(即computed函数的第一个参数,一个getter)变化,然后得到新的age数值!
**值得注意的是:computed函数参数中getter函数返回的是一个只读的响应式引用!**所以如果我们要使用.value
来获取计算属性的值!
2.6、小结
一路下来,你可能感觉到我们一直都在壮大我们的setup()函数,往里面不停的塞东西。这样做的好处就是在实例在被mount之前,我们就可以用setup函数来处理一些事情。
但是setup函数不断壮大也会带给维护很大麻烦,但是你反过来看我们上述讲到的ref
、watch
、computed
都是可以独立在组件外部使用的!只需要从Vue中import即可!!
那么这样的话,我们的单个逻辑关注点完全可以抽出一个单独的js/ts文件文件来完成编写,配合使用ref、watch、computed函数。多个逻辑关注点,在组件代码中进行导入并在setup函数中进行组合即可!!(如果不太明白,可以看看官方文档的介绍案例!)
三、响应性
本章属于Vue的原理学习,如果我们想要理解组合式API的一些操作,那么学习理解Vue的响应性是比不可少的!非侵入性的响应性系统也是Vue最独特的特性之一!
3.1、什么是响应性
学习了这么久的Vue,对于响应性应该并不陌生。官网给出了一个经典的例子:Excel编程,当我们对某个表格进行了编程设置为多个单元格之和时,那么当单元格数值发生变化就会引起编程单元格的值变化:
例如上图中A3单元格被编程为=SUM(A1:A2)即A1到A2单元格的求和,一旦求和范围单元格发生了变化,结果单元格随之变化!
可是在我们的JavaScript中,并没有这种效果:
我们用JavaScript代码模拟了我们在Excel中的操作,但是结果并不相同!也就是说普通的JavaScript代码是无法做到响应性更新的!
我们需要如何使用JavaScript完成这个任务呢?官方给出了一种解决思路:
- 检测其中某一个值是否发生变化
- 用跟踪 (track) 函数修改值
- 用触发 (trigger) 函数更新为最新的值
3.2、Vue中如何实现(难)
官方对这部分做了详细的说明,我们可以一步步进行模拟。
首先为了实现响应性更新,我们需要将我们更新数据的动作包裹成一个函数:
const updateSum = () => {
sum = a1 + a2
}
下一步就是当数据更新后,我们如何告知Vue调用这个函数?
Vue通过一个副作用(Effect)来跟踪当前正在运行的函数,副作用是一个函数包裹器,在函数调用前就启动了跟踪,并且副作用知道何时运行,能在需要时再次执行!
createEffect(() => {
sum = a1 + a2
})
createEffect()就是辅助追踪和执行的!它可以这样实现:
// 维持一个执行副作用的栈
const runningEffects = []
// fn 即我们上面的updateSum函数
const createEffect = fn => {
// 将传来的 fn 包裹在一个副作用函数中
const effect = () => {
runningEffects.push