vue组件化

本文详细介绍了Vue.js中组件间的通信方式,包括props、自定义事件、事件总线、Vuex状态管理、$parent/$children/$root、$refs、provide/inject等。同时,展示了如何处理表单组件的双向绑定、验证以及弹窗组件的动态创建。内容涵盖了组件复用、维护性和协同开发的最佳实践。
摘要由CSDN通过智能技术生成

组件化

  1. 提高开发效率
  2. 方便重复使用
  3. 提高维护性
  4. 便于多人协同开发

组件常用通信方式

  1. props
  2. eventbus
  3. vuex
  4. 自定义事件
    • 边界情况
      $parent
      $children
      $root
      $refs
      provide/inject
    • 非prop特性
      $attrs
      $listeners

props 父传子

// child
props: { msg:String }
// 父
<hellow msg="hello">

自定义事件

// 子
this.$emit('add',goods)
// 父
<cart @add="Add($event)">

事件总线

任意两个组件之间传值

class Bus {
	constructor(){
		this.callbacks = {}
	}
	$on(name, fn){
		this.callbacks[name] = this.callbacks[name] || []
		this.callbacks[name].push(fn)
	}
	$emiet(name, args){
		if(this.callbacks[name]){
			this.callbacks[name].forEach(cb => cb(args))
		}
	}
}
// main.js
Vue.prototype.$bus = new Bus()
// 子1
this.$bus.$on('foo', '12345')
// 子2
this.$bus.$emit('foo')

vuex 全局数据管理

$parent/$root

兄弟组件间通信可通过共同的同祖辈搭桥

// brother1
this.$parent.$on('foo', '123')
// brother2
this.$parent.$emit('foo') 

$children

父组件可通过$children访问子组件

// 父
this.$children[0].xxx = 'xxx'

注意:$children不能保证子元素顺序

$attrs/$listeners

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。当一个组件没有
声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外),并且可以通过 vbind=“$attrs” 传入内部组件——在创建高级别的组

// 子  并未在props中声明
<p>{{$attrs.foo}}</p>
// 父
<hello foo="foo"/>

文档

refs

获取子节点

<hello ref="hw" />
mounted(){
	this.$refs.hw.xx = 'xxxx'
}

provide/inject

实现祖先和后代之间通信

// 祖先
provide() {
	return { foo: 'foo'}
}
// 后代
inject: ['foo']

插槽 slot

匿名插槽

// 组件 comp
<div><slot><slot></div>
// 父
<comp>hello</comp>

具名插槽

将内容分发到子组件指定位置

// comp
<div>
	<slot></slot>
	<slot name="con"></slot>
</div>
// 父
<comp>
	<template v-slot:default>111</template>
	<template v-slot:con>222</template>
</comp>

作用域插槽

分发内容要用到子组件中的数据

// comp
<div><slot :foo="foo"></slot></div>
// 父
<comp>
	<template v-slot:default = "slotProps">{{slotProps.foo}}</template>
</comp>

表单组件

kInput.vue

<template>
  <div>
    <!-- 自定义组件双向绑定::value  @input -->
    <!-- v-bind="$attrs"展开$attrs -->
    <input :type="type" :value="value" @input="onInput" v-bind="$attrs">
  </div>
</template>

<script>
  export default {
    inheritAttrs: false, // 设置为false避免设置到根元素上
    props: {
      value: {
        type: String,
        default: ''
      },
      type: {
        type: String,
        default: 'text'
      }
    },
    methods: {
      onInput(e) {
        // 派发一个input事件即可
        this.$emit('input', e.target.value)

        // 通知父级执行校验
        this.$parent.$emit('validate')
      }
    },
  }
</script>

<style scoped>

</style>

index.vue

<template>
  <div>
    <!-- KForm -->
    <KForm :model="userInfo" :rules="rules" ref="loginForm">
      <!-- 用户名 -->
      <KFormItem label="用户名" prop="username">
        <KInput v-model="userInfo.username" placeholder="请输入用户名"></KInput>
      </KFormItem>
      <!-- 密码 -->
      <KFormItem label="密码" prop="password">
        <KInput type="password" v-model="userInfo.password" placeholder="请输入用户名"></KInput>
      </KFormItem>
      <!-- 提交按钮 -->
      <KFormItem>
        <button @click="login">登录</button>
      </KFormItem>
    </KForm>
  </div>
</template>

<script>
import ElementTest from "@/components/form/ElementTest.vue";
import KInput from "@/components/form/KInput.vue";
import KFormItem from "@/components/form/KFormItem.vue";
import KForm from "@/components/form/KForm.vue";
export default {
  data() {
    return {
      userInfo: {
        username: "tom",
        password: ""
      },
      rules: {
        username: [{ required: true, message: "请输入用户名称" }],
        password: [{ required: true, message: "请输入密码" }]
      }
    };
  },
  components: {
    ElementTest,
    KInput,
    KFormItem,
    KForm
  },
  methods: {
    login() {
      this.$refs["loginForm"].validate(valid => {
         if (valid) {
           alert("submit");
         } else {
           console.log("error submit!");
           return false;
         }
      });
    }
  }
};
</script>

