日常搬砖,项目有值传递是很普遍的行为, 我罗列了vue中涉及到值传递的使用场景,总结下在vue中的必使用的传值形式。
组件传值
父子、子父组件传值
props
// 父组件
<i-a msg="燕国使者荆轲向秦王献图了"></i-a>
// 子组件
props: ['msg']
复制代码
on/emit
// 父组件
<!--自定义组件使用on监听派发出的事件-->
<i-a @on="gx"></i-a>
// 子组件
this.$emit('gx') // 使用emit派发自定义事件: gx
复制代码
$parent/$children
this.$parent 得到父组件实例
this.$children 得到子组件实例,可能有多个,且不保证顺序
复制代码
ref
<template>
<div id="app">
<!--自定义组件-->
<i-a ref="ia" />
<!--普通标签-->
<p ref="p1">111</p>
</div>
</template>
<script>
export default {
methods: {
rename() {
this.$refs.ia // 获取到的是自定义组件实例,可以通过它调用子组件上的方法或是数据。
this.$refs.p1 // 获取的就是p标签,普通dom节点
},
},
};
</script>
复制代码
子孙后代组件传值
provoid/inject
兄弟两需要一起使用。provoid
对象 | 返回一个对象的函数。inject
字符串数组 | 对象。
案例1
先决条件:有A、B、C、D 4个组件,A嵌套B,B嵌套C,C嵌套D。在App.vue中引入A组件。
// App.vue
<template>
<div id="app">
<i-a></i-a>
</div>
</template>
<script>
import A from "./components/A";
export default {
name: "App",
components: {
"i-a": A,
},
provide: {
name: "大秦",
}
};
</script>
// A.vue B、C、D组件同理
<template>
<div class="hello">
A: {{ name }}
<div>
<i-b></i-b>
</div>
</div>
</template>
<script>
import B from "./B";
export default {
name: "A",
inject: ["name"],
components: {
"i-b": B,
},
};
</script>
复制代码
name的结果就被所有后待继承了。
案例2
你应该注意到了,provide中的name是写死的字符,正常情况下,这个值多数使用data中获取的。想要访问到this,provide就必须是个函数,return 出一个对象。
// App.vue
provide() {
return {
name: this.name
}
},
data: function () {
return {
name: "大秦帝国",
};
},
复制代码
案例3
既然是后代,对于app来说,a、b、c、d都是后代,对于a来说,b、c、d都是后代...,那么在A组件中使用provide,其后台是否也能获取嘞?答案是:裤裆着火啦-裆燃(当然)。
// A.vue
provide: {
wuchenghou: "武成侯-王翦",
},
// B.vue \ C.vue \ D.vue
inject: ["name", "wuchenghou"],
复制代码
案例4
如果共同的父级都有同样的值,那我应该听谁的?答案是:谁离你近就听谁的!
// App.vue
provide: {
shoudu: "咸阳",
}
// A.vue
provide: {
wuchenghou: "武成侯-王翦",
shoudu: "长安",
},
// ABCD组件
inject: ["name", "wuchenghou", "shoudu"]
<!--在页面使用-->
<p>首都: {{ shoudu }}</p>
复制代码
案例5
前面提到,inject处了支持字符串数组外,还支持对象。作为对象时,key就是本地的绑定名,value如果是。
- 在可用的注入内容中搜索用的 key(字符串或 Symbol),或
- 一个对象,该对象的:
from
property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)default
property 是降级情况下使用的 value
把B组件的inject改写下:

inject: {
name: "name",
wuchenghou: {
from: "wuchenghou",
default: "--",
},
shoudu: "shoudu",
},
复制代码
案例6
你有没有想过,provide在使用异步数据的时候还好不好使?假设我们需要在特定的情况下修改provide中的值,会不会与预期一样?
// App.vue
<template>
<button @click="rename">秦王政</button>
{{ this.name }}
<hr />
</template>
// 添加一个事件
provide() {
return {
name: this.name,
shoudu: "咸阳",
};
},
methods: {
rename() {
this.name = "大秦帝国: 始皇帝嬴政";
},
},
复制代码
然后点击"秦王政"按钮
app.vue 中的name已经发生变化了,但是他的后代门却没有改变。 provide
和 inject
绑定并不是可响应的。如果传入了一个可监听的对象,那么其对象的 property 还是可响应的。 就是说,如果你希望后代可以响应到异步得到的值,那么provide中与key对应的value值必须是个引用类型。
// App.vue
provide() {
return {
name: this.name,
shoudu: "咸阳",
qin: this.qin,
};
},
data: function () {
return {
name: "大秦帝国",
qin: {
ge: "秦王政",
},
};
},
methods: {
rename() {
this.name = "大秦帝国: 始皇帝嬴政";
this.qin.ge = "始皇帝嬴政";
},
},
// A.vue
inject: ["name", "shoudu", "qin"]
<!--使用-->
<p>异步数据: {{ qin.ge }}</p>
复制代码
这时候传递下去的异步数据就是:秦王政。点击秦王政按钮,动态更改为:始皇帝嬴政。
同级/兄弟组件传值
vuex
observable
EventBus
同级/兄弟组件,它们没有直接的隶属关系,对于这类的组件传值,通常会使用vuex、EventBus、observable。
这里主要搞下EventBus,也叫事件总线或者中央事件总线。说到底,无非就是利用Vue本身自带的 $on、$emit进行。
先决条件:存在两个同级组件A、B,有一个共同的跟组件(通常是$root或是共同的父级组件)。
// App.vue
<template>
<div id="app">
<i-a />
<i-b />
</div>
</template>
<script>
import A from "./components/A";
import B from "./components/B";
export default {
name: "App",
components: {
"i-a": A,
"i-b": B,
},
};
</script>
复制代码
在main.js中搞一个新的Vue实例,目的就是确保所有组件都可以访问到它。
// 新的实例挂载到了Vue的原型上, 组件中既可以使用this.$bus访问到
Vue.prototype.$bus = new Vue()
// A.vue
created() {
// 开始自定义事件的监听
this.$bus.$on("amsg", (msg) => {
this.msg = msg;
});
},
methods: {
setMsg() {
// 也可以提交一个自定义的事件
this.$bus.$emit("bmsg", "A-> 汉王:刘邦是也");
},
},
// B.vue
created() {
// 开始自定义事件的监听
this.$bus.$on("bmsg", (msg) => {
this.msg = msg;
});
},
methods: {
setMsg() {
// 也可以提交一个自定义的事件
this.$bus.$emit("amsg", "B-> 西楚霸王:项羽是也");
},
},
复制代码
点击对应的按钮,就可以把自己的信息传递到其他组件。 其实,这个方法也不是仅仅局限与同级/兄弟组件之间,只要保证任意两个组件之间存在一个共同的父级组件或者跟组件,就可以。
需要注意的是:销毁组件时记得移除$on绑定的事件,避免造成重复监听。
beforeDestroy() {
this.$bus.$off("bmsg")
// this.$bus.$off() 如果不指定移除的事件名称,表示移除所有绑定的事件。
}
复制代码
路由传值
操作url实现的
location
ue-route: query
vue-route: params
命名路由
// location
location.href = 'https://三达不溜.不坑你坑谁.com/?msg=霸王过江了吗'
// vue-route: query
this.$routes.push({name: 'xx', query: {msg: '霸王过江了吗'}})
// vue-route: params
this.$routes.push({name: 'xx', params: {msg: '霸王过江了吗'}})
// 命名路由,可以使用路由的params导向。
{
path: '/bw/:name',
name: 'Bw'
}
this.$routes.push({name: 'Bw', params: {name: '楚霸王'}})
this.$routes.push({name: 'Bw', params: {name: '项羽'}})
复制代码
前端存储实现的
localStorage
sessionStorage
cookie
indexedDB
窗口传值
这玩意比较特殊,vue基本都是单页面应用,极少有同时打开两个窗口,还需要传值处理的情况。但是,这玩意要是嵌入到app中使用,就另说了。
犹豫一些特殊的使用场景,嵌入app的路由跳转一律拦截,统一使用location跳转。没打开一个页面,就是一个新的实例被创建,vuex这些内部的东西统统无效。
localStorage
通过监听storage来更新值 (ios端webview的问题导致事件不能触发放弃)
// A组件
mounted() {
window.addEventListener('storage', this.setValFun, false)
}
methods: {
setValFun(e) {
if (e.storageArea?.val === '1') {
//...
localStorage.removeItem('val')
}
}
}
// B组件
localStorage.setIetm('val', '1')
复制代码
轮询函数查询存储的值
// A组件
mounted() {
this.setValFun()
}
methods: {
setValFun() {
if (getCookie('val') === '1') {
//...
delCookie('val')
}
setTimeout(() => {
this.setValFun()
}, 1000)
}
}
// B组件
setCookie('val', '1', '1d')
复制代码
番外
极简封装observable
// store/index.js
const store = {
state: Vue.observable({
name: ''
}),
commit: {
setName(n) {
store.state.name = n
},
}
}
export default {
install: function() {
Vue.prototype.$store = store
}
}
// main.js
import store from './store'
Vue.use(store)
复制代码