小程序目前已经支持组件化开发,可以将页面中的功能模块抽取成自定义组件,以便在不同的页面中重复使用;
也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护

一、常见组件

开发中常见的组件有两种:

  1. 公共组件:将页面的功能模块抽取成自定义组件,以便在不同的页面中重复使用
  2. 页面组件:将复杂的页面拆分成多个低耦合的模块,有助于代码维护

二、创建和注册组件

如果时公共组件,建议放在项目根目录的components文件夹中 如果时页面组件,建议放在对应页面的目录下
建议:一个组件一个文件夹

开发中常见的组件主要分为公共组件和页面组件两种,因此注册组件的方式也分为两种:
1. 全局注册:在app.json 文件中配置 usingComponents 进行注册,注册后可以再任意页面使用
2. 局部注册:在页面的json文件中配置 usingComponents  进行注册,注册后只能在当前页面使用

在 usingComponents 中进行组件注册时,需要提供 自定义组件的组件名和自定义组件文件路径 在将组件注册好以后,直接将
自定义组件的组件名当成组件标签名使用即可。

在这里插入图片描述

三、组件的数据以及方法

组件数据和方法需要在组件 .js 的 Component 方法中进行定义,Component 创建自定义组件

  1. data: 定义组件的内部数据
  2. methods:在组件中事件处理程序需要写到 methods 中才可以

四、组件的属性

Properties 是指组建的对外属性,主要用来接收组件使用者传递给组件内部的数据, 和 data 一同用于组件的模板渲染

// components/custom-checkbox/custom-checkbox.js
Component({
  data:{
    isChecked:false
  },
  // 组件的属性列表:组件的对外属性,主要用来接收使用
  properties:{
    // 如果需要接收传递的属性,有两种方式:全写、简写
    // label:String
    label:{
      // type 组件使用着传递的数据类型
      // 数据类型:String、Number、Boolean、Object、Array
      // 也可以设置为null,表示不限制类型
      type:String,
      value:''
    },
    position:{
      type:String,
      value:'right'
    }
  },
  /**
   * 组件的方法列表:在组件中,所有的事件处理都需要写到 methods 方法中
   */
  methods: {
    // 更新复选框的状态
    updateCheck(){
      console.log(this.properties.label)// 获取 properties 里的属性
      this.setData({
        isChecked:!this.data.isChecked,
        // 更改properties 里的属性,但是一般情况下,不建议修改,因为会造成数据流的混乱
        // 一般建议父子组件修改
        label:'在组件内部更改啦',
      })
    }
  }
})
注意事项:
设置属性类型需要使用 type 属性,属性类型是必填项,value 属性为默认值
属性类型可以为 String 、Number、Boolean、Object、Array ,也可以为null 表示不限制类型 

五、组件wxml的slot

在使用基础组件时,可以在组件中间些子节点,从而子节点的内容展示到页面中,自定义组件也可以接收子节点
只不过在组件模板中需要定义节点,用于承载组件中的子节点

在这里插入图片描述
在这里插入图片描述

默认情况下,一个组件的wxml 中 只能有一个slot(默认插槽)。 需要使用多slot时,可以在组件 js 中声明启用。

Component({
  options:{
    // 启用多 slot 支持
    multipleSlots:true
  },
})

同时需要给 slot 添加 name 属性来区分不同的 slot (具名插槽) 然后给子节点内容添加 slot 属性,属性值时对应
slot 的 name 名称,从而将内容插入到 对应的 slot中

在这里插入图片描述

六、自定义组件样式以及注意事项

