51

一、前言

如果小程序中有可复用的UI且具有一定的功能性,就可以使用自定义组件将其封装起来。(如果仅仅只需要复用UI可使用template)

下面介绍父子组件的数据传递方法,以及一个简单的组件和一个复杂的组件示例。

【由于刚开始写这篇文章的时候我还算是一个小程序的新手,自己看着官方文档研究并整理归纳的,有很多不足以及错误的地方。在经过一年的沉淀以后(虽然这一年我主要在写vue而不是小程序),我决定重新整理这篇浏览量比较大的文章,以免新手因我的文章走了弯路。】

二、父子组件传递数据的方法

1.父组件向子组件传递数据

parent.wxml

<my-component name="{{name}}" age="{{age}}"></my-component>

parent.js

data: {
  name: 'Peggy',
  age: 25
}

child.js

properties: {
  name: {
    type: String,
    value: '小明'
  },
  age: Number
}

父组件向子组件传值以属性的形式,子组件以properties接收,并可指定数据类型type以及默认值value。
在wxml里可直接以{{name}}的形式使用,而在js中以this.properties.name获取。

2.子组件向父组件传值

child.js

methods: {
  changeName() {
    this.triggerEvent('changeName', {
      name: '李四'
    })
  }
}

parent.wxml

<my-component name="{{name}}" age="{{age}}" bindchangeName="changeName"></my-component>

parent.js

changeName(event) {
  console.log(event.detail)

  // { name: '李四' }
}

子组件向父组件传递数据使用this.triggerEvent方法,这个方法接受3个参数:
this.triggerEvent('myevent', myEventDetail, myEventOption);

myevent为方法名,
myEventDetail是传到组件外的数据,
myEventOption为是否冒泡的选项,有三个参数可以设置:

bubbles    默认false 事件是否冒泡
composed 默认false 事件是否可以穿越组件边界
capturePhase 默认false 事件是否拥有捕获阶段

在父组件监听事件bindchangeName="changeName",在changeName方法里有一个event参数,可以从event.detail里拿到组件内部传出来的值。

三、简单的组件(计数器)

图片描述

1. 组件功能介绍

这个组件常见于外卖软件中,用于记录想要购买的商品的数量。初始化的时候只有一个加号,点击加号以后出现数字和减号,并最后将数字传到组件外供外部使用。

2. 创建组件

首先在根目录创建components文件夹(推荐),然后创建num-controller文件夹(我取的组件名字),在这个文件夹上点击右键新建一个component,名字为index。

图片描述

/components/num-controller/index.wxml

<view class="num-controller">
  <image src="images/minus.png" class="{{num <= min ?'hide': ''}}" bindtap="sub"></image>
  <text class="num">{{num}}</text>
  <image src="images/plus.png" bindtap="add"></image>
</view>

这段代码就是加减两个按钮和一个数字。

/components/num-controller/index.json

{
  "component": true,
  "usingComponents": {}
}

这个文件在创建component的时候会自动写入这段代码。

/components/num-controller/index.js

Component({
  /**
   * 组件的属性列表
   */
  properties: {
    nameId: {
      type: String
    },
    num: {
      type: Number,
      value: 0
    },
    int: {
      type: Number,
      value: 1
    },
    min: {
      type: Number,
      value: 0
    }
  },

  /**
   * 组件的初始数据
   */
  data: {

  },

  /**
   * 组件的方法列表
   */
  methods: {
    numChange() {
      this.triggerEvent('numChange', {
        num: this.properties.num,
        nameId: this.properties.nameId
      })
    },
    add() {
      this.setData({
        num: this.properties.num + this.properties.int
      })
      this.numChange()
    },
    sub() {
      this.setData({
        num: this.properties.num - this.properties.int
      })
      this.numChange()
    }
  }
})

在组件内部我定义了4个属性,properties是父组件传给子组件的属性。
nameId用来标识子组件的唯一性,如果在父组件内有多个计数器,子组件想把改变的数据传给父组件时可以用到;
num代表计数器中的数字,默认为0;
int代表加减一次改变多少,默认为1;
min代表计数器的最小值,等于这个值时减号会消失,默认为0。

同时在子组件内定义了两个方法:
add点击加号触发,首先改变子组件内部的数字,同时触发numChange方法将改变的数字传到组件外部
sub点击减号触发,首先改变子组件内部的数字,同时触发numChange方法将改变的数字传到组件外部

3. 引入组件

假如我要在index.wxml里引入组件:

<num-controller nameId="num1" num="{{num1}}" bindnumChange="numChange"></num-controller>
<num-controller nameId="num2" num="{{num2}}" int="{{5}}" min="{{5}}" bindnumChange="numChange"></num-controller>
<num-controller nameId="num3" num="{{num3}}" int="{{100}}" bindnumChange="numChange"></num-controller>

index.json

