目录

2024-12-24-微信小程序-----自定义组件

微信小程序 — 自定义组件

自定义组件

1. 创建-注册-使用组件

组件介绍

小程序目前已经支持组件化开发,可以将页面中的功能模块抽取成自定义组件,以便在不同的页面中重复使用;

也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。

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

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

如果是公共组件,建议将其放在小程序的目录下的 components 文件夹中

如果是页面组件,建议将其放在小程序对应页面目录下,当然你也可以放到页面的 components 文件夹中

同时建议: 一个组件一个文件夹,文件夹名称和组件名称保持一致

📌 注意事项

  1. 自定义组件的需要在 json 文件中需要配置 component 字段设为 true
  2. 自定义组件通过 Component 构造器进行构建,在构造器中可以指定组件的属性、数据、方法等

创建自定义组件:

创建组件的步骤很简单,以公共组件为例,创建的步骤如下:

  1. 在小程序的目录下新建 components 文件夹
  2. components 文件夹上,点击右键,选择 新建文件夹 ,然后输入文件夹名称,我们建议文件夹的名称和组件的名称保持一致,这样方便后期对组件进行维护。我们这里新的的组件名称叫做: custom-checkbox
  3. 在新建的组件文件夹上,点击右键,选择 新建 Component ,然后输入组件的名称,组件的名称建议和文件夹保持一致
  4. 此时就已经创建了一个功能组件

https://i-blog.csdnimg.cn/blog_migrate/ae713dbcdc7f3253a92d42dfbbdfdb1f.png

使用自定义组件

开发中常见的组件主要分为 公共组件 和 页面组件 两种,因此注册组件的方式也分为两种:

  1. 全局注册:在 app.json 文件中配置 usingComponents 节点进行引用声明,注册后可在任意组件使用
  2. 局部注册:在页面的 json 文件中配置 usingComponents 节点进行引用声明,只可在当前页面使用

https://i-blog.csdnimg.cn/blog_migrate/1cb167196c3eee9b633835adbb33b701.png

在配置 usingComponents 节点进行引用声明时,需要提供自定义组件的标签名和对应的自定义组件文件路径,语法如下:

{
  "usingComponents": {
    "自定义组件的标签名": "自定义组件文件路径"
  }
}

这样,在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。

{
  "usingComponents": {
    "custom-checkbox": "/components/custom-checkbox/custom-checkbox"
  }
}
<!--pages/index/index.wxml-->

<view>
  <!-- 将导入的自定义组件当成标签使用 -->
  <custom-checkbox
                   />
</view>

2. 自定义组件-数据和方法

在组件的 .js 中,需要调用 Component 方法创建自定义组件, Component 中有以下两个属性:

data 数据:组件的内部数据

methods 方法:在组件中事件处理程序需要写到 methods 中才可以

落地代码:

➡️ components/custom-checkbox/custom-checkbox.wxml

<!--components/custom-checkbox/custom-checkbox.wxml-->
<!-- <text>我是自定义组件</text> -->

<view class="custom-checkbox-container">
  <view class="custom-checkbox-box">
    <checkbox checked="{{ isChecked }}" bindtap="updateChecked" />
  </view>
</view>

➡️ components/custom-checkbox/custom-checkbox.wxss

/* components/custom-checkbox/custom-checkbox.wxss */

.custom-checkbox-container {
  display: inline-block;
}

➡️ components/custom-checkbox/custom-checkbox.js

Component({

  /**
   * 组件的初始数据:用来定义当前组件内部所需要使用的数据
   */
  data: {
    isChecked: false
  },

  /**
   * 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中
   */
  methods: {
    
    // 更新复选框的状态
    updateChecked () {

      this.setData({
        isChecked: !this.data.isChecked
      })

      console.log(this.data.isChecked)
    }

  }
  
})

➡️ app.json

{
  "usingComponents": {
    "custom-checkbox": "./components/custom-checkbox/custom-checkbox"
  }
}

➡️ index.wxml

<custom-checkbox />

<view class="line"></view>

<custom-checkbox />

3. 自定义组件-属性

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

📌 注意事项:

  1. 设置属性类型需要使用 type 属性,属性类型是必填项,value 属性为默认值
  2. 属性类型可以为 String、Number、Boolean、Object、Array ,也可以为 null 表示不限制类型

https://i-blog.csdnimg.cn/blog_migrate/a29f87811a2720eab196fcb725b29cd2.png

