美文网首页
[学习vue]组件化实战

[学习vue]组件化实战

作者: 巧克力_404 | 来源:发表于2020-06-18 14:55 被阅读0次

    组件化

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

    组件通信

    父组件 => 子组件:
    • 属性props
    //child
    props: {
        msg: String
    }
    //parent
    <HelloWorld msg="Welcome to Your Vue.js App" /> 
    
    • 特性$attrs
    //child
    <p>attrs: {{$attrs.foo}}</p>
    //parent
    <HelloWorld  foo="我是自组件attrs传值" />
    
    • 引用refs
    //parent
    <HelloWorld  ref="comeon" />
    mounted() {
        this.$refs.hw.comeon = '我是parent'
      },
    //child
     methods: {
        sayHello() {
          console.log(this.comeon)
        }
      },
    
    • 子元素$children
    //parent
    this.$children[0].xx = 'xx'
     
    
    子组件 => 父组件:自定义事件
    // child
    <div @click='testClick()'>
        this is test
    </div>
    testClick() {
          this.$emit('myclick','牛仔很忙');
    }
    // parent
    <test @myclick="onyourclick($event)"/>
    onyourclick($event){
          console.log('onyourClick',$event)
    },
    
    兄弟组件:通过共同祖辈组件

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

    // brother1
    this.$parent.$on('foo', handle)
    // brother2
    this.$parent.$emit('foo')
    
    祖先和后代之间

    由于嵌套层数过多,传递props不切实际,vue提供了 provide/inject API完成该任务,实现祖先给后代传值,缺点是非响应式

    // ancestor
    provide() {
        return {foo: '肖邦的夜曲'}
    }
    // descendant
    inject: ['foo']
    
    任意两个组件之间:事件总线 或 vuex

    事件总线:创建一个Bus类负责事件派发、监听和回调管理,EventBus来作为一个桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件,或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的灾难,因为才需要更完善的VUEX作为状态管理中心,将通知的概念上升到共享状态层次.

    //increment.vue (child)
    <button @click="increment()">+</button>
    increment() {
         this.$bus.$emit("incremented", {
               num:this.num
         })
    }
    
    //decrement.vue(child)
    <button @click="decrease()">-</button>
    decrease(){
         this.$bus.$emit('decreased',{
               num: this.num
         })
    }
    
    //app.vue
    <div class="increment">
           <increment />
    </div>
    <div class="show-front"> {{fontCount}} </div>
    <div class="decrement">
           <decrement />
    </div>
    
    this.$bus.$on("incremented", ({num}) => {
           this.fontCount += num
    });
    this.$bus.$on("decreased", ({num}) => {
          this.fontCount -= num
    })
    
    slot 插槽

    插槽可以分为具名插槽,作用域插槽,是在子组件中提供给父组件使用的一个占位符,用<slot></slot>表示,父组件可以在这个占位符中填充任何模版代码,例如html ,组件等,填充的内容会替换子组件的<slot></slot>标签。

    //子组件
    <!-- 插槽 -->
    <p><slot></slot></p>
    <p><slot name="content" :foo="桃花诺"></slot></p>
    
    //父组件 app.vue
    <HelloWorld>      
          <template>abc</template>
          <!-- 作用域插槽:显示数据来自子组件 -->
           <template v-slot:content="{foo}">content...{{foo}}</template>
    </HelloWorld>
    

    组件化实战

    模仿elementui 实现Form、FormItem、Input
    首先实现input,功能实现数据收集,表单元素的输入和校验反馈,实现双向绑定,监听@input,给:value赋值

    // input.vue
    <!-- input事件处理,值赋值 -->
    <input :type="type" @input="onInput" :value="value" v-bind="$attrs">
    export default {
            inheritAttrs: false, //不继承attrs
            props: {
                value: {
                    type: String,
                    default: ''
                },
                type: {
                    type: String,
                    default: 'text'
                },
            },
            methods: {
                onInput(e) {
                    // 转发input事件即可
                    this.$emit('input', e.target.value)
    
                    // 通知校验
                    this.$parent.$emit('validate')
                }
            },
        }
    

    formItem,表单项,用来校验,显示错误提示,显示input,显示label
    <slot></slot> 用来显示input 插槽
    引入async-validator校验包文件

     <div>
            <!-- label -->
            <label v-if="label">{{label}}</label>
            <slot></slot>
            <!-- 错误信息 -->
            <p class="error" v-if="error">{{error}}</p>
        </div>
    import Schema from 'async-validator';
        export default {
            inject: ['form'],
            props: {
                label: {
                    type: String,
                    default: ''
                },
                prop: {
                    type: String
                }
            },
            data() {
                return {
                    error: ''
                }
            },
            mounted() {
                this.$on('validate', () => {
                    this.validate();
                })
            },
            methods: {
                validate() {
                    // 0.获取校验规则和当前值
                    const rules = this.form.rules[this.prop];
                    const value = this.form.model[this.prop];
                    // 1.创建Schema实例
                    // this指向当前组件实例,this.prop是当前项的key
                    const schema = new Schema({
                        // [xxx] 计算属性
                        [this.prop]: rules 
                    })
                    // 2.使用该实例执行校验
                    return schema.validate({
                        [this.prop]: value
                    }, errors => {
                        if (errors) {
                            this.error = errors[0].message;
                        } else {
                            this.error = ''
                        }
                    })
                }
            },
        }
    

    form 表单 作为数据的容器传递给后代,实现父组件validate方法,收集所有item组件校验成功与否,回调给子组件成功或者失败参数。

    <slot></slot>
    export default {
      provide() {
        return {
          // this指的是表单组件实例
          form: this
        };
      },
      props: {
        model: {
          type: Object,
          required: true
        },
        rules: {
          type: Object
        }
      },
      methods: {
        validate(cb) {
          // 调用所有formitem的validate,只要一个失败就失败
          // 结果是Promise数组
          const tasks = this.$children
            .filter(item => !!item.prop) // 留下带prop属性的formitem
            .map(item => item.validate());
          // 判断所有结果
          Promise.all(tasks)
            .then(() => cb(true)) // 校验通过
            .catch(() => cb(false));// 校验失败
        }
      }
    }
    

    实现弹窗组件

    弹窗之类的组件的特点是它们存在与vue实例之外的独立存在,通常挂载与body,它们通过js独立创建,不需要在任何组件中声明,常用的姿势:

    this.$create(Notice, {
              title: "社会你杨哥喊你来搬砖",
              message: isValid?"提请求登录!":"校验失败!",
              duration: 2000
    }).show();
    

    接下来需要声明一个Notice,还需要实现一个create方法,能够创建我们的这个notice实例
    先创建一个通知组件
    一言不合就上代码
    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.js

    // 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;
    }
    

    最后在全局挂载一下这个方法,就可以使用了
    Vue.prototype.$create = create;

    <
    遗留问题
    1, 在实现表单组件,查找父级元素的时候,使用this.$parent 查找,如果页面嵌套结构发生改变,这种查找方法就会失效,怎么解决?
    2,弹窗组件中,创建组件实例,怎么用Vue.extend({}) 实现?

    相关文章

      网友评论

          本文标题:[学习vue]组件化实战

          本文链接:https://www.haomeiwen.com/subject/tagvtktx.html