文章目录
1. props
- 父组件向子组件传送数据,这应该是最常用的方式了
- 子组件接收到数据之后,不能直接修改父组件的数据。会报错,所以当父组件重新渲染时,数据会被覆盖。
- 如果子组件内要修改的话推荐使用 computed
// Parent.vue 传送
<template>
<child :msg="msg"></child>
</template>
// Child.vue 接收
export default {
// 写法一 用数组接收
props:['msg'],
// 写法二 用对象接收,可以限定接收的数据类型、设置默认值、验证等
props:{
msg:{
type:String,
default:'这是默认数据'
}
},
mounted(){
console.log(this.msg)
},
}
prop 只可以从上一级组件传递到下一级组件(父子组件),
即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。
- 第一,不应该在一个子组件内部改变 prop,这样会破坏单向的数据绑定,导致数据流难以理解。如果有这样的需要,可以通过 data 属性接收或使用 computed 属性进行转换。
- 第二,如果 props 传递的是引用类型(对象或者数组),在子组件中改变这个对象或数组,父组件的状态会也会做相应的更新,利用这一点就能够实现父子组件数据的“双向绑定”,虽然这样实现能够节省代码,但会牺牲数据流向的简洁性,令人难以理解,最好不要这样去做。
想要实现父子组件的数据“双向绑定”,可以使用 v-model 或 .sync。
2. .sync
可以帮我们实现父组件向子组件传递的数据 的双向绑定,所以子组件接收到数据后可以直接修改,并且会同时修改父组件的数据
// Parent.vue
<template>
<child :page.sync="page"></child>
</template>
<script>
export default {
data(){
return {
page:1
}
}
}
// Child.vue
export default {
props:["page"],
computed(){
// 当我们在子组件里修改 currentPage 时,父组件的 page 也会随之改变
currentPage {
get(){
return this.page
},
set(newVal){
this.$emit("update:page", newVal)
}
}
}
}
</script>
.sync 从功能上看和 v-model 十分相似,都是为了实现数据的“双向绑定”,本质上,也都不是真正的双向绑定,而是语法糖。相比较之下,.sync 更加灵活,它可以给多个 prop 使用,而 v-model 在一个组件中只能有一个。从语义上来看,v-model 绑定的值是指这个组件的绑定值,比如 input 组件,select 组件,日期时间选择组件,颜色选择器组件,这些组件所绑定的值使用 v-model 比较合适。其他情况,没有这种语义,个人认为使用 .sync 更好。
3. v-model
和 .sync 类似,可以实现将父组件传给子组件的数据为双向绑定,子组件通过 $emit 修改父组件的数据
// Parent.vue
<template>
<child v-model="value"></child>
</template>
<script>
export default {
data(){
return {
value:1
}
}
}
// Child.vue
<template>
<input :value="value" @input="handlerChange">
</template>
export default {
props:["value"],
// 可以修改事件名,默认为 input
model:{
event:"updateValue"
},
methods:{
handlerChange(e){
this.$emit("input", e.target.value)
// 如果有上面的重命名就是这样
this.$emit("updateValue", e.target.value)
}
}
}
</script>
v-model 是用来在表单控件或者组件上创建双向绑定的,他的本质是 v-bind 和 v-on 的语法糖,在一个组件上使用 v-model,默认会为组件绑定名为 value 的 prop 和名为 input 的事件。
4. ref
- ref 如果在普通的DOM元素上,引用指向的就是该DOM元素;
- 如果在子组件上,引用的指向就是子组件实例,然后父组件就可以通过 ref 主动获取子组件的属性或者调用子组件的方法
- $refs 不是响应式的,只能拿到获取它的那一刻子组件实例的状态,所以要避免在模板和计算属性中使用它。
// Child.vue
export default {
data(){
return {
name:"沐华"
}
},
methods:{
someMethod(msg){
console.log(msg)
}
}
}
// Parent.vue
<template>
<child ref="child"></child>
</template>
<script>
export default {
mounted(){
const child = this.$refs.child
console.log(child.name) // 沐华
child.someMethod("调用了子组件的方法")
}
}
</script>
5. $emit / v-on
子组件通过派发事件的方式给父组件数据,或者触发父组件更新等操作
// Child.vue 派发
export default {
data(){
return { msg: "这是发给父组件的信息" }
},
methods: {
handleClick(){
this.$emit("sendMsg",this.msg)
}
},
}
// Parent.vue 响应
<template>
<child v-on:sendMsg="getChildMsg"></child>
// 或 简写
<child @sendMsg="getChildMsg"></child>
</template>
export default {
methods:{
getChildMsg(msg){
console.log(msg) // 这是父组件接收到的消息
}
}
}
6. $attrs / $listeners ( 非父子组件通信 )
多层嵌套组件传递数据时,如果只是传递数据,而不做中间处理的话就可以用这个,比如父组件向孙子组件传递数据时
$attrs
:包含父作用域里除 class 和 style 除外的非 props 属性集合。通过this.$attrs
获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过v-bind="$attrs"
$listeners
:包含父作用域里.native
除外的监听事件集合。如果还要继续传给子组件内部的其他组件,就可以通过 `v-on=“$linteners”``
// Parent.vue
<template>
<child :name="name" title="1111" ></child>
</template
export default{
data(){
return {
name:"沐华"
}
}
}
// Child.vue
<template>
// 继续传给孙子组件
<sun-child v-bind="$attrs"></sun-child>
</template>
export default{
props:["name"], // 这里可以接收,也可以不接收
mounted(){
// 如果props接收了name 就是 { title:1111 },否则就是{ name:"沐华", title:1111 }
console.log(this.$attrs)
}
}
7. $children / $parent
- $children:获取到一个包含所有子组件(不包含孙子组件)的 VueComponent 对象数组,可以直接拿到子组件中所有数据和方法等
- $parent:获取到一个父节点的 VueComponent 对象,同样包含父节点中所有数据和方法等
// Parent.vue
export default{
mounted(){
this.$children[0].someMethod() // 调用第一个子组件的方法
this.$children[0].name // 获取第一个子组件中的属性
}
}
// Child.vue
export default{
mounted(){
this.$parent.someMethod() // 调用父组件的方法
this.$parent.name // 获取父组件中的属性
}
}
8. provide / inject
provide / inject 为依赖注入,说是不推荐直接用于应用程序代码中,但是在一些插件或组件库里却是被常用,所以我觉得用也没啥,还挺好用的
- provide:可以让我们指定想要提供给后代组件的数据或方法
- inject:在任何后代组件中接收想要添加在这个组件上的数据或方法,不管组件嵌套多深都可以直接拿来用
// 父组件
export default{
// 方法一 不能获取 methods 中的方法
provide:{
name:"沐华",
age: this.data中的属性
},
// 方法二 不能获取 data 中的属性
provide(){
return {
name:"沐华",
someMethod:this.someMethod // methods 中的方法
}
},
methods:{
someMethod(){
console.log("这是注入的方法")
}
}
}
// 后代组件
export default{
inject:["name","someMethod"],
mounted(){
console.log(this.name)
this.someMethod()
}
}
要注意的是 provide 和 inject 传递的数据不是响应式的,也就是说用 inject 接收来数据后,provide 里的数据改变了,后代组件中的数据不会改变,所以建议还是传递一些常量或者方法
但文档同时也说了: 如果传入了可监听的对象,对象的属性还是可响应的
8.1. 如何实现provide / inject响应式
方法一:传递的参数用一个方法返回
父组件:
provide() {
return {
grandpaMsg: () => {
return this.msg;
},
};
},
子组件
<template>
<div class="Sun">
sun组件
{{ grandpaMsg() }}
</div>
</template>
<script>
export default {
data() {
return {};
},
components: {},
inject: ["grandpaMsg"],
mounted() {},
};
</script>
方法二: 把需要传递的参数定义成一个对象
父组件:
data() {
return {
obj: {
msg: "Home组件的msg",
},
};
},
components: { LeftTop, LeftCenter, ProvideC },
provide() {
return {
grandpaMsg: this.obj,
};
},
mounted() {
this.obj.msg = "Home组件中msg的值被改变啦";
},
子组件
inject: ['grandpaMsg'],
使用
<div class="Sun">
sun组件
{{ grandpaMsg.msg }}
</div>
9. EventBus
EventBus 是中央事件总线,不管是父子组件,兄弟组件,跨层级组件等都可以使用它完成通信操作
定义方式有三种
- 方法一:
// 抽离成一个单独的 js 文件 Bus.js ,然后在需要的地方引入
// Bus.js
import Vue from "vue"
export default new Vue()
- 方法二 :直接挂载到全局
// main.js
import Vue from "vue"
Vue.prototype.$bus = new Vue()
- 方法三 :注入到 Vue 根对象上
// main.js
const app = new Vue({
el: "#app",
beforeCreate() {
Vue.prototype.$bus = this;
},
});
或者
// main.js
import Vue from "vue"
new Vue({
el:"#app",
data:{
Bus: new Vue()
}
})
使用如下,以方法一按需引入为例
// 在需要向外部发送自定义事件的组件内
<template>
<button @click="handlerClick">按钮</button>
</template>
import Bus from "./Bus.js"
export default{
methods:{
handlerClick(){
// 自定义事件名 sendMsg
Bus.$emit("sendMsg", "这是要向外部发送的数据")
}
}
}
// 在需要接收外部事件的组件内
import Bus from "./Bus.js"
export default{
mounted(){
// 监听事件的触发
Bus.$on("sendMsg", data => {
console.log("这是接收到的数据:", data)
})
},
beforeDestroy(){
// 取消监听
Bus.$off("sendMsg")
}
}
10. Vuex
Vuex 是状态管理器,集中式存储管理所有组件的状态。这一块内容过长,如果基础不熟的话可以看这个Vuex,然后大致用法如下
- 比如创建这样的文件结构
- index.js 里内容如下:
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'
import state from './state'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
},
getters,
actions,
mutations,
state
})
export default store
- 然后在 main.js 引入:
import Vue from "vue"
import store from "./store"
new Vue({
el:"#app",
store,
render: h => h(App)
})
然后在需要的使用组件里:
import { mapGetters, mapMutations } from "vuex"
export default{
computed:{
// 方式一 然后通过 this.属性名就可以用了
...mapGetters(["引入getters.js里属性1","属性2"])
// 方式二
...mapGetters("user", ["user模块里的属性1","属性2"])
},
methods:{
// 方式一 然后通过 this.属性名就可以用了
...mapMutations(["引入mutations.js里的方法1","方法2"])
// 方式二
...mapMutations("user",["引入user模块里的方法1","方法2"])
}
}
// 或者也可以这样获取
this.$store.state.xxx
this.$store.state.user.xxx
11. $root
$root 可以拿到 App.vue 里的数据和方法
12. slot
12.1. 定义
插槽(Slot)是vue为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。
12.2. 默认插槽
子组件中(假设子组件name为Chlid):
<template>
<div>
<h1>这是子组件</h1>
<slot>
占位符,父组件的结构可以渲染到这里,当父组件没有提供内容时,这里的文字将被正常渲染,反之,这里的默认内容将被清空
</slot>
</div>
</template>
父组件中:
<template>
<Child>
向子组件传递的内容写到这里,插槽内可以包含任何模板代码
</Child>
</template>
12.3. 具名插槽
当组件使用多个插槽时,为了让插槽内容在合适的地方渲染, 元素有一个特殊的 attribute:name,如:
子组件:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一个不带 name 的 出口会带有隐含的名字“default”
在向具名插槽提供内容的时候,可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称,如:
父组件:
<Child>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</Child>
注意 v-slot 只能添加在 上
12.4. 具名插槽的缩写
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header:
<Child>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</Child>
然而,和其它指令一样,该缩写只在其有参数的时候才可用
12.5. 作用域插槽
有时让父组件的插槽内容可以访问子组件中的数据是很有用的
子组件
<span>
<slot :user="user">
{{ user.lastName }}
</slot>
</span>
父组件
<Child>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</Child>