小程序 --- 自定义组件

0. 概述

**公共组件: ** 将页面内的功能模块抽取成自定义组件, 以便于在不同的页面中重复使用, 一般存在在项目根目录的 components 文件夹中, 一个组件一个文件夹

**页面组件: ** 将复杂的页面拆分成多个低耦合的模块, 有助于代码维护, 一般存在在对应页面的目录中

1. 创建

2. 注册

**公共组件, 全局注册: ** 在 app.json 文件中配置 usingComponents 进行注册, 注册后可以在任意页面中使用

{
	"usingComponents": {
         // key: 组件名, vlaue: 组件存放路径 
    	"custom-checkbox": "./components/custom-checkbox/custom-checkbox"
  	}
}

使用组件

<custom-checkbox />

**页面组件, 局部注册: ** 在 页面.json 文件中配置 usingComponents 进行注册, 注册后只能在当前页面使用

{
  "usingComponents": {
    // key: 组件名, vlaue: 组件存放路径 
    "custome-swiper": "./custome-swiper/custome-swiper"
  }
}

使用组件

<custome-swiper />

3. 数据、方法

组件的数据和方法需要在 组件.jsComponent 方法中定义,

Component({
  /**
   * 组件的属性列表
   */
  properties: {
	
  },

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

  },

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

  }
})

4. 样式隔离

  1. 自定义组件中不允许使用标签选择器、ID 选择器、属性选择器, 建议优先使用 class 选择器
  2. 子选择器只能用于 view 组件,用于其他组件可能会出现样式失效的问题
  3. 全局样式 或 组件所在的页面样式,都对自定组件无效
  4. 父组件和自定义组件,如果使用了后代选择器, 可能会出现一些非预期情况,要使用组件样式隔离

子组件的 js 文件中,配置 stylelsolation

Component({
  options:{
    // isolated: 开启样式隔离, 默认值
    // apply-shared: 父组件的样式,会影响子组件的样式, 反之不会
    // shared: 父组件的样式,会影响子组件的样式和其他使用了 apply-share 和 share 属性的自定义组件
    styleIsolation: "isolated"
  }
})

5. 外部样式类

默认情况下, 组件和组件使用者之间如果存在相同的类名不会相互影响, 组件使用者如果想修改组件的样式,需要解除样式隔离,但是解除样式隔离后,在极端情况下会产生样式冲突、css嵌套太深等问题,所以小程序中通过组件使用者给组件传入 CSS类名的方法来修改组件的样式

1. 组件使用者的 scss 文件中定义样式

.warpper-class{
    color: red !important;    // 如果确保外部样式类生效,需要添加 !important 来提高权重,以防组件中有相同的样式
}

2. 组件使用者中传递外部样式类

<custom extend-class="warpper-class" />

3. 在组件的 js 文件中定义 externalClasses 配置项

Component({
  externalClasses:['extend-class']   // 接收父组件传给子组件的外部样式类
})

4. 组件的 wxml 文件中,使用该外部样式类

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

6. 使用 CSS 变量

多个页面中使用这个组件、一个组件中批量修改样式、定制主题, 建议使用 CSS 变量

1. 多个页面中使用这个组件

全局 scss

page {
    --color: lightseagreen;   // 使用 --属性,来定义
}

组件使用者的 scss

.van-button--primary {
    font-size: 28rpx !important;   // 提高权重
    background-color: var(--color) !important;
    border: 1px solid var(--color) !important
}

2. 批量修改样式

<van-button type="default" class="my-button">默认按钮</van-button>
.my-button {
    --color: lightseagreen;
}

7. 数据监听

写法一: 一个一个监听