落地代码:

➡️ index.wxml

<!-- label 文本显示的内容 -->
<!-- position 控制文本显示的位置 -->

<custom-checkbox label="我已阅读并同意 用户协议 和 隐私协议" position="right" />

<view class="line"></view>

<custom-checkbox label="匿名提交" position="left" />

➡️ components/custom-checkbox/custom-checkbox.wxml

<!--components/custom-checkbox/custom-checkbox.wxml-->
<!-- <text>我是自定义组件</text> -->

<view class="custom-checkbox-container">
+   <view class="custom-checkbox-box {{ position === 'right' ? 'right' : 'left' }}">
+     <checkbox class="custom-checkbox" checked="{{ isChecked }}" bindtap="updateChecked" />
+ 
+     <view>
+       <text>{{ label }}</text>
+     </view>
  </view>
</view>

➡️ components/custom-checkbox/custom-checkbox.wxss

/* components/custom-checkbox/custom-checkbox.wxss */

.custom-checkbox-container {
  display: inline-block;
}

.custom-checkbox-box {
  display: flex;
  align-items: center;
}

.custom-checkbox-box.left {
  flex-direction: row-reverse;
}

.custom-checkbox-box.right {
  flex-direction: row;
}

.custom-checkbox {
  margin-left: 10rpx;
}

➡️ components/custom-checkbox/custom-checkbox.js

Component({

+   /**
+    * 组件的属性列表:组件的对外属性,主要用来接收组件使用者传递给组件内部的属性以及数据
+    */
+   properties: {
+     // 如果需要接收传递的属性,有两种方式:全写、简写
+     // label: String
+ 
+     label: {
+       // type 组件使用者传递的数据类型
+       // 数据类型:String、Number、Boolean、Object、Array
+       // 也可以设置为 null,表示不限制类型
+       type: String,
+       value: ''
+     },
+ 
+     // 文字显示位置
+     position: {
+       type: String,
+       value: 'right'
+     }
+   },

  /**
   * 组件的初始数据:用来定义当前组件内部所需要使用的数据
   */
  data: {
    isChecked: false
  },

  /**
   * 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中
   */
  methods: {
    
    // 更新复选框的状态
    updateChecked () {

      this.setData({
        isChecked: !this.data.isChecked,
+        // label: '在组件内部也可以修改 properties 中的数据'
      })

+       // 在 JS 中可以访问和获取 properties 中的数据
+       // 但是一般情况下,不建议修改,因为会造成数据流的混乱
+       // console.log(this.properties.label)
      // console.log(this.data.isChecked)
    }

  }
  
})

4. 组件 wxml 的 slot

在使用基础组件时,可以给组件传递子节点传递内容,从而将内容展示到页面中,自定义组件也可以接收子节点内容

只不过在组件模板中需要定义 <slot /> 节点,用于承载组件引用时提供的子节点

https://i-blog.csdnimg.cn/blog_migrate/995d532c55b5de811141ff286240e1e9.png

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

同时需要给 slot 添加 name 来区分不同的 slot,给子节点内容添加 slot 属性来将节点插入到 对应的 slot 中

https://i-blog.csdnimg.cn/blog_migrate/25963f34f1a8cee32bab952d510ea278.png

知识点讲解:

➡️ custom01.html

<view>
  <slot name="slot-top" />
  <!-- slot 就是用来接收、承载子节点内容 -->
  <!-- slot 只是一个占位符,子节点内容会将 slot 进行替换 -->
  <!-- 默认插槽 -->
  <view><slot /></view>

  <slot name="slot-bottom" />
</view>

➡️ custom01.js

// components/custom01/custom01.js

Component({

  options: {
    // 启用多 slot 支持
    multipleSlots: true
  }

})

➡️ cart.wxml

<custom01>
  <text slot="slot-top">我需要显示到顶部</text>
    
  <!-- 默认情况下,自定义组件的子节点内容不会进行展示 -->
  <!-- 如果想内容进行展示,需要再组件模板中定义 slot 节点 -->
  我是子节点内容
    
  <text slot="slot-bottom">我需要显示到低部</text>
</custom01>

完善复选框案例

➡️ custom-checkbox.html

<!--components/custom-checkbox/custom-checkbox.wxml-->
<!-- <text>我是自定义组件</text> -->

<view class="custom-checkbox-container">
  <view class="custom-checkbox-box {{ position === 'right' ? 'right' : 'left' }}">
    <checkbox class="custom-checkbox" checked="{{ isChecked }}" bindtap="updateChecked" />

+     <view>
+       <!-- lable 和 子节点内容都进行了展示 -->
+       <!-- 要么展示 lable 要么展示 子节点内容 -->
+       <!-- 如果用户传递了 lable 属性,就展示 lable -->
+       <!-- 如果用户没有传递 lable 属性,就展示 子节点内容 -->
+       <text wx:if="{{ label !== '' }}">{{ label }}</text>
+ 
+       <slot wx:else />
+     </view>
  </view>
</view>

➡️ index.html

<!-- label 文本显示的内容 -->
<!-- position 控制文本显示的位置 -->
<custom-checkbox label="我已阅读并同意 用户协议 和 隐私协议" position="right">
  我已阅读并同意 用户协议 和 隐私协议 - 111
</custom-checkbox>

<view class="line"></view>

<custom-checkbox label="匿名提交" position="left">
  匿名提交 - 222
</custom-checkbox>

5. 组件样式以及注意事项

选择器使用注意事项:

类似于页面,自定义组件拥有自己的 wxss 样式,组件对应 wxss 文件的样式,只对组件wxml内的节点生效。

编写组件样式时,需要注意以下几点:

  1. app.wxss 或页面的 wxss 中使用了标签名(view)选择器(或一些其他特殊选择器)来直接指定样式

    这些选择器会影响到页面和全部组件,通常情况下这是不推荐的做法

  2. 组件和引用组件的页面不能使用 id 选择器(#a)、属性选择器([a]) 和 标签名选择器,请改用 class 选择器

  3. 组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用

  4. 子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。

  5. 继承样式,如 font 、 color ,会从组件外继承到组件内。

  6. 除继承样式外, 全局中的样式、组件所在页面的的样式对自定义组件无效 (除非更改组件样式隔离选项)

#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */

落地代码:

➡️ custom02.wxml

<text id="content" class="content son">
  <text class="label">给自定义组件设置样式</text>
</text>

➡️ custom02.wxss

/* components/custom02/custom02.wxss */

/* 第一个注意事项:在自定义的 wxss 文件中,不允许使用标签选择器,ID 选择器,属性选择器 */
/* 请改为 class 选择器 */
/* text {
  color: lightseagreen;
} */

/* #content {
  color: lightseagreen;
} */

/* [id=content] {
  color: lightseagreen;
} */

/* .content {
  color: lightseagreen;
} */

/* 第二个注意事项:子选择器,只能用于 view 和 子组件,用于其他组件可能会出现样式失效的问题 */
/* .content > .label {
  color: lightseagreen;
} */

/* 第三个注意事项:继承样式,例如:color\font 都会从组件外继承 */

/* 第四个注意事项:全局样式、组件所在页面的样式文件中的样式都对自定义组件无效 */

/* 第五个注意事项:官方不推荐做法 */
/* 不建议在 全局样式文件 以及 父级页面之间使用标签选择器设置样式 */
/* 如果是在全局样式文件中设置样式,会影响项目中全部的相同组件 */
/* 如果是再页面样式文件中设置样式,会影响当前页面所有的相同组件 */

/* 第六个注意事项: */
/* 组件和组件使用者,如果使用了后代选择器,可能会出现一些非预期情况 */
/* 如果出现,请避免 */

➡️ cate.wxml

<view class="custom parent">
  <view>
    <custom02 />

    <view class="son test">我是父级页面中的结构</view>
  </view>
</view>

➡️ cate.wxss

/* pages/cate/cate.wxss */

/* .custom  {
  color: lightseagreen;
  font-size: 50rpx;
} */

/* .label {
  color: lightseagreen;
} */

/* text {
  color: lightseagreen;
} */

.parent .son.test {
  color: lightsalmon;
}

➡️ app.wxss

/* .label {
  color: lightseagreen;
} */

/* text {
  color: lightseagreen;
} */

6. 组件样式隔离

默认情况下,自定义组件的样式只受到自定义组件 wxss 的影响。除非以下两种情况:

  1. app.wxss 或页面的 wxss 中使用了标签名(view)选择器(或一些其他特殊选择器)来直接指定样式,这些选择器会影响到页面和全部组件。通常情况下这是不推荐的做法。

  2. 指定特殊的样式隔离选项 styleIsolation

    Component({
      options: {
        styleIsolation: 'isolated'
      }
    })

styleIsolation 选项它支持以下取值:

  • isolated 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);
  • apply-shared 表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;
  • shared 表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了 apply-sharedshared 的自定义组件。

