ホームページ >WeChat アプレット >ミニプログラム開発 >WeChat ミニ プログラムのコンポーネントをカスタマイズする方法の簡単な分析

WeChat ミニ プログラムのコンポーネントをカスタマイズする方法の簡単な分析

青灯夜游
青灯夜游転載
2022-03-25 11:33:356481ブラウズ

WeChat ミニ プログラムのコンポーネントをカスタマイズするにはどうすればよいですか?次の記事では、WeChat ミニ プログラムのコンポーネントをカスタマイズする方法を紹介します。

WeChat ミニ プログラムのコンポーネントをカスタマイズする方法の簡単な分析

WeChat アプレットの開発プロセス中、開発効率を向上させるために、複数のページで使用される可能性のある一部のページ モジュールをコンポーネントにカプセル化できます。 weui、vant などのコンポーネント ライブラリ全体を導入することもできますが、WeChat アプレットのパッケージ サイズ制限を考慮する場合もありますが、通常はカスタム コンポーネントとしてカプセル化する方が制御しやすいです。

また、一部のビジネス モジュールについては、コンポーネントとしてカプセル化して再利用できます。この記事では主に次の 2 つの側面について説明します。

  • コンポーネントの宣言と使用
  • コンポーネントの通信

コンポーネントの宣言と使用

WeChat ミニ プログラムのコンポーネント システムの最下層は、ミニ プログラムの基本ライブラリに組み込まれている Exparser コンポーネント フレームワークを通じて実装されます。ミニ プログラムには組み込みコンポーネントとカスタム コンポーネントが含まれており、すべて Exparser 組織によって管理されます。

カスタム コンポーネントには、ページの作成と同様に次のファイルが含まれます:

  • index.json
  • index.wxml
  • index.wxss
  • index.js
  • index.wxs

tab コンポーネントの作成を例として挙げます。 カスタム コンポーネントを作成するときは、json ファイルの component フィールドを true:

js の

{
    "component": true
}
に設定する必要があります。 このファイルでは、基本ライブラリには Page と Component という 2 つのコンストラクターが用意されています。Pag​​e に対応するページはページ ルート コンポーネントで、コンポーネントは次のものに対応します。

Component({
    options: { // 组件配置
        addGlobalClass: true,
        // 指定所有 _ 开头的数据字段为纯数据字段
        // 纯数据字段是一些不用于界面渲染的 data 字段,可以用于提升页面更新性能
        pureDataPattern: /^_/, 
        multipleSlots: true // 在组件定义时的选项中启用多slot支持
    },
    properties: {
        vtabs: {type: Array, value: []},
    },
    data: {
        currentView: 0,
    },
    observers: { // 监测
        activeTab: function(activeTab) {
            this.scrollTabBar(activeTab);
        }
    }, 
    relations: {  // 关联的子/父组件
        '../vtabs-content/index': {
            type: 'child', // 关联的目标节点应为子节点
            linked: function(target) {
                this.calcVtabsCotentHeight(target);
            },
            unlinked: function(target) {
                delete this.data._contentHeight[target.data.tabIndex];
            }
        }
    },
    lifetimes: { // 组件声明周期
        created: function() {
            // 组件实例刚刚被创建好时
        },
        attached: function() {
            // 在组件实例进入页面节点树时执行
        },
        detached: function() {
            // 在组件实例被从页面节点树移除时执行
        },
    },
    methods: { // 组件方法
        calcVtabsCotentHeight(target) {}
    } 
});

Vue2 を知っている友人がいる場合は、このステートメントは非常によく知られていることがわかります。

ミニ プログラムが開始されると、コンストラクターは

開発者によって設定されたプロパティ、データ、メソッド、およびその他の定義セクションを Exparser のコンポーネント レジストリに書き込みます。このコンポーネントが他のコンポーネントによって参照される場合、これらの登録情報に基づいてカスタム コンポーネントのインスタンスを作成できます。

テンプレート ファイル wxml:

<view class=&#39;vtabs&#39;>
    <slot />
</view>

スタイル ファイル:

.vtabs {}

外部ページ コンポーネントを使用するには、json

