久久久久A亚洲V无码专区首页|2021国产精品午夜久久|国产欧美日韩高清在线不卡|日韩精品无码视频免费专区

    <strong id="l5ws5"><span id="l5ws5"></span></strong>
    <rp id="l5ws5"></rp>
          1. <rt id="l5ws5"></rt>
            <rt id="l5ws5"></rt>
              <tt id="l5ws5"><form id="l5ws5"></form></tt>
              <video id="l5ws5"></video>
              <u id="l5ws5"></u>

                <b id="l5ws5"></b>

              1. 韶關市淺析微信小程序中自定義組件的方法

                發布日期:2022-04-21 16:02瀏覽次數:

                韶關微信小程序中怎么自定義組件?下面本篇文章給大家介紹一下微信小程序中自定義組件的方法,希望對大家有所幫助!


                在微信小程序開發過程中,對于一些可能在多個頁面都使用的頁面模塊,可以把它封裝成一個組件,以提高開發效率。雖然說我們可以引入整個組件庫比如 weui、vant 等,但有時候考慮微信小程序的包體積限制問題,通常封裝為自定義的組件更為可控。

                并且對于一些業務模塊,我們就可以封裝為組件復用。本文主要講述以下兩個方面:

                • 組件的聲明與使用
                • 組件通信

                組件的聲明與使用

                微信小程序的組件系統底層是通過 Exparser 組件框架實現,它內置在小程序的基礎庫中,小程序內的所有組件,包括內置組件和自定義組件都由 Exparser 組織管理。

                自定義組件和寫頁面一樣包含以下幾種文件:

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

                以編寫一個 tab 組件為例: 編寫自定義組件時需要在 json 文件中講 component 字段設為 true

                1

                2

                3

                {

                    "component": true

                }

                在 js 文件中,基礎庫提供有 Page 和 Component 兩個構造器,Page 對應的頁面為頁面根組件,Component 則對應:

                1

                2

                3

                4

                5

                6

                7

                8

                9

                10

                11

                12

                13

                14

                15

                16

                17

                18

                19

                20

                21

                22

                23

                24

                25

                26

                27

                28

                29

                30

                31

                32

                33

                34

                35

                36

                37

                38

                39

                40

                41

                42

                43

                44

                45

                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 的小伙伴,會發現這個聲明很熟悉。

                在小程序啟動時,構造器會將開發者設置的properties、data、methods等定義段,

                寫入Exparser的組件注冊表中。這個組件在被其它組件引用時,就可以根據這些注冊信息來創建自定義組件的實例。

                模版文件 wxml:

                1

                2

                3

                <view class='vtabs'>

                    <slot />

                </view>

                樣式文件:

                1

                .vtabs {}

                外部頁面組件使用,只需要在頁面的 json 文件中引入

                1

                2

                3

                4

                5

                6

                {

                  "navigationBarTitleText": "商品分類",

                  "usingComponents": {

                    "vtabs": "../../../components/vtabs",

                  }

                }

                在初始化頁面時,Exparser 會創建出頁面根組件的一個實例,用到的其他組件也會響應創建組件實例(這是一個遞歸的過程):

                組件創建的過程大致有以下幾個要點:

                • 根據組件注冊信息,從組件原型上創建出組件節點的 JS 對象,即組件的 this;

                • 將組件注冊信息中的 data 復制一份,作為組件數據,即 this.data;

                • 將這份數據結合組件 WXML,據此創建出 Shadow Tree(組件的節點樹),由于 Shadow Tree 中可能引用有其他組件,因而這會遞歸觸發其他組件創建過程;

                • 將 ShadowTree 拼接到 Composed Tree(最終拼接成的頁面節點樹)上,并生成一些緩存數據用于優化組件更新性能;

                • 觸發組件的 created 生命周期函數;

                • 如果不是頁面根組件,需要根據組件節點上的屬性定義,來設置組件的屬性值;

                • 當組件實例被展示在頁面上時,觸發組件的 attached 生命周期函數,如果 Shadow Tree 中有其他組件,也逐個觸發它們的生命周期函數。

                組件通信

                由于業務的負責度,我們常常需要把一個大型頁面拆分為多個組件,多個組件之間需要進行數據通信。

                對于跨代組件通信可以考慮全局狀態管理,這里只討論常見的父子組件通信:

                方法一 WXML 數據綁定

                用于父組件向子組件的指定屬性設置數據。

                子聲明 properties 屬性

                1

                2

                3

                4

                5

                Component({

                    properties: {

                        vtabs: {type: Array, value: []}, // 數據項格式為 `{title}`

                    }

                })

                父組件調用:

                1

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

                方法二 事件

                用于子組件向父組件傳遞數據,可以傳遞任意數據。

                子組件派發事件,先在 wxml 結構綁定子組件的點擊事件:

                1

                <view bindtap="handleTabClick">

                再在 js 文件中進行派發事件,事件名可以自定義填寫, 第二個參數可以傳遞數據對象,第三個參數為事件選項。

                1

                2

                3

                4

                5

                6

                7

                8

                9

                10

                11

                12

                13

                14

                15

                16

                handleClick(e) {

                    this.triggerEvent(

                        'tabclick',

                        { index },

                        {

                            bubbles: false// 事件是否冒泡

                            // 事件是否可以穿越組件邊界,為 false 時,事件只在引用組件的節點樹上觸發,

                            // 不進入其他任何組件的內部

                            composed: false

                            capturePhase: false // 事件是否擁有捕獲階段

                        }

                    );

                },

                handleChange(e) {

                    this.triggerEvent('tabchange', { index });

                },

                最后,在父組件中監聽使用:

                1

                2

                3

                4

                5

                <vtabs

                    vtabs="{{ vtabs }}"

                    bindtabclick="handleTabClick"

                    bindtabchange="handleTabChange"

                >

                方法三 selectComponent 獲取組件實例對象

                通過 selectComponent 方法可以獲取子組件的實例,從而調用子組件的方法。

                父組件的 wxml

                1

                2

                3

                <view>

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

                </view>

                父組件的 js

                1

                2

                3

                4

                5

                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 參數通信

                1.png

                在電商/物流等微信小程序中,會存在這樣的用戶故事,有一個「下單頁面A」和「貨物信息頁面B」

                • 在「下單頁面 A」填寫基本信息,需要下鉆到「詳細頁面B」填寫詳細信息的情況。比如一個寄快遞下單頁面,需要下鉆到貨物信息頁面填寫更詳細的信息,然后返回上一個頁面。
                • 在「下單頁面 A」下鉆到「貨物頁面B」,需要回顯「貨物頁面B」的數據。

                微信小程序由一個 App() 實例和多個 Page() 組成。小程序框架以棧的方式維護頁面(最多10個) 提供了以下 API 進行頁面跳轉,頁面路由如下

                • wx.navigateTo(只能跳轉位于棧內的頁面)

                • wx.redirectTo(可跳轉位于棧外的新頁面,并替代當前頁面)

                • wx.navigateBack(返回上一層頁面,不能攜帶參數)

                • wx.switchTab(切換 Tab 頁面,不支持 url 參數)

                • wx.reLaunch(小程序重啟)

                可以簡單封裝一個 jumpTo 跳轉函數,并傳遞參數:

                1

                2

                3

                4

                5

                6

                7

                8

                9

                10

                11

                12

                13

                14

                15

                16

                17

                18

                19

                20

                21

                22

                23

                24

                25

                26

                27

                28

                29

                30

                31

                32

                33

                34

                35

                36

                37

                38

                39

                40

                41

                42

                43

                export function jumpTo(url, options) {

                    const baseUrl = url.split('?')[0];

                    // 如果 url 帶了參數,需要把參數也掛載到 options 上

                    if (url.indexof('?') !== -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 === 'object') {

                                    // 對象轉字符串

                                    value = JSON.stringify(value);

                                }

                                if (typeof value === 'string') {

                                    // 字符串 encode

                                    value = encodeURIComponent(value);

                                }

                                return `${key}=${value}`;

                            }

                        ).join('&');

                    if (queryString) { // 需要組裝參數

                        url = `${baseUrl}?${queryString}`;

                    }

                     

                    const pageCount = wx.getCurrentPages().length;

                    if (jumpType === 'navigateTo' && pageCount < 5) {

                        wx.navigateTo({

                            url,

                            fail: () => {

                                wx.switch({ url: baseUrl });

                            }

                        });

                    } else {

                        wx.navigateTo({

                            url,

                            fail: () => {

                                wx.switch({ url: baseUrl });

                            }

                        });

                    }

                }

                jumpTo 輔助函數:

                1

                2

                3

                4

                5

                6

                7

                8

                9

                10

                11

                12

                13

                14

                15

                16

                17

                18

                19

                20

                21

                22

                23

                24

                25

                export const resolveSearch = search => {

                    const queries = {};

                    cosnt paramList = search.split('&');

                    paramList.forEach(param => {

                        const [key, value = ''] = param.split('=');

                        queries[key] = value;

                    });

                    return queries;

                };

                 

                export const resolveUrl = (url) => {

                    if (url.indexOf('?') === -1) {

                        // 不帶參數的 url

                        return {

                            queries: {},

                            page: url

                        }

                    }

                    const [page, search] = url.split('?');

                    const queries = resolveSearch(search);

                    return {

                        page,

                        queries

                    };

                };

                在「下單頁面A」傳遞數據:

                1

                2

                3

                4

                5

                6

                jumpTo({

                    url: 'pages/consignment/index',

                    {

                        sender: { name: 'naluduo233' }

                    }

                });

                在「貨物信息頁面B」獲得 URL 參數:

                1

                const sender = JSON.parse(getParam('sender') || '{}');

                url 參數獲取輔助函數

                1

                2

                3

                4

                5

                6

                7

                8

                9

                10

                11

                12

                13

                14

                15

                16

                17

                18

                19

                20

                21

                22

                23

                24

                25

                26

                27

                28

                29

                // 返回當前頁面

                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 不支持攜帶參數的問題。

                1

                2

                3

                4

                5

                6

                7

                // global-data.js

                // 由于 switchTab 不支持攜帶參數,所以需要考慮使用全局數據存儲

                // 這里不管是不是 switchTab,先把數據掛載上去

                const queryMap = {

                    page: '',

                    queries: {}

                };

                更新跳轉函數

                1

                2

                3

                4

                5

                6

                7

                8

                9

                10

                11

                12

                13

                14

                15

                16

                17

                18

                19

                20

                21

                22

                23

                24

                25

                export function jumpTo(url, options) {

                    // ...

                    Object.assign(queryMap, {

                        page: baseUrl,

                        queries: options

                    });

                    // ...

                    if (jumpType === 'switchTab') {

                        wx.switchTab({ url: baseUrl });

                    } else if (jumpType === 'navigateTo' && pageCount < 5) {

                        wx.navigateTo({

                            url,

                            fail: () => {

                                wx.switch({ url: baseUrl });

                            }

                        });

                    } else {

                        wx.navigateTo({

                            url,

                            fail: () => {

                                wx.switch({ url: baseUrl });

                            }

                        });

                    }

                }

                url 參數獲取輔助函數

                1

                2

                3

                4

                5

                6

                7

                8

                9

                10

                11

                12

                13

                14

                15

                16

                17

                18

                19

                20

                21

                22

                // 獲取頁面 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;

                }

                輔助函數

                1

                2

                3

                // 判斷當前路徑是否是 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 頁面

                1

                2

                3

                4

                5

                6

                7

                8

                9

                10

                11

                12

                13

                14

                //

                wx.navigateTo({

                    url: 'pageB?id=1',

                    events: {

                        // 為指定事件添加一個監聽器,獲取被打開頁面傳送到當前頁面的數據

                        acceptDataFromOpenedPage: function(data) {

                          console.log(data)

                        },

                    },

                    success: function(res) {

                        // 通過eventChannel向被打開頁面傳送數據

                        res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' })

                    }

                });

                pageB 頁面

                1

                2

                3

                4

                5

                6

                7

                8

                9

                10

                11

                Page({

                    onLoad: function(option){

                        const eventChannel = this.getOpenerEventChannel()

                        eventChannel.emit('acceptDataFromOpenedPage', {data: 'test'});

                    

                        // 監聽acceptDataFromOpenerPage事件,獲取上一頁面通過eventChannel傳送到當前頁面的數據

                        eventChannel.on('acceptDataFromOpenerPage', 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.navigateBack 、wx.redirectTo 會關閉當前頁面,調用 wx.switchTab 會關閉其他所有非 tabBar 頁面。

                所以很少會出現這樣無法回到上一頁面以監聽到數據的情況,如果真出現這種情況,首先要考慮的不是數據的監聽問題了,而是要保證如何能夠返回上一頁面。

                比如在「PageA」頁面中先調用 getCurrentPages 獲取頁面的數量,再把其他的頁面刪除,之后在跳轉「PageB」頁面,這樣就避免「PageA」調用 wx.redirectTo導致關閉「PageA」。但是官方是不推薦開發者手動更改頁面棧的,需要慎重。

                如果有讀者遇到這種情況,并知道如何解決這種的話,麻煩告知下,感謝。

                使用自定義的事件中心 EventBus

                除了使用官方提供的 EventChannel 外,我們也可以自定義一個全局的 EventBus 事件中心。 因為這樣更加靈活,不需要在調用 wx.navigateTo 等APi里傳入參數,多平臺的遷移性更強。

                1

                2

                3

                4

                5

                6

                7

                8

                9

                10

                11

                12

                13

                14

                15

                16

                17

                18

                19

                20

                21

                22

                23

                24

                25

                26

                27

                28

                29

                30

                31

                32

                33

                34

                35

                36

                37

                38

                39

                40

                41

                42

                43

                44

                45

                46

                47

                48

                49

                50

                51

                52

                53

                54

                55

                56

                57

                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 {

                                // 遍歷查找