落地代码:

➡️ custom03.wxml

<!--components/custom03/custom03.wxml-->

<text class="label">演示组件样式隔离</text>

➡️ custom03.wxss

/* components/custom03/custom03.wxss */

.test {
  color: lightseagreen;
  font-size: 50rpx;
}

➡️ custom03.js

// components/custom03/custom03.js
Component({

  options: {

    // styleIsolation:配置组件样式隔离

    // isolated:开启样式隔离,默认值
    // 在默认情况下,自定义组件和组件使用者如果存在相同的类名,类名不会相互影响

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

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

  }
    
})

➡️ cate.wxml

<custom03 />

➡️ cate.wxss

.label {
  color: lightsalmon;
}

7. 拓展-小程序修改checkbox样式

知识点:

技巧:在官方文档,找到官方提供的案例,审查元素,就能看到对应的类名

https://i-blog.csdnimg.cn/blog_migrate/1aaca4efe8afecf77d609fa72ed006e9.png

📌 注意事项

  1. .custom-checkbox .wx-checkbox-input {}:复选框没有选中时默认的样式
  2. .custom-checkbox .wx-checkbox-input-checked {}: 复选框选中时默认的样式
  3. .custom-checkbox .wx-checkbox-input.wx-checkbox-input-checked:before {}:复选框选中时 √ 样式

这几个类名,在全局样式文件、页面样式文件都可以对修改复选框样式,

但是在自定义组件内部使用的时候,需要添加 styleIsolation: 'shared' 属性

落地代码:

➡️ components/custom-checkbox/custom-checkbox.wxss

/* 复选框组件是公共组件 */
/* 以后需要再多个页面或者需要再多个项目中进行使用 */
/* 所以呢,需要先给复选框组件准备、设置一些默认样式 */
/* 如果在其他页面或者其他项目中使用的时候,发现样式不符合产品需求 */
/* 可以进行修改、对默认的样式进行修改 */

/* 1. 需要给复选框设置默认样式 */
/* 需要先找到小程序给复选框提供的类名,通过小程序给提供的类名修改才可以 */
/* 需要先打开小程序开发文档,找到复选框文档,审查元素,进行查找 */

/* 在自定义组件中,不能直接修改复选框样式 */
/* 如果需要进行修改,需要设置 styleIsolation 才可以 */
/* shared:修改其他页面的样式、组件使用者的样式、以及其他使用了 share 以及 apply-share 的组件 */
/* 这时候,不是想要的结果 */
/* 需求是:只想影响当前组件,可以添加命名空间 */

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

/* 复选框选中时默认的样式 */
.custom-checkbox .wx-checkbox-input-checked {
  background-color: #fda007 !important;
}

/* 复选框选中时 √ 样式 */
.custom-checkbox .wx-checkbox-input.wx-checkbox-input-checked:before {
  font-size: 22rpx;
  color: #fff;
}

/* 2. 组件使用者也能够修改默认的样式 */

➡️ components/custom-checkbox/custom-checkbox.js

Component({

  options: {
    styleIsolation: 'shared'
  }
  
})

➡️ index.wxss

/* 组件使用者修改复选框的样式 */
.custom .custom-checkbox .wx-checkbox-input {
  border: 1px solid lightseagreen !important;
}

.custom .custom-checkbox .wx-checkbox-input-checked {
  background-color: lightseagreen !important;
}

8. 数据监听器

知识点:

数据监听器可以用于监听和响应任何属性和数据字段的变化,有时,需要在一些数据字段被 setData 设置时,需要执行一些操作。那么就可以使用 observers 数据监听器来实现。语法如下:

Component({
  data: {
    num: 10,
    count: 1,
    obj: { name: 'Tom', age: 10 },
    arr: [1, 2, 3]
  },
  observers: {
    // key 是需要检测数据
    // value 是一个函数,函数接收一个形参作为参数,是最新的值
    num: function(newNum) {
      console.log(newNum)
    },
    
    // 数据监听器支持监听属性或内部数据的变化,可以同时监听多个
    'num, count': function (newNum, newCount) {
       console.log(newNum, newCount)
    }
    
    // 监听器可以监听子数据字段
    'obj.age': function(newAge) {
      console.log(newAge)
    },
    
    // 如果需要监听所有子数据字段的变化,可以使用通配符 ** 
    'obj.**': function(newAge) {
      console.log(newAge)
    },
        
    'arr[0]': function (val) {}
  }
})

