保姆级别Vue2组件通信(面试常问)

简介: 如果父组件传递了一些属性到子组件,但子组件并没有声明这些属性,则它们称之为attribute,这些属性会直接附着在子组件的根元素上

在面试过程中,经常会被面试官问的鸦雀无言,但是如果我们日常积累些知识点,等到被面试的时候,就可以和面试官来交流技术。


父子组件通信


绝大部分vue本身提供的通信方式,都是父子组件通信


prop


最常见的组件通信方式之一,由父组件传递到子组件


父组件


<template>
  <div id="app">
    <HelloWorld
      msg="Welcome to Your Vue.js App"
    />
  </div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
  components: {
    HelloWorld,
  },
};
</script>
复制代码


子组件


<template>
  <div>
    <h1>{{msg}}</h1>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  created(){
      console.log(this.msg) // 打印出父组件给子组件传递的值 Welcome to Your Vue.js App
  }
};
</script>
复制代码


event(emit)


最常见的组件通信方式之一,当子组件发生了某些事,可以通过event通知父组件


父组件


<template>
  <div id="app" 
:style="{fontSize: postFontSize + 'em'}">
    <HelloWorld
      msg="Welcome to Your Vue.js App"
      @enlarge-text="postFontSize += 0.1"
    />
  </div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
  components: {
    HelloWorld,
  },
  data(){
      return {
        postFontSize: 1
      }
  }
};
</script>
复制代码


子组件


<template>
  <div>
    <h1>{{msg}}</h1>
    // 点击按钮后,子组件会触发事件,传递到父组件中,放大字体
   <button @click="$emit('enlarge-text')">放大字号</button>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
};
</script>
复制代码


style和class


父组件可以向子组件传递style和class,它们会合并到子组件的根元素中


示例


父组件


<template>
  <div id="app">
    <HelloWorld
      style="color:red"
      class="hello"
      msg="Welcome to Your Vue.js App"
    />
  </div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
  components: {
    HelloWorld,
  },
};
</script>
复制代码


子组件


<template>
  <div class="world" style="text-align:center">
    <h1>{{msg}}</h1>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
};
</script>
复制代码


渲染结果:


<div id="app">
  <div class="hello world" style="color:red; text-aling:center">
    <h1>Welcome to Your Vue.js App</h1>
  </div>
</div>
复制代码


attribute


如果父组件传递了一些属性到子组件,但子组件并没有声明这些属性,则它们称之为attribute,这些属性会直接附着在子组件的根元素上


不包括style和class,它们会被特殊处理


示例


父组件


<template>
  <div id="app">
    <!-- 除 msg 外,其他均为 attribute -->
    <HelloWorld
      data-a="1"
      data-b="2"
      msg="Welcome to Your Vue.js App"
    />
  </div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
  components: {
    HelloWorld,
  },
};
</script>
复制代码


子组件


<template>
  <div>
    <h1>{{msg}}</h1>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  created() {
    console.log(this.$attrs); // 得到: { "data-a": "1", "data-b": "2" }
  },
};
</script>
复制代码


渲染结果:


<div id="app">
  <div data-a="1" data-b="2">
    <h1>Welcome to Your Vue.js App</h1>
  </div>
</div>
复制代码


子组件可以通过inheritAttrs: false配置,禁止将attribute附着在子组件的根元素上,但不影响通过$attrs获取


natvie修饰符


在注册事件时,父组件可以使用native修饰符,将事件注册到子组件的根元素上


示例


父组件


<template>
  <div id="app">
    <HelloWorld @click.native="handleClick" />
  </div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
  components: {
    HelloWorld,
  },
  methods: {
    handleClick() {
      console.log(1);
    },
  },
};
</script>
复制代码


子组件


<template>
  <div>
    <h1>Hello World</h1>
  </div>
</template>
复制代码


渲染结果


<div id="app">
  <!-- 点击该 div,会输出 1 -->
  <div>
    <h1>Hello World</h1>
  </div>
</div>
复制代码


$listeners


子组件可以通过$listeners获取父组件传递过来的所有事件处理函数, 爷爷组件


<template>
    <div class="father-page">
        <div>这是父组件</div>
        <child-1 @child2Info="getInfo"></child-1>
    </div>
</template>
<script>
    import child1 from './Child1'
    export default {
        name: 'Father',
        components:{child1},
        methods: {
            getInfo:function(data){
                console.log(data)
                //这是子组件1-1-1发送的信息
            }
        }
    }
</script>
复制代码


父亲组件 child-1


<template>
    <div class="child1-page">
        <div>这是子组件-1</div>
        <child-2 v-on="$listeners"></child-2>
    </div>
</template>
<script>
    import child2 from "./Child2"
    export default {
        name: 'Child1',
        components:{child2},
    }
