1、组件化

组件化是vue的核心思想,它能提高开发效率,方便重复使用,简化调试步骤,提升整个项目的可维护性,便于多人协同开发。

2、组件通信

父组件 => 子组件

  • 属性props

    1. // child
    2. props: {msg: String}
    3. //parent
    4. <Helloworld msg = "Welcome to Your Vue.js App"/>
  • 特性$attrs ```vue //child: 并未在props中声明foo

    {{$attrs.foo}}

//parent

  1. > 有很多属性没有办法都声明在props上,可以通过$attrs来继承,但是会继承一些没用的根组件属性,需要配合
  2. > inheritAttrs 来处理。
  3. - 引用refs
  4. ```vue
  5. //parent
  6. <Helloworld ref = 'hw' />
  7. mounted(){
  8. this.$refs.hw.xx = "xxx";
  9. }
  • 子元素$children
    1. //parent
    2. this.$children[0].xx = "xxx";

    子元素不保证顺序。

子组件 => 父组件:自定义事件

  1. //child
  2. this.$emit('add', good);
  3. //parent
  4. <Cart @add = "cartAdd($event)"></Cart>

兄弟组件:通过共同祖辈组件

通过共同祖辈组件搭桥,$parent或$root。

  1. //brother1
  2. this.$parent.$on('foo', handle);
  3. //brother2
  4. this.$parent.$emit('foo');

祖先和后代之间

由于嵌套层数过多,传递props不切实际,vue提供了provide/inject API 完成该任务。

  • provide/inject:能够实现组件给后台传值

    1. //ancestor
    2. provide(){
    3. return {foo: 'foo'}
    4. }
    5. //descendant
    6. inject: ['foo']

    任意两个组件之间:事件总线或vuex

  • 事件总线:创建一个Bus类负责事件派发、监听和回调管理。 ```javascript //Bus:事件派发、监听和回调管理 //实现一个事件总线 export default class Bus{ constructor() { this.callbacks = {}; } $on(name, fn){ this.callbacks[name] = this.callbacks[name] || []; this.callbacks[name].push(fn); } $emit(name, args){ if(this.callbacks[name]){

    1. //存在 遍历所有的callback
    2. this.callbacks[name].forEach(cb => cb(args));

    } } }

//main.js Vue.prototype.$bus = new Bus();

//child1 this.$bus.$on(‘foo’, handle); this.$bus.$emit(‘foo’);

  1. > 实践中可以用Vue代替Bus,因为它已经实现了相应的功能。
  2. ```javascript
  3. Vue.prototype.$bus = new Bus();
  4. //监听
  5. created(){
  6. //监听
  7. bus.$on("abc", () => {
  8. console.log("abc事件被触发了");
  9. this.name = "hellohello";
  10. })
  11. }
  12. //调用
  13. fn1(){
  14. bus.$emit("abc");
  15. }
  • vuex:创建唯一的全局数据管理者store,通过它管理数据并通知组件状态变更。

    3、插槽

    插槽的语法是Vue实现的内容分发API,用于复合组件开发。该技术在通用组件库开发中有大量应用。

匿名插槽

  1. //comp1
  2. <div>
  3. <slot></slot>
  4. </div>
  5. //parent
  6. <comp>hello<comp>

具名插槽

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

  1. //comp2
  2. <div>
  3. <slot></slot>
  4. <slot name = 'content></slot>
  5. </div>
  6. //parent
  7. <Comp2>
  8. <!-- 默认插槽用default做参数 -->
  9. <template v-slot:default>具名插槽</template>
  10. <!-- 具名插槽用插槽名做参数 -->
  11. <template v-slot:content>内容...</template>
  12. </Comp2>

作用域插槽

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

  1. // comp3
  2. <div>
  3. <slot :foo="foo"></slot>
  4. </div>
  5. // parent
  6. <Comp3>
  7. <!-- v-slot的值指定为作用域上下文对象 -->
  8. <template v-slot:default="slotProps">
  9. 来自子组件数据:{{slotProps.foo}}
  10. </template>
  11. </Comp3>

组件化实战:

实现Form、FormItem、Input

目标实现:element表单

创建components/form/KInput.vue

  1. <template>
  2. <div>
  3. <!-- input事件处理,值赋值 -->
  4. <input :type="type" @input="onInput" :value="value" v-bind="$attrs">
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. inheritAttrs: false,
  10. props: {
  11. value: {
  12. type: String,
  13. default: ''
  14. },
  15. type: {
  16. type: String,
  17. default: 'text'
  18. },
  19. },
  20. methods: {
  21. onInput(e) {
  22. // 转发input事件即可
  23. this.$emit('input', e.target.value)
  24. // 通知校验
  25. this.$parent.$emit('validate')
  26. }
  27. },
  28. }
  29. </script>

使用KInput

创建components/form/index.vue,添加如下代码

<template>
  <div>
    <h3>KForm表单</h3>
    <hr />
    <k-input v-model="model.username"></k-input>
    <k-input type="password" v-model="model.password"></k-input>
  </div>
</template> <script>
import KInput from "./KInput";
export default {
  components: { KInput },
  data() {
    return { model: { username: "tom", password: "" } };
  },
};
</script>