9. 组件间通信与事件

9.1 父往子传值

知识点:

父组件如果需要向子组件传递指定属性的数据,在 WXML 中需要使用数据绑定的方式

与普通的 WXML 模板类似,使用数据绑定,这样就可以向子组件的属性传递动态数据。

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

1.在父组件 WXML 中使用 数据绑定 的方式向子组件传递动态数据

2.子组件内部使用 properties 接收父组件传递的数据即可

知识点代码:

<!-- 引用组件的页面模板 -->
<view>
  <costom prop-a="{{ name }}" prop-b="{{ age }}" />
</view>

在组件内部,需要在 Component 构造器中通过 properties 接收传递的数据,接收方式有两种:

Component({
  /**
   * 组件的属性列表 props
   */
  properties: {
    propA: {
      type: String, // 传递的数据类型
      value: '' // 默认值
    },
    propB: Number // 简化的定义方式
  },

  // coding...
})

在子组件中也可以通过 this.setData()properties 中的数据进行修改,但是一般不建议修改

// components/custom01/custom01.js
Component({

  /**
   * 组件的方法列表
   */
  methods: {
    // 修改列表中的数据
    updateProp () {
      this.setData({
        propB: this.properties.propB + 1
      })
    }
  }
})

复选框组件案例:

➡️ index.js

Page({

  data: {
    isChecked: true
  },
    
  // coding...

})

➡️ index.wxml

<custom-checkbox
  label="我已阅读并同意 用户协议 和 隐私协议"
  position="right"
+   checked="{{ isChecked }}">
  我已阅读并同意 用户协议 和 隐私协议 - 111
</custom-checkbox>

➡️ components/custom-checkbox/custom-checkbox.js

Component({

  options: {
    styleIsolation: 'shared'
  },

  properties: {
    
    // coding...

    // 复选框组件公共组件
    // 需要再多个页面、在多个项目中进行使用
    // 在使用的时候,有的地方希望默认是选中的效果,有的地方希望默认是没有被选中的效果
    // 怎么处理 ?
    // 首先让复选框默认还是没有被选中的效果
    // 如果希望复选框默认被选中,这时候传递属性(checked=true)到复选框组件
+     checked: {
+       type: Boolean,
+       value: false
+     }
  },

  /**
   * 组件的初始数据:用来定义当前组件内部所需要使用的数据
   */
  data: {
    isChecked: false
  },

+   observers: {
+     // 如果需要将 properties 中的数据赋值给 data
+     // 可以使用 observers 进行处理
+     checked: function (newChecked) {
+       // console.log(newChecked)
+       this.setData({
+         isChecked: newChecked
+       })
+     }
+   },

  /**
   * 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中
   */
  methods: {
    
    // 更新复选框的状态
    updateChecked () {

      this.setData({
+         isChecked: !this.data.isChecked,
+         // checked: !this.properties.checked
        // label: '在组件内部也可以修改 properties 中的数据'
      })

      // 在 JS 中可以访问和获取 properties 中的数据
      // 但是一般情况下,不建议修改,因为会造成数据流的混乱
      // console.log(this.properties.label)
      // console.log(this.data.isChecked)
    }

  }
  
})

➡️ components/custom-checkbox/custom-checkbox.wxml

<!--components/custom-checkbox/custom-checkbox.wxml-->
<!-- <text>我是自定义组件</text> -->

<view class="custom-checkbox-container">
  <view class="custom-checkbox-box {{ position === 'right' ? 'right' : 'left' }}">
+     <checkbox class="custom-checkbox" checked="{{ isChecked }}" bindtap="updateChecked" />

    <view class="content">
      <!-- lable 和 子节点内容都进行了展示 -->
      <!-- 要么展示 lable 要么展示 子节点内容 -->
      <!-- 如果用户传递了 lable 属性,就展示 lable -->
      <!-- 如果用户没有传递 lable 属性,就展示 子节点内容 -->
      <text wx:if="{{ label !== '' }}">{{ label }}</text>

      <slot wx:else />
    </view>
  </view>