</script>
复制代码


孙子组件


<template>
    <div>这是子组件1-1</div>
</template>
<script>
    export default {
        mounted: function() {
            this.$emit("child2Info","这是子组件1-1-1发送的信息")
        }
    };
</script>
复制代码


可以在子孙组件中执行祖先组件的函数,从而实现数据传递。 demo或小型项目可以使用listeners进行数据传递,中大型项目不推荐,数据流会变的难于理解。‘listeners进行数据传递,中大型项目不推荐,数据流会变的难于理解。 `listeners`的真正目的是将所有的事件监听器指向这个组件的某个特定的子元素。


v-model


v-model 实际上是vue给表单做单的一个双向数据绑定


<input v-model="aaa" />
复制代码


等价于


<input
  :value="aaa"
  @input="aaa = $event.target.value"
>
复制代码


sync修饰符


和v-model的作用类似,用于双向绑定,不同点在于v-model只能针对一个数据进行双向绑定,而sync修饰符没有限制


示例


子组件


<template>
  <div>
    <p>
      <button @click="$emit(`update:num1`, num1 - 1)">-</button>
      {{ num1 }}
      <button @click="$emit(`update:num1`, num1 + 1)">+</button>
    </p>
    <p>
      <button @click="$emit(`update:num2`, num2 - 1)">-</button>
      {{ num2 }}
      <button @click="$emit(`update:num2`, num2 + 1)">+</button>
    </p>
  </div>
</template>
<script>
export default {
  props: ["num1", "num2"],
};
</script>
复制代码


父组件


<template>
  <div id="app">
    <Numbers :num1.sync="n1" :num2.sync="n2" />
    <!-- 等同于 -->
    <Numbers
      :num1="n1"
      @update:num1="n1 = $event"
      :num2="n2"
      @update:num2="n2 = $event"
    />
  </div>
</template>
<script>
import Numbers from "./components/Numbers.vue";
export default {
  components: {
    Numbers,
  },
  data() {
    return {
      n1: 0,
      n2: 0,
    };
  },
};
</script>
复制代码


v-model VS .sync


先明确一件事情,在 vue 1.x 时,就已经支持 .sync 语法,但是此时的 .sync 可以完全在子组件中修改父组件的状态,造成整个状态的变换很难追溯,所以官方在2.0时移除了这个特性。然后在 vue2.3时,.sync又回归了,跟以往不同的是,现在的.sync完完全全就是一个语法糖的作用,跟v-model的实现原理是一样的,也不容易破环院有的数据模型,所以使用上更安全也更方便。


  • 两者都是用于实现双向数据传递的,实现方式都是语法糖,最终通过 prop + 事件 来达成目的。


  • vue 1.x 的 .sync 和 v-model 是完全两个东西,vue 2.3 之后可以理解为一类特性,使用场景略微有区别


  • 当一个组件对外只暴露一个受控的状态,切都符合统一标准的时候,我们会使用v-model来处理。.sync则更为灵活,凡是需要双向数据传递时,都可以去使用。


$parent和$children


在组件内部,可以通过$parent和$children属性,分别得到当前组件的父组件和子组件实例


$slots 插槽


Vue.compopnent('my-cmp', {
  template: `
    <div class="container">
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
  `
})
复制代码


使用时


<my-cmp>
  <template v-slot:header>
    <h1>头部</h1>
  </template>
  <p>内容</p>
  <p>内容</p>
  <template v-slot:footer>
    <p>底部</p>
  </template>
</my-cmp>
复制代码


ref


父组件可以通过ref获取到子组件的实例


$parent


可以在子组件中访问父实例的数据。


对于 demo 或非常小型的有少量组件的应用来说这是很方便的。中大型项目不适用。会使应用难于调试和理解。


$children


可以在父组件中访问子实例的数据。


对于 demo 或非常小型的有少量组件的应用来说这是很方便的。中大型项目不适用。会使应用难于调试和理解


跨组件通信


Provide和Inject


示例


// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}
// 组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}
复制代码


详见:cn.vuejs.org/v2/api/?#pr…


router


如果一个组件改变了地址栏,所有监听地址栏的组件都会做出相应反应


最常见的场景就是通过点击router-link组件改变了地址,router-view组件就渲染其他内容


vuex


适用于大型项目的数据仓库


store模式


适用于中小型项目的数据仓库


// store.js
const store = {
  loginUser: ...,
  setting: ...
}
// compA
const compA = {
  data(){
    return {
      loginUser: store.loginUser
    }
  }
}
// compB
const compB = {
  data(){
    return {
      setting: store.setting,
      loginUser: store.loginUser
    }
  }
}
复制代码


eventbus


