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()
  • 相关阅读:
    Oracle常用命令大全(很有用,做笔记)
    表格驱动编程在代码中的应用
    mac 利用svn下载远程代码出现Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo.
    FAILURE: Build failed with an exception.
    There is an internal error in the React performance measurement code.Did not expect componentDidMount timer to start while render timer is still in progress for another instance
    react native TypeError network request failed
    Android向系统相册中插入图片,相册中会出现两张 一样的图片(只是图片大小不一致)
    react-native Unrecognized font family ‘Lonicons’;
    react-native SyntaxError xxxxx/xx.js:Unexpected token (23:24)
    Application MyTest has not been registered. This is either due to a require() error during initialization or failure to call AppRegistry.registerComponent.
  • 原文地址:https://www.cnblogs.com/haishen/p/11742002.html
Copyright © 2011-2022 走看看