vue组件&vue组件通信几种方式&vuex的使用

292 阅读6分钟

首先,说一下什么是组件化:组件化就是基于可重用的目的,将一个大的软件系统按照分离关注点的形式,拆分成多个独立的组件,已较少耦合。

大部分来说,组件主要分三层,业务组件,基础业务组件以及基础组件,组件之间只能通过接口耦合,也就是依赖倒置原则,每个组件都提供对外的接口文档以描述该组件提供的功能。

  • 其次,说一下组件化的好处:解耦,平台化,职责单一,复用性,编译集成。
  1. 解耦:每个组件都是一个单一的工程(项目),对外只提供接口。组件之间的依赖只能通过接口,通过工程或者项目的方式,可以很大程度避免代码之间的耦合。
  2. 职责单一:每个组件只提供单一的功能,专项专用嘛,每个组件都可以单独去维护扩展,只要接口不变。
  3. 复用性强:基于职责单一,那么新项目中就可以依赖需要的组件。
  4. 平台化:这个其实是最有价值的,如果你作为一个平台产品,其他业务或者兄弟部门的开发同学想集成到你的产品中,那么他在开发测试的时候就很方便的依赖必须的组件,方便调试。这样,在多部门,多team去联合调试的时候,会节省很多的时间,但是这个要求文档必须要够完善,以便于其他人能够很方便的去接入。类似于:支付宝,美团等等平台级的产品。
  5. 编译集成:单个组件化组合成一个产品,对于编译来说可以很快速的定位问题以及快速编译,打包。
  6. 可能还有很多好处,后续补充哈。
  • 组件化同样也会有一些问题,比如组件的粒度大小,如何区分业务组件以及基础业务组件。 这个就要根据具体项目具体分析了。总而言之一句话,一切皆组件。 话不多说,开始
  1. 公共组件 可任意调用

<template id="MyButtonTemplate">
		<div>
            <h1>{{title}}</h1>
			<button @click='handle' v-html='title'></button>
		</div>
</template>

Vue.component('MyButton', {
			//=>必须设置template或者render函数:来规定当前组件的渲染视图
			//1.只能有一个根元素节点 
			//2.template的值可以是字符串(包裹需要的结构),也可以把结构单独分离出来,放置到<template></template>标记中,然后和当前组件关联
			template: '#MyButtonTemplate', //具体是html结构标签 id
            /* template: `<div>
            <h1>{{title}}</h1>
			<button @click='handle' v-html='title'></button>
		    </div>` */
			//=>DATA必须是一个函数:相当于形成一个闭包,让返回的对象(需要的响应式数据)是当前实例的私有的
			data() {
				return {
					title: '哈喽 widow'
				}
			},
			//=>其余的操作和NEW VUE的情况基本类似了
			methods: {
				handle() {
					this.title = '你好,世界';
				}
			}
		});
    
  1. 局部组件
    //  创建局部组件 template中内容和全局一致
    const MyButton = {
			template: '#MyButtonTemplate',
			data() {
				return {
					title: '珠峰前端就业班'
				}
			}
		};
    //  注册全局组件 需在父组件中注册
    let vm = new Vue({
			el: '#app',
			data: {},
			//=>注册当前组件(视图中)需要使用的局部组件
			components: {
				MyButton
				// 可用别名: 'btn': MyButton
			}
		}); 
  1. 组件通讯
    <父组件>   //父传子   
		<子组件 内容名称="要传的内容" :src="10"></子组件>
	<父组件>
    //子组件
	<template id="MyVoteTemplate">
		{{src}}
	</template>
    //js  子组件
    const MyVote = {
			template: '#MyVoteTemplate',
			//=>子组件设置PROPS用来接收父组件基于属性传递进来的信息:在PROPS中注册的属性,和DATA一样,也会挂载到实例上 
            
            this.title/{{title}}
			props: ["title", "aaa"],
			// 注册属性可进行校验(不符合校验格式的,依然可以在组件中使用,只不过控制台会抛出异常信息)
            //基于props传递进来的值,只能获取使用,不能在子组件中修改(修改后会有对应的效果,子组件会重新渲染,但是控制台会报错)
			props: {
				title: {//=>数据格式 可用数组命名多个
					type: String, //type: [Number, Array],
					//=>设置默认值
					default: '我是设定的属性默认值',
					//=>自定义规则 val传递的属性值 返回TRUE和FALSE
					validator(val) {
						return val >= 0;
					}
				}
			}
			
	};
  1. 基于事件委托方式完成组件通讯
          //真实开发中子组件要在父组件之前创建
      //父组件