<style scoped>
</style>

KFormItem.vue

<template>
  <div>
    <!-- label -->
    <label v-if="label">{{label}}</label>

    <slot></slot>

    <!-- 校验信息显示 -->
    <p v-if="error">{{error}}</p>
  </div>
</template>

<script>
// Asyc-validator
import Schema from "async-validator";

export default {
  inject: ["form"],
  data() {
    return {
      error: "" // error是空说明校验通过
    };
  },
  props: {
    label: {
      type: String,
      default: ""
    },
    prop: {
      type: String
    }
  },
  mounted() {
    this.$on("validate", () => {
      this.validate();
    });
  },
  methods: {
    validate() {
      // 规则
      const rules = this.form.rules[this.prop];
      // 当前值
      const value = this.form.model[this.prop];

      // 校验描述对象
      const desc = { [this.prop]: rules };
      // 创建Schema实例
      const schema = new Schema(desc);
      return schema.validate({ [this.prop]: value }, errors => {
        if (errors) {
          this.error = errors[0].message;
        } else {
          // 校验通过
          this.error = "";
        }
      });
    }
  }
};
</script>

<style scoped>
</style>

kForm.vue

<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
export default {
  provide() {
    return {
      form: this
    };
  },
  props: {
    model: {
      type: Object,
      required: true
    },
    rules: {
      type: Object
    }
  },
  methods: {
    validate(cb) {
      // 获取所有孩子KFormItem
      // [resultPromise]
      const tasks = this.$children
        .filter(item => item.prop) // 过滤掉没有prop属性的Item
        .map(item => item.validate());

      // 统一处理所有Promise结果
      Promise.all(tasks)
        .then(() => cb(true))
        .catch(() => cb(false));
    }
  }
};
</script>

<style scoped>
</style>

弹窗组件

特点:它们在当前vue实例之外独立存在,通常挂载于body;它们是通过JS动态创建的,不需要在任何组件中声明。
样式:

this.$create(Notice, {
	title: '名称',
	message: '提示信息',
	duration: 1000
}).show();

create函数

import Vue from "vue";
// 创建函数接收要创建组件定义
function create(Component, props) {
// 创建一个Vue新实例
const vm = new Vue({
render(h) {
// render函数将传入组件配置对象转换为虚拟dom
console.log(h(Component, { props }));
return h(Component, { props });
}
}).$mount(); //执行挂载函数,但未指定挂载目标,表示只执行初始化工作
// 将生成dom元素追加至body
document.body.appendChild(vm.$el);
// 给组件实例添加销毁方法
const comp = vm.$children[0];
comp.remove = () => {
document.body.removeChild(vm.$el);
vm.$destroy();
};
return comp;
}
// 暴露调用接口
export default create;

Notice.vue

<template>
  <div class="box" v-if="isShow">
    <h3>{{ title }}</h3>
    <p class="box-content">{{ message }}</p>
  </div>
</template>
<script>
export default {
  props: {
    title: {
      type: String,
      default: "",
    },
    message: {
      type: String,
      default: "",
    },
    duration: {
      type: Number,
      default: 1000,
    },
  },
  data() {
    return {
      isShow: false,
    };
  },
  methods: {
    show() {
      this.isShow = true;
      setTimeout(this.hide, this.duration);
    },
    hide() {
      this.isShow = false;
      this.remove();
    },
  },
};
</script>
<style>
.box {
  position: fixed;
  width: 100%;
  top: 16px;
  left: 0;
  text-align: center;
  pointer-events: none;
  background-color: #fff;
  border: grey 3px solid;
  box-sizing: border-box;
}
.box-content {
  width: 200px;
  margin: 10px auto;
  font-size: 14px;
  padding: 8px 16px;
  background: #fff;
  border-radius: 3px;
  margin-bottom: 8px;
}
</style>

使用

import create from "@/utils/create";
import Notice from "@/components/Notice";



const notice = create(Notice, {
	title: "名称",
	message: valid ? "请求登录!" : "校验失败!",
	duration: 1000
});
	notice.show()

使用extend创建

import Vue from 'vue'
function create(comp,props) {
	const Ctor = Vue.extend(comp)
	const com = new Ctor({propsData: props})
	com.$mount()
	document.body.appendChild(com.$el)
	com.remove = () => {
		document.body.removeChild(com.$el)
		com.$destroy();
	}
	return com
}

变成插件形式

import Vue from 'vue'
import Notice from "@/components/Notice";
function create(component,props) {
   const Ctor = Vue.extend(component)
   const comp = new Ctor({propsData: props })
   comp.$mount()
   document.body.appendChild(comp.$el)
   comp.remove = () => {
   		document.removeChild(comp.$el)
   		comp.$destroy()
   }
   return comp
}
// 注册插件
export default {
	install(Vue) {
		Vue.prototype.$Notice = function(options) {
			return create(Notice, options)
		}
	}
}


// main.js
import create from "@/utils/create";
Vue.use(create)

// 使用

this.$Notice({
	title:"标题",
    message: "失败!",
    duration:1000
}).show()

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值