Component({
  properties: {
	label: "标题"
  },
  data: {
    num: 10,
    count: 100,
    obj: {
      name: "Tom",
      age: 28
    },
    arr: [1,2,3]
  },
  methods: {
    updateData(){
      this.setData({
        num: this.data.num + 1,
        count: this.data.count -1,
        "arr[1]": 100,
        "obj.name": "Anay"
      })
    }
  },
  // 监听数据是否发生变化
  observers: {
    num: function (newNum) {   // 监听数值类型
      console.log(newNum);
    },
    count: function (newCount){
      console.log(newCount);
    },
    "arr[1]": function (newItem) {  // 监听数组类型
      console.log(newItem);
    },
    "obj.name":function(newName){  // 监听对象中的指定属性
      console.log(newName)
    },
    "obj.**":function(newName){  // 监听对象中的所有属性
      console.log(newName)
    },
    // 对于用 properties 接收的数据,在页面初次渲染的时候,会立刻执行一次这个监听器
    // 之后每次发生改变也会执行一次这个监听器
    label: function(newLabel){   // 监听 properties 接受的数据
        console.log(newLabel)
    }
  }
})

**写法二: 同时监听多个数据 **

Component({
  data: {
    num: 10,
    count: 100,
    obj: {
      name: "Tom",
      age: 28
    },
    arr: [1,2,3]
  },
  methods: {
    updateData(){
      this.setData({
        num: this.data.num + 1,
        count: this.data.count -1
      })
    }
  },
  // 监听数据是否发生变化
  observers: {
    "num,count": function (newNum,newCount){
      console.log(newNum, newCount);
    }
  }
})

8. 组件通信

1. 父传子

1. properties 传递数据

pages/cart/cart.wxml

<view>
  <!-- label 和 position 都是传递的参数 -->
  <custom-checkbox label="我已阅读并同意 用户协议 和隐私协议" position="right"/>
</view>

components/custom-checkbox/custom-checkbox.wxml

<view>
  <view class="box {{ position==='right' ? 'right' : 'left'  }}">   <!-- 根据 position的值 来控制样式 -->
    <checkbox value="1" checked="{{ isChecked }}" bindtap="aggreyUser"/>
    <view>
      <text>{{ label }}</text>     <!-- 使用 双大括号{{}},来展示参数 -->
    </view>
  </view>
</view>

components/custom-checkbox/custom-checkbox.js

Component({
  // 接收父组件传递的参数
  properties: {
    position:{    	 // 全写方式
      type: String,  // 限制数据类型,只能是 String、Number、Boolmean、Object、Array、null(表示不限制类型)
      value:''       // 默认值
    },     
    label: String   // 简写方式, 只限制传递参数的数据类型
  },
  methods: {
      updateChecked(){
          console.log(this.properties.label)    // 访问父组件传给子组件的参数对象
          this.setData({
              label: "修改参数过来的值"    // 一般不建议修改, 因为有可能会造成数据的混乱
          })
      }
  }
})

2. slot 插槽传递标签

1. 默认插槽

默认情况下, 一个组件的 wxml 中只能有一个 slot(默认插槽)。

父组件的 .wxml 文件中

<custom-checkbox>
    <!-- 默认情况下. 自定义组件的子节点内容不会进行展示,如果想通过向子组件内传递内容,来展示不同的页面,需要通过 slot 插槽来实现 -->
    我是子组件的内容
</custom-checkbox>

子组件的 .wxml 文件中

<view>
  <view>
    <!-- 默认插槽,接收父组件传递过来的内容 -->
    <slot />
  </view>
</view>

2. 多具名插槽

如果需要使用多个 slot 时, 需要在组件的js中声明启用

父组件的 .wxmll 中

<view>
  <custom-checkbox>
    <text slot="slot-top">我需要显示在默认插槽的顶部</text>
    <text>我是默认插槽的内容</text>
    <text slot="slot-bottom">我需要显示在默认插槽的底部</text>
  </custom-checkbox>
</view>

子组件的 .js 文件中,开启多插槽功能

Component({
  options:{
    multipleSlots:true
  }
})

子组件的 .wxml 文件中

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

2. 子传父

1. 自定义事件

1. 子组件的 js 文件中定义传递的数据,以及自定义事件名