{
  "usingComponents": {
    "num-controller": "/components/num-controller/num-controller"
  }
}

想在页面中使用组件必须在json文件里注册组件。

index.js

Page({
  data: {
    num1: 0,
    num2: 10,
    num3: 100
  },
  numChange(event) {
    const {num, nameId} = event.detail
    this.setData({
      [nameId]: num
    })
  }
})

data里的num1, num2, num3是从组件外传入的num,在numChange方法里用event.detail可以拿到组件内部通过this.triggerEvent传出来的数据,然后根据业务需求做逻辑修改。

四、复杂的组件(筛选面板)

图片描述

这是一个二级菜单,点击左边(一级)会改变右边(二级)的展示。

1. 创建组件并引入

组件内部:
/components/filter-panel/index.wxml

<view class="filter-panel">
  <view class="panel-container">
    <view class="panel-left">
      ...
    </view>

    <view class="panel-right">
      ...
    </view>
  </view>
</view>

/components/filter-panel/index.json

{
  "component": true,
  "usingComponents": {}
}

组件外部:
假如我要在index.wxml里引入组件:

<filter-panel></filter-panel>

index.json

{
  "usingComponents": {
    "filter-panel": "/components/filter-panel/index"
  }
}

这样就成功引入组件啦~(说真的组件化做好了非常舒服,后期会省很多力气)

2.组件与外部的数据传递

(1) 固定数据渲染

组件外部:

index.wxml

<filter-panel list="{{list}}" active="{{active}}"></filter-panel>

index.js

data: {
  list: [
    ['附近', '地铁站'],
    [['不限', '1km', '2km', '3km'], ['江汉路', '积玉桥', '洪山广场', '楚河汉街', '光谷广场']]
  ],
  active: [0, 0]
},

组件内部:

/components/filter-panel/index.js

Component({
  /**
   * 组件的属性列表
   */
  properties: {
    list: Array,
    active: Array
  },
  /**
   * 组件的方法列表
   */
  methods: {
    ...
  }
})

如果想从组件外向组件内传递数据,直接在外部引用时以属性的方式传入。
这里我传入了2个属性:
list是二级选择面板渲染的数据。
active是用户选择的选项数据。

到这里组件已经可以正常展示了,但是点击显示选中项还未实现。

(2) 可变数据渲染

控制组件active项的是外部的数据active: [0, 0],通过组件以属性的形式传到了内部。

/components/filter-panel/index.wxml

<view class="filter-panel">
  <view class="panel-container">
    <view class="panel-left">
      <ul>
        <li 
          class="{{active[0] == index? 'active': ''}}" 
          wx:for="{{list[0]}}" 
          wx:key="{{index}}" 
          wx:index="index" 
          data-index="{{index}}"
          bindtap="changeLevel1"
        >
          {{item}}
        </li>
      </ul>
    </view>

    <view class="panel-right">
      <ul>
        <li 
          class="{{active[1] == index? 'active': ''}}" 
          wx:for="{{list[1][active[0]]}}" 
          wx:key="{{index}}" 
          wx:index="index" 
          data-index="{{index}}"
          bindtap="changeLevel2"
        >
          {{item}}
        </li>
      </ul>
    </view>
  </view>
</view>

/components/filter-panel/index.js

Component({
  /**
   * 组件的属性列表
   */
  properties: {
    list: Array,
    active: Array
  },
  /**
   * 组件的方法列表
   */
  methods: {
    changeLevel() {
      this.triggerEvent('changeLevel', {
        level1: this.properties.active[0],
        level2: this.properties.active[1]
      })
    },
    changeLevel1(event) {
      const index = event.target.dataset.index
      this.setData({
        active: [index, 0]
      })
      this.changeLevel()
    },
    changeLevel2(event) {
      const level2 = 'active[1]'
      const index = event.target.dataset.index
      this.setData({
        [level2]: index
      })
      this.changeLevel()
    }
  }
})

(3) 组件内数据传到外部

在这个组件内我定义了changeLevel这个方法,每次点击一级菜单或二级菜单的时候我就用过this.triggerEvent方法把active的值传到组件外部以供使用。

/components/filter-panel/index.js

methods: {
  changeLevel() {
    this.triggerEvent('changeLevel', {
      level1: this.properties.active[0],
      level2: this.properties.active[1]
    })
  },
  changeLevel1(event) {
    const index = event.target.dataset.index
    this.setData({
      active: [index, 0]
    })
    this.changeLevel()
  },
  changeLevel2(event) {
    const level2 = 'active[1]'
    const index = event.target.dataset.index
    this.setData({
      [level2]: index
    })
    this.changeLevel()
  }
}

五、总结

这个项目里倒是没用用到组件间的数据传递,所以只是组件和外部的传递,还算是比较简单,但是一定要思考清楚数据的变化状态。


Peggy7
677 声望22 粉丝