Mixin
对相同的代码逻辑进行抽取
const sayHelloMixin = {
created(){
console.log('hello world sayHelloMixin')
console.log('title-----', this.title)
this.sayHello()
},
data(){
return {
title: 'hello world title'
}
},
methods: {
sayHello(){
console.log('hello page component')
}
},
}
export default sayHelloMixin;
<template>
<div>
hello
</div>
</template>
<script>
import sayHelloMixin from '../utils/sayHello'
export default {
name: '',
mixins: [sayHelloMixin],
data(){
return {
title: 'hello world title'
}
},
methods: {
sayHello(){
console.log('hello world123')
}
},
created() {
console.log('hello world created')
this.sayHello()
}
}
</script>
<style lang="scss" scoped>
</style>
Mixin的合并规则
全局混入
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mixin({
data(){
return {}
},
methods: {
},
created() {
console.log('全局的created mixin');
}
})
app.mount('#app')
extends
类似于Mixin basePage
<template>
<div>
</div>
</template>
<script>
export default {
name: '',
data() {
return {
title: 'hello Page'
}
},
methods: {
bar(){
console.log("base page bar");
}
}
}
</script>
<style lang="scss" scoped>
</style>
home
<template>
<div>
<div>{{title}}</div>
<button @click="bar">dianji</button>
</div>
</template>
<script>
import BasePage from './BasePage.vue'
export default {
name: '',
extends: BasePage
}
</script>
<style lang="scss" scoped>
</style>
在开发中extends使用的非常少,在VUE2中比较推荐使用Mixin,而在Vue3中推荐使用Composition API
Options API的弊端
- 在Vue2中,我们编写组件的方式是Options API:
- Options API的一大特点就是在对应的属性中编写对应的功能模块;
- 比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命 周期钩子;
- 但是这种代码有一个很大的弊端:
- 当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中;
- 当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;
- 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);
- 下面我们来看一个非常大的组件,其中的逻辑功能按照颜色进行了划分:
- 这种碎片化的代码使用理解和维护这个复杂的组件变得异常困难,并且隐藏了潜在的逻辑问题;
- 并且当我们处理单个逻辑关注点时,需要不断的跳到相应的代码块中;
- 如果我们能将同一个逻辑关注点相关的代码收集在一起会更好。
- 这就是Composition API想要做的事情,以及可以帮助我们完成的事情。
- 也有人把Vue CompositionAPI简称为VCA。
setup
参数
- props
- context
props
父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可 以直接通过props参数获取
- 对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义;
- 并且在template中依然是可以正常去使用props中的属性,比如message;
- 如果我们在setup函数中想要使用props,那么不可以通过 this 去获取
- 因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可
context
称之为是一个SetupContext,它里面包含三个属性:
attrs
所有的非prop的attribute
slots
父组件传递过来的插件(这个在以渲染哈数返回时会有作用)
emit
当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件)
返回值
作用
- setup的返回值可以在模板template中被使用;
- 也就是说我们可以通过setup的返回值来替代data选项;
<template>
<div>
<div>title: {{title}}</div>
<button @click="increment">+1</button>
<div>{{counter}}</div>
</div>
</template>
<script>
export default {
name: '',
setup() {
let counter = 1;
const increment = () => {
counter++
console.log(counter)
}
return {
title: '标题',
counter,
increment
}
}
}
</script>
- 将 counter 在 increment 或者 decrement进行操作时,是否可以实现界面的响应式呢?
不可以,因为对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作;
Reactive API 和 Ref API
Reactive
- 处理对象(递归深度响应式)
- 内部使用Proxy来实现对对象内部所有数据的劫持,并通过Reflect操作对象内部数据
<template>
<div>
message: {{ message }}
<button @click="btnClick">按钮</button>
<div>{{state.counter}}</div>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: "",
props: {
message: {
type: String,
default: "xiaoxiao",
},
},
setup(props, { attrs, slots, emit }) {
const state = reactive({
counter: 1
})
const btnClick = function() {
// console.log('123yyy')
state.counter++
emit('btnClick', 123)
}
return {
btnClick,
state
}
}
};
</script>
传入基本数据类型(String、Number、Boolean)就会报一个警告
响应式的原因
使用reactive函数处理数据之后,数据再次被使用时就会进行依赖收集;当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);data选项也是在内部交给了reactive函数将其编程响应式对象的;
Ref
- 处理基本类型数据
- 通过给value属性添加getter/setter来实现对数据的劫持
- 如果用ref对象/数组,内部会自动将对象/数组转换为reactive的代理对象
注意事项
- 在模板中引入ref的值,Vue会自动进行解包,并不需要在模板中通过ref.value的方式来使用
- 在setup函数内部,依然是一个ref引用,所以对其进行操作时,依然需要使用ref.value的方式
模板中的解包是浅层的解包
<template>
<div>
<div>title: {{title}}</div>
<button @click="increment">+1</button>
<div>{{counter}}</div>
<!-- 如果最外层包裹的时候一个reactive可响应式对象,那么内容的ref可以解包 -->
<div>{{info.counter}}</div>
</div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
name: '',
components: {
About
},
setup() {
let counter = ref(1);
let info = reactive({
counter
})
const increment = () => {
counter.value++
console.log(counter.value)
}
return {
title: '标题',
counter,
increment,
info
}
}
}
</script>
readonly
- 通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?
- Vue3为我们提供了readonly的方法;readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);
参数
- 普通对象
- reactive返回的对象
- ref的对象
使用
在readonly的使用过程中,有如下规则:
- preadonly返回的对象都是不允许修改的;
- 但是经过readonly处理的原来的对象是允许被修改的;
- 比如 const info = readonly(obj),info对象是不允许被修改的;
- 当obj被修改时,readonly返回的info对象也会被修改;
- 但是我们不能去修改readonly返回的对象info;
- 其实本质上就是readonly返回的对象的setter方法被劫持了而已;
应用
传递给其他组件数据时,希望其他组件使用传递的内容,但是不允许它们修改
Reactive判断的API
isProxy
isReactive
isReadonly
toRaw
返回reactive或readonly代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用。)
<template>
<div>
<button @click="showInfo">showInfo</button>
</div>
</template>
<script>
import { reactive, toRaw } from 'vue'
export default {
name: '',
setup(){
const info = reactive({name: 'free'})
const rawInfo = toRaw(info)
const showInfo = () => {
console.log(rawInfo);
}
return {
showInfo
}
}
}
</script>
shallowReactive
shallowReadonly
toRefs
- 使用ES6的结构语法,对reactive返回的对象进行解构获取值,那么之后是修改解构后的变量,还是修改reactive返回的对象,数据都不再是响应式的
- 使用toRefs让解构出来的属性是响应式的
- Vue提供的toRefs的函数,可以将reactive返回的对象中的属性
都转成ref
- Vue提供的toRefs的函数,可以将reactive返回的对象中的属性
- 这种做法相当于在对象的属性(info.name)和ref.value之间建立了链接,任何一个修改都会引起另一个变化
<template>
<div>
<button @click="showInfo">showInfo</button>
<div>{{info.name}} - {{info.age}} - {{info.sex}}</div>
<div>{{name}} - {{age}}</div>
<div>{{sex}}</div>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
name: '',
setup(){
const info = reactive({name: 'free', age: 18, sex: '女'})
const rawInfo = info
// let { name, age } = info
let { sex } = info
let { name, age } = toRefs(info)
const showInfo = () => {
info.name = 'unfree'
info.sex = '男'
age.value++
console.log(rawInfo);
}
return {
showInfo,
info,
name,
age,
sex
}
}
}
</script>
toRef
只希望转换一个reactive对象中的属性为ref
<template>
<div>
<button @click="showInfo">showInfo</button>
<div>{{info.name}} - {{info.age}} - {{info.sex}}</div>
<div>{{name}} - {{age}}</div>
<div>{{sex}}</div>
</div>
</template>
<script>
import { reactive, toRefs, toRef } from 'vue'
export default {
name: '',
setup(){
const info = reactive({name: 'free', age: 18, sex: '女'})
const rawInfo = info
// let { name, age } = info
// let { sex } = info
let { name, age } = toRefs(info)
let sex = toRef(info, 'sex')
const showInfo = () => {
info.name = 'unfree'
info.sex = '男'
age.value++
console.log(rawInfo);
}
return {
showInfo,
info,
name,
age,
sex
}
}
}
</script>
unref
- 如果想要获取一个ref引用中的value,那么也可以通过unref方法
- 如果参数是一个ref,则返回内部值,否则返回参数本身
- 是
val = isRef(val) ? val.value : val的语法糖函数
const name = ref(why)
foo(name)
foo('why')
function foo(bar){
unref(bar) // 是val = isRef(val) ? val.value : val的语法糖函数
}
isRef
判断值是否是一个ref对象
shallowRef
创建一个浅层的ref对象
trigger
手动触发和shallowRef相关联的副作用
<template>
<div>
<button @click="changeInfo">changeInfo</button>
<div>info: {{info.name}}</div>
<button @click="changeInfo1">changeInfo1</button>
<div>info1: {{info1.name}}</div>
</div>
</template>
<script>
import { ref, shallowRef, triggerRef } from 'vue'
export default {
name: '',
setup(){
const info = ref({name: 'code'})
const info1 = shallowRef({name: 'high'})
const changeInfo = () => {
info.value.name = 'high'
console.log('info', info.value);
}
const changeInfo1 = () => {
info1.value.name = 'code'
console.log('info1', info1.value);
triggerRef(info1)
}
return {
info,
info1,
changeInfo,
changeInfo1
}
}
}
</script>
副作用
- 界面的刷新
- 依赖
customRef
- 创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制
- 它需要一个工厂函数,该函数接受
track和trigger函数作为参数 - 并且应该返回一个带有
- 它需要一个工厂函数,该函数接受
<template>
<div>
<input v-model="name" />
<div>{{name}}</div>
</div>
</template>
<script>
import { ref } from 'vue'
import debounceRef from '@/hook/useDebounceRef'
export default {
name: '',
setup(){
// const name = ref('zhansghan')
const name = debounceRef('')
return {
name
}
}
}
</script>
import { customRef } from "vue";
// 自定义ref
export default function (value) {
return customRef((track, trigger) => {
/*
track: 決定什么时候收集依赖
trigger:决定什么时候触发所有依赖来更新
*/
return {
get() {
// 收集依赖
track()
// 返回具体值
return value
},
set(newValue) {
value = newValue
// 触发所有依赖更新
trigger()
}
}
})
}
上面的使用与直接使用ref的效果一致
案例:对双向绑定的属性进行debounce(节流)的操作
import { customRef } from "vue";
// 自定义ref
export default function (value) {
return customRef((track, trigger) => {
// 防抖
let timer = null
/*
track: 決定什么时候收集依赖
trigger:决定什么时候触发所有依赖来更新
*/
return {
get() {
// 收集依赖
track()
// 返回具体值
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
// 触发所有依赖更新
trigger()
}, 1000);
}
}
})
}
computed
- 当某些属性是依赖其他状态是,可以使用计算属性来处理
- 在Options API中,使用computed选项来完成
- 在Composition API中,在setup函数中使用computed方法来编写一个计算属性
使用
- 方法一:接收一个getter函数,并为getter函数返回的值,返回一个不变的ref对象
- 方法二:接收一个具有get和set的对象,返回一个可变的(可读写)ref对象
<template>
<div>
<div>computed</div>
<br>
<input v-model="first.name" />
<br>
<br>
<input type="text" v-model="lastname" />
<br>
<br>
fullname: {{fullName}}
<button @click="changeFull">修改</button>
</div>
</template>
<script>
import { computed, reactive, ref } from 'vue'
export default {
name: '',
setup(){
const first = reactive({
name: 's'
})
const lastname = ref('jj')
// 1.用法1:传如一个getter函数
// computed的返回值是一个ref对象
// const fullName = computed(() => first.name + lastname.value)
// 2.用法2:传入一个对象,对象包含getter/setter
const fullName = computed({
get: () => first.name + lastname.value,
set: (newVal) => {
first.name = newVal.split(' ')[0]
lastname.value = newVal.split(' ')[1]
}
})
const changeFull = () => {
fullName.value = 'haohao xuexi'
}
return {
first,
lastname,
fullName,
changeFull
}
}
}
</script>
watchEffect
- 当侦听到某些响应式数据变化时,希望执行某些操作,这个时候可以使用watchEffect
watchEffect: 自动收集响应式的依赖
特点
- 传入的函数会被立即执行一次,并且在执行的过程中会收集依赖
- 只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行
<template>
<div>
<div>{{info.name}}</div>
<div>{{info.age}}</div>
<button @click="changeName">修改名称</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script>
import { reactive, watchEffect } from 'vue'
export default {
name: '',
setup(){
const info = reactive({
name: 'code',
age: 18
})
const changeName = () => {
info.name = 'codeWhy'
}
const changeAge = () => {
info.age++
}
const stopWatchEffect = watchEffect(() => {
console.log('info', info.name)
})
console.log(stopWatchEffect)
return {
info,
changeName,
changeAge
}
}
}
</script>
停止侦听
停止侦听,获取watchEffect的返回值函数,调用函数即可
const info = reactive({
name: 'code',
age: 18
})
const changeName = () => {
info.name = 'codeWhy'
}
const changeAge = () => {
info.age++
if (info.age > 26) {
stopWatchEffect()
}
}
const stopWatchEffect = watchEffect(() => {
console.log('info', info.name, info.age)
})
清除副作用
-
什么是清除副作用呢?
- 比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。
- 那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用;
-
在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate
- 当副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数;
- 我们可以在传入的回调函数中,执行一些清楚工作;
执行时机
<template>
<div>
<h2 ref="title"></h2>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
name: '',
setup(){
const title = ref(null)
watchEffect(() => {
console.log('title', title.value)
})
return {
title
}
}
}
</script>
打印结果打印了两次
- 是因为setup函数在执行时就会立即执行传入的副作用函数,这个时候DOM并没有挂载,所以打印为null;
- 而当DOM挂载时,会给title的ref对象赋值新的值,副作用函数会再次执行,打印出来对应的元素;
调整watchEffect的执行时机
延后执行
第二个参数,flush
- flush:
- pre(默认值): 元素 挂载或更新 之前执行
- post
- sync:强制效果始终同步触发
watchEffect(() => {
console.log('title', title.value)
}, {
flush: 'post'
})
setup中使用ref
定义一个ref对象,绑定到元素或者组件的ref属性上
<template>
<div>
<h2 ref="title"></h2>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
name: '',
setup(){
const title = ref(null)
return {
title
}
}
}
</script>
watch
- watch的API完全等同于组件watch选项的Property:
- watch需要侦听特定的数据源,并在回调函数中执行副作用;
- 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;
- 与watchEffect的比较,watch允许我们:
- 懒执行副作用(第一次不会直接执行);
- 更具体的说明当哪些状态发生变化时,触发侦听器的执行;
- 访问侦听状态变化前后的值;
侦听单个数据源
- watch侦听函数的数据源有两种类型:
- 一个getter函数:但是该getter函数引用响应式对象(比如reactive或者ref)
- 直接写入一个可响应式对象,reactive或者ref(比较常用的是ref)
<template>
<div>
<div>{{info}}</div>
<div>{{info.name}}</div>
<button @click="changeName">change</button>
<button @click="addNew">add</button>
</div>
</template>
<script>
import { watch, reactive } from 'vue'
export default {
name: '',
setup(){
const info = reactive({
name: 'zhangsan',
age: 12
})
const changeName = () => {
info.name = 'lisi'
info.age++
}
// watch(() => info.name, (newValue, oldValue) => {
// console.log(newValue, oldValue)
// })
watch(() => ({...info}), (newValue, oldValue) => {
console.log(newValue, oldValue)
})
const addNew = () => {
info.sex = 'nan'
}
return {
info,
changeName,
addNew
}
}
}
</script>
const name = ref('zhangsan')
const changeYourName = () => {
name.value = 'lisi'
}
watch(name, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
侦听多个数据源
侦听器还可以使用数组同时侦听多个源
<template>
<div>
<div>{{name}}</div>
<button @click="changeYourName">changeYourName</button>
<div>{{age}}</div>
<button @click="changeYourAge">changeYourAge</button>
</div>
</template>
<script>
import { watch, ref } from 'vue'
export default {
name: '',
setup(){
// ref
const name = ref('zhangsan')
const age = ref(12)
const changeYourName = () => {
name.value = 'lisi'
}
const changeYourAge = () => {
age.value++
}
// watch(name, (newValue, oldValue) => {
// console.log(newValue, oldValue)
// })
watch([name, age], (newValue, oldValue) => {
console.log(newValue, oldValue)
})
return {
name,
changeYourName,
age,
changeYourAge
}
}
}
</script>
侦听响应式对象
如果希望侦听一个数组或者对象,可以使用一个getter函数,并且对可响应式对象进行解构
const arr = reactive(['abc', 'nba', 'cba'])
watch(() => [...arr], (newValue, oldValue) => {
console.log(newValue, oldValue)
})
const changeArr = () => {
arr.push('bbb')
// arr[0] = 'gba'
}
watch选项
- deep 深度监听
- immediate 立即执行
const info = reactive({
name: 'zhangsan',
age: 12,
info: {
sonName: 'xiaoxiao'
}
})
const changeName = () => {
info.info.sonName = 'dada'
}
// watch(info, (newValue, oldValue) => {
// console.log(newValue, oldValue)
// }, {
// immediate: true
// })
watch(() => info, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, {
deep: true,
immediate: true
})
生命周期
声明周期函数可以多次使用
| options API | Composition API |
|---|---|
| beforeCreate | not needed* |
| created | not needed* |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | OnBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |
| renderTracked | onRenderTracked |
| renderTraggered | onRenderTraggered |
| activated | onActivated |
| deactivated | onDeactivated |
provide
import { provide, ref } from 'vue'
export default {
setup(){
const name = ref('codewhy')
let couter = ref(100)
provide('name', name)
}
}
import { inject, ref } from 'vue'
export default {
setup(){
const name = ref('codewhy')
let couter = ref(100)
inject('name', name) // 第二次参数:当provide没有传值时,为第二个参数(默认值)
}
}
能用ref就用ref,方便抽离和解耦
readonly
只读,不能修改
import { provide, ref } from 'vue'
export default {
setup(){
const name = ref('codewhy')
let couter = ref(100)
provide('name', readonly(name))
}
}
数据遵循单向数据流。避免多处子组件使用,而其中一个改变时导致父组件改变,从而使得其他子组件全部受到影响。
render函数
h函数
- h()函数时一个用于创建vnode的一个函数
- 更准确的命名是createVNode()函数,简化为h()函数
参数
type/tag
- 类型:String/Object/Function
- HTML标签名、组件、异步组件或函数式组件。使用返回null的函数将渲染一个注释。此参数是必需的。
props
- Object
- 一个对象,与我们将在模板中使用attribute、prop和事件相对应。可选。
children
- String|Array|Object
- 子代VNode,使用
h()生成,或者使用字符串来获取“文本VNode”,或带有插槽的对象。可选。
注意事项
- 如果没有props,那么通常可以将children作为第二个参数传入
- 如果会产生歧义,可以将null作为第二个参数传入,将children作为第三个参数传入
基本使用
h函数可以在两个地方使用
- render函数选项中
- setup函数选项中(setup本身需要是一个函数类型,函数再返回h函数创建的VNode)
<script>
import { h } from 'vue'
export default {
render() {
return h('div', { class: 'app'}, "hello world")
}
}
</script>
<script>
import { h } from 'vue'
export default {
setup(){
return () => h('div', { class: 'app'}, "hello world")
}
}
</script>
h函数计数器案例
<script>
import { h, ref } from 'vue'
export default {
setup(){
const counter = ref(1)
return () => h('div', { class: 'app'}, [
h('button', {
onClick: () => counter.value++
}, counter.value)
])
}
}
</script>
函数组件和插槽
sonComponent
<script>
import { h } from "vue";
export default {
render() {
return h("div", { class: "son" }, [
h('h2', null, '你好China'),
this.$slots.default ? this.$slots.default({info: 'hahahh'}) : h('span', null, '默认值')
]);
},
};
</script>
fatherComponent
<script>
import sonComponent from './sonComponent.vue'
import { h, ref } from 'vue'
export default {
setup(){
const counter = ref(1)
return () => h('div', { class: 'app'}, [
h('button', {
onClick: () => counter.value++
}, counter.value),
h(sonComponent, null, {
default: props => h('span', `app传入:${props.info}`)
})
])
}
}
</script>
更多相关学习
vue-h
JSX
JSX的babel配置
- 项目中使用JSX,需要使用babel进行支持
安装
npm install @vue/babel-plugin-jsx -D
babel.config.js配置文件中配置插件
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
'@vue/babel-plugin-jsx'
]
}
<script>
export default {
render () {
return (
<div>JSX使用</div>
)
}
}
</script>
jsx计数器案例
<script>
import { ref } from 'vue'
export default {
setup() {
const counter = ref(1)
const increment = () => counter.value++
return {
counter,
increment
}
},
render () {
return (
<div>
<div>当前计数:{this.counter}</div>
<button onClick={this.increment}>+1</button>
</div>
)
}
}
</script>
jsx组件的使用
export default {
render() {
return (
<div>
<h2>son</h2>
<div className="con">
{
this.$slots.default ?
this.$slots.default({name: 'code'})
:<span>默认值</span>
}
</div>
</div>
)
},
};
import sonComponent from './sonComponent.vue'
import { ref } from 'vue'
export default {
setup() {
const counter = ref(1)
const increment = () => counter.value++
return {
counter,
increment
}
},
render () {
return (
<div>
<div>当前计数:{this.counter}</div>
<button onClick={this.increment}>+1</button>
<sonComponent>
{{default: props => <button>{props.name}</button>}}
</sonComponent>
</div>
)
}
}
自定义指令
- 在Vue中,代码的复用和抽象主要还是通过组件
- 通常在某些情况下,需要对DOM元素进行底层操作,这个时候就会用到自定义指令
局部自定义指令
- 组件选项中使用directives
- 是一个对象,在对象中编写自定义指令的名称(注意:这里不需要加v-)
- 自定义指令有一个生命周期,是在组组件挂载后调用的mounted,可以在其中完成操作
<template>
<div>
<input type="text" v-focus />
</div>
</template>
<script>
export default {
// 局部指令
directives: {
focus: {
mounted(el, bindings, vnode, preVnode) {
console.log("focus mounted");
el.focus()
},
},
},
};
</script>
自定义全局指令
自定义一个全局的v-focus指令可以让我们在任何地方直接使用
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.directive('focus', {
mounted(el, bindings, vnode, preVnode) {
console.log("focus mounted");
el.focus()
}
})
app.mount('#app')
指令的生命周期
| 名称 | 作用 |
|---|---|
| created | 在绑定元素的attribute或事件监听器被应用之前调用 |
| beforeMount | 在指令第一次绑定到元素并且在挂载父组件之前调用 |
| mounted | 在绑定元素的父组件被挂载后调用 |
| beforeUpdate | 在更新包含组件的VNode之前调用 |
| updated | 在包含组件的VNode及其子组件的VNode更新后调用 |
| beforeUnmount | 在卸载绑定元素的父组件之前调用 |
| unmounted | 当指令与元素解除绑定且父组件已卸载时,只调用一次 |
指令的参数和修饰符
<template>
<div>
<button type="text" v-formaTtime:info.aaa.bbb="{ name: 'code', age: 18 }">
{{ counter }}
</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
// 局部指令
directives: {
formaTtime: {
mounted(el, bindings, vnode, preVnode) {
console.log("focus mounted", el, bindings, vnode, preVnode);
},
},
},
setup() {
const counter = ref(123);
return {
counter
}
},
};
</script>
- info是参数的名称
- aaa、bbb是修饰符的名称
- {name: 'code', age: 18}是传入的具体的值
时间格式化
- 自定义指令案例:时间戳的显示需求:
- 在开发中,大多数情况下从服务器获取到的都是时间戳;
- 我们需要将时间戳转换成具体格式化的时间来展示;
- 在Vue2中我们可以通过
过滤器来完成; - 在Vue3中我们可以通过
计算属性(computed) 或者 自定义一个方法(methods)来完成; - 其实我们还可以通过一个
自定义的指令来完成;
- 我们来实现一个可以自动对时间格式化的指令v-format-time:
- 封装一个函数,在
首页(main.js)中只需要调用这个函数并且传入app即可;
- 封装一个函数,在
<template>
<div>
<button type="text" v-formaTtime:info.aaa.bbb="'YYYY/MM/DD'">
{{ counter }}
</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const timestamp = new Date().getTime()
const counter = ref(timestamp);
return {
counter
}
},
};
</script>
设置全局的自定义指令
import formatTime from './formatTime'
export default function registerDirectives(app){
formatTime(app)
}
使用dayJS进行日期格式化 dayJS
import dayjs from 'dayjs'
export default function (app) {
// let format = 'YYYY-MM-DD HH:mm:ss' // 会有bug, 当第一次使用了,后期使用默认的但却还是第一次的,因为format被修改了
app.directive('formaTtime', {
created(el, bindings) {
bingings.format = 'YYYY-MM-DD HH:mm:ss'
if (bindings.value) {
// format = bindings.value
bingings.format = bindings.value
}
},
mounted(el, bindings, vnode, preVnode) {
console.log("focus mounted", el, bindings, vnode, preVnode);
const textContent = el.textContent
let timestamp = parseInt(textContent)
if (timestamp.length == 10) {
timestamp = timestamp * 1000
}
// el.textContent = dayjs(timestamp).format(format)
el.textContent = dayjs(timestamp).format(bingings.format)
},
})
}
main.js
import { createApp } from 'vue'
import App from './App.vue'
import registerDirectives from './directives'
const app = createApp(App)
registerDirectives(app)
app.mount('#app')
Teleport
-
在组件化开发中,我们封装一个组件A,在另外一个组件B中使用:
-
那么组件A中template的元素,会被挂载到组件B中template的某个位置;
-
最终我们的应用程序会形成一颗DOM树结构;
-
-
但是某些情况下,我们希望组件不是挂载在这个组件树上的,可能是移动到Vue app之外的其他位置:
- 比如移动到body元素上,或者我们有其他的div#app之外的元素上;
- 这个时候我们就可以通过teleport来完成;
-
Teleport是什么呢?
- 它是一个Vue提供的内置组件,类似于react的Portals;
- teleport翻译过来是心灵传输、远距离运输的意思;
-
它有两个属性:
Ø to:指定将其中的内容移动到的目标元素,可以使用选择器;
Ø disabled:是否禁用 teleport 的功能;
-
多个teleport应用到同一个目标上(to的值相同),那么这些目标会进行合并
<template>
<div>
<button>點擊</button>
<teleport to='#why'>
<button>+1</button>
</teleport>
</div>
</template>
认识插件
- 通常我们向Vue全局添加一些功能时,会采用插件的模式,它有两种编写方式:
对象类型:一个对象,但是必须包含一个install的函数,该函数会在安装插件时执行;- 函数类型:一个function,这个函数会在安装插件时自动执行;
- 插件可以完成的功能没有限制,比如下面的几种都是可以的:
添加全局方法或者 property,通过把它们添加到config.globalProperties上实现;添加全局资源:指令/过滤器/过渡等;- 通过
全局 mixin来添加一些组件选项; 一个库,提供自己的 API,同时提供上面提到的一个或多个功能;
对象
<template>
<div>
<button>點擊</button>
<teleport to='#why'>
<button>+1</button>
</teleport>
</div>
</template>
<script>
// getCurrentInstance拿到组件实例
import { getCurrentInstance } from 'vue'
export default {
name: '',
mounted(){
console.log(this.$name)
},
setup(){
/**
* setup拿不到this,因为没有进行this绑定
* getCurrentInstance拿到当前组件的实例
* $name 没有放在组件实例instance上,直接instance.$name 拿不到内容
* appContext是APP的上下文(app)
* instance.appContext.config.globalProperties.$name
*/
const instance = getCurrentInstance()
console.log(instance.appContext.config.globalProperties.$name);
}
}
</script>
function
export default function(app) {
// app.component('')
// app.mixin()
console.log(app)
}
学习笔记,版权归codeWhy所有