父子组件通信

熟悉自定义组件的开发,了解父子组件之间的通信方式,如:props,data,$dispatch(),$broadcast()

通过本节,你将学会:

  • 编写自定义组件
  • 引入自定义组件
  • 传递数据与数据改造
  • 父子组件之间的事件触发:$broadcast()$dispatch()、子组件$emit()节点绑定的事件
  • 兄弟组件之间的通信

编写自定义组件

开发页面时开发者必须用到Native组件,如:textdiv,这些组件是由各平台Native底层渲染出来的;如果开发一个复杂的页面,开发者把所有的UI部分写在一个文件的<template>,那代码的可维护性将会很低,并且模块之间容易产生不必要的耦合关系

为了更好的组织逻辑与代码,可以把页面按照功能拆成多个模块,每个模块负责其中的一个功能部分,最后页面将这些模块引入管理起来,传递业务与配置数据完成代码分离,那么这就是自定义组件的意义

自定义组件是一个开发者编写的组件,使用起来和Native一样,最终按照组件的<template>来渲染;同时开发起来又和页面一样,拥有ViewModel实现对数据、事件、方法的管理

这么来看,页面也是一种特殊的自定义组件,无需引入即可使用,同时服务于整个页面

示例如下:

<template>
  <div class="tutorial-page">
    <text class="tutorial-title">自定义组件:</text>
    <text>{{ prop1 }}</text>
    <text>{{ prop2Object.name }}</text>
  </div>
</template>

<style lang="less">
  .tutorial-page {
    flex-direction: column;
    padding-top: 20px;

    .tutorial-title {
      font-weight: bold;
    }
  }
</style>

<script>
  // 子组件
  export default {
    props: [
      'prop1',
      'prop2Object'
    ],
    data: {},
    onInit () {
      console.info(`外部传递的数据:`, this.prop1, this.prop2Object)
    }
  }
</script>

自定义组件比页面组件的不同之处在于多了一个props属性,用于声明该组件可接受的外部数据传递props是一个数组,数组中每个元素是暴露的属性名称

提示:

  • 如果属性名称使用驼峰定义,如:prop2Object,那么在外部传递数据时请使用-连接,如:prop2-object

引入自定义组件

传统引入文件的方式,是通过在<script>中使用importrequire来完成引入工作,如:VueReact

框架引入自定义组件的方式是通过<import>标签完成的,如下面代码所示

<import name="comp-part1" src="./part1"></import>

<import>标签中的的src属性指定自定义组件的地址,name属性指定在<template>组件中引用该组件时使用的标签名称

最终页面定义与引入方式如下:

<import name="comp-part1" src="./part1"></import>

<template>
  <div class="tutorial-page">
    <text class="tutorial-title">页面组件:</text>
    <text>{{ data1 }}</text>
    <text>{{ data2.name }}</text>
    <text onclick="evtType1Emit">触发$broadcast()</text>

    <comp-part1 prop1="{{data1}}" prop2-object="{{data2}}" onevt-type3="evtTypeHandler"></comp-part1>
  </div>
</template>

<style lang="less">
  .tutorial-page {
    flex-direction: column;
    padding: 20px 10px;

    .tutorial-title {
      font-weight: bold;
    }
  }
</style>

<script>
  // 父组件
  export default {
    private: {
      data1: 'string',
      data2: {
        name: 'object'
      }
    },
    onInit () {
      this.$page.setTitleBar({ text: '父子组件通信' })
    },
    evtTypeHandler (evt) {
      console.info(`父组件:事件响应: `, evt.type, evt.detail)
      // 结束事件传递
      // evt.stop()
    },
    evtType1Emit () {
      this.$broadcast('evtType1', { params: '额外参数' })
    }
  }
</script>

传递数据与数据改造

如上面所述,父组件向子组件传递数据,通过在子组件的props属性中声明对外暴露的属性名称,然后在组件引用标签上声明传递的父组件数据

如果你需要在子组件中对数据进行改造,但又不想改动父组件数据时,可以使用$watch()来满足需求。如果是监听对象中的属性,参数请使用.分割,如:$watch(xxx.xxx.xxx, methodName)

示例如下:

<script>
  // 子组件
  export default {
    props: [
      'prop1',
      'prop2Object'
    ],
    data () {
      return {
        upperProp1: this.prop1
      }
    },
    onInit () {
      console.info(`外部传递的数据:`, this.prop1, this.prop2Object)

      // 监听数据变化
      this.$watch('prop1', 'watchPropsChange')
      this.$watch('prop2Object.name', 'watchPropsChange')
    },
    /**
     * 监听数据变化,你可以对数据处理后,设置值到data上
     * @param newV
     * @param oldV
     */
    watchPropsChange (newV, oldV) {
      console.info(`监听数据变化:`, newV, oldV)
      this.upperProp1 = newV && newV.toUpperCase()
    }
  }
</script>

属性默认值 1010+

子组件声明属性时,可以设置默认值。当调用子组件没有传入该数据时,将会自动设为默认值。

如果需要设置默认值,props 属性的写法必须要要写成 Object 形式,不能写成 Array 形式。

示例如下:

<script>
  // 子组件
  export default {
    props: {
      prop1: {
        default: 'Hello' //默认值
      },
      prop2Object: {} //不设置默认值
    },
    onInit() {
      console.info(`外部传递的数据:`, this.prop1, this.prop2Object);
    }
  }
</script>

属性校验 1010+

子组件声明属性时,可以指定校验规格。如果传入的数据不符合规格,devtool 会发出警告。当组件给其他开发者使用时,这将会很有用处。

校验方式包括必填项检查、类型检查和函数检查。验证顺序是 必填项检查 -> 类型检查 -> 函数检查,如有警告,仅报告最前置的检查项目。

