mpvue開發小程式總結

tao892509844發表於2018-10-15

前期準備

1.框架選型

原生小程式開發方式與vue有些類似,所以用過vue的前端er會很容易上手。但是原生的開發體驗實在糟糕,在前端元件化的今天用原生開發元件顯得很無力。對於習慣vue開發方式的前端er來說mpvue再合適不過了。mpvue可以將H5程式碼打包成小程式程式碼,目前mpvue還做不到一套程式碼多端執行(畢竟各個端有自己的差異性,小程式沒有document和window,所以那些第三方移動端元件庫並不能適用於小程式),但是已經大大減少了開發的工作量。

2.專案的搭建

mpvue開發小程式總結

3.專案結構

project └───build └───config └───dist └───node_modules └───server//mock伺服器 └───src     └───assets        └───sass        |    common.scss   // 全域性樣式    └───components    └───pages    └───plugins //存放封裝的外掛    └───services    |    Api.js   // 封裝請求    |    WxApi.js   // 對小程式api二次封裝        └───store        └───modules // vuex模組資料夾        |    index.js // vuex處理檔案        |    action.js        |    state.js        |    mutations.js         |    type.js複製程式碼
    |   App.vue    |   config.js//配置資訊,如請求地址等    |   main.js└───static    //靜態資源    └───images//圖片    └───font//字型圖示│   README.md│   package.json  │   package-lock.json  複製程式碼

3.其他

小程式網路請求只能是https協議且不能有埠號

登入流程

1.關於OpenId和UnionId

OpenId 是一個使用者對於一個小程式/公眾號的標識,開發者可以通過這個標識識別出使用者。

UnionId 是一個使用者對於同主體微信小程式/公眾號/APP的標識,開發者需要在微信開放平臺下繫結相同賬號的主體。開發者可通過UnionId,實現多個小程式、公眾號、甚至APP 之間的資料互通了。

同一個使用者的這兩個 ID 對於同一個小程式來說是永久不變的,就算使用者刪了小程式,下次使用者進入小程式,開發者依舊可以通過後臺的記錄標識出來。

2.官方給出的最佳實踐

新版的小程式對獲取使用者資訊授權進行了改動,使用者在小程式中需要點選元件後,才可以觸發登入授權彈窗、授權自己的暱稱頭像等資料。因此官方給出的最佳實踐是

1.呼叫 wx.login 獲取 code,然後從微信後端換取到 session_key,用於解密 getUserInfo返回的敏感資料。
2.使用 wx.getSetting 獲取使用者的授權情況
1) 如果使用者已經授權,直接呼叫 API wx.getUserInfo 獲取使用者最新的資訊;
2) 使用者未授權,在介面中顯示一個按鈕提示使用者登入,當使用者點選並授權後就獲取到使用者的最新資訊。
3.獲取到使用者資料後可以進行展示或者傳送給自己的後端。

mpvue開發小程式總結

3.實現

  1. 製作一個登入頁面
  2. 在App.vue裡的onLaunch生命週期中判斷Storage中是否存在,如不存在跳轉到登入頁並把當前頁面的路由當作引數傳遞過去,如存在再呼叫wx.checkSession()檢查session_key 的有效性否則跳到登入頁並把當前頁面的路由當作引數傳遞過去
  3. 在ajax請求response攔截器裡判斷狀態碼為401表示token已過期,跳轉到登入頁重新登陸並把當前頁面的路由當作引數傳遞過去
  4. 登入按鈕加上open-type=”getUserInfo”屬性,並監聽getuserinfo事件,使用者點選後會返回加密後的使用者資訊,此時執行wx.login()獲取到code,將code和使用者資訊傳送到後臺換取token,並把token儲存到Storage

附官方的最佳實踐mp.weixin.qq.com/s/JBdC-G9Mw…

vuex模組化

在開發時有時會遇到一些變數需要跨兩三個頁面傳遞公用,所以引入vuex是比較好的解決方案。當一個專案比較大時,所有的狀態都集中在一起會得到一個比較大的物件,進而顯得臃腫,難以維護。為了解決這個問題,Vuex允許我們將store分割成模組(module),每個module有自己的state,mutation,action,getter。新建以下目錄

     store        └───modules // vuex模組資料夾               └───  kx //一級模組                 └───  jkdbb//二級模組                    | index.js                      ...        |    index.js // vuex處理檔案        |    action.js        |    state.js        |    mutations.js         |    type.js複製程式碼

action,state,mutations,type作為公共。

在index.js中設定namespaced為true

export default { 
namespaced: true, state: {
ywmkid: '1', formData:{
}
}, mutations:{
SET_YWMKID (state,ywmkid) {
state.ywmkid = ywmkid
}, SET_FORMDATA (state,formData) {
state.formData = formData
}
}
}複製程式碼

在store下的index.js

import Vue from 'vue'import Vuex from 'vuex'import state from './state'import mutations from './mutations'import actions from './actions'import jkdbb from './module/kx/jkdbb/index'Vue.use(Vuex)const isDebug = process.env.NODE_ENV !== 'production'export default new Vuex.Store({ 
state, mutations, actions, modules:{
jkdbb
}, strict: isDebug
})複製程式碼

訪問模組裡的state

this.$store.state.jkdbb.ywmkid複製程式碼

修改模組裡的state

this.$store.commit('jkdbb/SET_YWMKID',this.$route.query.id)複製程式碼

mpvue-router-patch

vue-router不相容小程式,所以為了和h5程式碼保持一致選用mpvue-router-patch,其和vue-router類似的使用方式,預設對應小程式的navigateTo。附小程式的三種跳轉方式的區別

wx.navigateTo:跳轉到某個頁面。會產生一個新的頁面
wx.redirectTo:重定向到某個頁面。替換掉當前頁面
wx.switchTab:切換tab
wx.reLaunch:重新啟動。清空之前的所有頁面
在小程式中有層級的限制,只能開啟十個頁面,因此要合理利用跳轉的方式。

在需要重啟應用時只需要設定reLaunch為true即可

this.$router.push({path:'/pages/index/main',reLaunch: true
})複製程式碼

在需要切換tab時只需要設定switchTab為true即可

this.$router.push({path:'/pages/index/main',switchTab: true
})複製程式碼

附完整api地址github.com/F-loat/mpvu…

flyio

axios不相容小程式,flyio相容Node.js微信小程式WeexReact NativeQuick App 和瀏覽器。以下的程式碼是對flyio的使用進行封裝

