生命周期
每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM,在数据变化时更新 DOM 等
同时在这个过程中也会运行一些叫做生命周期钩子的函数,目的是给予用户在一些特定的场景下添加他们自己代码的机会
//生命周期 组件从创建开始到销毁中每个阶段触发时机
// Vue中 有个虚拟DOM
创建前
创建后
// 挂载就是把虚拟DOM转换为真实DOM并且放在页面中
挂载前
挂载后
// 只能在挂载后获取真实DOM
// 当数据发生改变时,虚拟DOM先更新,然后转换真实DOM重新渲染
更新前
更新后
// 当从页面中删除时,就是销毁的过程
销毁前
销毁后
主要阶段:创建、挂载、更新、销毁
// 组合式开发
1.`setup触发的实际早于创建前`
2.接收父元素传递过来的数据并且修改,然后创建Data数据
3.在组合式当中不需要写data,在setup中return对象 就是data的数据
setup(){
console.log("setup")
}
//以下是选项式开发
// 选项式开发中需要写data
<button ref="bn" @click="++num">按钮</button>
`data触发的实际晚于创建前,在创建前和创建后之间`
data() {
return {
num: 1,
list: ["a", "b", "c", "d", "e"]
}
}
`创建`
1.创建虚拟DOM之前,在组合式中可以调用到data的数据,在选项式开发时不能调用到data数据
2.一般我们在这个时机向服务器请求获取数据
beforeCreate() {
console.log("创建前");
},
created() {
// 当虚拟DOM创建完成后触发这个,可以操作data中的数据进行处理,并且可以在这个时间段向服务器请求数据
console.log("创建后")
},
`挂载`
beforeMount() {
// 还没有挂载在真实D0M树上,但是虚拟D0M根据数据创建成功
console.log("挂载前")
},
mounted() {
// 在这个时间可以获取DOM对象,进行D0M对象的操作
console.log("挂载后");
console.log(this.$refs.bn)// 直接获取到真实DOM按钮
},
`更新`
beforeUpdate() {
// 数据发生改变,虚拟DOM改变,但是真实DOM没有发生改变
console.log("更新前")
},
updated() {
// 当真实DOM发生改变以后
console.log("更新后")
},
`销毁`
beforeUnmount() {
// 一整个组件销毁 才会到这
console.log("销毁前")
// 没有从真实DOM卸载掉,可以在这里拦截处理,不卸载了,可以在这里通知其他组件更新改变
},
unmounted() {
// 当前元素从DOM树中删除了,可能会产生位置改变,这时候可以通知其他组件进行位置填充
console.log("销毁后")
}
`vue3中在v-for中不能使用v-if 只能使用v-show`
//让列表中c不显示
<ul>
<li v-for="(value, index) in list" :key="index" v-show="value != 'c'">{{ value }}</li>
</ul>
`销毁一个组件`
//使用父子间通信
在App.vue中
<LifeCycle v-if="bool" @remove="bool=false"/>
data(){
return{
bool:true,
}
}
在LifeCycle.vue中
//子组件中触发的事件
<button @click="removeHandler">销毁</button>
removeHandler() {
// 触发自定义事件
this.$emit("remove");// 抛发事件 dispatchEvent
}
$emit的使用
`作用:用于子组件中触发父组件方法并传值`
//父组件中
//parent-event 要与子组件的自定义事件名相同
<child-component @parent-event="handleParentEvent"/>
<script>
export default {
name: 'ParentComponent',
// 注册子组件
components: {
ChildComponent
},
methods: {
handleParentEvent(data) {
// 处理自定义事件的逻辑
console.log(data); // 输出:'Hello, World!'
}
}
}
//子组件中
<template>
<button @click="handleChildEvent">子组件中触发的事件</button>
</template>
<script>
export default {
name:'ChildComponent',
methods: {
handleChildEvent() {
// 触发自定义事件,并传递数据给父组件
this.$emit('parent-event', 'Hello, World!');
}
}
}
响应式
`Vue的事件绑定原理是基于DOM事件的响应式机制`
1.使用事件绑定实现响应事件
<div>{{ count }}</div>
<button @click="++count">按钮</button>
//把两个输入框的值相加返回到div中
//使用v-model 双向绑定 当input中的值改变时 会把改变的值传回到表单
<input type="text" v-model.number="a" @input="inputHandler">
<input type="text" v-model.number="b" @input="inputHandler">
<div>{{ sum }}</div>
inputHandler(){
this.sum=Number(this.a)+Number(this.b);
}
`计算属性`
1).计算属性是基于它们的响应式依赖进行缓存的
2).计算属性比较适合对多个变量或者对象进行处理后返回一个结果值,也就是说多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化
3).同等条件下,计算属性优于 方法 以及 js表达式
4).使用computed计算属性 相当于使用了@事件这种
5).`计算属性中方法相当于 对象中getter方法`
6).`在计算属性中,如果调用的值发生改变时,会自动重新执行计算属性`
7).`在选项式开发中不管改变原数组还是产生新的数组覆盖,都会触发对应的computed计算属性`
2.使用计算属性 实现响应事件
<input type="text" v-model="a">
<input type="text" v-model="b">
<div>{{ sum1 }}</div>
computed: {
sum1() {
return String(this.a) + String(this.b)
}
}
3.事件绑定与计算属性一起用
list: [1, 2, 3]
<ul>
<li v-for="(value, index) in list" :key="index">{{ value }}</li>
</ul>
<div>{{ total }}</div>
<button @click="clickHandler">按钮</button>
//当点击按钮时会产生新数组,就会触发computed中的total,通过total把值返回到div中
clickHandler() {
this.list = this.list.concat(++this.count)
}
computed: {
total() {
return this.list.reduce((v, t) => v + t)
}
}
`在计算属性中使用 set、get`
1.set get 不能直接写在computed下
2.在使用计算属性时,如果set和get中有设置获取相同的变量时,就会在set后再次触发get
<input type="text" v-model="names">
<div><span>{{ firstname }}</span><span>{{ secondname }}</span></div>
<button @click="this.firstname = 'zhang', this.secondname = 'san'">按钮</button>
//实现流程
/*
在input中输入的数据通过" "进行截取,
" "前的是数组中的第0项赋值给firstname,
" "后的是数组中的第2项赋值给secondname,
在input若是一直输入但不输入" "时 就只会有firstname存在
若输入了" "时,从" "之后就是secondname
输入值时 通过set获取并赋值之后,通过get返回渲染到页面中
因为在按钮中也给firstname secondname赋值了 当点击按钮时值发生了变化,触发computed 重新通过set获取并赋值,在通过get返回渲染到页面中
*/
computed: {
set(value) {
var arr = value.trim().split(" ")
this.firstname = arr[0] || ""
this.secondname = arr[1] || ""
},
get() {
return this.firstname.trim() + (this.secondname.trim().length == 0 ? "" : " " + this.secondname.trim())
}
}
当watch和computed都可以完成这个事情的时候,我们优先选择computed
watch监听数据
1.使用watch时 会有两个参数
//newValue 新值(当前的值),oldValue 上次的旧值
2.使用watch来侦听data中数据的变化,watch中的属性一定是data 中已经存在的数据
3.
4.使用场景:数据变化时执行异步或开销比较大的操作
5.监听一个对象下的属性的变化
watch 默认是浅层的,如果侦听的是是引用类型,默认情况下在改变变量的引用地址后才会被触发,修改属性,不会触发
`使用`
abc: ""
<input type="text" v-model="abc">
<span style="color: red;" v-if="s == 2">输入错误</span>
<span style="color: green;" v-if="s == 1">输入正确</span>
//点击按钮是会触发点击事件 把input中的值替换为设置的值
<button @click="abc = 12345">按钮</button>
watch: {
// 监听了data中的abc
abc(newValue, oldValue) {
// newValue 新值(当前的值),oldValue 上次的旧值
console.log(newValue, oldValue);
if (/^\d*$/.test(this.abc)) {
this.s = 1;
} else {
this.s = 2;
}
}
}
//观察数据的变化
{{ data }}--{{ sum }}
//当点击第一个按钮时改变了data的引用类型会触发watch
<button @click="data={a:10,b:20}">按钮</button>
//当点击第二个按钮时 只是改变了其中的属性值 并不会触发watch
<button @click="data.a=20">按钮</button>
watch: {
// 如果是引用类型,默认情况下在改变变量的引用地址后才会被触发,修改属性,不会触发
// 监听了整个data
data(newValue,oldValue){
console.log(newValue,oldValue)
this.sum = this.data.a + this.data.b
}
}
// 只是监听data下的某个属性
//这样针对data下的某个属性才会触发 watch
"data.a":function(newValue,oldValue){
console.log(newValue,oldValue)
this.sum = this.data.a + this.data.b
}
//如何监听一个对象下的属性的变化?-->深监听
`深监听`
//deep的效率很低
1.当设置deep为true,就会对对象的属性做深度监听
2.当初始化的时候,一般不会进入watch,但是设置immediate,可以在初始化的时候进入一次watch
{{ data }}--{{ sum }}
<button @click="data = { a: 10, b: 20 }">按钮</button>
<button @click="data.a = 20">按钮</button>
watch: {
data: {
deep: true,
immediate: true,
handler(newValue, oldValue) {
console.log(newValue, oldValue)
this.sum = this.data.a + this.data.b
}
}
}
watch底层原理
var o = { a: 1, b: 2 };
var sum = 0;
function update(a, b) {
sum = a + b;
console.log(sum);
}
var p = new Proxy(o, {
set(target, key, value) {
if (key === "a") {
update(value, target.b);
} else if (key === "b") {
update(target.a, value);
}
return Reflect.set(target, key, value);
}
})
p.a = 10;
p.b = 20;
watch 和computed的区别
1、功能上
computed是计算属性
watch是监听一个值的变化,然后执行对应的回调。
2、是否调用缓存
computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取
watch在每次监听的值发生变化的时候都会执行回调。
3、是否调用return
omputed中的函数必须要用return返回
watch中的函数不是必须要用return。
4、computed默认第一次加载的时候就开始监听
watch默认第一次加载不做监听,如果需要第一次加载做监听,添加immediate属性,设置为true(immediate:true)
5、使用场景
computed----当一个属性受多个属性影响的时候,使用computed-----购物车商品结算
watch–当一条数据影响多条数据的时候,使用watch-----搜索框.
组件化
全局注册
1.全局注册的组件可以在此应用的任意组件的模板中使用
2.所有的子组件也可以使用全局注册的组件
`缺点`
1. 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
2. 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。
//在main.js 文件中
a) createApp(App).mount('#app') //等价于下面两句
b) var arr = createApp(App);
arr.mount("#app")
`全局注册组件`
//此处文件的创建是个示例 不必一定按照这样
1.在components 下创建一个文件夹componentsA 在其中创建文件ChildA.vue
2.注册:
//在main.js 文件中 使用b)方式 然后写入 app.component("ChiledA",ChildA) 全局注册成功
`注`:import ChildA from './components/componentsA/ChildA.vue';
引入文件的语句会自动在main.js 文件中补充
//注册成之后再App.js文件中引入即可使用
局部注册
局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用
//它的优点是使组件之间的依赖关系更加明确
//主文件
<template>
<div>
<ChildA/>
//以下两个都是引入ChildB.vue文件 方式不同
<ChildB/>
<foot-view />
</div>
</template>
<script>
// import ChildA from '@/components/componentsA/ChildA.vue';
import ChildB from "@/components/componentsA/ChildB.vue";
export default {
// 局部注册
components:{
//当对象的属性名和属性值相等时 : 和其后面内容可以省略
//所以以下两个效果相同
ChildA:ChildA,
ChildA,
// 注册组件的名称:引入的组件
//可以使用 注册组件的名称 引入文件
"foot-view":ChildB
}
}
</script>
//ChildA.vue
<template>
<div>
<input type="text" name="user">
<button>按钮</button>
<div>{{ "你好" }}</div>
</div>
</template>
//ChildB.vue
<template>
<div>
<input type="text" name="password">
<button>按钮</button>
</div>
</template>
组件间通信
`组件通信四大类`
1.父与子
2.子与父
3.子与子
4.跨层级
`八种方式`
1.props和$emit
2.$attrs和$listeners
3.中央事件总线 bus
当两个组件不是父子关系时,使用中央事件总线的方式。
新建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus.$on监听触发的事件
4.provide和inject
父组件通过provide来提供变量,再子组件中通过inject注入变量。只要再父组件的生命周期内,子组件都可以调用
5.v-model
父组件通过v-model给子组件传值时,会自动传递一个value的prop属性,在子组件中通过 this.$emit('input',val)自动修改v-model绑定的值
6.$parent和$children
7.boradcast和dispatch
8.使用vuex/pinia处理组件之间的交互
父向子组件传值,使用prop方式
<childA a="abc" :num="num"/>
props:["a","num"]
子向父组件传值,利用自定义事件$emit方式
<ChildA @xietian="xietianHandler" />
<button @click="$emit('xietian',num)">按钮</button>
this.$emit("xietian", this.num)
父组件给子组件传值
1.父组件中通过props传进来的数据是不能修改的,是一个只读属性
2.在props中没有声明的属性,可以从this.$attrs中获取到,被声明的属性 在this.$attrs中获取不到
3.如果当前组件的根节点只有一个,那么没有被声明在props中的属性就会被透传到当前组件的根组件中(当前组件只能有一个根元素)
4.如果当前组件有多个根元素就不会放在当前组件上
//父组件
<template>
<div>
<!-- a="abc" 向子组件中传入a的值 -->
<!-- :num="num" 获取父组件中变量num的值 向子组件中传入num的值-->
//此处设置的class="div1" 并没有在子组件的props声明 被透传到子组件的根组件中 (前提只有一个根组件)
//在子组件 根组件div 上就会有class="div1" <div class="div1">...</div> div1样式就给了根组件div
<childA a="abc" :num="num" :list="list" b="2" class="div1" />
</div>
</template>
<script>
//注册子组件
import childA from "@/components/propsView/childA.vue
export default {
components: {
childA
},
data() {
return {
num: 10,
list: [1, 2, 3, 4]
}
}
}
</script>
//子组件
<template>
<div>
<!-- `注:父项传值的变量 要和子项接收值的变量相同` -->
<!-- 从父项中获取a的值 -->
<div>{{ a }}</div>
<!-- 从父项中获取num的值 -->
<p>{{ num }}</p>
<!-- 从父项中获取list的值 -->
<ul>
<li v-for="(value, index) in list" :key="index">{{ value }}</li>
</ul>
<!-- //因为没有在props 中声明 若不加$attrs则获取不到 -->
<h3>{{ $attrs.b }}</h3>
</div>
</template>
<script>
export default {
//父组件给子组件传值 方式1
简易的设置props
// 使用props 数组形式获取父模块传入的值
props:["a","num","list"],
//父组件给子组件传值 方式2
约束props
// 使用对象的形式对于传入props的值进行约束
props:{
a:String,
num:Number,
list:Array
},
//父组件给子组件传值 方式3
`type: String 设置传入值的属性`
`default: "abc" 设置传入值的默认值`
`required: true 设置值为必填要求`
`validator 检测传入props属性的值是否符合要求`
props: {
a: {
type: String,
default: "abc",// 如果没有传入a属性,默认这个属性的值是"abc"
},
num: {
type: Number,
required: true,// 必填要求 一般default和required不同时使用
// 检测传入props属性的值是否符合要求,如果符合要求返回true,不符合要求返回false
validator(value) {
return value < 100 && value > 0
}
},
list: Array
},
mounted() {
//父组件中通过props传进来的数据是不能修改的,是一个只读属性
// console.log(this.a)
// 在props中没有声明的属性,可以从this.$attrs中获取到
// 声明的属性 在this.$attrs中获取不到
console.log(this.$attrs)
// 如果当前组件的根节点只有一个,那么没有被声明在props中的属性就会被透传到当前组件的跟组件中(当前组件只能有一个根元素)
// 如果当前组件有多个根元素就不会放在当前组件上
}
}
</script>
<style>
.div1 {
width: 300px;
height: 300px;
background-color: blueviolet;
}
</style>
子组件给父组件传值(监听事件)
可以直接使用 $emit 方法触发自定义事件
$emit() 方法在组件实例上也同样以 this.$emit() 的形式使用
`从子组件中获取num值`
//父组件
`父组件可以通过 v-on (缩写为 @) 来监听事件`
<template>
<div>
{{ num }}
</div>
//接收的事件名@xietian 要与子组件中设置的一样
//接收方式1
<ChildA @xietian="xietianHandler" />
//接收方式2
<childA @xietian="value=>num=value"/>
</template>
<script>
//注册子组件
import ChildA from "@/components/emiteView/ChildA.vue"
export default {
components: {
ChildA
},
data() {
return {
num: 0
}
},
methods: {
//value 就是子组件中穿的参数 this.num
xietianHandler(value,a) {
this.num=value;
console.log(a)
}
}
}
</script>
//子组件
`使用 this.$emit 抛出事件,还可以进行传参`
<template>
<div>
//抛发事件
//抛发方式1
<button @click="clickHandler">按钮</button>
//抛发方式2
<!-- emit可以传多个参数,第一个参数是事件类型,第二个参数后就是传出的参数 -->
<button @click="$emit('xietian',num)">按钮</button>
</div>
</div>
</template>
<script>
export default {
//设置自定义事件
`子组件给父组件传值-声明触发的事件`
emits: ["xietian"],
data() {
return {
num: 1
}
},
methods: {
clickHandler() {
//通过this.$emit 与父组件关联 向父元素传值
this.$emit("xietian", this.num)
}
}
}
</script>
emits对象语法
//父组件
<ChildA @xietian="xietianHandler" @abc="abcHandler"/>
methods:{
xietianHandler(value,a){
this.num=value;
console.log(a)
},
abcHandler(value){
console.log(value)
}
}
//子组件
<template>
<div>
<!-- <button @click="clickHandler">按钮</button> -->
<!-- emit可以传多个参数,第一个参数是事件类型,第二个参数后就是传出的参数 -->
<button @click="$emit('xietian', num, 55)">按钮</button>
<button @click="$emit('abc', 100)">按钮</button>
</div>
</template>
<script>
export default {
//设置自定义事件
`子组件给父组件传值-声明触发的事件`
emits: {
//给null 说明不需要检测内容 只要abc这个事件即可
abc:null,
// 检测传参的内容
"xietian": (a, b) => {
// console.log(a,b)
if (b < 50 || b > 100) return false;
return true;
}
},
data() {
return {
num: 1
}
},
methods: {
clickHandler() {
this.$emit("xietian", this.num)
}
}
}
</script>
传递 使用(v-module)
单一v-model
`如果父级使用了属性v-model进行传值 子元素中props定义的属性名必须是modelValue,则事件必须是 update:modelValue 即 :后要和定义的属性名一样`
eg: props[属性名] emits:["update:属性名"]
//单一v-model中 固定是
props:["modelValue"],
emits:["update:modelValue"]
//父组件
<template>
<div>
<!-- 通过 v-model向子组件传值-->
<ChildA v-model="num"/>
</div>
</template>
<script>
import ChildA from "@/components/transmitValue/ChildA.vue"
export default{
components:{
ChildA
},
data(){
return{
num:20,
}
},
methods: {
getData(val) {
console.log("接收到子组件的数据:" + val);
},
}
}
</script>
//子组件
<template>
<div>
<div>{{ modelValue }}</div>
<button @click="clickHandler">按钮</button>
</div>
</template>
<script>
// 如果父级使用了属性v-model 子元素中props定义的属性名必须是modelValue,则事件必须是"update:modelValue 即 :后要和定义的属性名一样
export default {
// 获取父组件传进来的值 这里是外面出入的num
props: ["modelValue"],
// 设置事件名
emits: ["update:modelValue"],
methods: {
// 通过点击事件当作一个媒介触发 自定义事件
clickHandler() {
// 使用this.$emit向父元素抛发事件
// 前面是事件名称,后面必须带一个需要修改成为的数据
this.$emit("update:modelValue", this.modelValue + 1)
}
}
}
</script>
多个v-model
//父组件
<template>
<div>
<!-- 通过 v-model向子组件传值-->
//多个要写成这种 v-model:变量名="变量名"
<ChildB v-model:count="count" v-model:msg="msg" />
{{ msg }}
</div>
</template>
<script>
import ChildB from "@/components/transmitValue/ChildB.vue"
export default {
components: {
// ChildA
ChildB
},
data() {
return {
count: 1,
msg: "abcdef"
}
},
methods: {
getData(val) {
console.log("接收到子组件的数据:" + val);
},
}
}
</script>
//子组件
<template>
<div>
<p>{{ count }}</p>
<p>{{ msg }}</p>
<button @click="clickHandler">按钮</button>
//父传子中如果使用v-model传入后的数据,在表单元素中不能是v-model
//要写属性绑定的形式
//不能是这种v-model:msg=msg 会报错
<input type="text" :value="msg" @input="inputHandler">
</div>
</template>
<script>
export default {
props: ["count", "msg"],
// 要求名字必须使用"update:prop中的属性名"
emits: ["update:count", "update:msg"],
methods: {
clickHandler() {
this.$emit("update:count", this.count + 1)
},
inputHandler(e) {
// e.target是input控件
this.$emit("update:msg", e.target.value)
}
}
}
</script>
总结
`单一 v-model`
1.如果父级使用了属性v-model
2.子元素中props定义的属性名必须是modelValue,
3.则事件必须是 update:modelValue
//以上是固定的
`多个v-model`
1.父组件传值时 多个要写成这种 v-model:变量名="变量名"
2.子组件 事件命名形式 要求名字必须使用"update:prop中的属性名"
3.且子组件获取值之后 使用时 写属性绑定的形式
透传元素
注:以下都是父元素传入到子元素,但是子元素并没有通过props或emits接收的数据或事件
`元素继承`
1.如果当前组件的根节点只有一个,那么没有被声明在props中的属性就会被透传到当前组件的根组件中(当前组件只能有一个根元素)
2.如果当前组件有多个根元素就不会放在当前组件上
`class style合并`
1.自动将父元素的class传递进入到attrs和当前的class内容合并 ,style也会合并,显示效果是父元素的class和style
2.如果style有相同的属性,父元素会覆盖当前元素的对应属性的值
//父组件中的class style把子组件中的class style的样式覆盖了
//父组件
<ChildA class="div1" style="font-size: 20px;color: blue;"/>
//子组件
<div class="div2" style="font-size: 5px; color: red;">{{ num }}</div>
`事件的继承`
//注:只能有当前组件一个根元素
1.当在父组件中添加了事件 没有在子组件通过emits获取时 会把该事件给到子组件中的根元素上
2.该子组件中的根元素会把事件冒泡到其中的元素上 即其中的元素都会触发该事件
//父组件
<ChildA @click="clickHandler"/>
methods:{
clickHandler(e){
//若是不加约束 在根元素中的所有元素都可以触发事件
//这样加了约束就会把 事件放到指定的元素上
if(e.target.id!=="bn1") return;
this.num++
}
}
//子组件
//根元素是 div
<div>
{{ num }}
<button id="bn1">按钮</button>
</div>
`禁用 元素继承`
inheritAttrs:false
//使用上面的语句 禁止继承来自父组件的class、style、事件以及属性
`多根节点的属性继承`
//父组件
<template>
<div>
//多写一个就会多生成一个
<ChildB type="a" />
<ChildB type="b" />
<ChildB type="c" />
</div>
</template>
<script>
import ChildB from "@/components/attrsView/ChildB.vue"
export default {
components: {
ChildB
}
</script>
//子组件
<template>
<div>
<input type="text" v-model="msg">
</div>
</template>
<script>
export default {
name: "ChildB",
data() {
return {
//上面并没有通过 props 获取父组件床来的元素
//所以要使用this.$attrs 调用
msg: this.$attrs.type,
}
}
}
</script>
`知识点`
1.在父传子中,props和attrs的数据都是只读,父级的写法都是相同的
2.子元素中props会单独的将某些属性罗列,然后直接通过this调用这个属性就行
3.子元素中如果没有通过props将会放在$attrs中,我们可以通过this.$attrs调用里面的属性
4.props的优势可以对属性进行约束,类型约束、是否必填、默认值、判断值是否符合条件,props传递可以使用v-model
5.attrs中不能做这些
特殊属性 -ref
`$refs和ref`
//在mounted()中使用 $refs 可以直接获取到真实DOM
1.$refs:一个包含 DOM 元素和组件实例的对象,通过模板引用注册。
2.ref 设置在标签上时,当mounted以后可以调用到这个DOM对象
3.ref 设置在组件上时,当mounted以后可以调用到这个组件的代理对象,可以直接使用该组件的属性和方法
4.如果需要在Vue中操作DOM我们可以通过ref和$refs这两个来实现
总结:$refs可以获取被ref属性修饰的元素的相关信息
//父组件
<template>
<div>
`childc 就是设置的代理`
<ChildC ref="childc" type="c" />
</div>
</template>
<script>
import ChildC from "@/components/attrsView/ChildC.vue"
export default {
components: {
ChildC
},
data() {
return {
num: 10
}
},
methods: {
clickHandler(e) {
if (e.target.id !== "bn1") return;
this.num++
}
},
mounted() {
//直接可以获取到子组件对象的本身的所有数据操作
console.log(this.$refs.childc);
//可以通过个代理对象获取到子组件中data中的值
console.log(this.$refs.childc.num);
//可以通过个代理对象获取到子组件中attrs中的值
console.log(this.$refs.childc.$attrs.type);
//改变子组件中data中的值
this.$refs.childc.num = 20
}
}
</script>
//子组件
<template>
<div>
{{ num }}
</div>
</template>
<script>
export default {
name: "ChildC",
data() {
return {
num: 10
}
}
}
</script>
其余属性
当前组件可能存在的父组件实例,如果当前组件是顶层组件,则为 null
`$parent $root 不建议使用`
//父组件
<template>
<div>
<ChildA />
</div>
</template>
<script>
import ChildA from "@/components/otherTransmit/ChildA.vue"
export default {
name: "OtherTransmit",
components: {
ChildA
},
data() {
return {
num: 1,
count:10,
list:[1,2,3]
}
}
}
</script>
//子组件
<template>
<div>{{ $parent.num }}</div>
<button @click="$parent.num++">按钮</button>
</template>
<script>
export default {
name: "childA",
created() {
// 可以直接通过this.$parent访问父组件中所有数据
console.log(this.$parent);
// 可以直接获取到父组件上的值
console.log(this.$parent.num);
console.log(this.$parent.list);
// 获取到根组件上的所有数据,App.vue中内容
console.log(this.$root);
}
}
</script>
非父子组件传值
1.借助父元素
//子组件先传给父组件 父组件在传给其他子组件,该方法只能实现兄弟间
`ChildC 向 ChildB传值`
//ChildC子组件
<template>
<div>
<button @click="clickHandler">按钮</button>
</div>
</template>
<script>
export default {
// 自定义事件
emits: ["change-event"],
name: "childC",
data() {
return {
count: 100
}
},
methods: {
// 通过click事件 触发自定义事件
clickHandler() {
// 使用this.$emit向父元素抛发事件 并传参
this.$emit("change-event", this.count)
}
}
}
</script>
//父组件
<template>
<div>
//把更改过的num值 传入到ChildB子组件中
<ChildB :data="num"/>
//value就是ChildC子组件中传的参数 this.count
//通过这个自定义事件把ChildC子组件中的 参数赋给父组件中的num
<ChildC @change-event="value=>num=value"/>
</div>
</template>
<script>
import ChildB from "@/components/otherTransmit/ChildB.vue"
import ChildC from "@/components/otherTransmit/ChildC.vue"
export default {
name: "OtherTransmit",
components: {
ChildB,
ChildC
},
data() {
return {
num: 1
}
}
}
</script>
2.使用js
//该方法可以实现多种关系间传值,但是会破坏vue原有的机制
//ViewModel.js
export default class ViewModel extends EventTarget {
static _instance;
constructor() {
super();
}
static get instance() {
return ViewModel._instance || (ViewModel._instance = new ViewModel())
}
}
//ChildC.vue
<template>
<div>
<button @click="clickHandler">按钮</button>
</div>
</template>
<script>
// 引入ViewModel.js
import ViewModel from "@/model/ViewModel.js"
export default {
// 自定义事件
name: "childC",
data() {
return {
count: 100
}
},
methods: {
// 通过click事件 触发自定义事件
clickHandler() {
// 自定义事件
var evt=new Event("changeCount");
// 在evt定义一个变量count 把ChildC中的count的值给它
evt.count=this.count;
// 抛出这个事件
ViewModel.instance.dispatchEvent(evt);
}
}
}
</script>
//ChildB.vue
<template>
<div>{{ data }}</div>
</template>
<script>
import ViewModel from "@/model/ViewModel.js"
export default {
name: "ChildB",
data(){
return{
data:0
}
},
created() {
// 侦听ChildC 中抛出的事件 其中e就是evt
ViewModel.instance.addEventListener("changeCount", e => this.changeHandler(e))
},
methods: {
changeHandler(e){
// console.log(e.count);
// 把从ChildC中获取到的值 赋值给给ChildB中的data
this.data=e.count
}
}
}
</script>
对象深比较
var o = {
a: 1,
c: {
d: 3,
e: 4,
},
b: 2,
e: {
a: 1,
},
};
var o1 = {
a: 1,
b: 2,
c: {
d: 3,
e: 4,
},
e: {
a: 1
},
};
function deepCompare(o, o1) {
//通过Reflect.ownKeys() 取出所有属性名 在进行排序
//okeys和okeys1 是数组 存的是对象的每一个属性名
var okeys = Reflect.ownKeys(o).sort();
var okeys1 = Reflect.ownKeys(o1).sort();
//判断 长度和属性名是否相等 不相等说明不同 跳出
if (okeys.length !== okeys1.length || JSON.stringify(okeys) !== JSON.stringify(okeys1)) return false;
// 因为okeys与okeys1中的属性名相同 使用其中一个进行遍历就行
for (var i = 0; i < okeys.length; i++) {
//key 是属性名
var key = okeys[i];
//判断内层是否是对象
if (typeof o[key] == "object" && typeof o1[key] == "object") {
//若是对象 就在把内层对象放入其中进行比较
if (!deepCompare(o[key], o1[key])) return false;
//判断内层是否是函数
} else if (typeof o[key] == "function" && typeof o1[key] == "function") {
//若是函数 看他们的函数样式是否相同
if (o[key].toString() !== o1[key].toString()) return false;
//若不满足以上条件 直接判断两者内层是否相同
} else if (o[key] !== o1[key]) return false;
}
return true;
}
var bool = deepCompare(o, o1)
console.log(bool)