const MyVote = {
			template: '#parent',
			data() {
				return {
					num: 0,
                    s:0,
					event: new Vue //把一个vue实例传给子组件
				}
			},
			components: {
				children1,
				children2
			},
			methods: {
				ParentNum(type) {
					this.num++;
				}
			}
		};
    //子组件1
const children1 = {
			template: '#children1',
			props: ['event'],
			data() {
				return {
					supNum: 0
				}
			},
			methods: {
				changeNum(type) {
					type === 'support' ? this.supNum++ : this.oppNum++;
				}
			},
			created() {
				//=>基于$on创建一个自定义事件:把指定的方法放到任务队列中
				this.event.$on('changesupport', this.changeNum);
			}
		};
    //子组件2
    const children2 = {
			template: '#children2',
			props: ['event'],
			data() {
				return {}
			},
			methods: {
				handle(type) {
					//=>通知任务队列中xxxxxx这个自定义事件执行:第一个参数是自定义事件类型,后面的参数都是通知方法执行的时候,给方法传递的参数
					//父组件上的方法可直接$emit执行 但要先注册<!-- 基于自定义事件,把父组件中的某个方法注册到任务队列中 -->
					//<子组件 :eventbus='eventBus' @ParentNum="ParentNum"></子组件>
					this.$emit('ParentNum', type);
                    //子组件上是挂在event上的,所以要加event
					this.event.$emit('changesupport', type);
				}
			}
		};
  1. ref完成组件通讯
        //子
    const childrenbtn = {
			template: '#children',
			methods: {
				handle(type) {
					// console.log(this.$parent);
                    //直接找到父组件上的num 进行++
					this.$parent.num++;
				}
			},
			data() {
				return {
					flag: true
				}
			}
		};
		//父
    const parent = {
			template: '#parent',
			data() {
				return {
					num: 0
				}
			},
			components: {
				childrenbtn
			},
			methods: {
				handle() {
                    this.$children是个数组存储所有的子组件
					// this.$children[0].flag = !this.$children[0].flag;
                    可通过给子组件标签加 ref快速找到目标子组件
                    //<childrenbtn ref='button'></childrenbtn>
					this.$refs.button.flag = !this.$refs.button.flag;
				}
			}
		};
  1. 基于provide和inject实现祖先与后代的通信
  • 一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效
  • provide -- 一个对象或返回一个对象的函数
  • inject -- 一个字符串数组,或一个对象,对象的key是本地的绑定名
父组件中提供:
provide() { 
           return {
                map_nodeObj  : {map_node: this.obj}
            // 提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
            }
        }
子组件中引入:
inject: {
    map_nodeObj: {
       default: ( ) => {
           return {map_node: '0' }
       }
   }
}
--->> 运行顺序
data
provide
created// 在这个阶段$el还未生成,在这先处理privide的逻辑,子孙组件才可以取到inject的值
mounted

关于不使用脚手架实现组件通信还有两种方式,但个人感觉不如上面四种好,在这里便不再多说

...

vuex 公共状态管理器 基于vue/cli方式

创建一个index.js作为vuex的根结点

