vue面试题(interview)vue

Vue中组件的通信方式-以及nextTick分析

2021-08-08  本文已影响0人  云高风轻

1. 前言

vue也是组件化开发框架,对于这种组件化开发来说,组件之间的通信方式通常都是非常重要的
所以单独开一个篇章来总结下有哪些通信方式


2. 首先列出常用的组件通信方式

1.props
2.$emit/$on
3.$children/$parent
4.$attrs / $listeners
5.ref
6.$root
7.eventBus
8.vuex

列出来后,可以自己先考虑下应用场景

下面不饶弯子了,以组件的关系来解说通信方式


3. 父子组件通信

3.1 props 父传子

1.父组件以属性的方式传值给子组件
2.子组件通过props方式接收数据

3.1.1父组件核心代码

在父组件中引入子组件并绑定parentData自定义属性

<Child:parentData="parentData"></Child >

<script>
import Child  from '@/components/child'
  export default{
    name:'Parent',
    components:{Child},
    data(){
      return{
        parentData:'我是父组件向子组件传递的值-props方式'
      }
    }
  }
</script>

3.1.2 子组件核心代码

1.在子组件中使用 props 接收父组件传递的数据,
2.props 里的名字跟父组件定义的属性名一致

<template>
  <div>我是父组件的数据:{{parentData}}</div>
  <div>我是父组件传递修改后的数据:{{mydata}}</div>
</template>
<script>
  export default{
    name:'Child',
    props:{
      parentData:{
        type:String,
        default:''
      }
    }
    data(){
      mydata:'俺的小破站 '+ this.parentData
    },
    watch:{
      parentData(newVal){
        this.mydata='俺的小破站 '+ newVal
      }
    },
  }
</script>

Vue的单向数据流机制,子组件不能够直接去修改父组件传递的修改的,否则能改的话那父组件的值就被污染了。

但是子组件内想要修改父组件传过来的值却不“污染”父组件的话,
可以在子组件内定义一个变量mydata去接收parentData数据,并使用 watch 监听parentData数据的变更


3.2 $emit/$on 子传父

1.子组件绑定自定义事件
2.使用 $emit() 触发更改数据

3.2.1 子组件核心代码

<el-button @click="handleEmit">告诉父组件我要更改数据啦</el-button>

<script>
 export default{
   name:'Child',
   methods:{
     handleEmit(){
       this.$emit('triggerEmit','我是来自子组件的数据')
     }
   }
 }
</script>

3.2.2父组件核心代码

1.父组件定义并绑定子组件传递的triggerEmit事件
2.triggerEmit事件名需跟子组件 $emit() 的事件名

<Child  @triggerEmit="changeData"></Child>

<script>
 import Child from '@/components/child'
 export default{
   name:'Parent',
   components:{Child},
   methods:{
     changeData(name){
       console.log(name) // => 我是来自子组件的数据
     }
   }
 }
</script>

3.3 $parent/$children

1.子组件通过 $parent 获得父组件实例

2.父组件通过 $children 获得子组件实例数组

3.3.1 子组件

<template>
  <div>我是子组件</div>
</template>

<script>
export default{
  name:"Child",
  data(){
    return{
      childTitle: '我是子组件的数据'
    }
  },
  methods:{
    childHandle(){
      console.log('我是子组件的方法')
    }
  },
  created(){
    console.log(this.$parent)
    console.log(this.$parent.parentTitle) // => 我是父组件的数据
    this.$parent.parentHandle() // => 我是父组件的方法
  }
}
</script>

this.$parent可以获取到父组件的方法、data的数据等,并可以直接使用和执行。


3.3.2 父组件

<template>
  <div>
    <Child>我是父组件</Child>
  </div>
</template>

<script>
import Child from './child.vue'

