zoukankan      html  css  js  c++  java
  • 一、Vue组件化 ---kkb

    组件通信

    1、父组件 => 子组件

    • 属性props
    // child
    props: { msg: String }
    // parent
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    • 引用refs  ---  特别适用于想要操作某些DOM结构时,refs就是这些DOM节点
    // parent
    <HelloWorld ref="hw"/>
    this.$refs.hw.xx = 'xxx'

    this.$refs 需要在mounted 中 或之后调用,因为父组件先于子组件挂载,在父组件 created 的时候,子组件还没挂上去,所以访问不到

    • 子组件chidren
    // parent
    this.$children[0].xx = 'xxx'

    子元素不保证顺序,需要小心使用

    2、子组件 => 父组件  自定义事件

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

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

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

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

    4、祖先和后代之间

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

    • provide / inject:能够实现祖先给后代传值
    // ancestor
    provide() {
      return {foo: 'foo'}
    }
    // descendant
    inject: ['foo']

    注意:provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序中。我们更多会在开源组件库中见到。

    但是,反过来想要后代给组件传值,这种方案就不行了,子组件如果想改的话,需要组件在provide中注入 setFoo 的函数,子组件通过setFoo来修改 foo 的值

    provide的时候,完全可以把当前组件(祖先组件)注入,直接传入 this 就行了

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

    • 事件总线:创建一个 Bus 类负责事件派发、监听 和 回调管理
    // Bus:事件派发、监听和回调管理
    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]) {
          this.callbacks[name].forEach(cb => cb(args));
        }
      }
    }
    
    // main.js
    Vue.prototype.$bus = new Bus();
    
    // 使用
    // child1
    this.$bus.$on("foo", handle);
    // child2
    this.$bus.$emit("foo");

    简单点的写法: Vue.prototype.$bus = new Vue()

    这里的 new Vue() 是一个新的、干净的vue实例,只在做监听、派发消息的事情。因为Vue已经实现上述 Bus 的接口了

    • vuex:创建唯一的全局数据管理者 store,通过它管理数据并通知组件状态变更

    插槽

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

    Vue 2.6.0 之后采用全新的 v-slot 语法取代之前的 slot、slot-scope

    匿名插槽

    // child
    <div>
      <slot></slot>
    </div>
    
    // parent
    <comp>hello</comp>

    具名插槽

    // child
    <div>
      <slot></slot>
      <slot name="content"></slot>
    </div>
    
    // parent
    <Comp2>
      // 默认插槽用default做参数
      <template v-slot:default>具名插槽</template>
      // 具名插槽用插槽名做参数 
      <template v-slot:content>内容...</template>
    </Comp2>

    作用域插槽

    // comp3
    <div>
      <slot :foo="foo"></slot>
    </div>
    
    // parent
    <Comp3>
      // 把v-slot的值指定为作用域上下文对象
      <template v-slot:default="ctx">
        来自子组件的数据:{{ctx.foo}}
      </template>
    </Comp3>    

    相当于子组件存有一个值,父组件可以从 v-slot 获取后,进行相应的操作

    另一种写法--对象的解构

    // comp3
    <div>
      <slot :foo="foo"></slot>
    </div>
    
    // parent
    <Comp3>
      // 把v-slot的值指定为作用域上下文对象
      <template v-slot:default="{foo}">
        来自子组件的数据:{{foo}}
      </template>
    </Comp3>  

     组件化实战

     实现 Form、FormItem、Input

    •  Input
      • 双向绑定:@input、:value
      • 派发校验事件
    <template>
      <div>
        <!-- 自定义组件要实现 v-model 必须实现 :value @inpit -->
        <!-- $attrs存储的是props之外的部分,这里用v-bind把这些东西展开 -->
        <input :value="value" @input="onInput" v-bind="$attrs"/>
      </div>
    </template>
    <script>
    export default {
      inheritAttrs: false, // 避免顶层容器继承属性---<div>上面就不会有 type=password 属性了
      props: {
        value: {
          type: String,
          default: ''
        }
      },
      methods: {
        onInput(e) {
          // 通知父组件数值变化
          this.$emit('input', e.target.value)
    
    // 通知FormItem校验
    this.$parent.$emit('validate'); } }, } </script>
    • 实现KFormItem
      • 任务1:给Inoput预留插槽 - slot
      • 任务2:能够展示label 和校验信息
      • 任务3:能够进行校验

    数据校验,思路:校验发生在FormItem,它需要知道何时校验(让Input通知它),还需要知道怎么校验(注入校验规则)

    任务1:Input通知校验

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

    任务2:FormItem 监听校验通知,获取规则并执行校验

    安装校验库:npm i async-validator -S      

    <template>
      <div>
        <label v-if="label">{{label}}</label>
        <slot></slot>
        <!-- 校验信息 -->
        <p v-if="errorMessage">
          {{errorMessage}}
        </p>
      </div>
    </template>
    <script>
    import Schema from 'async-validator'
    export default {
      inject: ['form'],
      props:{
        label: {
          type: String,
          default: ''
        },
        prop: String
      },
      data(){
        return{
          errorMessage: ''
        }
      },
      methods: {
        validate() {
          // 执行组件校验
          // 1、获取校验规则
          const rules = this.form.rules[this.prop]
          // 2、获取数据
          const value = this.form.model[this.prop]
          // 3、执行校验
          const desc = {
            [this.prop]: rules
          }
          const schema = new Schema(desc)
          // 参数1是值,参数2是校验错误对象数组
          // 返回的是Promise<boolean>
          return schema.validate({[this.prop]: value}, errors => {
            if(errors){
              // 有错
              this.errorMessage = errors[0].message
            } else {
              // 没错,就清除错误信息
              this.errorMessage = ''
            }
          })
        }
      },
      mounted() {
        // 监听校验事件,并执行监听
        this.$on('validate', () => {
          this.validate()
        })
      }
    }
    </script>
    • Form
      • 给FormItem留插槽
      • 设置数据和校验规则
      • 全局校验
    template>
      <div>
        <slot></slot>
      </div>
    </template>
    <script>
    export default {
      provide() {
        return {
          form: this // 传递Form实例给后代,比如FormItem用来校验
        }
      },
      props: {
        model: {
          type: Object,
          required: true
        },
        rules: Object
      },
      methods: {
        validate(cb) {
          // map 的结果是若干 Promise数组
          const tasks = this.$children
            .filter(item => item.prop)
            .map(item => item.validate())
          // 所有任务必须全部成功才算校验成功
          Promise.all(tasks).then(() => {
            cb(true)
          }).catch(() => {
            cb(false)
          })
        }
      },
    }
    </script>

    使用如下:index.vue

    <template>
      <div>
        <KForm :model="model" :rules="rules" ref="loginForm">
          <KFormItem label="用户名" prop="username">
            <KInput v-model="model.username"></KInput>
          </KFormItem>
          <KFormItem label="密码" prop="password">
            <KInput v-model="model.password" type="password"></KInput>
          </KFormItem>
          <KFormItem>
            <button @click="onLogin">登录</button>
          </KFormItem>
        </KForm>
        {{model}}
      </div>
    </template>
    <script>
    import KInput from './KInput'
    import KFormItem from './KFormItem'
    import KForm from './KForm'
    export default {
      data() {
        return {
          model: {
            username: 'tom',
            password: ''
          },
          rules: {
            username: [{required: true, message: '用户名必填'}],
            password: [{required: true, message: '密码必填'}]
          }
        }
      },
      methods: {
        onLogin() {
          this.$refs.loginForm.validate((isValid) => {
            if(isValid) {
              alert('登录!!!')
            }else{
              alert('有错!!!')
            }
          })
        }
      },
      components: {
        KInput,
        KFormItem,
        KForm
      }
    }
    </script>

    inheritAttrs   2.4.0 新增

    类型:boolean   默认:true

      默认情况下父作用域的不被认作props的特性绑定将会“回退”,且作为普通的 HTML 特性应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 为 false,这些默认行为将会被丢掉。而通过实例属性 $arrts 可以让这些特性生效,且可以通过 v-bind 显式绑定到非根元素上。

    注意:这个选项不影响 class 和 style 绑定

    组件由上至下广播

    可以解决 组件之间通信 父组件向子组件派发事件时,如果父子之间还有其他组件导致无法通信的问题 :$children  的派发事件

    function boardcast(eventName, data) {
      this.$children.forEach(child => {
        // 子元素触发$emit
        child.$emit(eventName, data)
        if(child.$children.length) {
          // 递归调用,通过call修改this指向 child
          boardcast.call(child, eventName, data)
        }
      })
    }
    
    Vue.prototype.$boardcast = function(eventName, data) {
      boardcast.call(this, eventName, data)
    }

    elementUI的做法

    // 事件广播
    function broadcast(componentName, eventName, params){
      // 遍历所有的children
      this.$children.forEach(child => {
        var name = child.$options.componentName;
    
        if(name === componentName){
          // 如果找到了 componentName 组件,则让child自己给自己派发事件
          // 然后子组件里面就可以自己监听这个事件了
          child.$emit.apply(child, [eventName].concat(params))
        }else{
          // 如果没找到,接着找,继续调用broadcast给子组件
          broadcast.apply(child, [componentName, eventName].concat([params]))
        }
      })
    }

    由下而上广播

    Vue.prototype.$dispatch = function(eventName, data) {
      let parent = this.$parent
      // 查找父元素
      while(parent){
        // 父元素用$emit触发
        parent.$emit(eventName, data)
        // 递归查找父元素
        parent = parent.$parent
      }
    }

    elementUI的做法

    // 自下而上的事件派发
    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root
      var name = parent.$options.componentName
    
      // 当parent存在,并且 name存在且name不等于componentName时进行循环
      while(parent && (!name || name !== componentName)){
        parent = parent.$parent
        if(parent){
          name = parent.$options.componentName
        }
      }
      if(parent){
        parent.$emit.apply(parent, [eventName].concat(params))
      }
    }
    broadcast(conponentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params)
    }

    v-model 和 .sync 的异同

    // v-model 是语法糖
    <Input v-model="username">
    
    // 默认等效于下面这行
    <Input :value="username" @input="username=$event">
    
    // 但是你也可以通过设置model选项修改默认行为, Checkbox.vue
    {
      model: {
        prop: 'checked',
        event: 'change'
      }
    }
    
    // 上面这样设置会导致上级使用 v-model 时行为变化,相当于
    <Checkbox :checked="model.remember" @change="model.remember = $event"></Checkbox>

    场景:v-model 通常用于表单控件,它有默认行为,同时属性名和事件名均可在子组件定义

    // sync 修饰符添加于 v2.4,类似于 v-model,它能用于修改传递到子组件的属性,如果像下面这样写
    <Input :value.sync="username">
    
    // 等效于下面这行,那么和v-model的区别只有事件名称的变化
    <Input :value="username" @update:value="username=$event">
    
    // 这里绑定属性名称更改,相应的属性名也会变化
    <Input :foo="username" @update:foo="username=$event">

    场景:父组件传递的属性子组件想修改

    所以sync修饰符的控制能力都在父级,事件名称也相对固定 update:xx

    习惯上表单元素用 v-model

    实现弹窗组件

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

    this.$create(Notice, {
      title: 'xx',
      message: 'xx',
      duration: 1000
    }).show()

    create

    create函数用于动态创建指定组件实例并挂载至body

    // 创建指定组件实例,并挂载到body上
    import Vue from 'vue';
    export default function create(Component, props) {
      // 0、先创建vue实例,这里先不挂载  $mount()
      const vm = new Vue({
        render(h) {
          // render方法提供给我们一个h函数(createElement函数的别名),它可以渲染VNode
          return h(Component, {props})
        }
      }).$mount()
    
      // 1、上面的vm帮我们创建组件实例
      // 2、通过$children获取该组件实例
      // console.log(vm.$root)
      const comp = vm.$children[0]
    
      // 3、追加至body
      document.body.appendChild(vm.$el)
    
      // 4、清理函数
      comp.remove = () => {
        document.body.removeChild(vm.$el)
        vm.$destroy()
      }
    
      // 5、返回组件实例
      return comp
    }

    创建通知组件 Notice.vue

    <template>
      <div v-if="isShow">
        <h3>{{title}}</h3>
        <p>{{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>

    使用方法:

    const notice = create(Notice, {
      title: 'xxx',
      message: 'blabla~~',
      duration: 1000
    })
    notice.show()
  • 相关阅读:
    2020年12月2日
    2020年12月1日
    2020年11月30日
    2020年11月29日
    2020年11月28日
    2020年11月27日
    2020年11月26日
    2020年11月25日
    浅谈扩展欧几里得算法
    Hello 2020
  • 原文地址:https://www.cnblogs.com/haishen/p/11742002.html
Copyright © 2011-2022 走看看