vue组件传值的终极解决方案(部分含ts版本)

1,440 阅读2分钟

一对一传值

父子

子 -> 父 $emit

参数

  • {string} eventName
  • [...args]

// child.vue
<script>
export default {
    methods: {
        emit() {
            this.$emit('onChildChange', 'dataFromChild')
        }
    }
}
</script>

// parent.vue
<template>
    <child @onChildChange="onChildChange" />
</template>

export default {
    methods: {
        onChildChange(e) {
            console.log(e); // dataFromChild
        }
    }
}

子 -> 父 $parent

当前vue实例通过$parent指针可以获得父组件实例

同理,你可以通过parent.parent一直向上获取data,直到根节点

// child.vue
<script>
export default {
    methods: {
        emit() {
            console.log(this.$parent.parentData); // a
            this.$parent.parentData = 'b';
            console.log(this.$parent.parentData); // b
        }
    }
}
</script>

// parent.vue
<template>
    <child />
</template>

<script>
export default {
    data() {
        return {
            parentData: 'a',
        }
    }
}
</script>

父 -> 子 $ref

通过$ref可以很方便的拿到组件实例,例如组件的属性和方法等等。

// parent.vue
<template>
    <child ref="child"/>
</template>

<script>
export default {
    created() {
        console.log(this.$refs.child) // -> child实例
        console.log(this.$refs.child.a) // -> child的a属性值
    }
}
</script>

父 -> 子 prop

prop是单向数据流

// parent.vue
<template>
    <child :father-a="father.A" :father-b="father.B"></child>
    // 等价于 <child v-bind="father"></child>
</template>

/*
	father:{
		A:'A',
    	B: { b: 'b' },
    }
*/


/*
  下行绑定,父组件的更新会下流到子组件,但反过来不行 */
<template>
    {{fatherA}} // a
</template>


prop:{
	fatherA:{
		type:"String",
		default:'default data',
	},
	fatherB:{
		type:"Object",
		default: function(){
			return {};
		},
	}
}
  • type支持的属性

    • String
    • Number
    • Boolean
    • Array
    • Object
    • Date
    • Function
    • Symbol

当你试图在子组件中修改父组件的数值时,就会发生错误,因为prop时单向的数据流,只允许父组件流向子组件。

当你的变量需要双向传值的时候,就需要用到.sync修饰符。

父子双向 .sync

.sync修饰符是v-bind:title="prop" v-on:update:title="prop = $event"的简写形式,当prop需要双向绑定时,可以用这种形式触发更新

js写法

// parent.vue
<template>
    <child :parent-flag.sync="parentFlag" />
</template>

<script>
export default {
    data() {
        return {
            parentFlag: false,
        }
    }
}
</script>


// child.vue
<template>
    <div v-if="childFlag"></div>
</template>

<script>
export default {
	prop: {
        parentFlag: {
            type: Boolean,
            default: false,
        },
    },
    methods: {
        changeFlag() {
            // this.parentFlag = true; // error
            this.$emit('update:parentFlag', true);
        }
    }
}    
</script>

ts版本

// Child.vue

<template>
  <main v-if="flag">
    <button @click="closeChild">点击关闭</button>
  </main>
</template>


<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";


@Component
export default class Child extends Vue {
  @Prop() private flag = false;

  closeChild() {
    this.$emit("update:flag", false);
  }
}
</script>

// Father.vue

<template>
  <div>
    <Child :flag.sync="flag"></Child>
    <button @click="showChild">点击打开</button>
  </div>
</template>


<script lang="ts">
import Child from "./Child.vue";
import { Component, Prop, Vue, Provide } from "vue-property-decorator";

@Component({
  components: {
    Child
  }
})
export default class Father extends Vue {
  @Provide() flag = false;

  showChild() {
    this.flag = true;
  }
}
</script>

祖孙 (provide, inject)

通过provide向子组件注入依赖,子组件(无论嵌套多少层)通过inject属性都可以获取最外层的参数。

js写法

// parent.vue
<template>
    <child />
</template>

<script>
export default {
    provide() {
        return {
            msg: 'data from parent',
        }
    }
}
</script>


// child.vue
<template>
    <div>{{ msg }}</div>
</template>

<script>
export default {
	inject: ['msg']
}    
</script>

ts写法

// parent.vue
<template>
  <main>
    <Child />
  </main>
</template>

<script lang="ts">
import Child from "./Child.vue";
import { Component, Vue, Provide } from "vue-property-decorator";

@Component({
  components: {
    Child
  }
})
export default class HelloWorld extends Vue {
  // 注入依赖
  @Provide() msg = "msg from father";
}
</script>

//child.vue
<template>
  <main>
    <grand-son />
  </main>
</template>