类型检查支持的类型包括 StringNumberBooleanFunctionObjectArray

如果需要设置验证规格,props 属性的写法必须要要写成 Object 形式,不能写成 Array 形式。

<script>
  // 子组件
  export default {
    props: {
      prop1: Number,  // 仅类型检查
      prop2Object: {
        type: String,  // 类型检查
        required: true,  // 必填项检查
        validator: function (value) {  //函数检查
          // 这个值必须匹配下列字符串中的一个
          return ['success', 'warning', 'danger'].indexOf(value) !== -1
        }
      }
    },
    onInit() {
      console.info(`外部传递的数据:`, this.prop1, this.prop2Object);
    }
  }
</script>

组件之间的事件触发

当子组件对数据进行改造后,把最终数据交给父组件甚至往上,往往有两种办法

  • 父组件传递的数据本身就是对象,子组件直接修改的就是这个对象中的属性;那么父组件同样也就拿到了最终数据
  • 子组件在data中保存了一份内部数据,需要交给父组件:子组件通过$dispatch()完成事件触发,父组件通过$on()绑定事件并响应,如:evtType2;
  • 子组件在data中保存了一份内部数据,需要交给父组件:子组件通过$emit()触发在节点上绑定的事件来执行父组件的方法,如:evtType3;

示例如下:

<script>
  // 子组件
  export default {
    props: [
      'prop1',
      'prop2Object'
    ],
    data () {
      return {
        upperProp1: this.prop1
      }
    },
    onInit () {
      console.info(`外部传递的数据:`, this.prop1, this.prop2Object)
      // 绑定VM的自定义事件
      this.$on('evtType1', this.evtTypeHandler)
      this.$on('evtType2', this.evtTypeHandler)
    },
    evtTypeHandler (evt) {
      console.info(`子组件:事件响应: `, evt.type, evt.detail)
      // 结束事件传递
      // evt.stop()
    },
    evtType2Emit () {
      this.$dispatch('evtType2', { params: '额外参数' })
    },
    evtType3Emit () {
      this.$emit('evtType3', { params: '额外参数' })
    }
  }
</script>

所以,框架向开发者提供了双向的事件传递

  • 向下传递:父组件触发,子组件响应;调用parentVm.$broadcast()完成向下传递,如:evtType1
  • 向上传递:子组件触发,父组件响应;调用childVm.$dispath()完成向上传递,如:evtType2

提示:

  • 触发时传递参数,再接收时使用evt.detail来获取参数
  • 当传递结束后,可以调用evt.stop()来结束传递

兄弟组件之间的通信

传统的兄弟等非父子组件之间通信,是通过Publish/Subscribe模型来完成。

开发者如果想要使用这样的能力,当然可以自己写一个Pub/Sub模型实现通信解耦;当然在业务逻辑相对简单的情况下,也可以使用ViewModel本身的事件绑定来处理:$on()$emit()

示例如下:

子组件定义了Sub端的逻辑处理,有processMessage()customEventInVm2(),后者同使用$on效果一致

<template>
  <div class="tutorial-page">
    <text class="tutorial-title">自定义组件2:</text>
    <text>处理消息:{{msg}}</text>
    <text>事件内容:{{eventDetail}}</text>
  </div>
</template>

<style lang="less">
</style>

<script>
  // 子组件: part2
  export default {
    props: [],
    data () {
      return {
        msg: null,
        eventDetail: null
      }
    },
    processMessage (msg) {
      const now = (new Date).toISOString()
      this.msg = `${now}: ${msg}`
    },
    /**
     * 通过events对象:绑定事件
     */
    events: {
      customEventInVm2 (evt) {
        const now = (new Date).toISOString()
        this.eventDetail = `${now}: ${evt.detail}`
      }
    }
  }
</script>

另外一个兄弟组件可以通过父组件中建立相互引用达到相互持有ViewModel的目的,通过在生命周期onReady()执行establishRef()实现,如下代码所示:

<import name="comp-part2" src="./part2"></import>
<import name="comp-part3" src="./part3"></import>

<template>
  <div class="tutorial-page">
    <!-- 兄弟VM通信 -->
    <comp-part2 id="sibling1"></comp-part2>
    <comp-part3 id="sibling2"></comp-part3>
  </div>
</template>

<style lang="less">
</style>

<script>
  // 父组件
  export default {
    onReady () {
      this.establishRef()
    },
    /**
     * 建立相互VM的引用
     */
    establishRef () {
      const siblingVm1 = this.$vm('sibling1')
      const siblingVm2 = this.$vm('sibling2')

      siblingVm1.parentVm = this
      siblingVm1.nextVm = siblingVm2
      siblingVm2.parentVm = this
      siblingVm2.previousVm = siblingVm1
    }
  }
</script>

那么另外一个子组件的Pub端定义就很简单了,执行sendMesssage()即可完成触发,如下代码所示:

<template>
  <div class="tutorial-page">
    <text class="tutorial-title">自定义组件3:</text>
    <text onclick="sendMesssage">点击发送消息</text>
  </div>
</template>

<style lang="less">
</style>

<script>
  // 子组件: part3
  export default {
    sendMesssage () {
      if (this.previousVm) {
        // Way1. 调用方法
        this.previousVm.processMessage('兄弟之间通信的消息内容')
        // Way2. 触发事件
        this.previousVm.$emit('customEventInVm2', '兄弟之间通信的消息内容')
      }
    }
  }
</script>

总结

自定义组件有助于更好的组织代码逻辑,结构更清晰;了解父子组件之间的数据通信,是开发自定义组件的必备技能

条匹配 "" 的结果

    没有搜索到与 "" 相关的内容