首先,说一下什么是组件化:组件化就是基于可重用的目的,将一个大的软件系统按照分离关注点的形式,拆分成多个独立的组件,已较少耦合。
大部分来说,组件主要分三层,业务组件,基础业务组件以及基础组件,组件之间只能通过接口耦合,也就是依赖倒置原则,每个组件都提供对外的接口文档以描述该组件提供的功能。
- 其次,说一下组件化的好处:解耦,平台化,职责单一,复用性,编译集成。
- 解耦:每个组件都是一个单一的工程(项目),对外只提供接口。组件之间的依赖只能通过接口,通过工程或者项目的方式,可以很大程度避免代码之间的耦合。
- 职责单一:每个组件只提供单一的功能,专项专用嘛,每个组件都可以单独去维护扩展,只要接口不变。
- 复用性强:基于职责单一,那么新项目中就可以依赖需要的组件。
- 平台化:这个其实是最有价值的,如果你作为一个平台产品,其他业务或者兄弟部门的开发同学想集成到你的产品中,那么他在开发测试的时候就很方便的依赖必须的组件,方便调试。这样,在多部门,多team去联合调试的时候,会节省很多的时间,但是这个要求文档必须要够完善,以便于其他人能够很方便的去接入。类似于:支付宝,美团等等平台级的产品。
- 编译集成:单个组件化组合成一个产品,对于编译来说可以很快速的定位问题以及快速编译,打包。
- 可能还有很多好处,后续补充哈。
- 组件化同样也会有一些问题,比如组件的粒度大小,如何区分业务组件以及基础业务组件。 这个就要根据具体项目具体分析了。总而言之一句话,一切皆组件。 话不多说,开始
- 公共组件 可任意调用
<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 = '你好,世界';
}
}
});
- 局部组件
// 创建局部组件 template中内容和全局一致
const MyButton = {
template: '#MyButtonTemplate',
data() {
return {
title: '珠峰前端就业班'
}
}
};
// 注册全局组件 需在父组件中注册
let vm = new Vue({
el: '#app',
data: {},
//=>注册当前组件(视图中)需要使用的局部组件
components: {
MyButton
// 可用别名: 'btn': MyButton
}
});
- 组件通讯
<父组件> //父传子
<子组件 内容名称="要传的内容" :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;
}
}
}
};
- 基于事件委托方式完成组件通讯
//真实开发中子组件要在父组件之前创建
//父组件
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);
}
}
};
- 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;
}
}
};
- 基于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');