zoukankan      html  css  js  c++  java
  • 微信小程序:自定义组件的数据传递

    一、前言

    如果小程序中有可复用的UI且具有一定的功能性,就可以使用自定义组件将其封装起来。下面介绍一个简单的组件和一个复杂的组件。

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

    图片描述

    1. 组件功能介绍

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

    2. 创建组件

    首先在根目录创建components文件夹(或者你喜欢的地方),然后创建num-controller文件夹(我取的组件名字),在这个文件夹上点击右键新建一个component,名字依然叫做num-controller。

    图片描述

    num-controller.wxml

    <view class="num-controller">
      <view class="iconfont icon-jianshao sub-btn" hidden="{{num<1}}" bindtap="sub"></view>
      <view class="goods-num" hidden="{{num<1}}">{{num}}</view>
      <view class="iconfont icon-zengjia add-btn" bindtap="add"></view>
    </view>

    这段代码就是加减两个按钮和一个数字,因为我使用的是字体图标所以view里什么都没有。

    num-controller.json

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

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

    num-controller.js

    Component({
      /**
       * 组件的属性列表
       */
      properties: {
        num: Number
      },
    
      /**
       * 组件的初始数据
       */
      data: {
    
      },
    
      /**
       * 组件的方法列表
       */
      methods: {
        add() {
          
          this.setData({
            num: this.data.num + 1
          })
          this.triggerEvent('numChange', this.data.num);
        },
        sub() {
          if(this.data.num > 0) {
            this.setData({
              num: this.data.num - 1
            })
          }
          this.triggerEvent('numChange', this.data.num);
        }
      }
    })
    

    组件内部接收一个参数num,类型是Number;
    点击加号触发add方法,首先把init状态变为false,然后数字+1,同时触发numChange方法将改变的数字传到组件外部
    点击减号触发sub方法,数字-1,如果数字为0则把init状态变为true,同时触发numChange方法将改变的数字传到组件外部

    将组件数据传到外部的方法为this.triggerEvent('方法名',{要传递的数据})

    3. 引入组件

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

    <num-controller num="{{num}}" bindnumChange="numChange"></num-controller>

    index.json

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

    在json文件里注册组件。

    index.js

    data: {
        num: 1
    },
    numChange(e) {
        const numi = e.detail;
        
     }

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

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

    图片描述

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

    1. 创建组件并引入

    组件内部:

    // filter-panel.wxml
    
    <view class="filter-panel">
      <view class="panel-container">
        <view class="panel-left">
          ...
        </view>
    
        <view class="panel-right">
          ...
        </view>
      </view>
    </view>
    // filter-panel.json
    
    {
      "component": true
    }

    组件外部:

    // index.wxml
    
    <filter-panel></filter-panel>
    index.json
    
    {
      "usingComponents": {
        "filter-panel": "/components/filter-panel/filter-panel-component"
      }
    }

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

    2.组件与外部的数据传递(重点)

    (1) 固定数据渲染

    // filter-panel-component.js
    
    Component({
      /**
       * 组件的属性列表
       */
      properties: {
        mode: String,
        panel: String,
        text: Array,
        active: Array
      },
    
      /**
       * 组件的初始数据
       */
      data: {
        filterActive:[]
      },
    
      /**
       * 组件的方法列表
       */
      methods: {
        ...
      }
    })
    // index.wxml
    
    <filter-panel mode="mode1" panel="panel1" text="{{panel1Text}}" active="{{panel1Active}}"></filter-panel>
    // index.js
    
    panel1Text: [
      {
        'location': '附近',
        'choice': ['不限', '1km', '2km', '3km']
      }, {
        'location': '地铁站',
        'choice': ['江汉路', '光谷广场', '陶家岭', '六渡桥']
      }
    ],
    panel1Active: [0, 0]

    从组件外向组件内传递数据,直接在外部引入的组件上传。
    这里我传入了4个数据,
    mode代表筛选面板的模式,虽然这里只写了一种,但是实际上有三种形式,我写在了一个组件里,所以每次引入的时候都要指定mode。
    panel代表是第几个面板,虽然模式有三种,但是首页有四个面板,其中有两个的mode是相同的,为了区分每一个面板传入panel。
    panel1Text是渲染的数据。
    panel1Active是用户选择的数据。

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

    (2) 可变数据渲染

    控制组件active项的是外部的数据panel1Active: [0, 0],通过组件传到了内部,在组件内部:

    // filter-panel-component.js
    
      properties: {
        mode: String,
        panel: String,
        text: Array,
        active: Array
      },
      data: {
        filterActive:[]
      },
      attached() {
        this.dataInit();
      },
    
      /**
       * 组件的方法列表
       */
      methods: {
        dataInit() {
          let active = this.properties.active;
          this.setData({
            filterActive: active
          })
        },
        _chooseMode1Left(e) {
          let mode1LeftIndex = e.currentTarget.dataset.index;
          let mode1Active = this.data.filterActive;
          if (mode1LeftIndex == 0) {
            mode1Active.fill(0, 1, 2);
          } else {
            mode1Active.fill(-1, 1, 2);
          }
          mode1Active.fill(mode1LeftIndex, 0, 1);
          this.setData({
            filterActive: mode1Active
          })
        },
        _chooseMode1Right(e) {
          let mode1RightIndex = e.currentTarget.dataset.index;
          let mode1Active = this.data.filterActive;
          mode1Active.fill(mode1RightIndex, 1, 2);
          this.setData({
            filterActive: mode1Active
          })
          wx.setStorageSync(this.properties.panel, this.data.filterActive);
          this.triggerEvent('closePanel', {});
        }
      }
    

    dataInit方法指把外部传进来的active数组赋给内部的私有变量filterActive,然后在内部控制点击的active项。

    // index.wxml
    
    <view class="filter-panel mode1" wx:if="{{mode == 'mode1'}}">
      <view class="panel-container">
        <view class="panel-left">
          <view class="left-item {{filterActive[0] == idx1?'active':''}}" bindtap="_chooseMode1Left" wx:for="{{text}}" wx:for-index="idx1" data-index="{{idx1}}">{{item.location}}</view>
        </view>
    
        <view class="panel-right {{filterActive[0] == idx1?'':'hide'}}" wx:for="{{text}}" wx:for-index="idx1" data-index="{{idx1}}">
          <view class="right-item {{filterActive[1] == idx2?'active':''}}" bindtap="_chooseMode1Right" wx:for="{{item.choice}}" wx:for-index="idx2" data-index="{{idx2}}">
            <span>{{item}}</span>
            <view class="iconfont icon-correct" hidden="{{filterActive[1] != idx2}}"></view>
          </view>
        </view>
      </view>
    </view>

    _chooseMode1Left和_chooseMode1Right是两个私有方法,官方建议是在函数名前面加上下划线以便区分。
    在点击右侧的时候触发外部的方法closePanel。

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

    triggerEvent方法可以把组件内部的数据传到外面,触发组件外的事件。它接收3个参数:
    this.triggerEvent('myevent', myEventDetail, myEventOption);

    myEventDetail和myEventOption都是对象,myEventDetail是传到组件外的数据,myEventOption有三个参数可以设置:

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

    但是这里我并没有用triggerEvent将数据传出去,因为我这么做了之后发现组件内外的数据开始同步了,这不是我想要的效果。在组件外部有可能点击其他地方隐藏筛选面板,就算用户点击了左侧也不改变上一次保留的数据,所以我用wx.setStorageSync来保存上一次用户确定的数据。如果用户点击其他地方隐藏面板,则从Storage里取出真实的数据渲染。

    在组件外部可以这样操作内部的方法:

    // index.js
    
    onReady() {
      this.panel1 = this.selectComponent("#panel1");
    },
    showPanel(e) {
      this.panel1.dataInit();
    },
    index.wxml
    
    <filter-panel mode="mode1" panel="panel1" text="{{panel1Text}}" active="{{panel1Active}}" bind:closePanel="closePanel" id="panel1"></filter-panel>

    通过id获取到组件,然后调用组件内的方法。可以在每次显示筛选面板的时候初始化数据。这就是我最后使用的解决办法。

    三、总结

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

  • 相关阅读:
    JavaScript-4(循环语句)
    JavaScript-3
    JavaScript-2(基本运算)
    JavaScript-1
    HTML基础-3(框架、表单与表格等)
    HTML基础-2(内容容器等)
    HTML基础-1(b,i,u命令等)
    【CSP-S2019模拟】10.07比赛总结
    JZOJ6380. 【NOIP2019模拟2019.10.06】小w与最长路(path)
    【CSP-S2019模拟】10.06比赛总结
  • 原文地址:https://www.cnblogs.com/10manongit/p/12719514.html
Copyright © 2011-2022 走看看