<script lang="ts">
import GrandSon from "./GrandSon.vue";
import { Component, Vue } from "vue-property-decorator";

@Component({
  components: {
    GrandSon
  }
})
</script>

// GrandSon.vue
<template>
  <main>
    {{ msg }}
  </main>
</template>

<script lang="ts">
import Child from "./Child.vue";
import { Component, Vue, Inject } from "vue-property-decorator";

@Component({
  components: {
    Child
  }
})
export default class GrandSon extends Vue {
  @Inject() private msg!: string;
}
</script>

兄弟 (子 -> 父 -> 兄)

兄弟组件传值,除了下面要讲的一对多方法,简单的情况还可以使用子组件向父组件流动,再由父组件传递给兄弟组件。

// parent.vue
<template>
    <main>
        <com-a @onDataChange="onDataChange"/>
        <com-b :prop="data"/>
    </main>
</template>

<script>
import comA from './comA.js'
import comB from './comB.js'

export default {
    data() {
        return {
			data: ''
        }
    },
    methods: {
        onDataChange(e) {
            // 当数据变更时,向组件B传递
            this.data = e;
        }
    }
}
</script>

// comA.vue
<script>

export default {
  created() {
      // 向父组件传递数据变更
      this.$emit('onDataChange', 'data from component A')
  }
}
</script>

// comB.vue
<template>
    <main> {{ data }} </main>
</template>

<script>
export default {
  prop: {
      data: {
          type: String,
          default: ''
      }
  }
}
</script>

一对多传值

eventBus

原理:引入一个vue实例作为一个中间件,通过emiton进行事件的分发和监听。emit方法同子->父的emit, 区别在于一个调用当前vue实例,eventBus调用中间vue实例。

// bus.js
export default new Vue();

// A.vue
<script>
import bus from './bus.js'

export default {
    methods: {
        onChange() {
            // onDataChange事件,值是'changed'字符串
            // 值将作为$on中定义回调函数的形参
            bus.$emit('onDataChange', 'changed');
        }
    }
}
</script>

// B.vue
<script>
import bus from './bus.js'

export default {
    created() {
        this.$on('onDataChange', (e) => {
            // 每一次$emit分发’onDataChange',都会触发回调
            console.log(e); 
        })
    }
}
</script>

// C.vue
<script>
import bus from './bus.js'

export default {
    created() {
        this.$on('onDataChange', (e) => {
            // 支持一对多传值,每一次$emit,将会触发所有订阅的回调
            console.log(e); 
        })
    }
}
</script>

vuex

相对于eventbus, vuex有一套更加完善的语法,state存储变量,getters作为计算属性,mutation中写所有对state操作的方法,actions支持异步操作,更加能够适应一些复杂的业务逻辑。

基本语法

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

const store = new Vuex.Store({
  state: {
     // 这里写要存储的数据
      count: 1,
  },
  getters: {
      // 类似于computed计算属性
      doubleCount: state => {
          return state.count * 2;
      }
  },
  mutations: {
      // 这里写操作state的所有方法
      setCount(state, count) {
          state.count = count
	  }
  },
  actions: {
      // 这里写所有的异步方法
      setCountAsync({ commit }, count) {
          setTimeout(() => {
              commit('setCount', count)
          })
      }
  }
})

// hello.vue
import store from './store.js';

export default {
    store,
    created() {
        console.log(store.getters.doubleCount) // -> 2
        console.log(store.state.count) // -> 1
    }
    methods: {
        submit() {
            store.dispatch('setCount', 2);
        }
    }
}

vuex 模块化

vuex的modules属性还支持了模块化,可以对全局变量进行拆分 ,适应复杂的业务场景。

具体语法可以参照官方文档,在这里就不赘述。

对比

模块化 可封装 包体积 持久化 可响应
vuex 配合localstorage
emitBus × × ×
  • 总结
    • vuex 由于可以注册模块和封装方法,因此适合复杂的业务传值情况,同时可以配合localstorage做持久化处理。
    • eventbus 适用于简单的一对多传值情景,注重于值本身的传递。

router-view 路由传值

params

  1. 在浏览器里不显示参数
  2. 基于router,刷新就消失了(类似于post)
  3. 基于name设置,否则params会被忽视掉
this.$route.push({
	name:'child',
	params:{
		options:'data'
	}
})

// router.js
// :options 要严格保持一致,url类似于 /child/data
// 否则刷新后页面数据将会丢失
{
    path:"child/:options",
    name:'child',
    component:child
}

query

  1. 在浏览器里显示参数
  2. 基于url,刷新还在(类似于get)
  3. 在编程式的导航中,如果使用了path,params会被忽略
// 父组件
this.$route.push({
	path:'/child',
	query:{
		options:'data'
	}
})

// 跳转后组件
this.$route.query.options;