vue核心面试题:Vue组件如何通信?

1.父子间通信

源码位置:src/core/instance/events.js

父->子:通过 props

子-> 父:$on、$emit (发布订阅)

父组件:
<template>
  <div id="app">
    <users :users="users"></users>//子组件
  </div>
</template>
export default {
  data(){
    return{
      users:["one","two","three"]
    }
  }
}

子组件:
<template>
  <div>
    <ul>
      <li v-for="user in users">{{user}}</li>// 显示父组件传递的值
    </ul>
  </div>
</template>
export default {
  props:{
    users:{  // 需要在子组件中props中写上父组件自定义的变量名
      type:Array
    }
  }
}
// 子组件
<template>
  <div>
    <button @click="changeVal">传值</button>
  </div>
</template>
export default {
  data() {
    return {
      dataVal: "Vue.js Demo"
    }
  },
  methods:{
    changeVal() {
      this.$emit("getVal",this.dataVal); //自定义事件  传递值“子向父组件传值”
    }
  }
}


// 父组件
<template>
  <div id="app">
    // 子组件
    <users @getVal="updateVal" ></users>// 与子组件中getVal自定义事件保持一致
  </div>
</template>
export default {
  data(){
    return{
      dataVal1:""
    }
  },
  methods:{
    updateVal(e){   //声明这个函数
      this.dataVal1= e; // 接收值
    }
  }
}

2.获取父子组件实例的方式:

(1)$parent、$children

源码位置:src/core/instance/lifecycle.js

组件在初始化的时候会初始一个$parent、$children,这样就可以拿到父实例和子实例

使用:在父组件中通过:this.$children访问子组件中的方法和数据

           在子组件中通过:this.$parent访问父组件中的方法和数据

(2)ref

源码位置:src/core/vdom/modules/ref.js

如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例

这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。这两种方法的弊端是,无法在跨级或兄弟间通信

使用:在子组件上写上ref值,通过this.$refs.定义的ref值访问子组件中的方法和数据

3.在父组件中提供数据子组件进行消费 Provide、inject 插件

源码位置:src/core/instance/inject.js

export function initProvide (vm: Component) {
  const provide = vm.$options.provide // 将数据放在了vm.$options
  if (provide) {
    vm._provided = typeof provide === 'function' // 如果是函数就调用一下
      ? provide.call(vm)
      : provide
  }// 最后会把提供的属性放到当前实例的_provided上
}
// 不停的找父组件
export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)// 找父组件有没有_provided属性
  if (result) {
    // 如果有,就把这个属性定义到它自己的身上,而且是一个响应式的,只要父组件的数据一变,就会更新子组件
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}

 // 遍历寻找上面是否有_provided属性,如果找到了就将这个结果return
export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      if (key === '__ob__') continue
      const provideKey = inject[key].from
      let source = vm
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
    }
    return result
  }
}

在父组件中提供好数据,在子组件中注入依赖,这样就可以在子组件中直接修改父组件的实例的属性

使用:

(1)父组件中provide:

// 父组件
(1)这样写可以传递属性,但是不会响应
provide() {
  return {
    theme: {
      color: this.color //这种方式绑定的数据并不是可响应的
    } // 即父组件的color发生变化,子组件不会变化
  };
}

(2)可以直接将整个实例赋值过去
provide() {
  return {
    theme: this //提供父组件的实例
  };
}

(3)使用Vue.observable 优化响应式 provide
provide() {
  this.theme = Vue.observable({
    color: "blue"
  })
  return {
    theme: this.theme
  }
}

(2)子组件中inject

// 子组件:
(1)数组接收
inject: ['tenme']
取值:this.tenme.color


(2)对象 函数
inject: {
  tenme: {
    default: () => ({})
  }
}
函数式组件取值:injections.theme.color

inject: {
  tenme1: {
    from: 'tenme'
    default: ''
  }
}
this.tenme1.color

4.Event Bus 实现跨组件通信

