v-model
在vue3中v-model是破环性更新
v-model其实是一个语法糖,通过props和emit组成
子组件要修改父组件的值
- prop:
value
->modelValue
; - 事件:
input
->update:modelValue
; v-bind
的.sync
修饰符和组件的model
选项已移除
v-model的更多用法
- 新增 支持多个v-model
- 新增 支持自定义 修饰符 Modifiers
单个v-model
案例:
父组件
<script setup lang="ts">
// 通过v-model来实现父子组件之间的双向绑定
import A from './components/A.vue'
import {ref} from "vue";
const flag = ref(true)</script><template><div class="switch">{{flag}}<button @click="flag = !flag">切换</button></div><transitionenter-active-class="animate__animated animate__backInDown"leave-active-class="animate__animated animate__backOutDown"><A v-model="flag"></A></transition>
</template><style scoped>
.switch {width: 200px;margin: 5px auto;
}
</style>
子组件
<script setup lang="ts">
defineProps<{modelValue:boolean
}>()
const emit = defineEmits<{(e:'update:modelValue', value: boolean): void
}>()
function submit() {emit('update:modelValue', false)
}
</script><template><div class="container" v-if="modelValue"><div><span>请输入:</span><input type="text"></div><button @click="submit">提交</button></div>
</template><style scoped>
.container {width: 500px;height: 500px;background-color: #ccc;border: 1px solid grey;margin: 0 auto;
}
.header {height: 50px;background-color: #eee;border: 1px solid grey;
}
.body {height: 400px;background-color: #ddd;border: 1px solid grey;
}
.footer {height: 50px;background-color: #eee;border: 1px solid grey;
}
</style>
也可以给一个组件绑定多个v-model
案例:
父组件
<A v-model="flag" v-model:title="title"></A>
子组件
<script setup lang="ts">
defineProps<{modelValue:boolean,title:string
}>()
const emit = defineEmits<{(e:'update:modelValue', value: boolean): void,(e:'update:title', value: string): void
}>()
function submit() {emit('update:modelValue', false)
}
function inputChange(e:any) {let value = e.target.valueemit('update:title', value)
}
</script><template><div class="container" v-if="modelValue"><div><span>请输入:</span><input type="text" :value="title" @input="inputChange($event)"></div><button @click="submit">提交</button></div>
</template><style scoped>
.container {width: 500px;height: 500px;background-color: #ccc;border: 1px solid grey;margin: 0 auto;
}
.header {height: 50px;background-color: #eee;border: 1px solid grey;
}
.body {height: 400px;background-color: #ddd;border: 1px solid grey;
}
.footer {height: 50px;background-color: #eee;border: 1px solid grey;
}
</style>
自定义修饰符
可以通过 modelModifiers
prop的方式自定义
案例:
自定义一个增加修饰符,有它就增加一个好
父组件
<A v-model="flag" v-model:title.add="title"></A>
子组件
<script setup lang="ts">
const props = defineProps<{modelValue:boolean,title:string,titleModifiers?: {[key: string]: boolean}
}>()
const emit = defineEmits<{(e:'update:modelValue', value: boolean): void,(e:'update:title', value: string): void
}>()
function submit() {emit('update:modelValue', false)if(props.titleModifiers?.add) {emit('update:title', props.title + '好')}
}
function inputChange(e:any) {let value = e.target.valueemit('update:title', value)
}
</script><template><div class="container" v-if="modelValue"><div><span>请输入:</span><input type="text" :value="title" @input="inputChange($event)"></div><button @click="submit">提交</button></div>
</template><style scoped></style>
directive-自定义指令
vue里面有一些系统定义好的指令,如v-model,v-show,v-if,v-bind,v-for等,它也可以支持自定义指令
1. 指令的生命周期钩子函数
和vue组件的生命周期函数很类似
- created 元素初始化的时候
- beforeMount 指令绑定到元素后调用 只调用一次
- mounted 元素插入父级dom调用
- beforeUpdate 元素被更新之前调用
- update 这个周期方法被移除 改用updated
- beforeUnmount 在元素被移除前调用
- unmounted 指令被移除后调用 只调用一次
2. 在setup语法糖模式下定义局部指令
但这里有一个需要注意的限制:必须以 vNameOfDirective
的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。
定义:
import {ref,Directive,DirectiveBinding} from "vue"; const vMove:Directive = {mounted(el:HTMLElement,binding:DirectiveBinding){console.log(binding)el.style.backgroundColor = '#bfa'} }
使用:
<A v-move:aaa.stop="title"></A>
钩子函数的参数:
第一个为,el即绑定的元素,第二个一个DirectiveBinding类型的对象,第一个属性arg为指令名字modifiers里放的就是修饰符
3. 权限按钮案例
你可能想在 mounted
和 updated
时触发相同行为,而不关心其他的钩子函数。那么你可以通过将这个函数模式实现
这个案例将会采用函数模式
<script setup lang="ts">
import { DirectiveBinding } from 'vue'
import type { Directive } from 'vue'
localStorage.setItem("userId",'taotao')
let permission = ['taotao:shop:edit','taotao:shop:delete','taotao:shop:manage'
]
let userId = localStorage.getItem('userId')
const vFocus: Directive = (el:HTMLElement,binding:DirectiveBinding) => {if (!permission.includes(userId + ':' + binding.value)) {el.style.display = 'none'}
}
</script><template><div class="context"><button v-focus="`shop:edit`">编辑</button><button v-focus="`shop:manage`">管理</button><button v-focus="`shop:delete`">删除</button></div>
</template><style scoped>
.context {width: 500px;height: 300px;background-color: #ccc;border: 1px solid grey;margin: 0 auto;
}button {width: 100px;height: 50px;margin: 10px;background-color: #fff;border: 1px solid grey;}
</style>
4.图片懒加载案例
想实现图片懒加载,首先需要将图片引入
可以采用vite提供的import.meta.glob("路劲")的形式
注意需要配置,这样才能直接全部导入
{ eager: true }
指定返回值的key为string类型,值必须是一个对象,且对象内部具有default属性
- Record 类型是TS中其众多强大特性之一
- 它为我们提供了创建键值对映射的强大能力
- 极大地增强了代码的灵活性与类型安全性
Record<string, { default:string }>
<script setup lang="ts">
// 实现图片懒加载
import {Directive,DirectiveBinding} from "vue";
let images: Record<string, { default:string }> = import.meta.glob("./assets/*.*",{ eager: true });
let imagesList = Object.values(images).map((item) => item.default);</script><template><div class="image"v-for="item in imagesList":key="item"><img :src="item" alt="图片"></div>
</template><style scoped>
.image img{width: 300px;
}
</style>
2. 需要观察视口的变化,图片有无在可显示的区域
<script setup lang="ts">
// 实现图片懒加载
import {Directive,DirectiveBinding} from "vue";
let images: Record<string, { default:string }> = import.meta.glob("./assets/*.*",{ eager: true });
let imagesList = Object.values(images).map((item) => item.default);
const vLazy:Directive = (el:HTMLImageElement,binding:DirectiveBinding)=> {let observer = new IntersectionObserver(async (entries) => {let img = await import ("./assets/image/vue.svg")el.src = img.defaultif (entries[0].isIntersecting && entries[0].intersectionRatio > 0) {setTimeout(()=> {el.src = binding.valueobserver.unobserve(el);},2000)}});observer.observe(el);
}</script><template><div class="image"v-for="item in imagesList":key="item"><img :src="item" alt="图片" v-lazy="item"></div>
</template><style scoped>
.image img{width: 300px;
}
</style>
自定义Hooks
Vue3 的自定义的hook
- Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数
- Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数
案例:将图片转为base64格式
app.vue
<script setup lang="ts">
// 自定义hook
let images: Record<string, { default:string }> = import.meta.glob("./assets/*.*",{ eager: true });
let imagesList = Object.values(images).map((item) => item.default);
import useHooks from './hooks/index.ts'
const base64 = useHooks({el: '#image'})
base64.then(res => {console.log(res.value)
})
</script><template><div class="image"><img :src="imagesList[0]" id="image"></div>
</template><style scoped>
.image img{width: 300px;
}
</style>
hooks/index.ts
type Options = {el: string
}
import {onMounted} from "vue";export default function (option:Options) {return new Promise((resolve) => {onMounted(()=> {const file:HTMLImageElement = document.querySelector(option.el) as HTMLImageElementfile.onload = () => {resolve({value:toBase64(file)})}})function toBase64(file:HTMLImageElement) {const canvas = document.createElement('canvas')canvas.width = file.widthcanvas.height = file.heightconst ctx = canvas.getContext('2d')if (ctx) {ctx.drawImage(file, 0, 0, file.width, file.height)return canvas.toDataURL('image/png')}}})
}