Component({
  properties: {},
  data: {
    num: 100
  },
  methods: {
    // 将数据传递给父组件
    sendData() {
      // 参数一: 自定义事件名
      // 参数二: 需要携带的数据
      this.triggerEvent("sendNum", this.data.num)
    }
  }
})

2. 父组件的 wxml 文件中,绑定自定义事件及事件处理函数

<!-- 父组件中,通过 bind:子组件中定义的自定义事件 来绑定时间处理函数 -->
<view>
    <text>子组件传过来的数据为: {{ num }}</text>
</view>
<custom01 bind:sendNum="saveNum"/>

3. 父组件的 js 文件中,声明事件处理函数

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

2. 子组件实例

父组件可以通过 this.selectComponent() 方法,获取子组件实例对象, 这样就可以直接访问子组件的任意数据和方法

1. 父组件的 html 文件中,给子组件一个 class 或 id

<custom01 class="child" id="child" />
<button plain type="warn" bind:tap="getData">获取子组件数据</button>

2. 父组件的 js 文件中,调用 this.selectComponent() 方法,获取子组件的数据和方法

Page({
  data:{
    num: ''
  },
  getData(){
    const childObj  = this.selectComponent(".child")   // 可以是类选择器 或 ID选择器
    this.setData({
      num: childObj.data.num
    })
  }
})

3. 兄弟组件

1. 事件总线

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

1. 官网

https://gitcode.com/mroderick/PubSubJS/overview

2 安装

npm i pubsub-js

3. 点击 工具 => 构建Npm

4. 数据发送方

// 1. 引入 pubsub-js
import PubSub from "pubsub-js"
Component({
  data:{
    name: "Tom"
  },
  methods: {
    sendToBrother(){
      // 2. 发布自定义事件,并携带数据
      PubSub.publish("getData",this.data.name)
    }
  }
})

5. 数据接收方

// 1. 导入 pubsub-js
import PubSub from 'pubsub-js'
Component({
  data:{
    name: ""
  },
  // 生命周期配置项中,声明 attached() 生命周期函数
  lifetimes:{
    attached(){
      // 2. 订阅监听自定义事件
      PubSub.subscribe("getData", (msg,data) =>{
        console.log(msg,data);   // msg 是需要监听的自定义事件名称, data 是携带的数据
        this.setData({
          name: data
        })
      })
    }
  }
})

4. 全局数据共享 --- getApp()

在小程序中,可以通过 getApp() 方法 获取到小程序全局唯一的 App 实例,因此在 app.js 文件中的 App() 方法中添加全局共享的数据、方法,从而实现页面、组件之间的数据传递

1. 基本使用

app.js

App({
  // 全局共享的数据
  globalData: {
    token: ""
  },

  // 全局共享的方法
  setToken(token){
    this.globalData.token = token
  }
})

pages/login/login.js

// 获取全局唯一的 App() 实例
const appInstance = getApp()

Page({
  login(){
    // 调用全局的方法 setToken()
    appInstance.setToken("123456");
  }
})

pages/index/index.js

// 获取全局唯一的 App() 实例
const appInstance = getApp()

Page({
  onLoad(){
      if (!appInstance.token){
           wx.redirectTo({
              url: '/pages/login/login',
              success: (res) => {},
              fail: (res) => {},
              complete: (res) => {},
            })
      }
  }
})

2. 注意事项

  1. 不要在 App() 方法中使用 getApp() 方法, 使用 this 就能拿到 app 实例
  2. 不要通过 app 实例调用生命周期函数

5. 全局数据共享 --- store

0. 官方文档

https://www.npmjs.com/package/mobx-miniprogram
https://www.npmjs.com/package/mobx-miniprogram-bindings

1. 介绍

上面已经有了 6 中小程序页面、组件间的数据通信方案, 分别是:

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

在中小型项目中, 使用这些数据通信方式已经能够满足项目需求。但是随着项目的业务逻辑越来越复杂, 组件和页面间通信就会变得非常复杂。例如: 有些状态需要再多个页面间进行同步使用, 一个地方发生变更, 所有使用的地方都需要发生改变, 这时候如果使用前面的数据通信方案进行传递数据, 管理和维护就爱那个存在很大的问题, 为了方便进行页面、组件之间数据的传递, 小程序官方提供了一个扩展工具库: mobx-miniprogram, 它是针对微信小程序开发的一个简单、高效、轻量级状态管理库, 它基于 Mobx 状态管理框架实现。使用 mobx-miniprogram 定义管理的状态是响应式的, 当状态一旦发生改变, 所有关联组件都会自动更新相对应的数据, 通过该扩展工具库, 开发者可以很方便的在小程序中全局共享状态, 并自动更新视图组件, 从而提升小程序的开发效率。

image-20240627222146273

**注意: ** 在使用 mobx-program 需要安装两个包: mobx-miniprogrammobx-miniprogram-bindings

  1. mobx-miniprogram 的作用: 创建 Store 对象, 用于存储应用的数据
  2. mobx-miniprogram-bindings 的作用: 将状态和组件、页面进行绑定关联, 从而在组件和页面中操作数据

2. 安装

npm i mobx-miniprogram mobx-miniprogram-bindings

3. 创建 Store 对象

1. 创建 Store 对象需要使用 mobx-miniprogram ,因此需要先熟悉 mobx-miniprogra 三个核心概念:

  1. observable:用于创建一个被监测的对象,对象的属性就是应用的状态(state),这些状态会被转换成响应式数据。
  2. action:用于修改状态(state)的方法,需要使用 action 函数显式的声明创建。
  3. computed:根据已有状态(state)生成的新值。计算属性是一个方法,在方法前面必须加上 get 修饰符

2. 开始使用

1. 在项目的根目录下创建 stores 文件夹,然后在该文件夹下新建 indexStore.js

2. 使用 observable 方法需要接受一个 store 对象,存储应用的状态

stores/indexStore.js

import {
  observable, // observable:用于创建一个被监测的对象,对象的属性就是应用的状态(state),这些状态会被转换成响应式数据。
  action      // action:用于显式的声明创建更新 state 状态的方法
} from 'mobx-miniprogram'
export const numStore = observable({

  // 创建应用状态
  numA: 1,
  numB: 2,

  // 使用 action 更新 numA 以及 numB
  update: action(function () {
    this.numA += 1
    this.numB += 1
  }),

  // 计算属性,根据已有的状态产生新的状态,前面需要使用 get 修饰符,
  get sum() {
    return this.numA + this.numB // 计算属性内部必须有返回值
  }
})

4. 在组件中使用数据和方法

如果需要 Page 或者 Component 中对共享的数据进行读取、更新操作,需要使用 mobx-miniprogram-bindings,其作用就是将 Store 和 页面或组件进行绑定关联

如果需要在组件中使用状态,需要 mobx-miniprogram-bindings 库中导入 ComponentWithStore 方法,在使用时:需要将 Component 方法替换成 ComponentWithStore 方法 ,原本组件配置项也需要写到该方法中

components/custom01/custom01.wxml

<view>
  <view>{{ numA }} + {{ numB }} = {{ sum }} </view>
  <button type="primary" plain bind:tap="updateNum">更新</button>
</view>

<view>
  <view>{{ numA }} + {{ numB }} = {{ sum }} </view>
  <!-- 或者可以直接绑定 store 中定义的方法, 因为 update 会被注入到 methods 对象中, 直接调用即可 -->
  <button type="primary" plain bind:tap="update">更新</button>
</view>

components/custom01/custom01.js