自定义组件拥有自己的 wxss 样式,组件 wxss 文件的样式,默认只对当前组件生效
编写组件样式时,需要注意一下几点:

  1. 在自定义的 wxss 文件中,不允许使用 id 选择器(#a)、属性选择器([a])和标签选择器,请改用class 选择器
  2. 子选择器(.a>.b),只能用于 view 和 子组件,用于其它组件可能会出现样式失效的问题
  3. 继承样式,如 font、color,会从组件外继承到组件内
  4. 全局样式、组件所在页面的样式文件中的样式都对自定义组件无效
  5. 官方不推荐做法:不建议在 全局样式文件 以及 父级页面之间使用标签选择器设置样式,如果在全局样式文件中设置样式,会影响项目中全部的相同组件。
  6. 组件和组件使用者,如果使用了后代选择器,可能会出现非预期情况,请避免

七、自定义组件的样式隔离

默认情况下,自定义组件的样式只受自身 wxss 的影响,但是有时候我们需要组件使用者的样式能够影响到组件
这时候就需要指定特殊的样式隔离选项, styleIsolation, 选择它支持以下取值:

  1. isolated:表示启用样式隔离,在自定义组件内外, 使用 class 指定的样式将不会互相影响(一般情况下的默认值)

  2. apply-shared:表示页面使用者、页面的 wxss 样式能够影响到自定义组件, 但自定义组件的样式不会影响组件使用者、页面的 wxss 样式

  3. shared: 表示页面使用者、页面的 wxss 样式能够影响到自定义组件 自定义组件的样式会影响组件使用者、页面的 wxss 样式
    和其它使用了 apply-share 以及 shared 属性的自定义组件

Component({
  options:{
    styleIsolation:"isolated"
  },
})

示例 修改checkbox样式
在自定义样式中,不能直接修改复选框样式 需要在当前自定义组件 .js 文件中添加以下代码 shared: 修改其它页面的样式、组件使用者的样式、以及其它使用了 share 以及 apply-share 的组件 这时候,不是想要的结果

Component({
  options:{
    styleIsolation:"shared"
  },
})

需求是:只想影响当前组件,可以添加命名空间 在 wxml中 添加class

<view>
  <checkbox class="custom-checkbox"></checkbox>
</view>

注意:
然后在当前自定义组件 wxss 样式文件中重写样式 记得添加权重

// 复选框没有选中时默认的样式
.wx-checkbox-input{
  width: 24rpx !important;
  height: 24rpx !important;
  border-radius: 50% !important;
  border: 1px solid #fda007 !important;
}

八、自定义组件 数据监听器 observers

数据监听器主要用于监听和响应任何属性(properties)和数据(data)的变化,当数据发生变化时就会
触发对应的回调函数,从而方便开发者进行业务逻辑的处理 在组件中如果需要进行数据监听,需要使用 observer 字段

  1. 监听一个数据
 observers:{
  //  key:需要监听的数据  value:就是一个回调函数,默认 形参:最新的数据
  // 监听一个数据
  num:function(newNum){
    console.log(newNum)
  },
}

2.监听多个数据

 observers:{
 // 同时监听多个数据
  'num,count':function(newNum,newCount){
    console.log(newNum,newCount)
  },
 }
  1. 监听属性以及内部数据的变化
 // 支持监听属性以及内部数据的变化
 observers:{
  'obj.name':function(newName){
    console.log(newName)
  },
  'arr[1]':function(newItem){
    console.log(newItem)
  },
 }
  1. 监听对象中所有属性
 observers:{
  // 监听对象中所有属性
  'obj.**':function(newObj){
    console.log(newObj)
  },
}
  1. 监听组件使用者传递的数据
//  用来监听数据或者属性是否发生了变化
 observers:{
  // 监听label
  // 只要组件使用者传递了数据,这时候监听器中就能获取传递的数据
  // 也就是说,监听器立即就执行了
  label:function(newLabel){
    console.log(newLabel)
  }
 },

九、组件通信

组件间的数据通信方案,分别是:
1. 数据绑定:properties
2. 获取组件实例:this.selectComponent()
3. 时间绑定:this.triggerEvent()
4. 获取应用实例:getApp()
5. 页面间通信:EventChannel
6. 时间总线:pubsub-js

1. 父往子传值 properties

父组件如果需要向子组件传递数据,只需要两个步骤:

  1. 在父组件 WXML 中使用 数据绑定 的方式向子组件传递动态数据
  2. 子组件内部使用 properties 接收父组件传递的数据即可
    在父组件中引用子组件
 "usingComponents": {
    "product-list":"/components/productList/productList"
  }

在子组件的 json 中,把自己定义为子组件

"component": true,

在父组件中,子组件的引用处,绑定一个属性,并传递想要给子组件的值

<product-list prData="{{prData}}"><product-list/>

在子组件中的 js 中使用 properties 获取值,这样就可以在子组件中用 this.data.prData 获取到这个值了

Component({
   properties: {
    prData: {
      type: String,
      value: "",
    }
  },
})

2.子往父传值 triggerEvent

子组件如果需要向父组件传递数据,可以通过小程序提供的事件系统实现,可以传递任意数据。

  1. 自定义组件内部使用 triggerEvent 方法发射一个自定义的事件,同时可以携带数据
  2. 自定义组件标签上通过 bind 方法监听 发射的事件,同时绑定事件处理函数,在事件函数中通过 通过事件对象获取传递的数据

在子组件中 child.wxml

<button type="primary" plain bindtap="sendData">传递数据</button>

子组件 child.js 文件 methods 定义绑定的方法

// components/child/child.js
Component({
  /**
   * 组件的初始数据
   */
  data: {
    num:100
  },
  /**
   * 组件的方法列表
   */
  methods: {
    // 将数据传递给父组件
    sendData(){
      // 如果需要使用 triggerEvent 发射自定义事件
      // 第二个参数,时携带的参数
      this.triggerEvent('myEvent',this.data.num)
    }
  }
})

使用子组件的【父组件】中 father.xml


<!-- 需要在自定义组件标签上通过 bind 方法绑定自定义事件,同时绑定事件处理函数 -->
<view>{{ num }}</view>
<child bind:myEvent="getData"></child>

使用子组件的 【父组件】中 father.js

Page({
  data:{
    num:''
  },
  // 可以通过事件对象.detail 获取子组件传递给父组件的数据
  getData(event){
   this.setData({
     num:event.detail
   })
  }
})

3.页面间通信 EventChannel

如果一个页面通过 wx.navigateTo 打开一个新页面,这两个页面间将建立一条数据通道
1.在wxnavigateTo 的success 回调中 通过 EventChannel 对象发射事件
2. 被打开的页面可以通过 this.getOpenerEventChannel() 方法来获得一个 EventChannel 对象,进行监听‘发射事件
3. wx.navigateTo 方法可以定义 events 配置项接收被打开页面发射的事件
在这里插入图片描述

示例:
在使用页面【打开页面】wxml 中:


<button plain type="primary" bindtap="handel">跳转到列表页面</button>

在使用页面【打开页面】js 中:

Page({
  // 点击按钮触发的事件处理函数
  handel(){
    wx.navigateTo({
      url: '/pages/list/list',
      events:{
        // key:被打开页面通过 eventChannel 发射的事件
        // value:回调函数
        // 为事件添加一个监听器,获取到被打开页面传递给当前页面的数据
        currentevent:(res)=>[
          console.log(res)//{age: 10}
        ]
      },
      success(res){
        // 通过 success 回调函数的形参,可以获取 eventChannel 对象
        // eventChannel 对象提供了 emit 方法,可以发射事件,同时携带参数
        res.eventChannel.emit("myevent",{name:'hl'})
      }
    })
  }
})

在被打开页面 【pages/list/list】js页面中

// pages/list/list.js
Page({
  onLoad(options) {
    // 通过 this.getOpenerEventChannel() 可以获取 EventChannel 对象
   const EventChannel= this.getOpenerEventChannel()
  //通过 EventChannel 提供的 on 方法 监听页面发射的自定义事件
   EventChannel.on('myevent',(res)=>{
     console.log(res)//{name: "hl"}
   })
  //  通过 EventChannel 提供的 emit 方法也可以向上一级页面传递数据
  // 需要使用 emit 定义自定义事件,携带需要传递的数据
  EventChannel.emit("currentevent",{age:10})
  },
})

4.事件总线 pubsub-js

随着项目功能的增加、业务逻辑也会变得很复杂,一个页面可能是由多个组件进行构成,并且这些组件之间需要进行数据的传递。这时候如果使用之前学习的组件传值方式进行数据的传递,就会比较麻烦

在这里插入图片描述

事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不用的组件之间进行彼此通信,常用于两个非父子关系组件和兄弟组件之间通讯。我们可以借助第三方的
发布订阅 JS 包,来实现事件总线的功能, PubSubJS

示例:
1.准备两个公共组件 custom06和custom08
2.新建个父组件 cart
3.在父组件中【cart.wxml】和【cart.wxss】中
4.安装 PubSubJS js包

cart.wxml

<view class="parent">
  <view class="title">父组件,子组件 a 和 子组件 b 是兄弟关系</view>
  <view class="container">
    <custom06 />
    <view class="line"></view>
    <custom08 />
  </view>
</view>

cart.wxss

.parent{
  background: lightcyan;
  height: 500rpx;
}
.line{
  margin: 20rpx 0;
}

custom06.wxml

<!--components/custom06/custom06.wxml-->
<view class="a-class">
子组件 a
  <button class="btn">传递数据给兄弟</button>
</view>

custom08.wxml

<view class="b-class">
子组件b
</view>

4.需要两个兄弟组件传递数据 也就是组件 a 传递给组件 b

4.1 需要给 custom06.wxml 添加点击事件

<!--components/custom06/custom06.wxml-->
<view class="a-class">
子组件 a
  <button class="btn" bind:tap="sendData">传递数据给兄弟</button>
</view>

4.2 引用事件总线 修改custom06.js 文件

import PubSub from 'pubsub-js'
Component({
  data:{
    name:'tom'
  },
  methods:{
    sendData(){
      // publish 发布、发射自定义事件
      // 1.自定义事件的名称
      // 2.需要传递的数据
      PubSub.publish('myevent',this.data.name)
    }
  }
})

4.3 兄弟组件 custom08.js 接收数据

subscribe 订阅、监听自定义的事件
1.需要订阅、监听的自定义事件名称
2.回调函数,回调函数有两个名称:
msg:自定义事件的名称
data: 传递过来的数据

import PubSub from 'pubsub-js'
Component({
  lifetimes:{
    attached(){
      PubSub.subscribe("myevent",(msg,data)=>{
        console.log(msg,data) // myevent tom
      })
    }
  }
})

十、获取组件实例 selectComponent()

父组件可以通过 【this.selectComponent】 方法,获取子组件实例对象,这样就可以直接访问子组件的任意数据和方法
【this.selectComponent】方法在调用时需要传入一个匹配选择器 selector 使用组件【父组件】father.wxml

<child class="childclass"></child>

<button bindtap="getChild">获取子组件实例对象</button>

使用组件【父组件】father.js

Page({
  // 获取子组件的实例对象
  // 获取到实例对象以后,就能获取子组件所有的数据,也能调用子组件的方法
  getChild(){
    console.log(this.selectComponent('.childclass'))
  }
})

十一、自定义组件 组件的生命周期

组件的生命周期:指的是组件自身的一些钩子函数,这些函数在特定的时间节点时被自动触发
组件的生命周期函数需要在 lifetimes
字段内进行声明 组建的生命周期函数有5个:created、attached、ready、moved、detached

在这里插入图片描述

Component({
  lifetimes:{
    // 组件实例被创建好以后执行
    // 在created钩子函数中不能调用 setData
    // 可以给组件添加一些自定义属性,可以通过 this 的方式进行添加
    created(){
      this.test="测试"
      console.log("created")
    },
    // 组件被初始化完毕,模板解析完成,已经把组件挂载到页面上
    // 一般页面中的交互会在 attached 钩子函数中进行实现
    attached(){
      console.log(this.test)
      console.log("attached")
    },
    // 组件被销毁时
    detached(){
      console.log("detached")
    }
  }
})

十二、自定义组件 组件所在页面的生命周期

组件还有一些特殊的生命周期,这类生命周期和组件没有很强的关联
主要用于组件内部监听父组件的展示、隐藏状态、从而方便组件内部执行以下业务逻辑的处理
组件所在页面的生命周期有4个:show、hide、resize、routeDone,需要在 pageLifetimes 字段内进行声明

在这里插入图片描述

Component({
  // 组件所在页面的生命周期
  pageLifetimes:{
    // 监听组件所在页面展示(后台切前台)状态
    show(){
      console.log('组件所在页面被展示')
    },
     // 监听组件所在页面隐藏(前台切后台、点击 tabBar等)状态
    hide(){
      console.log('组件所在页面被隐藏')
    },
  }
})

十三、自定义组件-拓展-使用 Component 构造函数

Component 方法用于创建自定义组件 小程序的页面也可以视为自定义组件,因此页面也可以使用Component
方法进行创建,从而实现复杂的页面逻辑开发 小程序页面也可以使用 Component 方法进行构造 注意事项:
1. 【 .json】 文件中 必须包含 usingComponents 字段
2. 立马的配置项需要和 Component 中的配置项保持一致
3. 页面中 Page 方法 有一些钩子函数、事件监听方法,这些钩子函数和事件监听必须放在methods 对象中
4. 组件的属性 properties 也可以接受页面的参数,在 onLoad 钩子函数中 可以通过 this.data 进行获取

为什么需要使用 Component 方法进行构造页面

Component 方法功能比 Page 方法强大很多 如果使用Component 方法构造页面,可以实现更加复杂的页面逻辑开发

十四、自定义组件 -拓展-组件复用机制 behaviors

小程序的 behaviors 方法是一种代码复用的方式,可以将一些通用的逻辑和方法提取出来,然后在多个组件中复用,从而减少代码冗余,提高代码的可维护性。
如果需要 behaviors 复用代码,需要使用 Behavior() 方法,每个 behavior 可以包含一组属性、数据、生命周期和方法
组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用
自定义组件文件夹内新建个文件 把公共方法放入到js文件内

const behavior=Behavior({
  /**
   * 组件的属性列表
   */
  properties: {
    label:{
      type:String,
      value:'我已同意该协议'
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    name:'Tome',
    obj:{
      name:"Tyke"
    }
  },

  /**
   * 组件的方法列表
   */
  methods: {
    updateName(){
      this.setData({
        name:'Jerry'
      })
    }
  },
  lifetimes:{
    attached(){
      console.log('我是属性的生命周期方法')
    }
  }
})
export default behavior

当前自定义组件的js里 引入 公共方法 behavior

import behavior from './behaviors'
Component({
  // 在以后开发中,使用 behaviors 进行代码复用的时候
  // 组件和behaviors 可能存在相同的字段
  // 如果存在相同的 properties ,就近原则,使用组件内部的数据
  
  // 如果存在相同的 data
  // 如果是对象类型的数据,会进行合并
  // 如果不是对象类型的数据,就近原则,展示的以组件内部为准
  behaviors:[behavior]
})

注意:

  1. 如果存在相同的 properties ,就近原则,使用组件内部的数据
  2. 如果存在相同的 data,如果是对象类型的数据,会进行合并, 如果不是对象类型的数据,就近原则,展示的以组件内部为准
  3. 如果存在相同的方法,就近原则,使用组件内部的数据
  4. 如果存在相同的生命周期函数,生命周期函数都会被触发

十五、自定义组件的 外部样式类

默认情况下,组件和组件使用者之间如果存在相同的类名不会相互影响,组件使用者如果想修改组件的样式,需要解除样式隔离,
但是解除样式隔离以后,在极端情况下,会产生样式冲突、CSS 嵌套太深等问题,从而给我们的开发带来一定的麻烦
外部样式类:在使用组件时,组件使用者可以给组件传入 CSS 类名,通过传入的类名修改组件的样式。
如果需要使用外部样式类修改组件的样式,在 Component 中需要 externalClasses 定义若干个外部样式类。

在父组件【使用组件】wxml 中

<!-- 属性是在 externalClasses 里面定义的元素 -->
<!-- 属性值必须是一个类名 -->
<custom08 extend-class="my-class"></custom08>

在父组件【使用组件】wxss 样式文件中

.my-class{
  color:red
}

在【子组件】wxml 中

<view class="extend-class">通过外部样式类修改组件的样式</view>

在【子组件】js 中

Component({
  // 组件接受的外部样式类
  externalClasses:['extend-class'],
})

注意

  1. 在同一个节点上,如果存在外部样式类 和普通的样式类
  2. 两个类的优先级是未定义的
  3. 如果 styleIsolation 属性值是 shared 这时候 externalClasses 选项会失效 建议:在使用外部样式类的时候,样式需要通过【 !important】添加权重

十六、总结自定义组件

  1. 组件基本使用:数据、属性、方法、插槽
  2. 组件样式使用:组件样式、注意事项、样式隔离、外部样式类
  3. 组件通信传值:父往子传值、子往父传值、获取组件实例
  4. 组件生命周期:组件的生命周期、组件所在页面的生命周期、总结了小程序全部的生命周期
  5. 组件数据监听器:observers
  6. 组件拓展:使用Component构建页面、组件复用机制 behaviors 等
Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