组件通知事件总线发生了某件事,事件总线通知其他监听该事件的所有组件运行某个函数


Vue.prototype.$bus = new Vue();
复制代码
Vue.component(‘cmp-a’, {
data () {
return {
a: ‘a’
}
},
methods: {
onClick () {
this.b u s . bus.bus.on(‘click’, this.a)
}
},
template: <div> <button @click="onClick">点击</button> </div>,
})
复制代码
Vue.component('cmp-a', {
  mounted () {
    this.$bus.$on('click', data => {
      console.log(data);  // 拿到a的数据
    })
  },
  template: `
    <div>b</div>
  `,
})
复制代码
```


相关文章
|
4月前
|
JavaScript 前端开发 应用服务中间件
【Vue面试题三十】、vue项目本地开发完成后部署到服务器后报404是什么原因呢?
这篇文章分析了Vue项目在服务器部署后出现404错误的原因,主要是由于history路由模式下服务器缺少对单页应用的支持,并提供了通过修改nginx配置使用`try_files`指令重定向所有请求到`index.html`的解决方案。
【Vue面试题三十】、vue项目本地开发完成后部署到服务器后报404是什么原因呢?
|
4月前
|
JavaScript 前端开发
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
这篇文章主要讨论了axios的使用、原理以及源码分析。 文章中首先回顾了axios的基本用法,包括发送请求、请求拦截器和响应拦截器的使用,以及如何取消请求。接着,作者实现了一个简易版的axios,包括构造函数、请求方法、拦截器的实现等。最后,文章对axios的源码进行了分析,包括目录结构、核心文件axios.js的内容,以及axios实例化过程中的配置合并、拦截器的使用等。
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
|
4月前
|
JavaScript 前端开发 数据处理
【Vue面试题二十八】、vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?
这篇文章讨论了Vue中实现权限管理的策略,包括接口权限、路由权限、菜单权限和按钮权限的控制方法,并提供了不同的实现方案及代码示例,以确保用户只能访问被授权的资源。
【Vue面试题二十八】、vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?
|
4月前
|
JavaScript 前端开发
【Vue面试题二十七】、你了解axios的原理吗?有看过它的源码吗?
文章讨论了Vue项目目录结构的设计原则和实践,强调了项目结构清晰的重要性,提出了包括语义一致性、单一入口/出口、就近原则、公共文件的绝对路径引用等原则,并展示了单页面和多页面Vue项目的目录结构示例。
|
3月前
|
缓存 JavaScript 前端开发
vue面试题
vue面试题
169 64
|
2月前
|
JavaScript 前端开发
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
42 0
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
|
4月前
|
JavaScript 安全 前端开发
【Vue面试题二十九】、Vue项目中你是如何解决跨域的呢?
这篇文章介绍了Vue项目中解决跨域问题的方法,包括使用CORS设置HTTP头、通过Proxy代理服务器进行请求转发,以及在vue.config.js中配置代理对象的策略。
【Vue面试题二十九】、Vue项目中你是如何解决跨域的呢?
|
4月前
|
JavaScript 前端开发 编译器
【Vue面试题三十二】、vue3有了解过吗?能说说跟vue2的区别吗?
这篇文章介绍了Vue 3相对于Vue 2的改进和新增特性,包括性能提升、体积减小、更易维护、更好的TypeScript支持、新的Composition API、新增的Teleport和createRenderer功能,以及Vue 3中的非兼容性变更和API的移除或重命名。
【Vue面试题三十二】、vue3有了解过吗?能说说跟vue2的区别吗?
|
4月前
|
JavaScript 前端开发 API
【Vue面试题三十一】、你是怎么处理vue项目中的错误的?
这篇文章讨论了Vue项目中错误的处理方式,包括后端接口错误和代码逻辑错误的处理策略。文章详细介绍了如何使用axios的拦截器处理后端接口错误,以及Vue提供的全局错误处理函数`errorHandler`和生命周期钩子`errorCaptured`来处理代码中的逻辑错误。此外,还分析了Vue错误处理的源码,解释了`handleError`、`globalHandleError`、`invokeWithErrorHandling`和`logError`函数的作用和处理流程。
【Vue面试题三十一】、你是怎么处理vue项目中的错误的?
|
4月前
|
移动开发 JavaScript 前端开发
【Vue面试题二十二】、什么是虚拟DOM?如何实现一个虚拟DOM?说说你的思路
这篇文章深入探讨了虚拟DOM的概念、必要性以及在Vue中的实现方式,解释了虚拟DOM如何作为真实DOM的轻量级抽象,通过优化DOM操作提高性能,并实现跨平台渲染的能力。
【Vue面试题二十二】、什么是虚拟DOM?如何实现一个虚拟DOM?说说你的思路