import {
  ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
  numStore
} from '../../stores/indexStore' // 导入 store
ComponentWithStore({
  methods: {
    updateNum() {
      this.update() // 直接使用 this.update() 使用 store 中定义的方法
    }
  },
    
  // 用来配置当前组件需要和哪些 store 进行关联, 在从 store 对象中引入数据和方法后
  // 如果是数据,则会被注入到 data 对象中
  // 如果是方法, 则会被注入到 methods 对象中
  storeBindings: {
    store: numStore, // 关联的store
    fields: ['numA', 'numB', 'sum'], // 关联的数据列表
    actions: ['update'] // 关联的方法列表
  }
})

4. 在替换以后,就会新增一个 storeBindings 配置项,配置项常用的属性有以下三个:

  1. store: 指定要绑定的 Store 对象

  2. fields: 指定需要绑定的 data 字段

  3. actions: 指定需要映射的 actions 方法

// components/custom01/custom01.js
import { ComponentWithStore } from 'mobx-miniprogram-bindings'
import { numStore } from '../../stores/numstore'
 
ComponentWithStore({
  data: {
    someData: '...'
  },
  storeBindings: {
    store: numStore,
    fields: ['numA', 'numB', 'sum'],
    actions: ['update']
  }
})

5. 注意事项:

  1. 导入的数据会同步到组件的 data 中

  2. 导入的方法会同步到组件的 methods 中

5. 在页面中使用数据和方法

1. 方式一

