一.组合式api
以往组件选项卡痛点
通常一个业务逻辑会散落在不同的选项(生命周期、计算属性等等配合完成)中,复杂的组件会有多个业务逻辑,那么这些不同的业务员逻辑点都会散落在同一个的选项中,例:一个methods选项中可能会有完成不同业务逻辑的方法。
那么在代码阅读上就会很困难,往往一个逻辑的阅读需要在methods、mounted、watch、compouted中来回跳跃,尤其是每个选项内又有复杂且不同的逻辑块时
data(){
return{
}
},
methods:{
// 获取用户列表
getUserList(){
// 100行代码
},
// 获取角色列表
getRoleList(){
// 100 行代码
}
},
computed:{
// 用户降序的列表
orderUsers(){
// 100行代码
},
// 角色分组列表
RoleGroup(){
// 100 行代码
}
},
mounted(){
// 获取用户列表动作
// 50 行代码
// 获取角色列表
// 50行代码
}
像上面这个组件就是有用户和角色两个逻辑分散在各个选项中,想完成其中一个逻辑的分析阅读会很麻烦,若是比这更复杂的组件,阅读性可想而知
组合式api (拆分逻辑) setup
在setup中运用各种函数创建data、computed、生命周期钩子函数等,所以就可以将完成同一逻辑的各种属性封装到一个单独的模块中,然后在setup中把所有的模块汇总暴漏给组件。
避免使用this
setup的调用在data、computed、methods被解析之前,所以在setup中无法直接通过this调用到其中的数据
外部调用setup中声明的数据
setup是个函数,将其中创建的响应式数据ref、计算属性computed等最终return返回就可以了,其效果和正常选项卡声明是一样的。
setup中api函数使用方法
1.创建响应式数据(同data选项卡)
- ref
- reactive
- toref
- torefs
import { ref, toRef, toRefs, reactive } from 'vue'
// 创建
const name = ref('张三')
const student = reactive({name:'张三'})
// 保持引用连接
const sName = toRef(student,'name')
const {sName} = toRefs(student)
return { name,student }
后面再对以上四个的作用及区别做详细讲解
2.注册生命周期钩子
beforeCreate、created:不需要在setup中使用,因为setup是围绕beforeCreate和created生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在setup函数中编写。 2.beforeDestroy改名为beforeUnmount3.destroyed改名为unmounted
import { onMounted } from 'vue'
setup(){
onMounted(()=>{})
}
3.计算属性
computed
import { computed,ref } from 'vue'
const stu = reactive({name:'张三',age:20})
const name = computed(() => stu.name)
const age = computed(() => stu.age)
return { stu, name, age }
4.监听
watch函数接受三个参数
- 一个想要侦听的响应式引用或 getter 函数
- 一个回调 3.可选的配置选项
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
5.访问props中的数据
setup接受两个参数:
- props
- context ( { attrs, slots, emit } )
import { toRefs, computed }
setup(props){
const { name } = props
const comName = computed( () => name)
return comName
}
当props中的name发生改变时,comName并不会发生改变
ES6的解构会消除
proxy的响应性,解构本身就是一种拷贝,自然就会失去和原本数据引用的连接,成为一个独立的数据,那么从vue层面来看,就像是失去了响应性
可以使用toRefs保留响应连接
import { toRefs, computed }
setup(props){
const { name } = toRefs(props)
const comName = computed( () => name.value)
return comName
}
computed会随着props中的name的改变而改变
6.emit抛出事件
利用setup第二个参数context
context包含attrs、slots、emit三个属性
emits:['nameChange'],
setup(props,{emit}){
emit('nameChange','张三')
}
7.元素ref引用
vue2.x
<div ref="myRef"></div>
this.$refs.myRef
// 如果元素是循环生成的,即多个同名的 ref时,$refs获取到的则是一个数组
vue3.x
<div ref="myRef1"></div>
<button @click="showRef"> showRef </button>
setup(){
const myRef1 = ref()
function showRef(){
console.log(myRef1)
}
return { myRef1,showRef }
}
当多个同名的ref时,使用方式也发生了改变,在本文末有详细介绍。
8.调用路由对象
useRouter、useRoute
import { useRouter, useRoute } from 'vue-router'
setup(){
const router = useRouter()
const route = useRoute()
}
需要注意的是,必须在setup顶层使用,例如下面这种就会有问题
import { useRouter, useRoute } from 'vue-router'
setup(){
function nameChange(){
const router = useRouter()
const route = useRoute()
console.log(router) // undefined
console.log(route) // undefined
}
return { nameChange }
}
9.调用vuex对象
vuex4.x版本是支持vue3的,目前在setup中使用vuex,无法使用map辅助函数。useStore
import { useStore } from 'vuex'
import { computed } from 'vue'
setup () {
const store = useStore()
const userName = computed(() => store.state.user.userName )
return { userName }
}
同样需要注意的是,必须在setup顶层使用,例如下面这种就会有问题
import { useStore } from 'vuex'
setup(){
function nameChange(){
const store = useStore()
console.log(store) // undefined
}
return { nameChange }
}
ref、reactive、toRefs、toRef的区别以及作用
1.添加响应状态
1.ref
ref是将单独的(基本类型) 包装进一个有value属性的可变响应式对象中
<template>
<div>{{ name }}</div>
<div>{{ name.value }}</>
</template>
import {ref} from 'vue'
const name = ref('张三')
console.log(name.value) // 张三
return {name,stu:{name}}
在模板中访问时,浅层次的ref会被解包,无需.value。而嵌套的深层次结构则需要.value
2.reactive
reactive为js对象创建响应式状态
<template>
<div>{{ stu.name }}</div>
</template>
import { reactive } from 'vue'
const stu = reactive( {name: '张三'} )
console.log( stu.name ) // 张三
return {stu}
区别:
ref运用与基本数据类型响应式数据的创建,reactive运用于对象响应式数据的创建。
为什么说ref运用基本类型,reactive运用对象?
const stu = ref({name:'张三'})
console.log(stu)
首先,上面的代码是没有问题的,最起码从语法上来讲,是允许的。我们先看一下会输出什么
可以看到stu本身是Ref类型,没问题,但是.value的值却是Proxy。vue3的双向绑定的实现手法就是利用ES6的Proxy代理实现的,reactive内部也是使用同样的方法实现响应式状态的添加。
由此可以看出,若ref函数传入的是Object,其最终也是调用了reactive。
另外,若reactive传入的是基本数据类型则不会具备响应性,数据发生更改,视图不会发生更改。
所以基本数据类型使用ref,复杂数据类型(Array的最终原型也是Object)使用reactive
2.保持响应连接
toRef、toRefs
<template>
<div>{{ showAge }}</div>
<button @click='changeAge'>changeAgeBtn</button>
</template>
setup: () => {
const stu = reactive({
name:{
value:'张三'
},
age:24
})
let {age} = stu
const changeAge = function(){
age += 1
}
const showAge = computed(()=>{
return '我的年龄是' + age
})
return { showAge,changeAge }
}
虽然stu是使用reactive添加的响应式状态,但是ES6的解构会消除响应状态,即age只是将值拷贝了,但是消除了响应性,当调用changeAge改变age的值时,并不会触发computed以及视图的更新。
此问题只有解构出的是基本类型时会出现,若解构获取的是
name。因为引用类型的特性,会指向stu,即会触发stu的响应状态,视觉上来看,相当于name保留了响应状态
可以使用toRef、toRefs,将我们要解构的数据包装成ref,保留对源对象的引用连接及响应状态
修改上述代码
<template>
<div>{{ showAge }}</div>
<button @click='changeAge'>changeAgeBtn</button>
</template>
setup: () => {
const stu = reactive({
name:{
value:'张三'
},
age:24
})
let {age} = toRefs(stu)
// let age = toRef(stu,'age')
const changeAge = function(){
age.value += 1
}
const showAge = computed(()=>{
return '我的年龄是' + stu.age
// return '我的年龄是' + age.value
})
return { showAge,changeAge }
此时改变age.value那么stu.age也会发生变化,无论它俩谁发生改变。都会触发computed以及改变视图
区别:
toRefs可以包装多个数据,但是不会创建不存在的property
const { name,age } = toRefs(stu)
toRef 指定某个property,即使property不存在,也会返回一个可用的ref
const name = toRef(stu,'name')
const addres = toRef(stu,'addres') // 即使不存在 也会返回一个带有value属性的对象
自定义事件 $emit
vue3多了一个emit选项卡,必须在其中显示定义要抛出的事件名,这将是区分自定义组件原生事件和自定义事件的关键所在
emit:['changeAge']
1.自定义组件v-model
与vue2.X最大的改变,就是组件可以绑定多个v-model了
vue2.x时的v-model
在vue2.X时期,当一个封装好的组件有多个数据需要v-model这种双向数据流通需求时,我们大部分有三种选择:
sync修饰符,使prop在子组件可更改。- 利用
model&emit&props实现自定义组件v-model(可选择默认事件名和props,也可显示指定) - 子组件内部对数据进行操作后,利用
$emit抛出事件并携带参数,然后父组件捕捉该事件后,将获取的参数再次赋值给子组件定义的prop(自定义组件v-model的实现原理也是这样,只是vue帮我们写好了一个语法糖) 下面为自定义组件v-model和手动父子传值的实现sub.vue
<template>
<div>
<!-- v-model -->
<input type="text" :value="name" @input="emit('modelName',$event.target.value)">
<!-- 另类v-model -->
<input type="text" :value="name" @input="emit('changeAge',$event.target.value)">
</div>
</template>
<script>
export default {
props:{
name:String,
age:Number
},
model:{
prop:'name',
event:'modelName'
},
}
</script>
parent.vue
<template>
<sub v-model="name" :age="age" @changeAge="changeAge" />
</template>
<script>
export default {
data(){
return {
name:'张三',
age:20
}
},
methods:{
changeAge(param){
this.age = param
}
}
}
vue3的v-model
1.修改v-model默认的事件名和prop,并且不再需要model选项
parent.vue
<my-component v-model:title="parTitle"></my-component>
myComponent.vue
<input type:"text" :value="title" @input="$emit('update:title',$event.target.value)" />
props:{
title:String
},
emit:['update:title']
子组件抛出
update:prop事件,父组件绑定v-model时传参v-model:prop。就完成了指定prop为v-model。在emit选项注册自定义要抛除的事件,是因为native修饰符移除后,用来区分原生事件和自定义事件的。
2.多个v-model
在上面的基础之上,指定多个prop就可以了
parent.vue
<my-component v-model:title="parTitle" v-model:age="parAge"></my-component>
myComponent.vue
<input type:"text" :value="title" @input="$emit('update:title',$event.target.value)" />
<input type:"text" :value="age" @input="$emit('update:age',$event.target.value)" />
props:{
title:String,
age:Number
},
emit:['update:title','update:age']
v-model修饰符
vue3除了原先硬编码的那三个v-model修饰符外,增加了自定义修饰符
parent.vue
<my-component v-model:title.show="myText"></my-component>
myComponent.vue
props:{
title:String,
titleModifiers:{
default: () => ({})
}
},
emit:['update:title'],
methods:{
emitValue( value ){
if(this.titleModifiers.show){
this.$emit('update:title', value)
}
}
}
修饰符的值在[prop]Modifiers命名的prop中获取,具体看上例:
title的修饰符show,在命名为titleModifiers的prop中获取
二、script setup
script setup是组合式api基础上的语法糖
在3.0版本是实验阶段,随着3.2版本的更新,script setup正式宣布可以稳定使用。
具体内容参考:script setup官方文档
1.script setup与普通script的关系
script setup最终会被编译成setup()函数,在官方文档中有提到,一个组件可以有多个script块,和一个script setup块,其原因就是一个组件不可能同时存在两个同名的函数,也就是两个setup()函数。
在官方文档中script setup与普通script混合使用的描述中并不清晰,他们的确可以混合使用,但是在书写顺序上,普通的script要在script setup之上,不然就会报错,vue文档中的顺序是正确的,但是没有提到这一点。
另外script setup也会顶掉普通script中的setup()。普通script中的setup()并不会执行。其他选项卡内代码是互不影响的。
defineProps、defineEmits、defineExpose
它们不需要被导入!直接使用即可,遇到问题,解决方案看下方坑位
他们俩是不需要
```js
<script></script>
<script setup></script>
其他细节改动
1.native修饰符移除
现在在子组件上触发未被emit选项中定义过的事件,都会被当作原生事件处理。
2.template v-for
现在建议将template v-for 的key直接写在template标签上
3.多个ref
vue2.x
<div v-for="v in list" :key="v.key" ref="myRef"></div>
this.$refs.myRef[0]
会将相同的ref转化为数组获取
vue3.x
<div v-for="v in list" :key="v.key" :ref="setRef"></div>
setup(){
let refList = []
const setRef = el => {
refList.push(el)
}
return { setRef }
}
现在是为其绑定一个函数,在函数中将dom灵活存储
坑位
1.自定义组件v-show无效
vue3的template支持了多根节点,但是需要注意的是
v-show作用在多根节点组件上时会失效,v-show需要一个顶级的根节点,来通过样式来控制显示隐藏,如果是多个根节点的话,显然是有问题的。v-if可以正常使用,因为其直接控制的是组件的加载与否
2.script setup与普通script混合使用时,会顶掉普通函数内的setup()
3. defineProps/defineEmits/defineExpose is not defined
这是eslint的报错,只需要在eslint的配置项里(eslintrc.js),配上这三个的全局变量就可以了
globals: {
defineProps: true,
defineEmits: true,
defineExpose: true,
},
4.vue3.2更新的style新特性(使用js变量)问题
这个问题主要是由于
vue文档的疏忽导致的,经过一段时间未看到vue有更改,所以就提一下吧。
在vue3.2中,style可以使用<script>中声明的变量了,并且也是可以响应式更改的,具体的语法就是在js中声明变量,然后style中v-bind(nameColor)引用。问题在于响应性这里,文档中在介绍script setup与style结合使用时,变量并没有采用reactive或ref设置响应性,故如果采用文档中的代码,时不会响应更改的.
script setup中需要使用reactive等为数据设置响应性。
结束语
vue3的更改远不如此,本笔记仅记录使用层面的更改,强烈建议将vue2迁移vue3指南的文档详细看一遍。 v3.cn.vuejs.org/guide/migra…