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

青灯夜游
リリース: 2022-05-16 21:02:30
転載
6329 人が閲覧しました

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 までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート