vue2.0自定义v-model组件
注意下面是vue2的自定义v-model组件使得子组件和父组件的其他dom实现双向绑定,但是vue2.0的实现方式有诸多缺点
比如:
- 只能双向绑定一个值,不能双向绑定多个值
- 实现方式比较繁琐,并且难以理解
子组件CustomVModel.vue
<template>
<!-- 例如:vue 颜色选择 -->
<input type="text"
:value="content"
@input="$emit('changeEvent', $event.target.value)"
>
<!--
1. 上面的 input 使用了 :value 而不是 v-model
2. 上面的 changeEvent 和 changeEvent 要对应起来
-->
</template>
<script>
export default {
model: {
prop: 'content', // 这个content和下面的props里面的content是自己取的但是两者要对应一样
event: 'changeEvent'
},
props: {
content: String,
default() { //默认值
return ''
}
}
}
</script>
父组件index.vue
<template>
<div>
<!-- <p>vue 高级特性</p>
<hr> -->
<!-- 自定义 v-model -->
<p>{{name}}</p>
<!-- -->
<CustomVModel v-model="name"/>
</div>
</template>
<script>
import CustomVModel from './CustomVModel'
export default {
components: {
CustomVModel
},
data() {
return {
name: '123',
}
}
}
</script>
vue3.0实现自定义v-model
根据上图的比较,我们得知vue3.0实现自定义v-model必须具备以下条件
- 必须具备modelValue属性
- 需要在更新的时候必须触发一个onUpdate:modelValue的事件
必须具备modelValue属性
在子组件中ValidateInput:
vue3.0的context 是 setup() 的第二个形参,它是一个上下文对象,可以通过 context 来访问Vue的实例 this
在父组件中:
$nextTick
- Vue是异步渲染的框架
- data改变之后,DOM不会立刻渲染
$nextTick会在DOM渲染之后被触发,以获取最新的DOM节点
<template>
<div id="app">
<ul ref="ul1">
<li v-for="(item, index) in list" :key="index">
{{item}}
</li>
</ul>
<button @click="addItem">添加一项</button>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem() {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
const ulElem = this.$refs.ul1 //获取上面的DOM ul元素
console.log('没有用$nextTick '+ulElem.childNodes.length )
// 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
// 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
this.$nextTick(() => {
// 获取 DOM 元素
const ulElem = this.$refs.ul1 //获取上面的DOM ul元素
console.log(' 用了$nextTick '+ ulElem.childNodes.length )
})
}
}
}
</script>
点第一次
- 异步渲染,$nextTick 待 DOM 渲染完再回调
- 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
vue的动态组件
一句话来概括就是将组件以变量的形式进行渲染
语法如下
<template>
<div>
<component :is="HelloWorldName"/>
</div>
</template>
import HelloWorld from './HelloWorld'
export default {
components: {
HelloWorld
},
data() {
return {
HelloWorldName: "HelloWorld",
}
}
}
</script>
vue异步加载组件
异步加载组件的简单理解就是什么时候需要用到什么时候就加载
异步组件不需要用import引入了 在下面是这种方式: FormDemo: () => import(’…/…/BaseUse/FormDemo’) 在components里面引入的
点击这个按钮就会出现FormDemo组件(此组件就不贴代码了)
<template>
<div>
<FormDemo v-if="showFormDemo"/>
<button @click="showFormDemo = true">show form demo</button>
</div>
</template>
<script>
export default {
components: {
FormDemo: () => import('../../BaseUse/FormDemo'),// 异步加载组件
},
data() {
return {
showFormDemo: false
}
}
}
</script>
keep-alive
先从字面意思 上理解,坚持活着,这里的主语是组件,其实本质上就是缓存组件的意思,缓存频繁切换的组件,使其不重复渲染。可以优化vue项目的性能
下面的实例营造了这样一个场景:有三个按钮A,B,C,分别点击不同的按钮,下面会出现不同的 组件,其实本质上就是一个简单的tabBar
将要渲染的三个组件 KeepAliveStageA,
KeepAliveStageB, KeepAliveStageC 用 keep-alive 组件包裹,他们就不会被销毁 了,除非页面被关闭
KeepAlive.vue
<template>
<div>
<button @click="changeState('A')">A</button>
<button @click="changeState('B')">B</button>
<button @click="changeState('C')">C</button>
<keep-alive> <!-- tab 切换 -->
<KeepAliveStageA v-if="state === 'A'"/>
<KeepAliveStageB v-if="state === 'B'"/>
<KeepAliveStageC v-if="state === 'C'"/>
</keep-alive>
</div>
</template>
<script>
import KeepAliveStageA from './KeepAliveStateA'
import KeepAliveStageB from './KeepAliveStateB'
import KeepAliveStageC from './KeepAliveStateC'
export default {
components: {
KeepAliveStageA,
KeepAliveStageB,
KeepAliveStageC
},
data() {
return {
state: 'A'
}
},
methods: {
changeState(state) {
this.state = state
}
}
}
</script>
KeepAliveStateA.vue
<template>
<p>state A</p>
</template>
<script>
export default {
mounted() {
// eslint-disable-next-line
console.log('A mounted')
},
destroyed() {
// eslint-disable-next-line
console.log('A destroyed')
}
}
</script>
KeepAliveStageB.vue
<template>
<p>state B</p>
</template>
<script>
export default {
mounted() {
// eslint-disable-next-line
console.log('B mounted')
},
destroyed() {
// eslint-disable-next-line
console.log('B destroyed')
}
}
</script>
KeepAliveStageC.vue
<template>
<p>state C</p>
</template>
<script>
export default {
mounted() {
// eslint-disable-next-line
console.log('C mounted')
},
destroyed() {
// eslint-disable-next-line
console.log('C destroyed')
}
}
</script>
mixin
多个组件之间有相同的逻辑,抽离出来。
这里其实思想和react的自定义hook一模一样
vue3中也有自定义hook的功能了
mixin语法有如下缺点:
- 变量来源不明确,不利于阅读
- 多个mixin可能会造成命名冲突
- mixin和组件可能出现多对多的关系,复杂度较高
朋友们不需要担心,现在vue3出来了,里面也有自定义hook了,完全可以代替mixin
下面一个场景就是,如果多个组件都需要使用到showName方法,就可以showName方法写到一个单独的js文件中,但是此js文件用的是vue的script相同的模板
mixin.js
export default {
data() {
return {
city: '北京'
}
},
methods: {
showName() {
// eslint-disable-next-line
console.log(this.name)
}
},
mounted() {
// eslint-disable-next-line
console.log('mixin mounted', this.name)
}
}
将mixin.js引入到要渲染的组件,这个组件用到minxin的showName方法和city属性
<template>
<div>
<p>{{name}} {{major}} {{city}}</p>
<button @click="showName">显示姓名</button>
</div>
</template>
<script>
import myMixin from './mixin'
export default {
mixins: [myMixin], // 可以添加多个,会自动合并起来
data() {
return {
name: 'lisi',
major: 'English'
}
},
methods: {
},
mounted() {
// eslint-disable-next-line
console.log('component mounted', this.name)
}
}
</script>
vue3中的自定义hook函数
下面的自定义hook函数传入参数是一个dom节点,监听document中的点击事件,看点击的区域是否在这个节点内部
import { ref, onMounted, onUnmounted,Ref } from "vue";
const useClickOutside = (elementRef: Ref<null | HTMLElement>) => {
const isClickOutside = ref(false)
const handler = (e: MouseEvent) => {
if (elementRef.value) {
if (!elementRef.value.contains(e.target as HTMLElement)) {
isClickOutside.value = false
}else{
isClickOutside.value = true
}
}
}
onMounted(() => {
// console.log(123132132);
document.addEventListener('click', handler)
// console.log(isClickOutside.value);
})
onUnmounted(() => {
document.removeEventListener('click', handler)
})
return isClickOutside
}
export default useClickOutside
const isClickOutside = useClickOutside(dropRef);
watch(isClickOutside, () => {
// console.log(isClickOutside.value);
if (isOpen.value && !isClickOutside.value) {
console.log("watch");
isOpen.value = false;
}
});