在使用vue框架搭建项目时总会碰到父子组件,隔代组件,兄弟组件之间的传值,这里我介绍六种传值方式
父组件使用v-bind绑定相应的值,在子组件中使用props进行接收,获得父组件的值
其中使用this.$emit()来进行调用父组件的事件
数据向下行,事件向上流
在父组件Parent中:
<Child :data-list='datalist' v-on:parent-chang-count="parentcount"></Child>
<script>
data(){
return {
datalist:[
{
id:1,name:1,count:1,sex:1},
{
id:1,name:1,count:1,sex:1},
{
id:1,name:1,count:1,sex:1}
]
}
}
methods:{
perentcount(index,row){
console.log(index)
console.log(row)
}
}
</script>
在子组件Child中:
<template>
<div class="hello">
<el-table :data="datalist" style="width:100%" border>
<el-table-column props="id"></el-table-column>
.....
<el-table-column label="购买数量" min-width=' 200' align="center">
<template slot-scope="scope">
<i class="el-icon-minus" v-on:click="jiaCount(scope.$index, scope.row)"></i>
<el-input v-model="scope.row.count" size='mini'></el-input>
<i class=" el-icon-plus" v-on:click="ianCount(scope.$index, scope.row)"></i>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: "Child",
props: ['datalist'],
methods: {
jianCount(index, row) {
row.count--;
if (row.count < 1) row.count = 1;
//在这里使用了$emit来使用父组件的事件函数
this.$emit('parent-change-count', index, row);
}
,
jiaCount(index, rom) {
row.count++;
//可以判斷是否超过库存数量;
this.$emit('parent- change count', index, row);
}
}
</script>
在store中创建一个bus.js(名字任意)
凡是要共享数据 的组件,都要引用bus.js文件;
每一个Vue实例对象,都会有一个 $on(), $emit()两个方法;
//EventBus 主要解决非父子组件之间的通信。不适合用于大型的项目;大型项目,一般用Vuex来处理;
//提示: 页面跳转路由时,不建议使用EventBus来通信。
import Vue from 'vue'
export default new Vue();
在需要使用的组件和视图中引用bus.js
如在app.vue中
<script>
import Bus from '@/store/bus.js'
data(){
return {
ordernumber:0
}
}
mounted(){
Bus.$on('changeShopingCount',num=>{
this.orderNumber=num;})
}
beforeDestroy(){
Bus.$off('changeShopingCount')
}
</script>
vuex实现状态管理 可以在多个不同组件中使用相同的值,一个值的变化可以改变多个组件里面的内容
修改vuex里面值的方法主要分非严格和严格模式
非严格模式可以使用this指向直接修改变的值
在非严格模式下修改值可以不遵循单向数据流向
严格模式必须使用actions或者mutations来修改变量的值:
使用actions修改值的时候使用dispatch
使用mutations修改值的时候使用commit
以下是非严格模式的情况
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//状态:
state: {
isLogin: false,
token: ''
},
//相当于是计算属性:
getters: {
},
//方法: mutations只支持同步操作,不支持异步操作;
mutations: {
//创建一个方法,用于直接修改state中的登录状态属性的值:
setLoginState(state, payLoad) {
//payLoad:载荷,用于接收传的参数;
// state.isLogin = payLoad //普通参数
state.isLogin = payLoad.stateValue; //对象做参数
},
// 修改token:
setToken(state, payLoad) {
state.token = payLoad.token;
},
getToken(state){
return state.token;
}
},
//方法: actions还可以支持异步的操作;
actions: {
// 触发mutations中的方法,修改登录状态 :
//Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象:
setLoginStateActions(context, obj) {
context.commit('setLoginState', obj)
},
getToken(context){
// new Promise((resolve, reject)=>{
context.commit('getToken')
// .then(res=>{
// return res;
// })
// })
},
// 修改token:
setTokenActions(context, obj) {
context.commit('setToken', obj);
}
},
//多仓库的应用;
modules: {
},
//使用严格模式
//在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
// strict:true
strict: process.env.NODE_ENV !== 'production' //不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
})
在vue组件中实现登录并存储token,可以不用使用多种方式改变state
<template>
<div>
<h1>axios 请求后台和带token请求</h1>
<div>
登录状态:{
{
$store.state.isLogin}}
</div>
<div>
token: {
{
$store.state.token}}
</div>
<div>
<el-button type="primary" @click="userLogin">登录</el-button>
</div>
</div>
</template>
<script>
export default {
name: "myAxios",
data() {
return {
// token: ''
bg: 'red'
}
},
methods: {
userLogin() {
let that = this;
this.$axios.post('/api/user/login',
this.qs.stringify({
usernameOrTel: 'lisi',
password: '123'
})
)
.then((res) => {
console.log(res)
console.log(res.headers)
// 获取token,并存储
// that.token = res.headers['authenticate']
//把token放在本地存储中;
sessionStorage.setItem('token', res.headers['authenticate']);
//把token放在VUEX的状态中中;
that.$store.state.token = res.headers['authenticate'];
that.$store.state.isLogin = true;
})
.catch((res) => {
console.log(res)
})
}
}
}
</script>
在严格模式下需要遵循单数据流向 component–>actions–>mutations实现数据的修改
store中找需要修改的vuex文件;添加严格模式 strict: process.env.NODE_ENV !== ‘production’
以下是严格模式的情况:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//状态:
state: {
isLogin: false,
token: ''
},
//相当于是计算属性:
getters: {
},
//方法: mutations只支持同步操作,不支持异步操作;
mutations: {
//创建一个方法,用于直接修改state中的登录状态属性的值:
setLoginState(state, payLoad) {
//payLoad:载荷,用于接收传的参数;
// state.isLogin = payLoad //普通参数
state.isLogin = payLoad.stateValue; //对象做参数
},
// 修改token:
setToken(state, payLoad) {
state.token = payLoad.token;
},
getToken(state){
return state.token;
}
},
//方法: actions还可以支持异步的操作;
actions: {
// 触发mutations中的方法,修改登录状态 :
//Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象:
setLoginStateActions(context, obj) {
context.commit('setLoginState', obj)
},
getToken(context){
context.commit('getToken')
},
// 修改token:
setTokenActions(context, obj) {
context.commit('setToken', obj);
}
},
//多仓库的应用;
modules: {
},
//使用严格模式
//在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
// strict:true
strict: process.env.NODE_ENV !== 'production' //不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
})
在登录组件中只能使用actions或者mutation来改变state
<template>
<div>
<h1>axios 请求后台和带token请求</h1>
<div>
登录状态:{
{
$store.state.isLogin}}
</div>
<div>
token: {
{
$store.state.token}}
</div>
<div>
<el-button type="primary" @click="userLogin">登录</el-button>
<el-button type="primary" @click="getUser">获取用户</el-button>
</div>
</div>
</template>
<script>
export default {
name: "myAxios",
data() {
return {
// token: ''
bg: 'red'
}
},
methods: {
userLogin() {
let that = this;
this.$axios.post('/api/user/login',
this.qs.stringify({
usernameOrTel: 'lisi',
password: '123'
})
)
.then((res) => {
console.log(res)
console.log(res.headers)
//把token放在本地存储中;
sessionStorage.setItem('token', res.headers['authenticate']);
// 在严格模式下的修改token的方法:
that.$store.dispatch({
type: 'setTokenActions', token: res.headers['authenticate']});
that.$store.dispatch({
type: 'setLoginStateActions', stateValue: true})
})
.catch((res) => {
console.log(res)
})
}
}
}
</script>
以下是介绍严格模式获取state和修改state的情况
<template>
<div>
<h1>Vuex通信: </h1>
<div>
<!-- 在组件的模板中,直接引用VUEX的状态中的属性:-->
登录状态:{
{
$store.state.isLogin}}
</div>
<div>
token: {
{
$store.state.token}}
</div>
<div>
<el-button type="primary" @click="modifyIsLoginState">修改状态</el-button>
</div>
</div>
</template>
<script>
export default {
name: "myVuex",
methods: {
modifyIsLoginState(){
console.log("modifyIsLoginState");
// 操作状态的方法1:
// this.$store.state.isLogin = 'aaaa'; //严格模式下,不要直接修改state。系统会抛出一个错误异常!
// 操作状态的方法2:正规的单向数据方式,去修改state。
// this.$store.dispatch('setLoginStateActions',true)
// 以对象做参数时,type是固定的属性名;多参数时,添加对象的属性名就可以了
// this.$store.dispatch({type: 'setLoginStateActions', stateValue: true});
// 操作状态的方法3: 直接通过commit(),触发mutations中的函数去修改状态:
// this.$store.commit('setLoginState',true)
this.$store.commit({
type: 'setLoginState',stateValue: true}) ;
},
},
mounted() {
console.log(this)
}
}
</script>
<style scoped>
</style>
在组件中使用vuex的属性,显示相关的值有两种方式
在vuex文件里面的getters属性添加相关的方法:
getters: {
getToken(state){
//注入一个state做参数;
return state.token; //返回一个值;
}
},
在组件中显示相关的值:
<div style="border: 1px solid blue;height:100px;">
token: {
{
$store.state.token}}
</div>
//绑定在v-html中
<div style="border: 1px solid red;height:100px;" v-html="$store.getters.getToken">
getters来获取 token:
</div>
映射vuex里面的数据
在需要使用数据的组件中引入vuex的方法
组件里面使用相关的方法时 传参是 其中的对象名vuex里面的对象名一致。
<template>
<div>
<h1>Vuex通信: </h1>
<div>
<!-- 在组件的模板中,直接引用VUEX的状态中的属性:-->
登录状态:{
{
$store.state.isLogin}}
</div>
<div>
token: {
{
$store.state.token}}
</div>
<div style="border: 1px solid red; height: 100px;" v-text="getToken">
没有token
</div>
<div>
<el-button type="primary" @click="modifyIsLoginState">修改状态</el-button>
<el-button type="primary" @click="setLogin({stateValue: 789})">映射Actions中的方法</el-button>
<el-button type="primary" @click="setTokenActions({token: '天王盖地虎,...'})">映射Actions中的方法,修改token</el-button>
</div>
</div>
</template>
<script>
import {
mapGetters, mapMutations, mapActions} from 'vuex';
export default {
name: "myVuex",
computed:{
//映射Getters中的方法到本地,做当前组件的计算属性中的方法;
...mapGetters(['getToken'])
},
methods: {
// 通过映射mutations中的方法,到当前组件的methods中。调用时,当本地方法来用;
...mapMutations(['setLoginState','setToken']), //映射方式1:数组的方法
// 映射Actions中的方法到本地:
...mapActions (['setLoginStateActions','setTokenActions'])
// ...mapActions({setLogin: 'setLoginStateActions'}) //映射方式2:对象,取一个别名的方式,setLogin就是别名;
},
mounted() {
console.log(this)
}
}
</script>
其中 $attrs与 $props类似,都是接受值的方法
而 $listeners是指后代触发祖先事件的方法
在祖先组件中ancestors中:
<template>
<el-row>
<el-col :span="24">
<el-page-header title="案例4:跨级通信" content="$attrs,$listeners:"></el-page-header>
</el-col>
<el-col :span="24">
//在这里将值绑定到子组件上去
<courseSystem v-on:myTest="myTest" :html="html" :jsoo="jsoo" :css="css" :vue="vue" web="WEB前端体系"></courseSystem>
</el-col>
</el-row>
</template>
<script>
//es6语法
const courseSystem = ()=>import('@/components/course/courseSystem.vue');
// import courseSystem from '@/components/course/courseSystem.vue';
export default {
name: "myAttrs",
components:{
courseSystem},
data (){
return {
html:'HTML体系',
jsoo: 'JavaScript面向对象体系',
css: 'Cascading Style Sheets 体系',
vue: 'VUE全家桶'
}
},
methods :{
myTest(a){
console.log('myTest方法:',a)
}
}
}
</script>
在爷爷组件grandfather中
<template>
<el-row style="border: 1px solid red;">
<el-col :span="24">
<h1>我们的体系:{
{
web}}</h1>
<div>$attrs:{
{
$attrs}}</div>
//v-bind="$attrs" 把$attrs传给它的子组件 ,它的子组件中,放可有$attrs属性的值
// v-on="$listeners" :通过 v-on 绑定$listeners,把父组件中所有监听的事件,传给子组件;
<course-list v-bind="$attrs" v-on="$listeners" v-on:test11="test11"></course-list>
</el-col>
</el-row>
</template>
//1. $attrs: 用来接收除 props 属性中包括的值以外的父组件传递进来的属性的值;
//注意在props里面如果接收了父组件传过来的属性与值,在$attrs里面是不会接收到改属性的,$attrs会将该属性剔除。
<script>
const courseList = ()=>import('@/components/course/courseList.vue');
export default {
name: "courseSystem",
components:{
courseList},
props:['web'], //声明了props包括的属性,那么其它绑定的属性,则由$attrs来接收。
mounted() {
console.log('$attrs:',this.$attrs);
console.log('$props:',this.$props);
console.log('$listeners:',this.$listeners); //监听父级组件作用域中,所有的v-on的事件:
},
methods:{
test11(){
console.log('test11')
}
},
inheritAttrs: false// 默认是true, 把$attrs对应的属性渲染到子组件的根元素上;false:不渲染$attrs 绑定的属性到子组件的根元素.
}
</script>
<style scoped>
</style>
在爸爸组件father中:
<template>
<div style="border: 1px solid blue;margin: 10px;">
<h3>课程列表:{
{
html}}</h3>
<div> $attr:{
{
$attrs}}</div>
<ul><li v-for="item in $attrs">{
{
item}}</li></ul>
<div><button type="info" @click="triggerTest">触发caseThree组件中的事件</button></div>
<hr>
<div>
//给子组件绑定$attrs
<check-course v-bind="$attrs" v-on="$listeners"></check-course>
</div>
</div>
</template>
<script>
const checkCourse = ()=>import('@/components/course/checkCourse.vue')
export default {
name: "courseList",
props:['html'],
components:{
checkCourse},
inheritAttrs: false,
methods:{
triggerTest (){
// 在子组件中,通过$emit()来触发外层组件上v-on绑定的事件:
this.$emit('myTest', 'I LOVE YOU!');
console.log('courseList 触发 myAttrs组件上的事件 ')
}
}
}
</script>
在儿子组件child中:
<template>
<div style="border:1px solid darkgreen; margin: 10px;">
<h3>选择课程:{
{
jsoo}}</h3>
<div>$attr:{
{
$attrs}}</div>
<label v-for="item in $attrs">
<input type="checkbox">{
{
item}}
</label>
<button type="button" @click="test2">触发最外层绑定的事件</button>
</div>
</template>
<script>
export default {
name: "checkCourse" ,
props: ['jsoo'],
inheritAttrs: false,
methods:{
test2(){
this.$emit('myTest','1111111111')
console.log('test2方法的触发:触发myTest方法')
this.$emit('test11')
}
},
mounted() {
console.log("checkCourse:",this.$listeners)
}
}
</script>
$attrs和 $props可以搭配使用 但是一个用了 另一个不能再用。
每一层都会使用props来接收某些值,所以每一层显示的attrs会越来越少
注意 $listeners与 $emit的搭配使用,实现子代调用祖先的方法
在爷爷组件father中:
使用provide来返回数据给子组件
在子组件中使用inject来注入父组件提供的数据
<template>
<div style="border: 1px solid red; padding: 20px;">
<h1>产品列表 <button @click="modifyData1">外层修改数据</button></h1>
<provideA></provideA>
</div>
</template>
<script>
import provideA from '@/components/provide/provideA.vue'
export default {
name: "myProvide",
components: {
provideA},
methods: {
modifyData1(){
this.dataList.splice(3,1, {
id: 3, name: 'BBBBB',des:'都是皇帝的新装', count: 1})
this.resUser.uname = '皇帝'
}
},
data (){
return {
dataList: [
{
id: 1, name: '衣服1',des:'都是皇帝的新装', count: 1},
{
id: 2, name: '衣服2',des:'都是皇帝的新装', count: 1},
{
id: 3, name: '衣服3',des:'都是皇帝的新装', count: 1},
{
id: 4, name: '衣服4',des:'都是皇帝的新装', count: 1},
{
id: 5, name: '衣服5',des:'都是皇帝的新装', count: 1}
],
// 给provide相关的一个属性
resUser: {
uid: 10001, uname: '张三'}
}
},
// 有几种使用方式
//第一种
// provide:{ // 给后代组件提供共享的数据:
// dataList: [
// {id: 1, name: '衣服1',des:'都是皇帝的新装', count: 1},
// {id: 2, name: '衣服2',des:'都是皇帝的新装', count: 1},
// {id: 3, name: '衣服3',des:'都是皇帝的新装', count: 1},
// {id: 4, name: '衣服4',des:'都是皇帝的新装', count: 1},
// {id: 5, name: '衣服5',des:'都是皇帝的新装', count: 1}
// ]
//}
//第二种
provide (){
return {
// dataList: [
// {id: 1, name: '衣服1',des:'都是皇帝的新装', count: 1},
// {id: 2, name: '衣服2',des:'都是皇帝的新装', count: 1},
// {id: 3, name: '衣服3',des:'都是皇帝的新装', count: 1},
// {id: 4, name: '衣服4',des:'都是皇帝的新装', count: 1},
// {id: 5, name: '衣服5',des:'都是皇帝的新装', count: 1}
// ],
//第三种
dataList: this.dataList,
user: this.resUser //把组件的属性赋值给user。就是给后代组件,提供了当前的this对象。
}
}
}
</script>
父组件father中:
<template>
<div>
provideA inject: {
{
user.uname}}
<ul>
<li v-for="item in dataList">
{
{
item.name}}
</li>
</ul>
<div style="border: 1px solid blue; height: 100px;">
<provideB></provideB>
</div>
</div>
</template>
<script>
import provideB from '@/components/provide/provideB.vue'
export default {
name: "provideA",
components:{
provideB},
inject:['dataList','user'] //注入父级提供的数据:
}
</script>
子组件child中:
父组件传递实例对象的数据
子组件使用实例对象的方式来修改父组件的数据
<template>
<div>
provideB当中的inject: {
{
user.uname}}
<ol>
<li v-for="item in dataList" style="display: inline-block; width: 100px;">
{
{
item.name}}
</li>
</ol>
<button @click="modifyData">修改数据</button>
</div>
</template>
<script>
export default {
name: "provideB",
inject:['dataList','user'], //只要在子孙组件中,通过inject来注入属性名,就可以拿 到最外层组件提供的数据。
methods:{
modifyData () {
let arr = this.dataList;
arr.splice(0,1, {
id: 1, name: '衣服AAA',des:'都是皇帝的新装', count: 1})
this.user.uname = '皇子'
}
}
}
</script>
输出this.$children时,当组件有多个时,输出的是一个数组
父组件中给子组件绑定ref传值给子组件
<template>
<div>
<h1>ref属性, $parent, $children : {
{
title}}</h1>
<p> $parent, $children这两种方法的弊端是,无法在跨级或兄弟间通信。</p>
<!-- 在子组件使用时,添加ref属性,通过$refs.comA对象来获取子组件对象 -->
<childrenA ref="comA"></childrenA>
<childrenB ref="comB"></childrenB>
</div>
</template>
<script>
import childrenA from '@/components/children/childrenA.vue'
import childrenB from '@/components/children/childrenB.vue'
export default {
name: "myRef",
data (){
return {
title: 'CaseFive父组件'
}
},
components: {
childrenA,childrenB
},
mounted() {
console.log(this.$refs);//{comA: VueComponent, comB: VueComponent}
const comA = this.$refs.comA;
console.log(comA.title)
comA.title = '今天天气好热';
// console.log(this.$children) //[VueComponent, VueComponent]
//通过下标来操作子组件对象:
console.log(this.$children[0].title)
this.$children[0].title = '今天不打滚'
}
}
</scrip t>
子组件一
在子组件中是用this.$parent时获得父组件的值
<template>
<div>
<h1> {
{
title}}</h1>
</div>
</template>
<script>
export default {
name: "childrenA",
data (){
return {
title:"组件A"
}
},
mounted() {
const parentCom = this.$parent;
console.log(parentCom.title)//CaseFive父组件
console.log(parentCom.$children[1].title) //组件b
parentCom.title = '哈哈哈哈'
}
}
</script>
子组件二
<template>
<div>
<h1> {
{
title}}</h1>
</div>
</template>
<script>
export default {
name: "childrenB",
data (){
return {
title:"组件b"
}
}
}
</script>