实现KFormItem

创建components/form/KFormItem.vue

<template>
  <div>
    <label v-if="label">{{label}}</label>
    <slot></slot>
    <p v-if="error">{{error}}</p>
  </div>
</template>
<script>
export default {
  props: {
    label: {
      // 输入项标签
      type: String,
      default: "",
    },
    prop: {
      // 字段名
      type: String,
      default: "",
    },
  },
  data() {
    return {
      error: "", // 校验错误
    };
  },
};
</script>

使用KFormItem

components/form/index.vue,添加基础代码:

<template>
  <div>
    <h3>KForm表单</h3>
    <hr />
    <k-form-item label="用户名" prop="username">
      <k-input v-model="model.username"></k-input>
    </k-form-item>
    <k-form-item label="确认密码" prop="password">
      <k-input type="password" v-model="model.password"></k-input>
    </k-form-item>
  </div>
</template>

实现KForm

<template>
  <form>
    <slot></slot>
  </form>
</template>

<script>
export default {
  provide() {
    return {
      form: this, // 将组件实例作为提供者,子代组件可方便获取
    };
  },
  props: { 
    model: { type: Object, required: true }, 
    rules: { type: Object } 
  }
};
</script>

使用KForm

components/form/index.vue,添加基础代码:

<template>
  <div>
    <h3>KForm表单</h3>
    <hr />
    <k-form :model="model" :rules="rules" ref="loginForm">...</k-form>
  </div>
</template> <script>
import KForm from "./KForm";
export default {
  components: { KForm },
  data() {
    return {
      rules: {
        username: [{ required: true, message: "请输入用户名" }],
        password: [{ required: true, message: "请输入密码" }],
      },
    };
  },
  methods: {
    submitForm() {
      this.$refs["loginForm"].validate((valid) => {
        if (valid) {
          alert("请求登录!");
        } else {
          alert("校验失败!");
        }
      });
    },
  },
};
</script>

数据校验

input 通知校验

onInput(e) { 
  // ... 
    // $parent指FormItem 
  this.$parent.$emit('validate'); 
}

FormItem监听校验通知,获取规则并执行校验

inject: ['form'], // 注入 
  mounted(){// 监听校验事件 
    this.$on('validate', () => { this.validate() }) 
  },
  methods: { 
    validate() { 
      // 获取对应FormItem校验规则 
      console.log(this.form.rules[this.prop]); 
      // 获取校验值 
      console.log(this.form.model[this.prop]); 
    } 
 }

安装async-validator: npm i async-validator -S

import Schema from "async-validator"; 
validate() { 
  // 获取对应FormItem校验规则 
  const rules = this.form.rules[this.prop]; 
  // 获取校验值 
  const value = this.form.model[this.prop]; 
  // 校验描述对象 
  const descriptor = { [this.prop]: rules }; 
  // 创建校验器 
  const schema = new Schema(descriptor); 
  // 返回Promise,没有触发catch就说明验证通过 
  return schema.validate({ [this.prop]: value }, errors => { 
    if (errors) { 
      // 将错误信息显示 
      this.error = errors[0].message; 
    } else { 
      // 校验通过 
      this.error = ""; 
    }
  }); 
}

表单全局验证,为Form提供validate方法

validate(cb) { 
  // 调用所有含有prop属性的子组件的validate方法并得到Promise数组 
  const tasks = this.$children
        .filter(item => item.prop) 
        .map(item => item.validate()); 
  // 所有任务必须全部成功才算校验通过,任一失败则校验失败 
  Promise.all(tasks) 
    .then(() => cb(true)) 
    .catch(() => cb(false)) 
}

实现弹窗组件

弹窗这类组件的特点是它们在当前vue实例之外独立存在,通常挂载于body;它们是通过JS动态创建的,不需要在任何组件中声明。常见使用姿势:

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

create函数

// 1.创建传入组件实例
// 2.挂载它从而生成dom结构
// 3.生成dom结构追加到body
// 4.淘汰机制:不需要时组件实例应当被销毁
import Vue from 'vue'

export default function create(Component, props) {
    // 1.创建传入组件实例
    // Vue.extend({})

    const vm = new Vue({
        render(h) { // h即是createElement(tag, data, children)
            // 返回虚拟dom
            return h(Component, {props})
        }
    }).$mount(); // 只挂载,不设置宿主,意思是执行初始化过程,但是没有dom操作

    document.body.appendChild(vm.$el)

    // 获取组件实例
    const comp = vm.$children[0];
    // 附加一个删除方法
    comp.remove = () => {
        // 移除dom
        document.body.removeChild(vm.$el)
        // 销毁组件
        vm.$destroy();
    }
    return comp;
}

通知组件

建通知组件,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>

使用create api

测试,components/form/index.vue

<script>
import create from "@/utils/create";
import Notice from "@/components/Notice";
export default {
  methods: {
    submitForm(form) {
      this.$refs[form].validate((valid) => {
        const notice = create(Notice, {
          title: "标题",
          message: valid ? "请求登录!" : "校验失败!",
          duration: 1000,
        });
        notice.show();
      });
    },
  },
};
</script>