自定义组件解决什么
一个项目里,商品卡片、列表项、弹窗这些界面元素会在好几个页面重复出现。把它们写成自定义组件,一处改、处处生效,不用每页复制粘贴一遍。这就是自定义组件最直接的价值:复用。
但写组件不是把一段 WXML 抽出来就完了。好的组件要能接收外部参数、能响应内部状态、还能让外部往里面塞自己的内容。properties、observers、插槽,就是解决这三件事的三把钥匙。
项目小的时候,直接在页面里写其实更快。但同一个元素出现在三个页面以上,就值得抽成组件了。别为了抽而抽,抽得太早反而难维护。
写组件还有个好处:组件内部的状态自己管,页面不用记着每一处该怎么更新。比如一个开关组件,点开关合的动画、状态切换都封装在里面,页面只需要问它现在是开还是关。这种「黑盒化」能大幅减少页面里的胶水代码。
组件化的代价是要提前设计好接口。参数叫什么、事件抛什么、插槽留几个,都得想清楚。接口定得好,后面谁用都顺;接口定得含糊,用一次改一次,组件就成了负担。
组件化的目标不是「抽得多」,而是「改一处就够」。
- properties外部往里传参数。
- observers监听属性变化做联动。
- slots让外部往组件里塞内容。
- 通信组件和页面互相传值。
properties:对外传参
组件长得像什么样,由外部通过 properties 传入。定义好属性名、类型和默认值,外部用的时候传不同的值,组件就呈现不同样子。
Component({
properties: {
title: { type: String, value: "默认标题" },
count: { type: Number, value: 0 }
}
});
页面里用的时候直接传:自定义标签上加 title="某个标题"。默认值要给合理的,外部不传也不至于渲染出奇怪的东西。类型也要标清楚,传错了组件内部才好察觉。
observers:监听属性变化
光接收参数还不够。有时候一个属性变了,派生出的别的状态也要跟着变,这时候就用 observers 监听。比如价格从分换算成元,监听 price 一变就重新算展示值。
| 能力 | 说明 |
|---|---|
| properties | 外部传入初始值 |
| observers | 属性变化时联动计算 |
| data | 组件内部自己的状态 |
observers: {
"price": function (n) { this.setData({ yuan: (n / 100).toFixed(2) }); }
}
别在 observers 里死循环改自己监听的那个属性,不然会越转越停不下来。
slots:插槽让组件可扩展
组件内部结构是写死的,但有时候外部想在组件里插一段自己的内容。这就是插槽干的事:组件留个坑,外部往里填。
// 组件 WXML 里留一个坑位
<view class="body"><slot></slot></view>
- 默认插槽:一个坑位用默认。
- 命名插槽:多个坑位各有名字。
- 有了插槽,组件才能既通用又灵活。
没有插槽,组件每要一种新布局就得改组件本身;有了插槽,外部自己往里填内容,组件不用动。
用插槽的时候注意,插槽里放的是外部页面的内容,样式默认按页面的来。如果想让插槽内容和组件内部风格统一,可以给插槽套一层组件自己的样式容器。这样外面塞什么进来,看起来都还是组件该有的样子。
组件内外通信
组件里发生了事情(比如被点了一下),要告诉外面的页面。做法是组件触发一个自定义事件,页面监听这个事件再处理。
this.triggerEvent("tapitem", { id: itemId });
页面这边在组件标签上 bind:tapitem 接收。这样组件只管抛事件,具体怎么处理是页面的事,组件不绑死业务。
除了向上抛事件,组件之间兄弟通信一般别直接互相调用,那样会把耦合搞乱。真要共享数据,放到页面层统一管,兄弟组件通过各自的 properties 拿、通过各自的事件报,页面做个中转就行。这样数据流是单向的,出问题好查。
常见组件坑
| 坑 | 后果 | 应对 |
|---|---|---|
| observers 改回监听属性 | 死循环 | 别回写同一个属性 |
| 组件里写死业务逻辑 | 没法复用 | 抛事件让页面处理 |
| 没给默认值 | 外部不传就崩 | properties 给默认 |
组件实战清单
- 重复三次以上的界面元素才抽组件。
- properties 标好类型和默认值。
- 派生状态用 observers 联动。
- 可变内容用插槽留给外部。
- 组件事件抛出去,业务留页面。
组件写得越通用,后面改起来越省心。