3.0较比于2.x的特性,没用过或者会忘的知识
一、组合式API
这可是VUE3.0的重头戏
在此之前,我们有mixin嘛,就是创建一个对象mixin,然后里面也是有data,created之类的,然后在某个和组件中直接导入就可以使用
组合式API就是更高级的mixin这种
setup组件可以包含mixin,然后mixin也可以包含data\created这些。
1)setup
最早创建的东西,比mounted还要早,里面可以包容万物,data,method,computed都可以,只要在最后面return出去就行
<template>
<div>
<div @click="handleClick">
{{ name }}
</div>
</div>
</template>
<script>
export default {
name: 'Index',
components: {},
//最早执行
setup(props, context) {
return {
//相当于data中的dell
name: "dell",
//方法也是可以的
handleClick: () => {
alert(123)
}
}
},
created() {
//可以打印,也可以调用setup里面的东西
console.log(this.$options.setup())
},
}
</script>
2)处理响应式数据:ref和reactive
but,单单在setup里面,不是响应式的
处理基础类型 的用ref,处理非基础类型的用reactive
比如现在这个name 2s过后由dell变成aaa,对象也变
<template>
<div>
<div>
<!-- 因为在setup里面的,所以写name默认读取的是name.value-->
{{ name }}
<br>
{{ obj }}
</div>
</div>
</template>
<script>
import {ref, reactive} from "vue";
export default {
name: 'Index',
components: {},
setup(props, context) {
//相当于let name = 'dell' 的响应式写法,就只是利用ref
//其原理就是将 'dell' 变成 proxy({value: 'dell'})的一个响应式引用
let name = ref('dell');
//{age:18} 变成 proxy({age:18})的一个响应式引用过
let obj = reactive({age: 18})
//异步操作
setTimeout(() => {
//基础类型是要 .value,因为在ref已经改变了
name.value = 'aaa'
obj.age = 24
}, 2000)
return {
name,
obj
}
},
}
</script>
3)更简单地写ref(实验功能)
可以看到,我们想直接改变ref中的数,需要写a.value = xx 这样才能改,但是vue 的实验版本更新了个好用的语法糖
const a = $ref(0)
console.log(a) // 输出0
// console.log(a.value) //会报错
虽然好用,但要开启vue的实验功能
目前还没定稿,实验功能是这样写的,所以真实项目里面就不要用了
二、readonly
正如其名,只读,其写也是一样
setup(props, context) {
const {readonly} = Vue
let obj = reactive({age: 18})
//此时,这个是不可改的
let copyObj = readonly(obj)
//异步操作
setTimeout(() => {
//基础类型是要 .value,因为在ref已经改变了
name.value = 'aaa'
obj.age = 24
}, 2000)
return {
name,
obj
}
},
三、toRefs
比如之前的,我展示的是{{obj.age}}
,那我为何不在return的时候直接返回age呢,返回一个字段就好,不用返回某个对象。但是这样就要用toRefs了,不然没有响应式
setup(props, context) {
//{age:18} 变成 proxy({age:18})的一个响应式引用过
let obj = reactive({age: 18})
//异步操作
setTimeout(() => {
obj.age = 24
}, 2000)
//加toRefs
const { age } = toRefs(obj)
return {
age
}
},
四、toRef
和上面的一样,但是,如果obj里面只有age但没有name,后来想在对象加一个字段,要用这个方法才可以响应式
setup(props, context) {
let obj = reactive({age: 18})
//加了一个从来没有过的字段,要这样做
let name = toRef(obj,'name')
//异步操作
setTimeout(() => {
obj.age = 24
//2s后修改
obj.name = 'qiang'
}, 2000)
return {
obj
}
},
五、 context
终于到context的部分了,本节的3个例子都是在子组件里面的
1)attrs
可以打印父中的non-props(就是没有倍props接受的字段)
父
<template>
<div>
<child test="test1"></child>
</div>
</template>
子
setup(props, context) {
const {attrs} = context
console.log(attrs.test)
},
test1
2)slots
可以拿到插槽的东西
父
<template>
<div>
<child>i am parent</child>
</div>
</template>
子
setup(props, context) {
const {slots} = context
console.log(slots.default())
},
打印就是一个虚拟dom
[
{
"__v_isVNode": true,
"__v_skip": true,
"props": null,
"key": null,
"ref": null,
"scopeId": null,
"slotScopeIds": null,
"children": "i am parent",
"component": null,
"suspense": null,
"ssContent": null,
"ssFallback": null,
"dirs": null,
"transition": null,
"el": null,
"anchor": null,
"target": null,
"targetAnchor": null,
"staticCount": 0,
"shapeFlag": 8,
"patchFlag": 0,
"dynamicProps": null,
"dynamicChildren": null,
"appContext": null
}
]
3)emit
这个有点绕,我真不知道这玩意存在意义是什么
父写具体的用法,子写emit??
父
<template>
<div>
<child @change="changeMeth">i am parent</child>
</div>
</template>
<script>
import child from "./components/child.vue";
import {ref, reactive, toRef,readonly} from "vue";
export default {
name: 'Index',
components: {
child
},
methods: {
changeMeth(){
console.log("SSss")
}
},
}
</script>
子
<template>
<div>
<div @click="changeMeth">123</div>
</div>
</template>
<script>
export default {
name: 'child',
setup(props, context) {
const {emit} = context
function changeMeth() {
emit('change')
}
return {changeMeth}
},
}
</script>
六、整理格式和编写
可以看到,setup好像也没有好到哪里去,反而写法更复杂了,所有东西都丢到setup中,就很烦
比如我们可以这样写,具体做什么的可以不用管,懂大概这么个意思就行
<template>
<div>
<div>
<input :value="inputValue" @input="handleInputValueChange" />
//这里这样,是因为
<button @click="() => addItemToList(inputValue)">提交</button>
</div>
<ul>
<li v-for="(item, index) in list" :key="index">{{item}}</li>
</ul>
</div>
</template>
<script>
import child from "./components/child.vue";
import {ref, reactive, toRef,readonly} from "vue";
// 1、这里做一个方法,这个方法就专门是对list做处理的
const listRelativeEffect = () => {
const list = reactive([]);
const addItemToList = (item) => {
list.push(item);
}
return { list, addItemToList }
}
// 2、关于 inputValue 操作的内容进行了封装
const inputRelativeEffect = () => {
const inputValue = ref('');
const handleInputValueChange = (e) => {
inputValue.value = e.target.value
}
return { inputValue, handleInputValueChange}
}
export default {
name: 'Index',
components: {
child
},
//3、然后我们可以把封装的东西全丢在setup这里,在里面导入然后再return导出,就生成了对应的data和method
setup() {
// 流程调度中转
const { list, addItemToList } = listRelativeEffect();
const { inputValue, handleInputValueChange} = inputRelativeEffect();
return {
list, addItemToList,
inputValue, handleInputValueChange
}
},
props: {},
data() {
return {}
},
watch: {},
computed: {},
methods: {
},
created() {
console.log(this.$options.setup())
},
mounted() {
},
activated() {
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
七、computed
computed还是老样子,不过也可以写在setup里面
<template>
<div>
{{ count }}
<br>
{{ count5 }}
</div>
</template>
<script>
import {ref, reactive, toRef, readonly, computed} from "vue";
export default {
name: 'Index',
setup() {
const count = ref(0)
let count5 = computed({
get: () => {
return count.value + 5
},
set: (param) => {
}
})
return {count, count5}
},
}
</script>
0
5
八、watch
watch虽然也和之前一样,但还是有点不一样的
1)ref基础类型
<template>
<div>
<input v-model="name"> <br>
{{ name }}
</div>
</template>
<script>
import {ref, watch} from "vue";
export default {
name: 'Index',
setup() {
//基础类型ref
const name = ref('dell')
//watch,第一个是监听值,第二个是function,
watch(name, (currentValue, preValue) => {
console.log(currentValue, preValue)
})
return {name}
},
}
</script>
//输入l
delll dell
2)监听reactive对象类型中的某个值
基础类型直接用,但是对象类型的话,还没试过,但试过对象类型的某个值
<template>
<div>
<input v-model="obj.name"> <br>
{{ obj.name }}
</div>
</template>
<script>
import {reactive, ref, watch} from "vue";
export default {
name: 'Index',
setup() {
//创建对象类型
const obj = reactive({name: 'dell'})
//监听对象类型的某个值时,要加箭头函数
watch(() => obj.name, (currentValue, preValue) => {
console.log(currentValue, preValue)
})
return {obj}
},
}
</script>
//输入l
delll dell
3)一个watch监听多个值
就尼玛离谱,虽然看起来很离谱,但实际还真有这种写法
<template>
<div>
<input v-model="obj.name"> <br>
{{ obj.name }} <br>
------- <br>
<input v-model="obj.job"> <br>
{{ obj.job }}
</div>
</template>
<script>
import {reactive, ref, watch} from "vue";
export default {
name: 'Index',
setup() {
const obj = reactive({name: 'dell', job: 'teacher'})
//一个watch监听多个值,用数组形势,而后面接受的,也是用数组接受的,
watch([() => obj.name, () => obj.job], ([curName,curJob],[preName,preJob])=>{
console.log(curName, curJob)
console.log('-----------')
console.log(preName,preJob)
})
return {obj}
},
}
</script>
上面输入l,下面输入s
delll teacher
dell teacher
隔开
delll teachers
delll teacher
这样就一次性监听多个了
4)watchEffect
这个不需要传任何参数,如果有什么变化的,他就会自动打印的
<template>
<div>
<input v-model="obj.name"> <br>
{{ obj.name }} <br>
------- <br>
<input v-model="obj.job"> <br>
{{ obj.job }}
</div>
</template>
<script>
import {reactive, ref, watch, watchEffect} from "vue";
export default {
name: 'Index',
setup() {
const obj = reactive({name: 'dell', job: 'teacher'})
watchEffect(() => {
console.log(obj.name)
console.log(obj.job)
})
return {obj}
},
}
</script>
5)两者区别
watch和watchEffect,都差不多
- watch
- 具有惰性
- 参数可以拿到原始数据和当前的值
- 可以监听多个数据变化
- watchEffect
- 立即执行,没有惰性
- 不需要传递监听值,全局的
- 不能获取变化之前的值
- 不需要传参,写个回调函数就行
- 多用于异步处理,多用于不需要获取变化前的情况
6)停止监听
都是一样的写法
5秒后,再也不监听
const stopWatch = watchEffect(() => {
setTimeout(()=>{
stopWatch()
},5000)
})
九、生命周期在setup中的写法
就比如
setup() {
onMounted(() => {
console.log('sss')
});
},
更多生命周期函数参考官网
https://cn.vuejs.org/api/composition-api-lifecycle.html#onmounted
十、setup中的父传子
1)基本使用
说了那么多,父传子的情况肯定会有,但是setup api的还没试过,这就来
父
<template>
<div>
<child/>
</div>
</template>
<script>
import child from "./components/child.vue";
import {onMounted, provide, reactive, ref, watch, watchEffect} from "vue";
export default {
name: 'Index',
components: {
child
},
setup() {
const name = ref('dell')
//provide是传给子的工具,key => value
provide('name',name)
},
}
</script>
子:
<template>
<div>
{{ name }}
</div>
</template>
<script>
import {inject, reactive} from "vue";
export default {
name: 'child',
components: {},
setup() {
// inject接受父的setup api传过来的值
const name = inject('name')
console.log('父传子', name.value)
return {name}
},
}
</script>
2)改变值,类似于emit
<template>
<div>
<child/>
</div>
</template>
<script>
import child from "./components/child.vue";
import {onMounted, provide, reactive, ref, watch, watchEffect} from "vue";
export default {
name: 'Index',
components: {
child
},
setup() {
const name = ref('dell')
//provide是传给子的工具,key => value
provide('name', name)
//传给子的函数,相当于平时的emit,拿到参数后改变些东西
provide('click', (value) => {
name.value = value
})
},
}
</script>
子
<template>
<div>
<div @click="click">{{ name }}</div>
</div>
</template>
<script>
import {inject, reactive} from "vue";
export default {
name: 'child',
components: {},
setup() {
// inject接受父的setup api传过来的值
const name = inject('name')
//父传过来的function,回调给父
const changeValue = inject('click')
//点击事件
const click = () => {
changeValue('lee')
}
return {name, click}
},
}
</script>
八、复选框
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
// 当选中时:
vm.toggle === ‘yes’
// 当未选中时:
vm.toggle === ‘no’
九、父传子还能传函数类型
比如这个,num是一个函数类型的,然后子用Function接受,也是能调用父函数的
感觉这样做,就没有那个$emilt的什么事了
十、父传子的校验
比如这个,平时可以写类型,默认值,还能写校验值
十一、父传子的一个驼峰命名bug
父得到名字有-
的,但是子只用用驼峰法来写,如果用-
写,会识别不出来
十二、单向数据流
一直困扰了我好久的问题,现在终于知道了。
比如子用props接受一个字,虽然可以用 this.xxx接受,只能读不能改,这叫单向数据流。所以解决方法就是在子data里面创建一个变量 b=this.xxx,然后改b就行
十三、没有filter过滤器了
呜呜呜怎么好好的过滤器没有了
尤大推荐用computed来计算。
如果想做成类似全局过滤器那种,可以
// main.js
const app = createApp(App)
app.config.globalProperties.$filters = {
currencyUSD(value) {
return '$' + value
}
}
<template>
<h1>Bank Account Balance</h1>
<p>{{ $filters.currencyUSD(accountBalance) }}</p>
</template>
十三、函数的特殊写法
function a (param){ }
//等价于
const a = (param) =>{ }
//都是一样的意思,但是用的话都是一样地用a(param)就可以
十四、vue2到vue3的过渡:选项式和组合式的写法
1)简介
vue3.2 以上,默认使用setup包围,这样就不用写return了
<script setup>
</script>
vue2中,我们的写法都是
export default {
name: 'Index',
components: {},
props: {},
data () {
return {}
},
watch: {},
computed: {},
methods: {},
created () {
},
mounted () {
},
activated () {
},
}
但是在vue3中的组合式API,没有这样了,每一个都变了。虽然也可以用vue2的写法,但不推荐,因为时代的潮流不可逆转,程序员注定要学新的东西
2)name
选项式
export default {
name: 'Index',
}
组合式
3中,名字省略了,你的文件名就是你的name
3)components
选项式
<script>
import Nav from '@/components/Nav'
export default {
components: { Nav },
}
</script>
<template>
<div>
<Nav />
</div>
</template>
组合式
没有components一个括号,直接导入就可以直接用
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld msg="You did it!" />
</template>
4)props
选项式
export default {
props: ['a','b'],
}
又或者是
export default {
props: {
// 基础类型检查
//(给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或者数组应当用工厂函数返回。
// 工厂函数会收到组件所接收的原始 props
// 作为参数
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
}
}
组合式
这样写,用defineProps
函数,然后一个常量接受,再使用即可,在html上可以{{foo}}
直接使用
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
又或者是
const props = defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})
5)computed
选项式
export default {
data() {
return {
name: 'John Doe',
}
},
computed: {
// 一个计算属性的 getter
nameComputed() {
// `this` 指向当前组件实例
return name + 'aaaaa'
}
}
}
组合式
import { ref, computed } from 'vue'
const name = ref('John Doe')
// 一个计算属性 ref
const nameComputed = computed(() => {
return name + 'aaaaa'
})
如果想要set和get
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
6)data
选项式
export default {
data () {
return {
a:'123',
obj:{
name: 'tom'
}
}
},
}
组合式
没有说直接一个data括号起来,只要在外面定义,用ref
或者reactive
函数,都默认是当作data响应式的
其中ref用于基础类型,reactive用于非基础类型
const a = ref('123')
const obj = reactive({
name:'tom'
})
7)watch
选项式
export default {
data () {
return {
someObject:{
name:'tom'
}
}
},
watch: {
// 每当 someObject 改变时,这个函数就会执行
someObject(newValue, oldValue) {
// 变化要做的
}
},
}
组合式
import { reactive, watch } from 'vue'
const someObject = reactive({name:'tom'})
// 每当 someObject 改变时,这个函数就会执行
watch(someObject, (newQuestion, oldQuestion) => {
// 变化要做的
})
番外:watchEffect
watch是每次都只监听一个,watchEffect可以监听所有。每当有数据变化的时候,都会执行该函数
watchEffect(async () => {
//做异步操作
const response = await fetch(url.value)
data.value = await response.json()
})
watch
只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch
会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。watchEffect
,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
8)methods
选项式
export default {
methods: {
functionName(){
}
},
}
组合式
组合式就像写普通函数这样用就好了
function functionName(){
}
9)生命周期
生命周期的区别
2和3的周期有点不一样
2:
3:
选项式
export default {
created () {
},
mounted () {
},
activated () {
},
}
//等等诸如此类
组合式
import { onMounted } from 'vue'
onMounted(() => {
//
})
10)emit
选项式
在子中this.$emit('函数名',参数)
<template>
<HelloWorld @clickFunction="clickFunction"/>
</template>
export default {
methods: {
clickFunction(param){
console.log('打印参数',param)
}
},
}
</script>
export default {
methods: {
clickFunction(param){
console.log('打印参数',param)
}
},
mounted () {
const param = 123
this.$emit('clickFunction',param)
},
}
组合式
<template>
<HelloWorld @clickFunction="clickFunction"/>
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
const clickFunction = (param) =>{
console.log('打印参数',param)
}
</script>
import {onMounted} from "vue";
//先调用defineEmits函数,里面是array['函数名']
const emit = defineEmits(['clickFunction'])
//这是一个methods
const clickEmit = () =>{
//和this.$emit() 的写法一样
emit('clickFunction','123321')
}
//最后在mounth的时候调用它
onMounted(()=>{
clickEmit()
})
十五、依赖注入:provide和inject
如果一个页面,是由树型的结构的,就是子组件又有子组件又有子组件,这样如果要一层层地往下传递数据,是非常难的。所以可以在第一级的时候,用provide进行传递,任意的后辈组件都可以用inject 来进行接受
import {provide} from "vue";
//第一季父组件
provide('key',123)
//孙组件
import {inject, onMounted} from "vue";
onMounted(()=>{
const key = inject('key')
console.log(key) //打印123
})
十六、组合式函数(重要)
这是一个非常非常非常重要的知识点,涉及到后面很常用的VueUse的使用
-
尤雨溪说在3中,推荐用组合式函数而不用mixin,因为mixin都是隐形的,mixin多了就会不知道哪个属性是从哪里过来的了
-
组合式函数和mixin有点相似,也是把同样的东西抽离出去,打包,但是组合式函数更好用
-
一般文件名或者function,以
use
开头
1)简单例子
比如这个,组件中使用组合式 API 实现鼠标跟踪功能
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const x = ref(0)
const y = ref(0)
//鼠标移动调用该函数,改变x,y值
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
//原生事件,监听鼠标移动而已,鼠标移动了就调用update函数
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>
//显示xy值
<template>Mouse position is at: {{ x }}, {{ y }}</template>
如果多个组件中用同样的逻辑,这时候就要用到组合式函数打包了
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
//这里用了ref是因为这些数据要被到处去,相当于data
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
// return {x,y,update:update} //还可以返回一个function,这样给外面就能直接调用
}
然后其他组件直接使用就可以
<script setup>
//直接调用
import { useMouse } from './mouse.js'
//组合式函数return什么,这里就能拿到什么,而且是响应式的
const { x, y } = useMouse()
//如果想用对象接受也可以
const mouse = reactive(useMouse()) //mouse.x,mouse.y就可以使用
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
而且useMouse
是个函数,你也可以传一些参数过去
2)异步状态的例子
如果传参是动态的,会变的,而每次会变的时候我都希望执行该组合式函数,我们可以
- 判断传参是否为ref或者reactive,是的话利用监听器,不是的话执行一次就可以
- 如果利用了监听器,则在
onUnmounted()
的时候要清除监听器
// fetch.js
import { ref, isRef, unref, watchEffect } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
function doFetch() {
// 在请求之前重设状态...
data.value = null
error.value = null
//这是一个异步函数
fetch(xxx)
}
if (isRef(url)) {
// 若输入的 URL 是一个 ref,那么启动一个响应式的请求,启动监听器
watchEffect(doFetch)
} else {
// 否则只请求一次
// 避免监听器的额外开销
doFetch()
}
return { data, error }
}
3)VueUse
建议学习VueUse,因为这个库做的就是组合式函数的应用,都打包好了,下载就行