Component 方法用于创建自定义组件,小程序的页面 (page) 也可以视为自定义组件,因此页面也可以使用 Component 方法进行构建,从而实现复杂的页面逻辑开发。如果我们使用了 Component 方法来构建页面,那么页面中如果想使用 Store 中的数据,使用方式和组件的使用方式是一样的(同上面的在在组件中使用一样, 将 Page() 替换成 ComponentWithStore()

pages/index/index.wxml

<view>
  <view>{{ numA }} + {{ numB }} = {{ sum }} </view>
  <button type="primary" plain bind:tap="update">更新</button>
</view>

pages/index/index.js

import {
  ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
  numStore
} from '../../stores/indexStore'
ComponentWithStore({   // 将 page() 替换为 ComponentWithStore()
  storeBindings: {
    store: numStore,
    fields: ['numA', 'numB', 'sum'],
    actions: ['update']
  }
})

2. 方式二

如果不想使用 Component 方法构建页面。这时候需要使用 mobx-miniprogram-bindings 提供的 BehaviorWithStore 方法来和 Store 建立关联,小程序的 behavior 方法是一种代码复用的方式,可以将一些通用的逻辑和方法提取出来,然后在多个组件中复用,从而减少代码冗余,提高代码的可维护性。在页面中也可以使用 behaviors 配置项

1. 页面文件夹中新建 behavio 文件

pages/index/behaviors.js

import { BehaviorWithStore } from 'mobx-miniprogram-bindings'
import { numStore } from '../../stores/numstore'
 
export const indexBehavior = BehaviorWithStore({
  storeBindings: {
    store: numStore,
    fields: ['numA', 'numB', 'sum'],
    actions: ['update'],
  }

2. 在页面 js 文件中引入 behavior.js

pages/index/index.js

import {
  indexBehavior
} from './behaviors'
Page({
  behaviors: [indexBehavior]   // 注册 indexBehavior
})

3. 页面中使用

pages/index/index.wxml

<view>
  <view>{{ numA }} + {{ numB }} = {{ sum }} </view>
  <button type="primary" plain bind:tap="update">更新</button>
</view>

6. fields、actions 的对象写法

1. fields 的对象写法 --- 映射形式

pages/index/behaviors.js

import {
  BehaviorWithStore
} from 'mobx-miniprogram-bindings'
import {
  numStore
} from '../../stores/indexStore'

export const indexBehavior = BehaviorWithStore({
  storeBindings: {
    store: numStore,
    fields: {     // 映射形式
      a: 'numA',
      b: 'numB',
      s: 'sum'
    },
    actions: ['update']
  }
})

pages/index/index.wxml

<view>
  <!-- 需要使用 key 声明 numA -->
  <view>{{ a }} + {{ b }} = {{ s }} </view>
  <button type="primary" plain bind:tap="update">更新</button>
</view>

2. fields 的对象写法 --- 函数形式

pages/index/behaviors.js

import {
  BehaviorWithStore
} from 'mobx-miniprogram-bindings'
import {
  numStore
} from '../../stores/indexStore'

export const indexBehavior = BehaviorWithStore({
  storeBindings: {
    store: numStore,
    fields: {
      a: () => numStore.numA,    // 可以在函数形式中再次进行计算等操作
      b: () => numStore.numB,
      s: () => numStore.sum
    },
    actions: ['update']
  }
})

pages/index/index.wxml

<view>
  <!-- 需要使用 key 声明 numA -->
  <view>{{ a }} + {{ b }} = {{ s }} </view>
  <button type="primary" plain bind:tap="update">更新</button>
</view>

3. actions 的对象写法 --- 映射形式

pages/index/behaviors.js

import {
  BehaviorWithStore
} from 'mobx-miniprogram-bindings'
import {
  numStore
} from '../../stores/indexStore'

export const indexBehavior = BehaviorWithStore({
  storeBindings: {
    store: numStore,
    fields: {
      a: () => numStore.numA,
      b: () => numStore.numB,
      s: () => numStore.sum
    },
 
    // 使用映射形式获取 Store 中的 action 名字
    actions: {
      // key 自定义,为当前组件中调用的方法
      // 值为 store 中对应的 action 名字
      buttonTap: 'update'
    }
  }
})

pages/index/index.wxml

<view>
  <view>{{ a }} + {{ b }} = {{ s }} </view>
    
  <!-- 使用 actions 中的 key 来调用对应的 store 中的方法 -->
  <button type="primary" plain bind:tap="buttonTap">更新</button>
</view>

7. 绑定多个 store 及 命名空间解决冲突

在实际开发中,一个页面或者组件可能会绑定多个 Store ,如果多个 Store 中存在相同的数据和方法,会造成冲突, 解决方法:

1. 方法一: fieldsactions 换成对象方式写法

方法二: 添加命名空间配置项, 但命名空间只对数据有效, 方法同样需要换成对象方式写法

pages/index/behaviors.js

import { BehaviorWithStore } from 'mobx-miniprogram-bindings'
import { numStore } from '../../stores/numstore'
 
export const indexBehavior = BehaviorWithStore({
  storeBindings: [   // 将 storeBindings 换成数组
    {
      namespace: 'numStore',  // 如果多个 store 中的数据或者方法冲突, 则需要加命名空间
      store: numStore,
      fields: ['numA', 'numB', 'sum'],
      actions: ['update'],
    }
  ]
})

pages/index/index.wxml

<!-- 在页面使用需要加上命名空间 -->
<view>{{ numStore.numA }} + {{ numStore.numB }} = {{numStore.sum}}</view>

6. 页面间通信

如果一个页面通过 wx.navigateTo 打开一个新页面, 这两个页面之间将建立一条数据通道

pages/index/index.wxml

<text>index</text>

<button type="primary" plain bind:tap="toListPage">跳转列表页</button>

pages/list/list.wxml

<text>list</text>

1. 在 wx.navigateT0() 的 success 回调中 通过 EventChannel.emit() 发射事件并携带数据

pages/index/index.js

Page({
  data: {
    num: 10
  },
  toListPage() {
    // 跳转页面
    wx.navigateTo({
      url: '/pages/list/list',
      success: (res) => {
        // 发射自定义事件,并传递数据
        res.eventChannel.emit("indexToList", this.data.num)
      }
    })
  }
})

2. 在 被打开的页面可以通过 this.getOpenerEventChannel() 方法来获得一个 EventChannel.on() 进行监听、或者使用 EventChannel.emit() 发射事件

pages/list/list.js

Page({
  onLoad(options) {
    // 接收首页发来的数据
    const EventChannel = this.getOpenerEventChannel()
    EventChannel.on("indexToList",(res) => {
      console.log("获取index页面传递的数据",res);
    })

    // 发送数据给首页
    EventChannel.emit("listToIndex",{name: "我是 list 页面发送的数据"})
  },
})

3. wx.navigateTo() 方法中可以定义 events 配置项接收被打开页面发射的事件

pages/index/index.js

Page({
  data: {
    num: 10
  },
  toListPage() {
    wx.navigateTo({
      url: '/pages/list/list',
      // events 中配置被打开页面发射的自定义事件和其回调函数
      events:{
        listToIndex: (res) =>{
          console.log(res);
        }
      },
      success: (res) => {
        res.eventChannel.emit("indexToList", this.data.num)
      }
    })
  }
})

4. 先触发 发送数据页面中配置的接收被发送数据页面的自定义事件及数据,然后再触发接收页面中配置的自定义事件及数据

9. 使用 Component 构造页面

Component 方法是用于创建自定义组件的方法,小程序的页面也可以视为自定义组件,因此页面也可以使用 Component 方法创建,从而实现复杂的页面逻辑开发,但是有如下注意项:

  1. json 文件中必须有 usingComponents 这个配置项
  2. 里面的配置项需要和 Component 中的配置项一致
  3. 页面中 Page 方法中定义的钩子函数、事件监听API,都必须放在 methods 配置项中
  4. url携带的参数可以通过 onLoad(options) 来接收,也可以通过 properties 接收

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

这是因为 Component 方法 比 Page 方法 的功能要强大很多,可以实现更加复杂的页面逻辑开发

10. 复用机制 --- behaviors

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

如果需要使用 behavior 复用代码, 需要使用 Behavior() 方法, 每个 behavior 可以包含一组属性、数据、生命周期函数和方法,组件引用它时, 它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用

1. 组件中新建 behavior.js 文件

coonst behavior = Behavior({
  properties:{
    label: String,
    value: "我已同意该协议"
  },
  data:{
    name: "Tom",
    obj: {
      name: "Anay",
      age: 16
    }
  },
  methods:{
    updateName(){
      this.setData({
        name: "Jerry"
      })
    }
  },
  lifetimes: {
    attached(){
      console.log("我是组件的生命周期函数");
    }
  }
})
export default behavior

2. 组件中使用

import behavior from './behavior'

Component({
    behaviors: [behavior]  // 注册 behavior
    // 如果组件中有和 behavior 中定义的属性、方法、生命周期函数会有以下不同的情况
    // 1. 如果存在相同名字的 properties ,优先使用组件内部的
    // 2. 如果存在相同名字的 data, 如果是对象类型,相同名字的对象会合并,其他类型优先使用组件内部的
    // 3. 如果存在相同名字的 方法 ,优先使用组件内部的
    // 4. 如果存在相同名字的 生命周期函数 ,优先执行 behavior 中定义的生命周期函数,然后再执行组件内部的
})

11. 计算属性 和 监听器

小程序框架没有提供计算属性相关的 api ,但是官方为开发者提供了拓展工具库 miniprogram-computed。该工具库提供了两个功能:

  1. 计算属性 computed

  2. 监听器 watch

如果需要在组件中使用计算属性功能,需要 miniprogram-computed 库中导入 ComponentWithComputed 方法, 在使用时:需要将 Component 方法替换成 ComponentWithComputed 方法. 在替换以后,就可以新增 computed 以及 watch 配置项。

1. 安装

安装 miniprogram-computed, 在安装以后,需要点击 工具 => 构建 npm,进行本地构建

npm install miniprogram-computed

2. 计算属性 computed

components/custom01/custom01.js

import { ComponentWithComputed } from 'miniprogram-computed'
 
ComponentWithComputed({
  data: {
    a: 1,
    b: 1
  },
  
  computed: {
    total(data) { 
      // computed 函数中不能使用 this 访问 data 中的数据,通过形参 data 来访问
      // 这个函数的返回值会被设置到 this.data.sum 字段中
      // 计算属性具有缓存,计算属性方法只会执行一次, 只要计算属性所依赖的数据没有发生改变, 即使后续多次使用计算属性,返回的始终是第一次执行后的记过
      console.log('~~~~~')
        
      return data.a + data.b
    }
  }
})

components/custom01/custom01.wxml

<view> {{ a }} + {{ b }} = {{ total }}</view>
<view>{{ total }}</view>
<view>{{ total }}</view>

3. 监听器 watch

在使用时:需要将 Component 方法替换成 ComponentWithComputed 方法 ,在替换以后,就可以新增 computed 以及 watch 配置项;

import { ComponentWithComputed } from 'miniprogram-computed'
 
ComponentWithComputed({
  data: {
    a: 1,
    b: 1,
    c: ''
  },
  
  watch: {
 
    // key:需要监听的数据
    // value:是回调函数,回调函数有一个形参,形参就是最新的、改变后的数据
    a:function(a){
        console.log(a)
	}
 
    // 同时对 a 和 b 进行监听
    'a, b': function (a, b) {
      this.setData({
        c: a + b
      })
    }
  },
  
})
 

12. store 与 计算属性 同时使用

两个框架扩展提供的 ComponentWithStoreComponentWithComputed 方法无法结合使用。如果需要在一个组件中既想使用 mobx-miniprogram-bindings 又想使用 miniprogram-computed, 解决方案是:

  1. 使用旧版 API(全部都是用旧的api写, 自定义组件仍然使用 Component 方法构建组件,将两个扩展依赖包的使用全部改为旧版 API

  2. 使用兼容写法:(取其中任一一个写成旧api就行了,另一个照常写就行了)即要么使用 ComponentWithStore 方法构建组件,要么使用 ComponentWithComputed 方法构建组件, 如果使用了 ComponentWithStore 方法构建组件,计算属性写法使用旧版 API, 如果使用了 ComponentWithComputed 方法构建组件,Mobx写法使用旧版 API

兼容写法

1.如果使用了 ComponentWithStore 方法构建组件,计算属性使用旧版 API

import { ComponentWithComputed } from 'miniprogram-computed'
 
// 导入计算属性
const computedBehavior = require('miniprogram-computed').behavior
 
ComponentWithStore({   // 替换为 ComponentWithStore
  behaviors: [computedBehavior],  // 注册
 
  data: {
    a: 1,
    b: 1,
    sum: 2
  },
    
  watch: {
    'a, b': function (a, b) {
      this.setData({
        total: a + b
      })
    }
  },
    
  computed: {
    total(data) {
      return data.a + data.b + data.sum
    }
  },
    
  // 实现组件和 Store 的关联
  storeBindings: {
    store: numStore,
    fields: ['numA', 'numB', 'sum'],
    actions: ['update']
  }
})

2.使用了 ComponentWithComputed 方法构建组件,store使用旧版 API

import { ComponentWithComputed } from 'miniprogram-computed'
 
// 导入 storeBindingsBehavior 方法实现组件和 Store 的关联
import { storeBindingsBehavior } from "mobx-miniprogram-bindings"
// 导入 Store 
import { numStore } from '../../stores/numstore'
 
ComponentWithComputed({
  behaviors: [storeBindingsBehavior],
 
  data: {
    a: 1,
    b: 1,
    sum: 2
  },
  watch: {
    'a, b': function (a, b) {
      this.setData({
        total: a + b
      })
    }
  },
  computed: {
    total(data) {
      return data.a + data.b + data.sum 
    }
  },
    
  // 实现组件和 Store 的关联
  storeBindings: {
    store: numStore,
    fields: ['numA', 'numB', 'sum'],
    actions: ['update']
  }
})
posted @ 2024-06-18 00:02  河图s  阅读(114)  评论(0)    收藏  举报