export default{
  name: 'parent',
  components:{
    Child
  },
  data(){
    return{
      parentTitle: '我是父组件的数据'
    }
  },
  methods:{
    parentHandle(){
      console.log('我是父组件的方法')
    }
  },
  mounted(){
    console.log(this.$children)
    console.log(this.$children[0].childTitle) // => 我是子组件的数据
    this.$children[0].childHandle() // => 我是子组件的方法
  }
}
</script>

3.3.3 注意钩子的使用

父组件是在 mounted()生命周期中获取子组件实例的,并且获取的实例是一个数组形式


3.3.4 问题 层级发生变化的时候咋办呢 ???

1.源码其实有更高层次的封装,在引用parent到时候抽象一个更高级的方法类似 dispatch,
2.直接指定比较重要的父组件的类型或者名称,在循环查找的时候避免程序的脆弱性


3.4 ref

父组件使用 $refs 获得组件实例

<template>
  <div>
    <Child ref="child"></Child >
  </div>
</template>

<script>
import Child from './child.vue'

export default{
  name: 'parent',
  components:{
    Child 
  },
  mounted(){
    console.log(this.$refs.child ) /*组件实例*/
  }
}
</script>

1.注意 钩子 mounted

  1. 父组件就可以直接使用this.$refs.xx获取子组件的实例

3.5 $attrs/$listeners


4. 兄弟组件

核心就是找共同点, 搭建桥梁,中间人,话事人的感觉

4.1 $parent

既然是兄弟往上找 总能找到共同的祖先
不常用,可以参考文章上面 的 写法


4.2 $root

其实 根也是共享的


4.3 eventBus 也是都可以访问的

4.3.1 创建一个Vue实例

作为调度中心 eventBus

import Vue from "vue"
export default new Vue()

4.3.2 需要进行通信的组件中 引入

<template>
  <div>
    <div>我是通信组件A</div>
    <el-button @click="changeName">修改</el-button>
  </div>
</template

<script>
import { EventBus } from "../bus.js"
export default{
  data(){
    return{}
  },
  methods:{
    changeName(){
      EventBus.$emit("editName", '俺的小破站')
    }
  }
}
</script>

4.3.3 监听事件

<template>
  <div>我是通信组件B</div>
</template

<script>
import { EventBus } from "../bus.js"
export default{
  data(){
    return{}
  },
  mounted:{
    EventBus.$on('editName',(name)=>{
      console.log(name) // 俺的小破站!
    })
  }
}
</script>

4.4 vuex

具体可以看我之前写的vuex-0系列


5. 跨级组件

5.1eventBus


5.2 vuex

1.相当于一个公共数据的仓库
2.提供一些方法管理仓库数据
3.具体可以看我之前写的vuex-0系列


5.3.provide/inject

5.3.1 简介

父组件使用 provide 注入数据
子组件使用 inject 使用数据


5.3.2 父组件
export default{
 provide: {
   return{
     provideName: '俺的小破站'
   }
 }
}

5.3.3 分析

provideName这个变量可以提供给它其下的所有子组件,包括曾孙、孙子组件等,只需要使用 inject 就能获取数据


5.3.4 子组件
export default{
  inject: ['provideName'],
  created () {
    console.log(this.provideName) // 俺的小破站
  }
}

5.3.5 优缺点

1.父组件不需要知道哪个组件使用它提供出去的数据
2.子组件不需要知道这个数据从哪里来
3.更加的原生,不会对第三方库产生依赖

缺点:单向的;只能祖辈向子辈传值


6.vue3 通信方式

6.1 props和emit

setup函数可以接受两个参数, prop 和 context ,其中context可以解构出emit slots attrs
利用 emit实例来传参


6.2 子组件 代码

<template>
  <el-button @click="handle">子组件  ---点击</el-button>
  <div>我是父组件传过来的数据:{{name}}</div>
</template>

