# 组件间关系

有时需要实现这样的一对组件:

<custom-ul>
  <custom-li> item 1 </custom-li>
  <custom-li> item 2 </custom-li>
</custom-ul>

这个例子中,<custom-ul><custom-li> 都是自定义组件,它们有相互间的关系,相互间的通信往往比较复杂。此时,可以使用 relations 让它们之间能相互通信。

使用 relations 时,相关联的两个组件要将对方加入 relations 中。(两个组件都需要将对方加入 relations 中,否则不会生效。)

# Definition 形式的 relations

以上述关系为例。对于 <custom-ul> 组件:

export default Component({
  relations: {
    './custom-li': { // 关联到的组件的相对路径
      type: 'child', // 关联的目标节点应为子节点
      linked: function(target) {
        // 每次有 custom-li 被插入时执行
        // target 是该 custom-li 节点实例对象,触发在该节点 attached 生命周期之后
      },
      linkChanged: function(target) {
        // 每次有 custom-li 被移动后执行
        // target 是该 custom-li 节点实例对象,触发在该节点 moved 生命周期之后
      },
      unlinked: function(target) {
        // 每次有 custom-li 被移除时执行
        // target 是该 custom-li 节点实例对象,触发在该节点 detached 生命周期之后
      },
    },
  },
  lifetimes: {
    attached() {
      // 使用 getRelationNodes 可以获得 nodes 数组,包含所有已关联的 custom-li(按节点树顺序排列)
      var nodes = this.getRelationNodes('./custom-li')
    },
  },
})

对于 custom-li 组件:

export default Component({
  relations: {
    './custom-ul': { // 关联到的组件的相对路径
      type: 'parent', // 关联的目标节点应为父节点
      linked: function(target) {
        // 每次被插入到 custom-ul 时执行
        // target 是 custom-ul 节点实例对象,触发在 attached 生命周期之后
      },
      linkChanged: function(target) {
        // 每次被移动后执行
        // target 是 custom-ul 节点实例对象,触发在 moved 生命周期之后
      },
      unlinked: function(target) {
        // 每次被移除时执行
        // target 是 custom-ul 节点实例对象,触发在 detached 生命周期之后
      },
    },
  },
})

# Chaining 形式的 relations 和 trait behaviors

在 Chaining 形式使用时,relations 定义在 init 中。虽然也可以直接指定 relations 的目标组件,但更推荐使用 trait behaviors 来进行逻辑解耦。

trait behaviors 的组件框架支持情况请参考其文档。

首先,在其中一个组件上实现一个 trait behavior。例如,<custom-ul><custom-li> 两个组件,可以在 <custom-li> 组件中导出一些接口给 <custom-ul> 使用,那么,在 <custom-li> 上:

// common.ts

// 定义一些导出的接口
interface IListItem {
  getId(): string
}

// 包装成 trait behavior
export const listItem = Behavior.trait<IListItem>()
// custom-li.ts
import { listItem } from './common'

export default Component()
  .data(() => ({
    id: '',
  }))
  .init(function ({ data, implement, relation }) {
    // 定义关联关系
    relation({
      type: 'parent',
      target: './custom-ul',
      linked: function(parent) {
        // 每次被插入到 custom-ul 时执行
      },
      linkChanged: function(parent) {
        // 每次被移动后执行
      },
      unlinked: function(parent) {
        // 每次被移除时执行
      },
    })

    // 实现接口
    implement(listItem, {
      getId() {
        return data.id
      },
    })
  })
  .register()

<custom-ul> 中,定义关系时不要直接指定 target 为目标组件,而是用 listItem 代替:

import { listItem } from './common'

export default Component()
  .init(function ({ relation, lifetime }) {
    // 定义关联关系
    // relation 应在 init 函数返回前调用
    // 不应该写在生命周期函数、事件回调函数、其他方法内部
    const liRelation = relation({
      type: 'child',
      target: listItem, // 使用 trait behavior
      linked(child) {
        // 每次有 custom-li 被插入时执行
        // 此时可以访问 trait behavior 中的接口
        const li = child.traitBehavior(listItem)!
        li.getId()
      },
      linkChanged: function(child) {
        // 每次有 custom-li 被移动后执行
      },
      unlinked: function(child) {
        // 每次有 custom-li 被移除时执行
      },
    })

    lifetime('attached', () => {
      wx.nextTick(() => {
        // listAsTrait 方法可以列出来所有子节点所实现的 trait behavior
        for (let li of liRelation.listAsTrait()) {
          li.getId()
        }
      })
    })
  })
  .register()

# relations 定义选项

每个关联定义包含目标组件路径及其对应选项,可包含的选项见下表。

选项 类型 是否必填 描述
type String 目标组件的相对关系,对于父子关系应分别为 parentchild,对于祖先-后代关系,应分别为 ancestordescendant
linked Function 关系生命周期函数,当关系被建立时触发,触发时机在组件 attached 生命周期之后
linkChanged Function 关系生命周期函数,当关系发生改变时触发,触发时机在组件 moved 生命周期之后
unlinked Function 关系生命周期函数,当关系被销毁时触发,触发时机在组件 detached 生命周期之后
target String 如果这一项被设置,则它表示关联的目标节点所应具有的 trait behavior 或 behavior,所有满足要求的组件节点都会被关联