使用的就是$on 和$emit,绑定事件和触发事件必须在同一个实例上,所以用一个全局的来通信

(1)方法一:全局挂载

// main.js 在项目中的 main.js 初始化 Bus :
Vue.prototype.$Bus = new Vue()
// 发送事件
this.$Bus.$emit("user", {
  name: 'wthreesix',
  age: 17
})
// 接收事件
this.$Bus.$on("user", ({name, age}) => {
  console.log(name, age)
})

(2)方法二:单独建立一个bus.js文件

import Vue from 'vue'
export const Bus = new Vue()

 在使用的组件中引入

import { Bus} from "../bus.js"

// 发送事件
Bus.$emit("user", {
  name: 'wthreesix',
  age: 17
})
// 接收事件
Bus.$on("user", ({name, age}) => {
  console.log(name, age)
})

移除事件监听者

Bus.$off() 移除所有事件频道, 注意不需要添加任何参数 。

Bus.$off('user') 移除指定的事件

5.$attrs/$listeners

(1)$attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind= "$attrs" 传入内部组件——在创建高级别的组件时非常有用。(理解为:继承所有的父组件属性,除了prop传递的属性、class 和 style )

(2)$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。(理解为:$listeners它是一个对象,里面包含了作用在这个组件上的所有监听器,你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素)

(3)inheritAttrs:默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例属性 $attrs 可以让这些特性生效,且可以通过 v-bind 显性的绑定到非根元素上。

简单理解inheritAttrs:用来设置禁用继承属性,当设置inheritAttrs: true(默认)时,子组件的顶层标签元素中会渲染出父组件传递过来的属性,当设置inheritAttrs: false时,子组件的顶层标签元素中不会渲染出父组件传递过来的属性,不管inheritAttrs为true或者false,子组件中都能通过$attrs属性或者props获取到父组件中传递过来的属性。

使用:

组件childCom:

<template>
  <div>
    <h2>前端:</h2>
    <child-com1
      :css="css"
      :html="html"
      :js="js"
      @childVal="getChildVal"
    ></child-com1>
  </div>
</template>

<script>
const childCom1 = () => import("./childCom1.vue")
export default {
  components: { childCom1 },
  data() {
    return {
      css: "css",
      html: "Html",
      js: "js"
    };
  },
  methods: {
    getChildVal(val) {
      console.log('childVal:', val) 
    }
  }
}
</script>

组件childCom1:

<template>
  <div class="border">
    <p>css: {{ css }}</p>
    <p>childCom1的$attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs" v-on="$listeners"></child-com2>
    // 在子组件上绑定$listeners是,在该组件里就能够拿到父组件中绑定的非原生事件,通过$emit触发
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的属性,除在props声明的
  props: {
    css: String
  },
  created() {
    console.log(this.$attrs); // {html: "Html", js: "js"}
  }
};
</script>

 组件childCom2:

<template>
  <div class="border">
    <p @click="sendVal">html: {{ html }}</p>
    <p>childCom2: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  inheritAttrs: true,
  props: {
    html: String
  },
  created() {
    console.log(this.$attrs);//{js: "js"}
  },
  methods: {
    sendVal() {
      this.$emit('childVal', '子子组件的值') // 触发父组件中的方法
    }
  }
};
</script>

页面显示:

inheritAttrs属性false和true的dom渲染区别:false时子组件上不渲染属性,true会将属性渲染在子组件元素上。

 

6.Vuex状态管理实现通信

将数据都存放在一个容器里,大家一起去共享。

Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 进行,Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。

vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在 vuex 里数据改变的时候把数据拷贝一份保存到 localStorage 里面,刷新之后,如果 localStorage 里有保存的数据,取出来再替换 store 里的 state。或者npm安装vuex-persistedstate:

在store中写:

plugins: [

    persistedstate({

      storage: window.sessionStorage,

    })

  ]

注:

1.组件中的数据共有三种形式:data、props、computed

2.vue组件是单向数据流

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值