</view>
9.2 子往父传值

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

事件系统是组件间通信的主要方式之一,自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件,流程如下:

  1. 自定义组件触发事件时,需要使用 triggerEvent 方法发射一个自定义的事件
  2. 自定义组件标签上通过 bind 方法监听发射的事件

触发事件:

<!-- 在自定义组件中 -->
<button type="primary" plain bindtap="sendData">传递数据</button>
// components/custom05/custom05.js
Component({

  // 组件的初始数据
  data: {
    num: 666
  },

  // 组件的方法列表
  methods: {

    // 将数据传递给父组件
    sendData () {

      // 如果需要将数据传递给父组件
      // 需要使用 triggerEvent 发射自定义事件
      // 第二个参数,是携带的参数
      this.triggerEvent('myevent', this.data.num)
      
    }

  }
})

监听事件:

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

  data: {
    num: ''
  },

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

})

复选框组件案例:

➡️ components/custom-checkbox/custom-checkbox.js

Component({

  /**
   * 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中
   */
  methods: {
    
    // 更新复选框的状态
    updateChecked () {

      this.setData({
        isChecked: !this.data.isChecked,
        // label: '在组件内部也可以修改 properties 中的数据'
      })

      // 在 JS 中可以访问和获取 properties 中的数据
      // 但是一般情况下,不建议修改,因为会造成数据流的混乱
      // console.log(this.properties.label)
      // console.log(this.data.isChecked)

+       // 目前复选框组件的状态是存储在复选框组件内部的、存储在自定义组件内部的
+       // 但是,在以后实际开发中,组件使用者、父组件有时候也需要获取到复选框内部的状态
+       // 怎么办 ?
+       // 这时候,自定义组件内部就需要发射一个自定义事件,
+       // 如果组件使用者、父组件需要使用数据,绑定自定义事件进行获取即可
+       this.triggerEvent('changechecked', this.data.isChecked)
    }

  }
  
})

➡️ index.html

<custom-checkbox
  label="我已阅读并同意 用户协议 和 隐私协议"
  position="right"
  checked="{{ isChecked }}"
  class="getchild"
+  bind:changechecked="getData"
>
  我已阅读并同意 用户协议 和 隐私协议 - 111
</custom-checkbox>

➡️ index.js

Page({

  data: {
    isChecked: true
  },

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

    if (event.detail) {
      console.log('提交')
    } else {
      console.log('请同意协议!')
    }
  }

})
9.3 获取组件实例

如果前面两种方式不足以满足需要。

可在父组件里调用 this.selectComponent() ,获取子组件的实例对象,就可以直接拿到子组件的任意数据和方法。调用时需要传入一个匹配选择器 selector ,如: this.selectComponent(".my-component")

<!-- 父组件 -->
<costom bind:myevent="getData" class="custom" />
<button bindtap="getChildComponent"></button>
// 父组件
Page({
  data: {},
  getChildComponent: function () {
    const child = this.selectComponent('.custom')
    console.log(child)
  }
})

复选框组件案例:

➡️ index.html

<custom-checkbox
  label="我已阅读并同意 用户协议 和 隐私协议"
  position="right"
  checked="{{ isChecked }}"
+   class="child"
+  id="child"
  bind:changechecked="getData"
>
  我已阅读并同意 用户协议 和 隐私协议 - 111
</custom-checkbox>

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

➡️ index.js

Page({

  // coding...

  // 获取子组件的实例对象
  getChild () {

    // this.selectComponent 方法获取子组件实例对象
    // 获取到实例对象以后,就能获取子组件所有的数据、也能调用子组件的方法
    const res = this.selectComponent('#child')
    console.log(res.data.isChecked)

  }

})

10. 组件生命周期

组件的生命周期:指的是组件自身的一些钩子函数,这些函数在特定的时间节点时被自动触发

组件的生命周期函数需要在 lifetimes 字段内进行声明

最重要的生命周期是 created attached detached 包含一个组件生命周期流程的最主要时间点

定义段描述
created在组件实例刚刚被创建时执行,注意此时不能调用 setData (还没有对模板解析)
attached在组件实例进入页面节点树时执行 (模板已经解析完毕,并且挂载到页面上)
ready在组件布局完成后执行
moved在组件实例被移动到节点树另一个位置时执行
detached在组件实例被从页面节点树移除时执行 (组件被销毁了)