import Config from '@/config'import {CacheUtil
} from '@/services/WxApi' let Fly=require("flyio/dist/npm/wx") let fly=new Fly;
class Response {
constructor (res) {
this.rawData = res this.code = res.code this.messages = res.messages this.data = res.data this.wwRequestEntity = res.wwRequestEntity
} resolve () {
if (this.isSuccess()) {
return Promise.resolve(this.rawData)
} if (this.isError()) {
let message = Config.defErrorMessage
} return Promise.reject(this.messages)
} isSuccess () {
return this.code === 1
} isError () {
return this.code === 0
}
}class ApiManager {
constructor (apiPrefix) {
fly.config.baseURL = apiPrefix || Config.apiPrefix;
fly.config.timeout = 120000 fly.interceptors.request.use(request =>
{
const token = CacheUtil.getStorage('token');
if (token) {
// 判斷是否存在token,如果存在的話,則每個http header都加上token request.headers.Authorization = `Bearer ${token
}
`;

} return request
}) fly.interceptors.response.use(res =>
{
if (res.status >
= 200 &
&
res.status <
300) {
let response = new Response(res.data) return response.resolve()
} return Promise.reject(res)
}, error =>
{
const {
response
} = error;
if (!response) return Promise.reject(error) if (response.status === 401) {
//沒有許可權跳轉到授權頁面 return Promise.reject(null)
} return Promise.reject(error)
})
} post(uri, data, config){
return fly.post(uri, data, config);

} get(uri, data){
return fly.get(uri, data);

} put (uri, data) {
return fly.put(uri, data)
} delete (uri, data) {
return fly.delete(uri, data)
}
}export function httpManager(baseURL){
return new ApiManager(baseURL)
}export let apiManager = httpManager()複製程式碼

在main.js引用並掛載

Vue.prototype.apiManager = apiManager;
複製程式碼

之後在頁面中就可以這樣使用

this.apiManager.post複製程式碼

二次封裝Storage

小程式的Storage沒有設定過期時間的引數,我們可以對他進行二次封裝

export const CacheUtil = { 
getStorage(key){
let data = wx.getStorageSync(key);
if (!data) {
return null;

}else{
let now = new Date().getTime() if (now >
= parseInt(data.expired)) {
wx.removeStorageSync(key) return null;

}else{
return data.data;

}
} return
}, setStorage(key,data,expired){
let time = new Date().getTime() + expired;
wx.setStorageSync(key,{data:data,expired:time
})
}, removeStorage(key){
wx.removeStorageSync(key)
}
}複製程式碼

表單驗證

小程式的表單驗證似乎不多,常用的就是WxValidate。但是我覺得不是那麼好用,提示判斷還要自己寫。所以採用we-validator,它支援微信小程式、支付寶小程式、瀏覽器以及Nodejs端使用。我們可以在plugins資料夾中新建一個js用於新增自定義的校驗規則

import WeValidator from 'we-validator'WeValidator.addRule('Mobile', function(value, param){ 
const reg = 11 &
&
/^(((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8
})$/;
return reg.test(value)
})export default{
WeValidator
}複製程式碼

然後再main.js中引用並掛載

Vue.prototype.$WeValidator = WeValidator.WeValidator;
複製程式碼

在頁面中

    mounted(){ 
this.oValidator = new this.$WeValidator({
rules: {
sbmc: {
required: true
}, sbxh: {
required: true
}, fx: {
required: true
}, yxqk: {
required: true
}, azrq: {
required: true
}, ssqx: {
required: true
}, azdz: {
required: true
}
}, messages: {
sbmc: {
required: '請選擇裝置名稱'
}, fx: {
required: '請選擇方向'
}, sbmc: {
required: '請選擇裝置名稱'
}, yxqk: {
required: '請選擇執行情況'
}, azrq: {
required: '請選擇安裝日期'
}, ssqx: {
required: '請選擇所屬區縣'
}, azdz: {
required: '請輸入安裝地址'
}
}
})
}, methods:{
submit(){
if(this.oValidator.checkData(this.form)){

}
}
}複製程式碼

獲取當前位置

小程式的api獲取當前位置返回的資訊比較少,如有需要獲取到地址需要引入地圖的地址解析。我用的是騰訊地圖,在引用的時候會報錯,是因為它不是用export匯出的,所以可以在最後修改為export default QQMapWX。

import QQMapWX from '../plugins/qqmap-wx-jssdk.js'let qqmapsdk = new QQMapWX({ 
key: config.txMapKey
});
export function getPosition(){
return new Promise((resolve,reject)=>
{
wx.showLoading({
title:'正在定位', mask:true
}) wx.getLocation({
success:function(res){
qqmapsdk.reverseGeocoder({
coord_type:0, location: {
latitude: res.latitude, longitude: res.longitude
}, success: function(data) {
resolve({
latitude: res.latitude, longitude: res.longitude, adcode: data.result.ad_info.adcode, addr:data.result.address
})
}, fail: function(err) {
reject(err);

}, complete: function(err) {
wx.hideLoading()
}
});

}, fail:function(err){
reject(err)
}
})
})
}複製程式碼

封裝input元件

我們可以將input做成一個元件,可以去配置輸入框的標題,清空輸入框等功能。isShowArea如下一條所說的作用。在向父頁面傳遞值時需要一個settimeout,如果不新增會有閃爍的bug。

<
template>
<
div class="yg-input">
<
label :placeholder="placeholder" :style="{width:labelWidth
}"
>
{{label
}
}<
/label>
<
div class="yg-input-control yg-textarea-control" v-if="type=='textarea'">
<
textarea v-if="isShowArea" :maxlength="maxlength" v-bind="$attrs" :cursor-spacing="100" :placeholder="placeholder" :disabled="disabled||readonly" :value="currentVal" @input="updateVal($event.target.value)">
<
/textarea>
<
div v-if="!isShowArea">
{{currentVal
}
}<
/div>
<
/div>
<
div class="yg-input-control" v-else>
<
input :placeholder="placeholder" :maxlength="maxlength" :cursor-spacing="100" :disabled="disabled||readonly" :value="currentVal" v-bind="$attrs" @input="updateVal($event.target.value)" :type="type" />
<
i v-if="value &
&
$attrs.readonly!==!!'true'"
@click="clear" class="iconfont iconfont-close">
<
/i>
<
/div>
<
/div>
<
/template>
<
script>
export default {
inheritAttrs: false, data() {
return {
currentVal:this.value
}
}, computed:{
isShowArea(){
return this.$store.state.mask;

}
}, methods:{
clear(){
this.currentVal = '';
this.$emit('input', '');

}, updateVal(val) {
this.currentVal = val;
setTimeout(()=>
{
this.$emit('input', val);

},0)
}
}, watch:{
value: {    handler(newValue, oldValue){
this.currentVal = newValue;
    
}   
}
}, props: {
label:{
default:'', type:String
}, labelWidth:{
default:'75px', type:String
}, type:{
default:'text', type:String
}, value:{
default:'', type:String
}, placeholder:{
default:'', type:String
}, maxlength:{
default:'-1', type:String
}, disabled:{
default:false, type:Boolean
}, readonly:{
default:false, type:Boolean
}
}
}<
/script>
<
style lang="scss">
<
/style>
複製程式碼

可以將它定義為全域性元件,在main.js中

Vue.component('yg-input',YgInput)複製程式碼

在父頁面使用

<
yg-input :readonly="status==3" v-model="form.sbxh" label="裝置型號" placeholder="請輸入裝置型號">
<
/yg-input>
複製程式碼

textarea的bug

textarea是原生元件因此層級最高,因此在頁面上自定義的彈窗會被textarea的內容覆蓋。因此我們可以在彈窗是將它隱藏,在關閉彈窗時顯示。可以在vuex中定義一個變數控制,然後在彈窗元件中對這個變數賦值。

<
textarea v-if="isShowArea">
<
/textarea>
<
div v-if="!isShowArea">
{{currentVal
}
}<
/div>
//在textarea隱藏時顯示textarea填寫的內容複製程式碼

還有一個問題是textarea在ios真機上會有一個預設的padding導致和其他表單元素不對齊。這個官方還是沒修復。

封裝select元件

原生的picker不能自定義樣式,而卻選項的文字多了會顯示…,專案中還要顯示中英文所以自己寫了一個

<
template>
<
div class="yg-input yg-select">
<
label :style="{width:labelWidth
}"
>
{{label
}
}<
/label>
<
div class="yg-input-control" @click="showPicker">
<
input :value="selectVal" :placeholder="placeholder" v-bind="$attrs" :disabled="true"/>
<
i class="iconfont iconfont-arrow-r">
<
/i>
<
/div>
<
div class="popup-mark" v-if="toggle">
<
ul class="yg-popup">
<
li class="yg-popup-head">
<
span @click.stop="cancel">
取消<
/span>
<
span @click.stop="confirm">
確定<
/span>
<
/li>
<
li v-for="(item,$index) in selectOption" :class="{'active':item.active
}"
:key="index" @click.stop="itemClick(item)">
{{item.text
}
}<
/li>
<
/ul>
<
/div>
<
/div>
<
/template>
<
script>
export default {
inheritAttrs: false, data() {
return {
selectVal:'', toggle:false, cache_selectVal:'', cache_selectTxt:'', isFirstEnter:true, selectOption:[]
}
}, methods:{
cancel(){
this.selectOption = [...this.setActive(this.value,this.option)];
this.hide();

}, confirm(){
if(this.selectVal != this.cache_selectTxt&
&
this.cache_selectTxt){
this.selectVal = this.cache_selectTxt;
this.$emit('input', this.cache_selectVal);
this.$emit('change', this.cache_selectVal,this.cache_selectTxt);

} this.hide();

}, itemClick(item){
this.cache_selectVal = item.value;
this.cache_selectTxt = item.text;
this.selectOption = [...this.setActive(item.value,this.option)];

}, setActive(value,arr){
for(let item of arr){
if(value==item.value){
item.active=true;

}else{
item.active=false;

}
} return arr;

}, hide(){
this.$store.commit('SET_MARK',true);
this.toggle = false;

}, showPicker(){
if(this.disabled||this.readonly){
return false;

} if(!this.option.length){
this.$message('沒有相關選項');
return false;

} this.$store.commit('SET_MARK',false);
this.toggle = true;

}
}, props: {
label:{
default:'', type:String
}, disabled:{
default:false, type:Boolean
}, readonly:{
default:false, type:Boolean
}, labelWidth:{
default:'75px', type:String
}, value:{
default:'', type:String
}, placeholder:{
default:'', type:String
}, option:{// default:function(){
return []
}, type:Array
}, defaultFirst:{
default:false, type:Boolean
}
}, watch:{
value: {    handler(newValue, oldValue){
this.selectOption = this.selectOption.map((item)=>
{
if(this.value==item.value){
item.active=true;
this.selectVal=item.text;

}else{
item.active=false;

} return item;

})    
}, immediate: true   
}, option: {    handler(newValue, oldValue){
this.selectOption = [...newValue];
let _value = '';
for(let item of newValue){
if(this.value==item.value){
_value = item.value;
break;

}
} if(_value){
this.$emit('input', _value);

} if(this.option.length!==0&
&
!_value&
&
this.defaultFirst){
this.$emit('input', this.option[0].value);

} if(this.option.length==0&
&
!_value&
&
!this.isFirstEnter){
this.$emit('input', '');
this.isFirstEnter = false;

}     
},    deep: true, immediate: true   
}
}
}<
/script>
<
style lang="scss">
@keyframes popup{
0%{
transform:translate(0,100%);

} 100%{
transform:translate(0,0)
}
} .popup-mark{
position:fixed;
z-index:9999;
background:rgba(0,0,0,0.4);
top:0;
left:0;
right:0;
bottom:0;
display:flex;
align-items:flex-end;
.yg-popup{
animation:popup .3s;
flex:1;
max-height:800px;
min-height:500px;
overflow:auto;
background:#fff;
li{
&
.yg-popup-head{
background:#fbf9fe;
color:#999;
line-height: 1;
padding:0;
overflow:hidden;
padding:30px;
span:nth-of-type(1){
float: left;

} span:nth-of-type(2){
float: right;
color:#2b79fb;

}
} background:#fff;
border-bottom:1px solid #eee;
line-height: 1;
padding:30px 50px 30px 30px;
font-size:32px;
/*no*/ color:#666;
position:relative;
&
.active{
color:#2b79fb;
&
:before{
content:'';
position:absolute;
width:8PX;
height:8PX;
border-radius:50%;
top:50%;
background:#2b79fb;
margin-top: -4PX;
right: 40px;

}
}
}
}
}<
/style>
複製程式碼

下拉重新整理上拉載入

1.上拉載入更多

在列表的下面新增以下節點,loadTip預設為空,用於載入時的提示

<
div class="page-list-loading">
{{loadTip
}
}<
/div>
複製程式碼

定義maxPage表示最後一頁

在onReachBottom鉤子里載入下一頁

onReachBottom(){ 
this.loadTip = '正在載入中';
this.form.page++;
this.loadData();

},複製程式碼

在loadData的回撥裡

this.list = this.list.concat(res.data);
this.loadTip = '';
this.maxPage = Math.ceil(res.total/this.form.size);
//算出最後一頁複製程式碼

2.下拉重新整理

在需要下拉重新整理的頁面同級新建main.json

{ 
"enablePullDownRefresh": true
}複製程式碼

在onPullDownRefresh鉤子里載入資料

this.maxPage = null;
this.form.page = 1;
this.list = [];
this.loadTip = '';
wx.showNavigationBarLoading();
//顯示標題欄的載入動畫//請求成功後wx.hideNavigationBarLoading();
wx.stopPullDownRefresh();
//停止下拉複製程式碼

截至2018-10-15,下拉重新整理還存在bug,ios上頭部head設定position為fixed時會擋住載入動畫,在安卓上頭部head又會跟隨下滑

粘性導航

在h5中可以方便地利用window物件獲取滾動距離,元素距離頂部距離等。但是在小程式中並沒有window物件只能使用小程式的api獲取相關資料。

<
template>
<
div class="Home">
<
div class="com-part-class">
//導航 <
div class="affix">
<
div :class="{'fixed':isfixed,'nav-list-wrap':true
}"
>
<
div class="nav-list">
<
div v-for="(item,index) in affixArr" @click="changeSt(index)" :class="{'active':navActive==index,'nav-item':true
}"
:key="index">
{{item
}
} <
/div>
<
/div>
<
/div>
//內容 <
div class="affix-pane-wrap">
<
div v-for="(item, index) in indexDataLists" :key="index" :class="item.style">
<
div class="com-bar-ti">
{{item.label
}
} <
/div>
<
app-list @jump="toPage" :parent-index="index" @loadMore="loadMore" :more="true" :data="item.data">
<
/app-list>
<
/div>
<
/div>
<
/div>
<
/div>
<
/div>
<
/div>
<
/template>
<
script>
function getNavActiveIndex(num,arr){//返回第幾個高亮 if(num===0){
return 0;

} for(var i=0;
i<
arr.length;
i++){
if(num>
=arr[i]){
if (!arr[i + 1]) {
return arr.length - 1;

} else if (num <
arr[i + 1]) {
return i;

}
}else if (num <
arr[i]) {
if (!arr[i - 1]) {
return 0;

} else if (num >
= arr[arr - 1]) {
return i-1;

}
}
}
}import AppList from './AppList.vue'export default {
data () {
return {
offsetTopArr:[], indexDataLists:[], affixArr:[], navActive:0, isfixed:false
}
}, components: {
AppList
}, methods: {
changeSt(index){//點選導航跳轉對應內容區 this.navActive = index;
wx.pageScrollTo({
scrollTop: this.offsetTopArr[index]
})
}
}, onPageScroll(e){
const query = wx.createSelectorQuery();
query.select('.affix').boundingClientRect((res)=>
{//判斷相對於顯示區域的距離是否小於0即頁面滾動到導航的位置 if(res){
if(res.top<
0){
this.isfixed = true;

}else{
this.isfixed = false;
this.navActive = 0;

}
}
}).exec();
this.navActive = getNavActiveIndex(e.scrollTop,this.offsetTopArr);

}, mounted () {
this.$showLoading();
Promise.all([this.apiManager.post('/home/indexLists'),this.apiManager.post('/home/frequentlyUsed'),this.apiManager.post('/gaywtz/wrapNotice')]).then(res=>
{
this.affixArr = res[0].data[0].indexLabelLists this.indexDataLists=res[0].data[0].indexDataLists;
setTimeout(()=>
{
const query = wx.createSelectorQuery();
query.selectAll('.affix-pane').boundingClientRect((res)=>
{
res.map((item)=>
{
this.offsetTopArr.push(item.top-45);
//45是導航的高度,用px佈局
})
}).exec();
this.$hideLoading();

}, 200)
})
}
}<
/script>
複製程式碼

px2rpx

rpx 是微信小程式解決自適應螢幕尺寸的尺寸單位。以iPhone6為例,螢幕寬度為375px,把它分為750rpx後, 1rpx = 0.5px。px2rpx可以很方便地把px轉成rpx。文件中說和px2rem使用一樣,但是當某個屬性並不需要將px轉成rpx時,設定/*no*/並沒有用,解決辦法是把px改為PX。

mpvue-wxparse

後臺返回的富文字編輯器中的內容是html節點,這在小程式中是解析不出來的。因此需要mpvue-wxparse去轉換

jsimport wxParse from 'mpvue-wxparse'components:{ 
wxParse
}html<
wxParse :content="content" @preview="preview" @navigate="navigate" />
複製程式碼

所有頁面的created鉤子函式在小程式開啟時就全部執行

改用mouted鉤子

重新進入頁面會保留之前的資料

假設有三個頁面:列表頁A,表單頁一B,表單頁二C。當填寫完表單頁C後跳轉到列表頁,這時如果再從列表頁進入表單頁B會發現B還是之前填寫的資料。這是因為mpvue在離開頁面時並沒有呼叫destroyed鉤子,因此目前的解決方案是在小程式的onUnload中重置data函式裡的資料。可以將它封裝成一個外掛

const resetPageData = { 
install: function (Vue) {
Vue.mixin({
onUnload() {
if (this.$options.data) {
Object.assign(this.$data, this.$options.data())
}
}
})
}
}export default resetPageData;
複製程式碼

然後在main.js中使用

Vue.use(resetPageData);
複製程式碼

其他需要注意的

1.background不支援本地路徑

2.不能使用v-show需替換成v-if

3.在map中使用cover-view需要直接使用cover-view,如使用div會有問題,文件中寫到目前cover-view支援動畫,開發者工具中有效實際在真機無效,且不支援單邊border,rotate等

4.solt不支援動態渲染,在封裝業務元件時很是蛋疼

5.不支援自定義指令

最後吐槽

小程式還很年輕,還存在很多不足(bug超多),開發體驗說實話目前為止感覺不太好。但是什麼技術都不可能一出來就做得很完美,作為前端只能跟著潮流掌握這些新技術了。

來源:https://juejin.im/post/5bc00a8bf265da0ab503a0fd#comment

相關文章