封裝 avm 元件經驗分享

海的盡頭發表於2022-07-12

avm.js 是一個跨端開發框架,AVM(Application-View-Model)前端元件化開發模式基於標準Web Components元件化思想,提供包含虛擬DOM和Runtime的程式設計框架avm.js以及多端統一編譯工具,完全相容Web Components標準,同時相容Vue和React語法糖編寫程式碼,編譯工具將Vue和React相關語法糖編譯轉換為avm.js程式碼。

有Vue和React 開發經驗的很容易上手。

1. 元件的定義和引用:

     1.1 使用stml定義一個元件 / 頁面

stml元件相容Vue單檔案元件(SFC)規範,使用語義化的Html模板及物件化js風格定義元件或頁面。stml最終被編譯為JS元件 / 頁面,渲染到不同終端。

定義元件:

// api-test.stml:
<template>  
    <view class='header'>
        <text>{this.data.title}</text>
    </view>  
</template>  
<script>
    export default {  
        name: 'api-test',
        data(){
            return {
                title: 'Hello APP'
            }
        }
    }
</script>
<style scoped>
    .header{
        height: 45px;
    }
</style>

  1.2 元件引用:

// app-index.stml:

<template>  
    <view class="app">  
        <img src="./assets/logo.png" />  
        <api-test></api-test> 
    </view>  
</template>
<script>
    import './components/api-test.stml'  
    
    export default {  
        name: 'app-index',  
        data: function () {  
            return {
                title: 'Hello APP'
            }
        }  
    }  
</script>  
<style>  
    .app {   
        text-align: center;  
        margin-top: 60px;  
    }  
</style>

2. 向子元件傳值

向子元件傳值採用 props 的方式,這裡以一個示例來進行說明。

定義子元件,在 props 裡面註冊一個 title 屬性:

// api-test.stml:

<template>
    <text>{title}</text>
</template>
<script>
    export default {
        name:'api-test',
        props:{
            title: String
        }
    }
</script>

這裡定義的title屬性型別為String,屬性型別包括 String、Number、Boolean、Array、Object、Function等。

     2.1 在其它頁面使用子元件時傳遞靜態值:

// app-index.stml:

<template>  
    <view>  
        <api-test title="Hello App!"></api-test> 
    </view>  
</template>
<script>
    import './components/api-test.stml'  
    
    export default {  
        name: 'app-index'
    }  
</script>

     2.2 通過資料繫結傳遞動態值:

// app-index.stml:

<template>
    <view>
        <api-test v-bind:title="msg"></api-test>
    </view>
</template>
<script>
    import './components/api-test.stml'  
    
    export default {
        name: 'app-index',
        data() {
            return {
              msg: 'Hello App!'
          }
        }
    }
</script>

傳遞靜態值時只能傳遞字串型別資料,通過資料繫結的方式則可以傳遞任意型別的資料。

3. 監聽子元件事件**

監聽子元件事件和監聽普通事件類似,如:

// api-index.stml:

<template>
    <view>
        <api-test onresult="onGetResult"></api-test>
    </view>
</template>
<script>
    import './components/api-test.stml'  
    
    export default {
        name: 'app-index',
        methods: {
            onGetResult(e){
                console.log(e.detail.msg);
            }
        }
    }
</script>

以上示例中監聽了子元件的result事件,子元件裡面通過fire方法來觸發監聽的事件:

// app-test.stml:

<template>
    <text onclick="onclick">Hello App!</text>
</template>
<script>
    export default {
        name:'api-test',
        methods:{
            onclick(){
                let detail = {msg:'Hi'};
                this.fire('result', detail);
            }
        }
    }
</script>

fire方法有兩個引數,第一個引數為事件名稱,第二個引數為要傳遞的自定義資料,在父元件監聽方法裡面通過e.detail獲取傳遞的資料。

// api-index.stml:

methods: {
  onGetResult(e){
      console.log(e.detail.msg);
  }
}

4.  聲網元件例項

瞭解了以上元件的規則和用法,就可以封裝自己的元件了 。下面看一個基於agoraRtc聲網模組,實現1對1語音通話的元件例項:

<template>
    <view class="agorartc-call-voice_page">
        <safe-area></safe-area>
        <view class="agorartc-call-voice_list" v-for="(item,index) in userList">
            <view class="agorartc-call-voice_userinfo">
                <image class="agorartc-call-voice_userinfo-image" thumbnail='false'
                    style="width: 64px; height: 64px; margin-right:10px" src={{item.userimg }}></image>
                <text class="agorartc-call-voice_userinfo-text">{{item.username }}</text>
            </view>
            <view class="agorartc-call-voice_callimg">
                <image @click="fnstart_voice_call(item.userid)" thumbnail='false' style="width: 50px; height: 50px"
                    src="../../image/voice-call.png"></image>
            </view>
        </view>
        <view class="agorartc-call-voice_connected" v-if="connected">
            <image class="agorartc-call-voice_voice" thumbnail='false' style="width: 200px;"
                src="../../image/video-voice.gif"></image>
            <image class="agorartc-call-voice_hangup" @click="fnhangup()" thumbnail='false'
                style="width: 64px; height: 64px;" src="../../image/video-hangup.png"></image>
        </view>
    </view>
</template>
<script>
export default {
    name: 'agorartc-call-voice',
    props: {
        channel: String,
        userList: Array,
        rtcAppId: String
    },

    installed() {
        this.fnishasper_mic();
    },
    data() {
        return {
            connected: false
        };
    },
    methods: {
        fnishasper_mic(_userid) {
            var resultList = api.hasPermission({
                list: ["microphone"]
            });
            if (resultList[0].granted) {

            } else {
                api.toast({
                    msg: "需要啟用麥克風許可權"
                });
                api.requestPermission({
                    list: ["microphone"]
                }, res => {
                    if (res.list[0].granted) {

                    }
                });
            }
        },
        fnstart_voice_call(_userid) {
            this.fnrtc_init();
            this.fnerr_listener();
            this.fnjoin_channel(_userid);
        },
        fnrtc_init() {
            console.log('初始化');
            var agoraRtc = api.require('agoraRtc');
            agoraRtc.init({
                appId: this.props.rtcAppId
            });
        },
        fnjoin_channel(_userid) {
            console.log('121:---' + _userid);
            this.data.connected = true;
            var agoraRtc = api.require('agoraRtc');
            agoraRtc.joinChannelSuccessListener(function (ret) {
                console.log(ret.uid + 'uid------');
            });

            agoraRtc.remoteUserJoinedListener((ret) => {
                console.log(ret.uid + 'remoteUserJoinedListener------');
                if (ret.uid) {
                    this.data.connected = true;
                }
            });
            // 多人語音通話 ,需設定角色為主播 
            agoraRtc.setClientRole({
                role: 1
            }, function (ret) {
                if (ret.code == 0) {
                    //success
                    console.log('設定主播模式成功')
                }
            });

            agoraRtc.enableAudio((ret) => {
                if (ret.code == 0) {
                    //success
                    console.log('開啟音訊成功---' + this.props.channel);
                    agoraRtc.joinChannel({
                        channel: this.props.channel,
                        uid: _userid
                    }, function (ret) {
                        if (ret.code == 0) {
                            console.log('加入頻道成功');
                        }
                    });
                }
            });

            agoraRtc.remoteUserOfflineListener((ret) => {
                api.toast({
                    msg: '對方已結束通話'
                })
                this.fnhangup();
            });
        },
        fnerr_listener() {
            var agoraRtc = api.require('agoraRtc');
            agoraRtc.errorListener(function (ret) {
                if (ret.errorCode == 0) {
                    var agoraRtc = api.require('agoraRtc');
                    agoraRtc.leaveChannel(function (ret) {
                        if (ret.code == 0) { //success
                        }
                    });
                    api.toast({
                        msg: '通話出錯!'
                    });
                }
            });
        },
        fnhangup() {
            var agoraRtc = api.require('agoraRtc');
            agoraRtc.leaveChannel(function (ret) {
                if (ret.code == 0) {
                    //success
                }
            });
            this.data.connected = false;
        }
    }
};
</script>
<style>
.agorartc-call-voice_page {
    height: 100%;
    width: 100%;
    background-color: #fff;
}

.agorartc-call-voice_list {
    height: 64px;
    width: 100%;
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: flex-start;
    margin-bottom: 10px;
}

.agorartc-call-voice_userinfo {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: flex-start;
    align-items: center;
    padding-left: 20px;
}

.agorartc-call-voice_callimg {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: flex-end;
    align-items: center;
    flex-grow: 2;
    padding-right: 20px;
}

.agorartc-call-voice_connected {
    position: absolute;
    top: 0;
    left: 0;
    background-color: #fff;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    align-items: center;
}

.agorartc-call-voice_hangup {
    margin-top: 30px;
}
</style>

avm.js 預設使用 flex 彈性盒子佈局,實現UI時,應充分利用 flex 彈性佈局原理進行佈局。而實現聲網語音通話的核心邏輯很簡單:兩個使用者加入同一個頻道即可 。

相關文章