https://i-blog.csdnimg.cn/blog_migrate/859c4716b87c51c7596e69c21da67eed.png

  1. 【组件实例刚刚被创建好时】, created 生命周期被触发。此时,组件数据 this.data 就是在 Component 构造器中定义的数据 data此时还不能调用 setData 通常情况下,这个生命周期只应该用于给组件 this 添加一些自定义属性字段。
  2. 【在组件完全初始化完毕】、进入页面节点树后, attached 生命周期被触发。此时, this.data 已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。
  3. 【在组件离开页面节点树后】, detached 生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则 detached 会被触发。
Component({
  
  lifetimes: {
    created: function () {
      // 在组件实例刚刚被创建时执行,注意此时不能调用 setData 
      // 一般用来为组件添加一些自定义属性字段。
    },
    attached: function() {
      // attached 在组件完全初始化完毕、进入页面节点树后执行
      // 模板已经解析完毕,并且挂载到页面上
      // 一般都是在这里写对应的交互
    },
    detached: function() {
      // 在组件实例被从页面节点树移除时执行
    },
    
    // coding...
  }
    
  // coding...
})

11. 组件所在页面的生命周期

组件还有一些特殊的生命周期,这类生命周期和组件没有很强的关联

主要用于组件内部监听父组件的展示、隐藏状态,从而方便组件内部执行一些业务逻辑的处理

组件所在页面的生命周期有 4 个: show、 hide、 resize、 routeDone,需要在 pageLifetimes 字段内进行声明

https://i-blog.csdnimg.cn/blog_migrate/91a88f0efbb62260bcee9e6e8a1d3af3.png

// components/custom06/custom06.js
Component({

  // coding...

  // 组件所在页面的生命周期
  pageLifetimes: {

    // 监听组件所在的页面展示(后台切前台)状态
    show () {
      console.log('组件所在的页面被展示')
    },

    // 监听组件所在的页面隐藏(前台切后台、点击 tabBar)状态
    hide () {
      console.log('组件所在的页面被隐藏')
    }

  }

})

12. 小程序生命周期总结

小程序冷启动,钩子函数执行的顺序

https://i-blog.csdnimg.cn/blog_migrate/da3e38d9e284cd04c77e20859bb79eac.png

保留当前页面(navigate) 以及 关闭当前页面(redirect)

https://i-blog.csdnimg.cn/blog_migrate/34c72d8197698018bb051a8ea71d27ba.png

切后台 以及 切前台(热启动)

https://i-blog.csdnimg.cn/blog_migrate/878ec465444a523a1e417c9f7ba81ca3.png

13. 拓展:使用 Component 构造页面

Component 方法用于创建自定义组件

小程序的页面也可以视为自定义组件,因此页面也可以使用 Component 方法进行创建,从而实现复杂的页面逻辑开发

📌 注意事项:

  1. 要求对应 json 文件中包含 usingComponents 定义段
  2. 页面使用 Component 构造器创建,需要定义与普通组件一样的字段与实例方法
  3. 页面 Page 中的一些生命周期方法(如 onLoad() 等以“on”开头的方法),在 Component 中要写在 methods 属性中才能生效
  4. 组件的属性 Properties 可以用于接收页面的参数,在 onLoad() 中可以通过 this.data 拿到对应的页面参数

落地代码:

Component({

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

  // 小程序页面也可以使用 Component 方法进行构造
  // 注意事项:
  // 1. 要求 .json 文件中必须包含 usingComponents 字段
  // 2. 里面的配置项需要和 Component 中的配置项保持一致
  // 3. 页面中 Page 方法有一些钩子函数、事件监听方法,这些钩子函数、事件监听方法必须方法 methods 对象中
  // 4. 组件的属性 properties 也可以接受页面的参数,在 onLoad 钩子函数中可以通过 this.data 进行获取

  properties: {
    id: String,
    title: String
  },

  data: {
    name: 'tom'
  },

  // onLoad () {
  //   console.log('页面加载 - 1')
  // },

  methods: {

    // 更新 name
    updateName() {
      this.setData({
        name: 'jerry'
      })
    },

    onLoad (options) {
      // console.log('页面加载 - 2')
      // console.log(options)
      console.log(this.data.id)
      console.log(this.data.title)
      console.log(this.properties.id)
    },

  }

})

14. 拓展:behaviors

