# 自定义组件

> 官方文档：https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/

基础库 1.6.3+ 支持。

## 创建自定义组件

一个自定义组件由 `json` `wxml` `wxss` `js` 四个文件组成。

```json
// my-component.json — 声明为组件
{ "component": true }
```

```xml
<!-- my-component.wxml -->
<view class="inner">
  {{innerText}}
  <slot></slot>
</view>
```

```css
/* my-component.wxss — 样式只作用于本组件 */
.inner { color: red; }
```

```javascript
// my-component.js
Component({
  properties: {
    innerText: {
      type: String,
      value: 'default value'
    }
  },
  data: {
    someData: {}
  },
  methods: {
    customMethod() {}
  }
})
```

### 使用自定义组件

```json
// 页面或组件的 .json
{
  "usingComponents": {
    "my-component": "/components/my-component/my-component"
  }
}
```

```xml
<!-- 页面 wxml -->
<my-component inner-text="Some text">
  <view>这里是插入到 slot 中的内容</view>
</my-component>
```

**注意事项**：
- 标签名只能是小写字母、中划线、下划线的组合
- 组件和页面所在项目根目录名不能以 "wx-" 为前缀
- 使用 `usingComponents` 会使页面的 `this` 原型稍有差异（多了 `selectComponent` 等方法）
- 使用 `usingComponents` 时，`setData` 内容不会被深复制（性能优化）

## Component() 构造器

```javascript
Component({
  // ===== 组件属性（外部传入） =====
  properties: {
    myProperty: {
      type: String,       // 类型：String, Number, Boolean, Object, Array, null(任意)
      value: '',          // 默认值
      observer(newVal, oldVal) {
        // 属性变化时触发（已不推荐，建议用 observers）
      }
    },
    myProperty2: String   // 简化定义
  },

  // ===== 组件内部数据 =====
  data: {
    someData: 'initial'
  },

  // ===== 生命周期（推荐写在 lifetimes 中） =====
  lifetimes: {
    created() {
      // 组件实例刚被创建时
      // 此时不能调用 setData，通常用于给 this 添加自定义属性
    },
    attached() {
      // 组件实例进入页面节点树时
      // 大多数初始化工作在此进行
    },
    ready() {
      // 组件在视图层布局完成后
    },
    moved() {
      // 组件实例被移动到节点树另一个位置时
    },
    detached() {
      // 组件实例被从页面节点树移除时
      // 清理工作（如清除定时器）
    },
    error(err) {
      // 组件方法抛出错误时（基础库 2.4.1+）
    }
  },

  // ===== 组件所在页面的生命周期 =====
  pageLifetimes: {
    show() {
      // 页面被展示时
    },
    hide() {
      // 页面被隐藏时
    },
    resize(size) {
      // 页面尺寸变化时
    },
    routeDone() {
      // 页面路由动画完成时（基础库 2.31.2+）
    }
  },

  // ===== 数据监听器（基础库 2.6.1+） =====
  observers: {
    'numberA, numberB'(numberA, numberB) {
      // numberA 或 numberB 变化时触发
      this.setData({ sum: numberA + numberB })
    },
    'some.subfield'(subfield) {
      // 监听子数据字段
    },
    'arr[12]'(val) {
      // 监听数组某一项
    },
    'some.field.**'(field) {
      // 使用通配符监听所有子数据字段
    },
    '**'() {
      // 监听所有 setData（每次 setData 都触发，慎用）
    }
  },

  // ===== 方法 =====
  methods: {
    onMyButtonTap() {
      this.setData({ someData: 'new value' })
    },
    // 内部方法建议以下划线开头
    _myPrivateMethod() {
      this.setData({ 'A.B': 'myPrivateData' })
    }
  },

  // ===== behaviors =====
  behaviors: [],

  // ===== 其他选项 =====
  options: {
    multipleSlots: true,           // 启用多 slot（默认只能一个）
    styleIsolation: 'isolated',    // 样式隔离模式
    pureDataPattern: /^_/,         // 纯数据字段正则
    virtualHost: true              // 虚拟化组件节点（基础库 2.11.2+）
  },

  // ===== 外部样式类 =====
  externalClasses: ['my-class'],

  // ===== 组件间关系 =====
  relations: {},

  // ===== 导出（配合 wx://component-export） =====
  export() {
    return { myField: 'myValue' }
  }
})
```

## 组件模板和样式

### 多 slot

```javascript
// 组件 js
Component({
  options: { multipleSlots: true }
})
```

```xml
<!-- 组件 wxml -->
<view>
  <slot name="before"></slot>
  <view>组件内部内容</view>
  <slot name="after"></slot>
</view>

<!-- 使用 -->
<my-component>
  <view slot="before">before 内容</view>
  <view slot="after">after 内容</view>
</my-component>
```

### 样式隔离 styleIsolation