+ import Vue from 'vue';
+ import Vuex from 'vuex';
+ import logger from 'vuex/dist/logger'; //监听数据改变前 中 后的变化
+ import Person from './Person';   //导入的组件
+ import Product from './Product';
+ Vue.use(Vuex);
//公共组件示例 当然可以不使用公共组件管理
+ export default new Vuex.Store({     //=>创建STORE容器并且导出
	//=>把每一个模块中的STATE~ACTIONS都进行合并
	//1.STATE会按照各版块进行区分 state={Person:{...},Product:{...},isLogin:true}
	//2.但是GETTERS/MUTATIONS/ACTIONS默认不会进行模块区分,默认是全部合并在一起的,这样会导致冲突  解决方案:每个模块设置namespaced:true,这样最后虽然也是把每个模块中的方法合并在一起了,但是会以模块的名字做为前缀,来进行标识和区分 mutations={'Person/changeName':function xxx,'Product/changeName':function xxx,changeLogin:function xxx,...}
	modules: { //导入的其他模块
		Person,
		Product
	},
	//=>各个板块公共的状态和方法
	state: {
		isLogin: true
	},
	//=>MUTATIONS存储SYNC FUNCTION,这些方法改变STATE中的状态信息
	mutations: {
		example(state, payload) {
			//=>STATE容器中存储的状态信息 PAYLOAD是COMMIT执行的时候传递进来的参数信息
			//=>this.$store.commit('example',100)
		}
	},
	//=>这些方法首先异步获取需要的数据,然后再基于COMMIT触发MUTATIONS中的方法,从而改变STATE
	actions: {
		exampleAction(context, payload) {
			//=>this.$store.dispatch('exampleAction','珠峰')
			setTimeout(_ => {
				context.commit('example', 1000);
			}, 1000);
		}
	},
	//=>GEETERS存储的方式等价于COMPUTED计算属性:监听当前容器中STATE的计算属性
	getters: {

	},
    //使用vuex中的插件 方便查看每次变化的信息
	plugins: [logger()]
});

扩展-改名字

//创建./store-types 大型项目为避免重名要这样做 当然可以不用
export const PRODUCT_MUTATION_CHANGE_NAME = "PRODUCT_MUTATION_CHANGE_NAME";
export const PRODUCT_GETTER_QUERY_BASE = "PRODUCT_GETTER_QUERY_BASE";
//在各个组件中引入
import * as types from './store-types';
        [types.PRODUCT_GETTER_QUERY_BASE](state) {...}

在组件中调用 这里直接基于最优方式 let { mapState, mapMutations ,mapGetters ,mapActions} = createNamespacedHelpers("Product");

// 选择这个Product组件示例
<template>
  <div>{{name}}</div>
</template>
<script>
import { createNamespacedHelpers } from "vuex"; //获取全局vuex
import * as types from "./store/store-types";
let { mapState, mapMutations ,mapGetters ,mapActions} = createNamespacedHelpers("Product");
//每个map直接访问Product里的数据/方法
    // 还可以这样直接获取全局xxx,不建议
    // this.$store.state....
    // this.$store.dispatch("Person/actionDemo", val);
    // this.$store.commit("Person/actionDemo", val)
export default {
  computed: { //最好使用计算属性接收state中的数据 因为子组件刷新不会刷新data()
    ...mapState(["name"])
  },
  methods: {//在这里注册方法
    ...mapMutations([types.PRODUCT_MUTATION_CHANGE_NAME])
  },
  mounted() {//在这里可直接使用一定是Product.js中的方法
    setTimeout(() => {
      this[types.PRODUCT_MUTATION_CHANGE_NAME](val);
    }, 1000);
  }
};
</script>

如果只有一个文件(vuex)那么直接使用map即可

import { mapState, mapMutations /*mapGetters ,mapActions */ } from "vuex";

export default {
  computed: {
    ...mapState(["number"])
  },
  methods: {
    ...mapMutations(['fn'])
  },
  created(){
    this.fn();
  }

最后在main.js中注册

//最后在main.js中注册
import Vue from 'vue';
import App from './App.vue';
import store from './store/index';

Vue.config.productionTip = false;
new Vue({
  //=>以后所有的组件都可以基于this.$store获取STORE容器
  store,
  render: h => h(App)
}).$mount('#app');