小程序的 behaviors 方法是一种代码复用的方式,可以将一些通用的逻辑和方法提取出来,然后在多个组件中复用,从而减少代码冗余,提高代码的可维护性。

如果需要 behavior 复用代码,需要使用 Behavior() 方法 ,每个 behavior 可以包含一组属性、数据、生命周期函数和方法

组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。

注册 behavior:

如果需要注册一个 behavior ,需要借助 Behavior() 方法,接受一个 Object 类型的参数

// my-behavior.js

module.exports = Behavior({
  behaviors: [],
  properties: {
    myBehaviorProperty: {
      type: String
    }
  },
  data: {
    myBehaviorData: 'my-behavior-data'
  },
  created: function () {
    console.log('[my-behavior] created')
  },
  attached: function () {
    console.log('[my-behavior] attached')
  },
  ready: function () {
    console.log('[my-behavior] ready')
  },

  methods: {
    myBehaviorMethod: function () {
      console.log('[my-behavior] log by myBehaviorMehtod')
    },
  }
})

使用 behavior:

// my-component.js
const myBehavior = require('my-behavior')

Component({
  behaviors: [myBehavior]
  
  // coding...
})

组件和它引用的 behavior 中可以包含同名的字段,对这些字段的处理方法如下:

  1. 如果有同名的属性或方法,采用 “就近原则”,组件会覆盖 behavior 中的同名属性或方法
  2. 如果有同名的数据字段且都是对象类型,会进行对象合并,其余情况会 采用 “就近原则” 进行数据覆盖
  3. 生命周期函数和 observers 不会相互覆盖,会是在对应触发时机被逐个调用,也就是都会被执行

详细的规则:

15. 拓展:外部样式类

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

外部样式类:在使用组件时,组件使用者可以给组件传入 CSS 类名,通过传入的类名修改组件的样式。

如果需要使用外部样式类修改组件的样式, 在 Component 中需要用 externalClasses 定义若干个外部样式类。

外部样式类的使用步骤:

1.在 Component 中用 externalClasses 定义段定义若干个外部样式类

2.自定义组件标签通过 属性绑定 的方式提供一个样式类,属性是 externalClasses 定义的元素,属性值是传递的类名

3.将接受到的样式类用于自定义组件内部

📌 注意事项:

​ 在同一个节点上使用普通样式类和外部样式类时,两个类的优先级是未定义的

​ 因此需要添加 !important 以保证外部样式类的优先级

落地代码:

➡️ custom09.js

// components/custom09/custom09.js
Component({

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

➡️ custom09.wxml

<!-- 在同一个节点上,如果存在外部样式类 和 普通的样式类 -->
<!-- 两个类的优先级是未定义的 -->
<!-- 建议:在使用外部样式类的时,样式需要通过 !important 添加权重 -->
<view class="extend-class box">通过外部样式类修改组件的样式</view>

➡️ custom09.wxss

.box {
  color: lightseagreen;
}

➡️ profile.wxml

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

➡️ profile.wxss

/* pages/index/index.wxss */

.my-class {
  color: lightsalmon !important;
}

16. 完善复选框案例并总结自定义组件

总结自定义组件:

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

完善复选框案例

➡️ components/custom-checkbox/custom-checkbox.wxml

<!--components/custom-checkbox/custom-checkbox.wxml-->
<!-- <text>我是自定义组件</text> -->

<view class="custom-checkbox-container">
  <view class="custom-checkbox-box {{ position === 'right' ? 'right' : 'left' }}">
+    <label class="custom-label">
      <checkbox class="custom-checkbox" checked="{{ isChecked }}" bindtap="updateChecked" />

      <view class="content">
        <!-- lable 和 子节点内容都进行了展示 -->
        <!-- 要么展示 lable 要么展示 子节点内容 -->
        <!-- 如果用户传递了 lable 属性,就展示 lable -->
        <!-- 如果用户没有传递 lable 属性,就展示 子节点内容 -->
        <text wx:if="{{ label !== '' }}">{{ label }}</text>

        <slot wx:else />
      </view>
+    </label>
  </view>
</view>

➡️ components/custom-checkbox/custom-checkbox.wxss

+ .custom-checkbox-box .custom-label {
  display: flex;
  align-items: center;
}

68747470733a2f2f62:6c6f672e6373646e2e6e65742f71715f36333335383835392f:61727469636c652f64657461696c732f313336333032343132