<script>
export default {
  name:"Child",
  props: {
    name: {
      type: String,
      default: ''
    }
  },
  setup(props,{ emit }) {
    console.log(props.name) //  yzs
    function handle() {
      emit('handleClick', 'Vue3  学起来')
    }
    return {
      handle
    }
  }
}
</script>

6.3 简要分析

Vue3this的概念了,所以就不会有this.$emit存在,
所以可以从setup传入的context解构出emit实例,从而派发事件给父组件


6.4 父组件

<template>
  <Test name="yzs" @handleClick="myClick">父组件---点呀</Test>
</template>

<script>
import Test from './index.vue'
export default {
  name:"parent",
  components: { Test },
  setup() {
    function myClick(name) {
      console.log(name)// Vue3  学起来
    }
    return {
      myClick
    }
  }
}
</script>

7. ref

Vue3我们可以从Vue中导出 ref 方法,得到子组件的实例

7.1 分析

1.通过,在子组件声明ref属性,
2.属性值必须和const btnRef = ref(null)这里声明的变量名一致,否则会报错
3.拿到子组件实例后就可以直接调用组件的sendParent方法了


7.2 父组件代码

<template>
  <Test ref="btnRef">
    <el-button @click="click">点我呀</el-button>
  </Test>
</template>
<script>
import { ref } from "vue"
import Test from './index.vue'
export default {
  components: {Test},
  setup() {
    const btnRef = ref(null)
    function click() {
      btnRef.value?.sendParent() //  我是给父组件调用的方法
    }
    return {
      btnRef,
      click
    }
  }
}
</script>

这里使用的btnRef.value?.是可选链操作符语法,
代表?前面的值为true才继续执行后面的语句


7.3 使用

<template>
  <slot></slot>
</template>
<script>
export default {
  setup() {
    function sendParent() {
      console.log("我是给父组件调用的方法")
    }
    return {
      sendParent
    }
  }
}
</script>

8. provide/inject

和vue2差不离儿

8.1 父组件

<template>
  <Test></Test>
</template>
<script>
import { provide } from "vue"
import Test from './index.vue'
export default {
  components: {Test},
  setup() {
    //我已经把数据注入到fromParent里面去了
    provide('fromParent', '俺的小破站')
    return {}
  }
}
</script>

8.2 子组件

<template>
  <slot></slot>
  <div>我是父组件注入的数据:{{parentData }}</div>
</template>
<script>
import { inject } from "vue"
export default {
  setup() {
    //我来去父组件注入的数据
    let parentData = inject('fromParent')
    return {
      parentData 
    }
  }
}
</script>

子孙组件使用inject获取到父组件注入的数


8.3 区别

其实主要就是 Vue3采用了 这个模块化
所以inject 和 provide 需要单独导入


9. nextTick 是什么

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

官方案例

// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
  // DOM 更新了
})

// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick()
  .then(function () {
    // DOM 更新了
  })

10.为什么 需要nextTick

由于vue的异步更新策略导致我们对数据的修改不会立刻体现在dom变化上,此时如果想要立即获取更新后的dom状态,就需要使用这个方法

vue在更新DOM时是异步执行的.只要侦听到数据变化,vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更.如果同一个watcher被多次触发,只会会被推入到队列中1次.
这种在缓冲时取出重复数据对于避免不必要的计算和DOM操作是非常重要的.
nextTick方法会在队列中加入一个回调函数,确保该函数在前面的DOM 操作完成后才调用

所以当我们想在数据修改后立即看到都DOM执行结果就需要用到nextTick方法


11. 源码分析

主要就是通过 callbacks 回调函数的数组,存的就是nextTick传的函数,
使用函数 timerFunc通过异步机制调用


参考资料

attrs $listeners 传值
proviede inject 传值
vuex-0系列


初心

我所有的文章都只是基于入门,初步的了解;是自己的知识体系梳理;
如果能帮助到有缘人,非常的荣幸,一切为了部落的崛起;
共勉
上一篇下一篇

猜你喜欢

热点阅读