を導入するだけです。ページのファイル
{
  "navigationBarTitleText": "商品分类",
  "usingComponents": {
    "vtabs": "../../../components/vtabs",
  }
}

ページを初期化すると、Exparser はページ ルート コンポーネントのインスタンスを作成し、使用される他のコンポーネントもコンポーネント インスタンスを作成することで応答します (これは再帰的なプロセスです):

コンポーネント作成の流れ 大きく分けて以下のような流れになります。

  • コンポーネントの登録情報に従い、コンポーネントのプロトタイプからコンポーネントノードの JS オブジェクトを作成します。コンポーネントの this

  • コンポーネント登録情報の data をコンポーネントデータ、つまり this としてコピーします。 .data;

  • このデータをコンポーネント WXML と結合し、Shadow Tree ( Shadow Tree 他のコンポーネントを参照する可能性があるため、これにより他のコンポーネントの作成プロセスが再帰的にトリガーされます;

  • ShadowTree を に接続します合成ツリー (最終的に結合されたページ ノード ツリー)、コンポーネントの更新パフォーマンスを最適化するためにいくつかのキャッシュ データを生成します。

  • 作成された をトリガーします。コンポーネントのライフサイクル関数;

  • ページルートコンポーネントでない場合は、コンポーネントノードの属性定義に従ってコンポーネントの属性値を設定する必要があります;

  • コンポーネント インスタンスがページに表示されるとき 起動すると、コンポーネントの

    attached ライフ サイクル関数がトリガーされます。Shadow Tree に他のコンポーネントがある場合は、 、それらのライフサイクル関数も 1 つずつトリガーされます。

コンポーネント通信

ビジネス上の責任により、大きなページを複数のコンポーネントに分割する必要がよくあります。コンポーネント間ではデータ通信が必要です。

世代間のコンポーネント通信については、グローバルな状態管理を考慮できます。ここでは、一般的な親子コンポーネント通信についてのみ説明します:

方法 1 WXML データ バインディング

データを子コンポーネントの指定されたプロパティに設定するために親コンポーネントによって使用されます。

子宣言プロパティ

Component({
    properties: {
        vtabs: {type: Array, value: []}, // 数据项格式为 `{title}`
    }
})

親コンポーネント呼び出し:

    <vtabs vtabs="{{ vtabs }}"</vtabs>

メソッド 2 イベント

用途子コンポーネントが親コンポーネントにデータを渡すには、任意のデータを渡すことができます。

サブコンポーネントからイベントをディスパッチするには、まずサブコンポーネントのクリック イベントを wxml 構造にバインドします:

   <view bindtap="handleTabClick">

次に、イベントを js ファイルにディスパッチします。イベント名はカスタマイズできます。 2 番目のパラメータはデータ オブジェクトを渡すことができ、3 番目のパラメータはイベント オプションです。

 handleClick(e) {
     this.triggerEvent(
         &#39;tabclick&#39;, 
         { index }, 
         { 
             bubbles: false,  // 事件是否冒泡
             // 事件是否可以穿越组件边界,为 false 时,事件只在引用组件的节点树上触发,
             // 不进入其他任何组件的内部
             composed: false,  
             capturePhase: false // 事件是否拥有捕获阶段 
         }
     );
 },
 handleChange(e) {
     this.triggerEvent(&#39;tabchange&#39;, { index });
 },

最後に、親コンポーネントをリッスンし、次を使用します:

<vtabs 
    vtabs="{{ vtabs }}"
    bindtabclick="handleTabClick" 
    bindtabchange="handleTabChange" 
>

#メソッド 3 selectComponent を使用して、コンポーネント インスタンス オブジェクトを取得します# #PassselectComponent

メソッドはサブコンポーネントのインスタンスを取得できるため、サブコンポーネントのメソッドを呼び出すことができます。

親コンポーネントの wxml

<view>
    <vtabs-content="goods-content{{ index }}"></vtabs-content>
</view>

親コンポーネントの js

Page({
    reCalcContentHeight(index) {
        const goodsContent = this.selectComponent(`#goods-content${index}`);
    },
})

selector は CSS セレクターと似ていますが、次の構文のみをサポートします。

  • ID选择器:#the-id(笔者只测试了这个,其他读者可自行测试)
  • class选择器(可以连续指定多个):.a-class.another-class
  • 子元素选择器:.the-parent > .the-child
  • 后代选择器:.the-ancestor .the-descendant
  • 跨自定义组件的后代选择器:.the-ancestor >>> .the-descendant
  • 多选择器的并集:#a-node, .some-other-nodes

方法四  url 参数通信

WeChat ミニ プログラムのコンポーネントをカスタマイズする方法の簡単な分析

在电商/物流等微信小程序中,会存在这样的用户故事,有一个「下单页面A」和「货物信息页面B」

  • 在「下单页面 A」填写基本信息,需要下钻到「详细页面B」填写详细信息的情况。比如一个寄快递下单页面,需要下钻到货物信息页面填写更详细的信息,然后返回上一个页面。
  • 在「下单页面 A」下钻到「货物页面B」,需要回显「货物页面B」的数据。

微信小程序由一个 App() 实例和多个 Page() 组成。小程序框架以栈的方式维护页面(最多10个) 提供了以下 API 进行页面跳转,页面路由如下

  • wx.navigateTo(只能跳转位于栈内的页面)

  • wx.redirectTo(可跳转位于栈外的新页面,并替代当前页面)

  • wx.navigateBack(返回上一层页面,不能携带参数)

  • wx.switchTab(切换 Tab 页面,不支持 url 参数)

  • wx.reLaunch(小程序重启)

可以简单封装一个 jumpTo 跳转函数,并传递参数:

export function jumpTo(url, options) {
    const baseUrl = url.split(&#39;?&#39;)[0];
    // 如果 url 带了参数,需要把参数也挂载到 options 上
    if (url.indexof(&#39;?&#39;) !== -1) {
        const { queries } = resolveUrl(url);
        Object.assign(options, queries, options); // options 的优先级最高
    } 
    cosnt queryString = objectEntries(options)
        .filter(item => item[1] || item[0] === 0) // 除了数字 0 外,其他非值都过滤
        .map(
            ([key, value]) => {
                if (typeof value === &#39;object&#39;) {
                    // 对象转字符串
                    value = JSON.stringify(value);
                }
                if (typeof value === &#39;string&#39;) {
                    // 字符串 encode
                    value = encodeURIComponent(value);
                }
                return `${key}=${value}`;
            }
        ).join(&#39;&&#39;);
    if (queryString) { // 需要组装参数
        url = `${baseUrl}?${queryString}`;
    }
    
    const pageCount = wx.getCurrentPages().length;
    if (jumpType === &#39;navigateTo&#39; && pageCount < 5) {
        wx.navigateTo({ 
            url,
            fail: () => { 
                wx.switch({ url: baseUrl });
            }
        });
    } else {
        wx.navigateTo({ 
            url,
            fail: () => { 
                wx.switch({ url: baseUrl });
            }
        });
    } 
}

jumpTo 辅助函数:

export const resolveSearch = search => {
    const queries = {};
    cosnt paramList = search.split(&#39;&&#39;);
    paramList.forEach(param => {
        const [key, value = &#39;&#39;] = param.split(&#39;=&#39;);
        queries[key] = value;
    });
    return queries;
};

export const resolveUrl = (url) => {
    if (url.indexOf(&#39;?&#39;) === -1) {
        // 不带参数的 url
        return {
            queries: {},
            page: url
        }
    }
    const [page, search] = url.split(&#39;?&#39;);
    const queries = resolveSearch(search);
    return {
        page,
        queries
    };
};

在「下单页面A」传递数据:

jumpTo({ 
    url: &#39;pages/consignment/index&#39;, 
    { 
        sender: { name: &#39;naluduo233&#39; }
    }
});

在「货物信息页面B」获得 URL 参数:

const sender = JSON.parse(getParam(&#39;sender&#39;) || &#39;{}&#39;);

url 参数获取辅助函数

// 返回当前页面
export function getCurrentPage() {
    const pageStack = wx.getCurrentPages();
    const lastIndex = pageStack.length - 1;
    const currentPage = pageStack[lastIndex];
    return currentPage;
}

// 获取页面 url 参数
export function getParams() {
    const currentPage = getCurrentPage() || {};
    const allParams = {};
    const { route, options } = currentPage;
    if (options) {
        const entries = objectEntries(options);
        entries.forEach(
            ([key, value]) => {
                allParams[key] = decodeURIComponent(value);
            }
        );
    }
    return allParams;
}

// 按字段返回值
export function getParam(name) {
    const params = getParams() || {};
    return params[name];
}

参数过长怎么办?路由 api 不支持携带参数呢?

虽然微信小程序官方文档没有说明可以页面携带的参数有多长,但还是可能会有参数过长被截断的风险。

我们可以使用全局数据记录参数值,同时解决 url 参数过长和路由 api 不支持携带参数的问题。

// global-data.js
// 由于 switchTab 不支持携带参数,所以需要考虑使用全局数据存储
// 这里不管是不是 switchTab,先把数据挂载上去
const queryMap = {
    page: &#39;&#39;,
    queries: {}
};

更新跳转函数

export function jumpTo(url, options) {
    // ...
    Object.assign(queryMap, {
        page: baseUrl,
        queries: options
    });
    // ...
    if (jumpType === &#39;switchTab&#39;) {
        wx.switchTab({ url: baseUrl });
    } else if (jumpType === &#39;navigateTo&#39; && pageCount < 5) {
        wx.navigateTo({ 
            url,
            fail: () => { 
                wx.switch({ url: baseUrl });
            }
        });
    } else {
        wx.navigateTo({ 
            url,
            fail: () => { 
                wx.switch({ url: baseUrl });
            }
        });
    }
}

url 参数获取辅助函数

// 获取页面 url 参数
export function getParams() {
    const currentPage = getCurrentPage() || {};
    const allParams = {};
    const { route, options } = currentPage;
    if (options) {
        const entries = objectEntries(options);
        entries.forEach(
            ([key, value]) => {
                allParams[key] = decodeURIComponent(value);
            }
        );
+        if (isTabBar(route)) {
+           // 是 tab-bar 页面,使用挂载到全局的参数
+           const { page, queries } = queryMap; 
+           if (page === `${route}`) {
+               Object.assign(allParams, queries);
+           }
+        }
    }
    return allParams;
}

辅助函数

// 判断当前路径是否是 tabBar
const { tabBar} = appConfig;
export isTabBar = (route) => tabBar.list.some(({ pagePath })) => pagePath === route);

按照这样的逻辑的话,是不是都不用区分是否是 isTabBar 页面了,全部页面都从 queryMap 中获取?这个问题目前后续探究再下结论,因为我目前还没试过从页面实例的 options 中拿到的值是缺少的。所以可以先保留读取 getCurrentPages 的值。

方法五 EventChannel 事件派发通信

前面我谈到从「当前页面A」传递数据到被打开的「页面B」可以通过 url 参数。那么想获取被打开页面传送到当前页面的数据要如何做呢?是否也可以通过 url 参数呢?

答案是可以的,前提是不需要保存「页面A」的状态。如果要保留「页面 A」的状态,就需要使用 navigateBack 返回上一页,而这个 api 是不支持携带 url 参数的。

这样时候可以使用 页面间事件通信通道 EventChannel。

pageA 页面

// 
wx.navigateTo({
    url: &#39;pageB?id=1&#39;,
    events: {
        // 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据
        acceptDataFromOpenedPage: function(data) {
          console.log(data) 
        },
    },
    success: function(res) {
        // 通过eventChannel向被打开页面传送数据
        res.eventChannel.emit(&#39;acceptDataFromOpenerPage&#39;, { data: &#39;test&#39; })
    }
});

pageB 页面

Page({
    onLoad: function(option){
        const eventChannel = this.getOpenerEventChannel()
        eventChannel.emit(&#39;acceptDataFromOpenedPage&#39;, {data: &#39;test&#39;});
   
        // 监听acceptDataFromOpenerPage事件,获取上一页面通过eventChannel传送到当前页面的数据
        eventChannel.on(&#39;acceptDataFromOpenerPage&#39;, function(data) {
          console.log(data)
        })
      }
})

会出现数据无法监听的情况吗?

小程序的栈不超过 10 层,如果当前「页面A」不是第 10 层,那么可以使用 navigateTo 跳转保留当前页面,跳转到「页面B」,这个时候「页面B」填写完毕后传递数据给「页面A」时,「页面A」是可以监听到数据的。

如果当前「页面A」已经是第10个页面,只能使用 redirectTo 跳转「PageB」页面。结果是当前「页面A」出栈,新「页面B」入栈。这个时候将「页面B」传递数据给「页面A」,调用 navigateBack 是无法回到目标「页面A」的,因此数据是无法正常被监听到。

不过我分析做过的小程序中,栈中很少有10层的情况,5 层的也很少。因为调用 wx.navigateBackwx.redirectTo 会关闭当前页面,调用 wx.switchTab 会关闭其他所有非 tabBar 页面。

所以很少会出现这样无法回到上一页面以监听到数据的情况,如果真出现这种情况,首先要考虑的不是数据的监听问题了,而是要保证如何能够返回上一页面。

比如在「PageA」页面中先调用 getCurrentPages 获取页面的数量,再把其他的页面删除,之后在跳转「PageB」页面,这样就避免「PageA」调用 wx.redirectTo导致关闭「PageA」。但是官方是不推荐开发者手动更改页面栈的,需要慎重。

如果有读者遇到这种情况,并知道如何解决这种的话,麻烦告知下,感谢。

使用自定义的事件中心 EventBus

除了使用官方提供的 EventChannel 外,我们也可以自定义一个全局的 EventBus 事件中心。 因为这样更加灵活,不需要在调用 wx.navigateTo 等APi里传入参数,多平台的迁移性更强。

export default class EventBus {
 private defineEvent = {};
 // 注册事件
 public register(event: string, cb): void { 
  if(!this.defineEvent[event]) {
   (this.defineEvent[event] = [cb]); 
  }
  else {
   this.defineEvent[event].push(cb); 
  } 
 }
 // 派遣事件
 public dispatch(event: string, arg?: any): void {
  if(this.defineEvent[event]) {{
            for(let i=0, len = this.defineEvent[event].length; i<len; ++i) { 
                this.defineEvent[event][i] && this.defineEvent[event][i](arg); 
            }
        }}
 }
 // on 监听
 public on(event: string, cb): void {
  return this.register(event, cb); 
 }
 // off 方法
    public off(event: string, cb?): void {
        if(this.defineEvent[event]) {
            if(typeof(cb) == "undefined") { 
                delete this.defineEvent[event]; // 表示全部删除 
            } else {
                // 遍历查找 
                for(let i=0, len=this.defineEvent[event].length; i<len; ++i) { 
                    if(cb == this.defineEvent[event][i]) {
                        this.defineEvent[event][i] = null; // 标记为空 - 防止dispath 长度变化 
                        // 延时删除对应事件
                        setTimeout(() => this.defineEvent[event].splice(i, 1), 0); 
                        break; 
                    }
                }
            }
        } 
    }

    // once 方法,监听一次
    public once(event: string, cb): void { 
        let onceCb = arg => {
         cb && cb(arg); 
         this.off(event, onceCb); 
        }
        this.register(event, onceCb); 
    }
    // 清空所有事件
    public clean(): void {
        this.defineEvent = {}; 
    }
}

export connst eventBus = new EventBus();

在 PageA 页面监听:

eventBus.on(&#39;update&#39;, (data) => console.log(data));

在 PageB 页面派发

eventBus.dispatch(&#39;someEvent&#39;, { name: &#39;naluduo233&#39;});

小结

本文主要讨论了微信小程序如何自定义组件,涉及两个方面:

  • 组件的声明与使用
  • 组件的通信

如果你使用的是 taro 的话,直接按照 react 的语法自定义组件就好。而其中的组件通信的话,因为 taro 最终也是会编译为微信小程序,所以 url 和 eventbus 的页面组件通信方式是适用的。后续会分析 vant-ui weapp 的一些组件源码,看看有赞是如何实践的。

感谢阅读,如有错误的地方请指出

【相关学习推荐:小程序开发教程

以上がWeChat ミニ プログラムのコンポーネントをカスタマイズする方法の簡単な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。