```javascript
Component({
  options: {
    styleIsolation: 'isolated'
    // 'isolated'（默认）：组件样式完全隔离
    // 'apply-shared'：页面 wxss 样式会影响组件，但组件不影响页面
    // 'shared'：页面和组件样式互相影响
  }
})
```

也可在 json 中配置：
```json
{ "styleIsolation": "isolated" }
```

### 外部样式类

```javascript
// 组件
Component({
  externalClasses: ['my-class']
})
```

```xml
<!-- 组件 wxml -->
<view class="my-class">这段文本的颜色由外部决定</view>

<!-- 使用时 -->
<my-component my-class="red-text"/>
```

## 组件间通信

### 父 → 子：properties

```xml
<my-component prop-a="{{dataA}}" prop-b="staticValue"/>
```

### 子 → 父：triggerEvent

```javascript
// 子组件
Component({
  methods: {
    onTap() {
      this.triggerEvent('myevent', { value: 'data' }, {
        bubbles: false,     // 是否冒泡
        composed: false,    // 是否穿越组件边界
        capturePhase: false // 是否有捕获阶段
      })
    }
  }
})
```

```xml
<!-- 父组件/页面 -->
<my-component bind:myevent="onMyEvent"/>
<!-- 或 bindmyevent="onMyEvent" -->
```

```javascript
// 父组件/页面
Page({
  onMyEvent(e) {
    e.detail // { value: 'data' }
  }
})
```

### 父获取子实例：selectComponent

```xml
<my-component id="the-id" class="the-class"/>
```

```javascript
const child = this.selectComponent('#the-id')
// 或 this.selectComponent('.the-class')
child.setData({ ... })
child.someMethod()
```

## behaviors（代码复用）

类似 mixins / traits。

```javascript
// my-behavior.js
module.exports = Behavior({
  behaviors: [],        // 可以引用其他 behavior
  properties: {
    myBehaviorProperty: { type: String }
  },
  data: {
    myBehaviorData: {}
  },
  attached() {},
  methods: {
    myBehaviorMethod() {}
  }
})
```

```javascript
// 组件中使用
const myBehavior = require('my-behavior')
Component({
  behaviors: [myBehavior],
  // 组件自身的 properties/data/methods 会与 behavior 合并
  // 同名字段：组件 > behavior > 更早的 behavior
  // 同名生命周期：都会执行（behavior 先于组件）
})
```

### 内置 behaviors

| behavior | 说明 |
|----------|------|
| `wx://form-field` | 使组件像表单控件，form 可识别 |
| `wx://form-field-group` | form 识别组件内部所有表单控件（2.10.2+） |
| `wx://form-field-button` | form 识别组件内部 button（2.10.3+） |
| `wx://component-export` | 自定义 selectComponent 返回值（2.2.3+） |

## 纯数据字段

不用于渲染的数据，不会参与 setData 传输，提升性能。

```javascript
Component({
  options: {
    pureDataPattern: /^_/ // 以 _ 开头的字段为纯数据
  },
  data: {
    a: true,    // 普通数据，参与渲染
    _b: true    // 纯数据，不参与渲染
  }
})
```

## 组件间关系 relations

```javascript
// custom-ul
Component({
  relations: {
    './custom-li': {
      type: 'child',
      linked(target) {},    // 子组件 attached 时
      linkChanged(target) {},
      unlinked(target) {}   // 子组件 detached 时
    }
  }
})

// custom-li
Component({
  relations: {
    './custom-ul': {
      type: 'parent',
      linked(target) {},
      linkChanged(target) {},
      unlinked(target) {}
    }
  }
})
```

**type 可选值**：`parent` / `child` / `ancestor` / `descendant`

## 抽象节点 componentGenerics

```json
// selectable-group.json
{
  "componentGenerics": {
    "selectable": {
      "default": "path/to/default"
    }
  }
}
```

```xml
<!-- selectable-group.wxml -->
<view wx:for="{{labels}}">
  <selectable disabled="{{false}}"></selectable>
</view>

<!-- 使用时指定具体组件 -->
<selectable-group generic:selectable="custom-radio"/>
```

## 用 Component 构造器构造页面

```javascript
Component({
  properties: {
    paramA: Number,  // 接收页面参数 ?paramA=123
    paramB: String
  },
  methods: {
    onLoad() {
      this.data.paramA // 123
    },
    onShow() {},
    onPullDownRefresh() {}
    // 页面生命周期写在 methods 中
  }
})
```

对应 json 需包含 `usingComponents`：
```json
{ "usingComponents": {} }
```

好处：可以使用 behaviors 提取所有页面公用代码。

## 在线查询

如需更详细信息，可抓取：
- Component 参考：https://developers.weixin.qq.com/miniprogram/dev/reference/api/Component.html
- Behavior 参考：https://developers.weixin.qq.com/miniprogram/dev/reference/api/Behavior.html
- 组件生命周期：https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html
