taro开发小程序遇到的问题

一、技术栈

taro + react + taro-ui + mobx
最初因为内嵌有h5页面,h5是vue开发的,故选择了taro的vue版本进行开发。但是后续问了小程序开发经验丰富的同学,他说vue版的容易出问题,建议还是使用react版本的taro开发小程序。我就去网上查了下资料,还真是如此。官方虽然对vue,react语法都支持,但是其状态管理setData的设计思路明显更偏向于react,整理的设计思路更适合react。所以我们的项目最后将语法转换为了react版本的。

二、遇到的问题

1.页面报错:一个页面只能允许一个web-view

image.png

web-view类似于pc端的iframe,可以将其他站点的内容直接嵌套到本地项目中使用。但是web-view会全屏展开,它不允许和其他内容同时显示。
最初,我把web-view和主页面的逻辑写一起,当没登录展示主页面逻辑,登录后,跳转到web-view页面

render(){
  if(login){
    return  <WebView src='https://www.baidu.com' />
  }
  return <View>登录逻辑</View>
}

这样写页面有些逻辑更改,导致setState变更时候,会导致页面重新渲染,这样页面就报错一个页面只能插入一个web-view。
于是转换思路,将web-view单独抽离出来写成一个页面,这个问题就迎刃而解啦,如下

//login.js
 if(this.token){ //如果获取到了token就跳转到web-view页面
      Taro.redirectTo({
            url: `/pages/webView/index?token=${this.token}`
        })
 }
//webView.js
render(){
  return  <View>
                <WebView src='https://www.baidu.com' />
          </View>
  }

2.上线后web-view内容频繁白屏

本地真机联调,预览和体验版本都没有问题,但是提交审核通过发布后的版本,会频繁白屏。这种本地没法复现的bug,只能通过其他途径解决了。
第一:先看看出问题的手机型号及微信版本号,在到对应的小程序开发工具上调试到对应版本看看能否复现问题。


image.png

第二:看看web-view的src是否的拼接而成。
若是拼接的,可能会导致部分手机白屏,我之前的src是用 src + token拼接而成的。解决思路如下:

// store 
import {observable } from "mobx";
export class WXLoginClass {   
    @observable src=''
    @observable localSrc=''
    @observable isShowWebView=false
  
    changeSrc(src){
        this.src = src
        this.localSrc = src
        this.isShowWebView = true
    }
    hideWebView(){
        this.src = ''
        this.localSrc = ''
        this.isShowWebView = false
    }
}
export default new WXLoginClass();
// webview.js
import { Component } from 'react'
import { WebView,View} from '@tarojs/components'
import { observer,inject } from 'mobx-react'
@inject('webViewStore')
@observer
class Index extends Component {
    myTimeId = null

    componentWillMount(){
        // 进页面前先清空数据
        this.props.webViewStore.hideWebView()
    }

    onLoad(options){ // 第一次进入这个页面时候 调用,第二次不会调用
        let src = `https://www.baidu.com`
        if(obj.token){ // obj.token是自己获取的token
            src = src + `?token=${obj.token}`
        }
        this.myTimeId = setTimeout(()=>{
            this.props.webViewStore.changeSrc(src)
        },0)        
    }

   componentWillUnmount(){ //组件销毁时候清空定时器
       clearTimeout(this.myTimeId)
    }

    render () {
        let {src,isShowWebView} = this.props.webViewStore
        return  <View>
            {
                isShowWebView && 
                <WebView src={src} onMessage={this.shareTheProject.bind(this)}/>
            }
        </View>
    }
}
export default Index

这样就解决了安卓机上web-view内嵌页面白屏的bug。

3.web-view内部h5页面触发右上角分享逻辑

因为内嵌页面是H5页面,没法直接触发微信小程序的api,但是小程序里提供了web-view分享的方法。
h5页面的入口index.html里引入js,router里写逻辑

//index.html
 <script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>
//h5里面的router.js
router.beforeEach(async (to, from, next) => {
  wx.miniProgram.postMessage({data:{ url: to.path ,name:to.meta}})
  .....
  next()
})

web-view页面写分享的逻辑
当web-view里的页面点击右上角的分享按钮时候,会触发onMessage 里的shareTheProject函数,这个函数能拿到当前转发的页面路径url和当前页面的名称name,然后触发webView.js里的分享逻辑onShareAppMessage,可以在这个函数里写转发的路径,和要传递的参数。若是有转发成功后要处理的逻辑,可以写在success这个回调里。

// store
import {observable } from "mobx";

export class WXLoginClass {
    @observable shareObj = {} //分享的对象
    changeShareInfo(e){
        this.shareObj = e.detail.data[e.detail.data.length - 1]
        let url = this.shareObj.url
        // url路径里若是有token要去除
        url = url.includes('?token') ? url.split('?token')[0]:url
        this.shareObj.url = url
    }
}
export default new WXLoginClass();
// webview.js

@inject('webViewStore')
@observer
class Index extends Component {
   
    shareTheProject(e) {
        this.props.webViewStore.changeShareInfo(e)
    }

    onShareAppMessage(options) {   //转发时执行
        return {
            title: '转发的主题',
            path:'/pages/index/index?value=123&share=true',//小程序向h5传递参数
            success(e) {
                wx.showShareMenu({
                    // 要求小程序返回分享目标信息
                    withShareTicket: true
                });
            },
        }
    }
   
    render () {
        return  <View>
                <WebView src='https://www.baidu.com' onMessage={this.shareTheProject.bind(this)}/>
        </View>
    }
}
export default Index

4. getStorage首次获取数据不生效

当你后面的操作必须依赖于storage的数据,这时候就需要使用存储的api。直接使用setStorage,第一次获取数据获取不到,就是因为这个api是异步的。若要每次都能获取到数据,则需要使用同步存储的api setStorageSync,使用方法如下:

// 异步存储
Taro.setStorage({
  key:"key",
  data:"value"
})

// 获取异步存储
Taro.getStorage({
  key: 'key',
  success: function (res) {
    console.log(res.data)
  }
})

// 同步存储
try {
  //同步和异步不同,同步是对象,异步直接是key,value
  Taro.setStorageSync('key', 'value')
} catch (e) { }

// 获取同步存储
try {
  var value = Taro.getStorageSync('key')
  if (value) {
    // Do something with return value
  }
} catch (e) {
  // Do something when catch error
}

两者的区别:
异步就是不管保没保存成功,程序都会继续往下执行。同步是保存成功了,才会执行下面的代码.
使用异步,性能会更好;而使用同步,数据会更安全

5.小程序-高德地图定位的使用

微信小程序|高德地图api官网
第一步:去官网注册获取个key

第二步:获取地址,按官网提示一步步来操作即可。


获取地址

class Index extends Component {
    state = {
        gaodeMapAds: '', // 高德地图的位置
    }
  
    componentWillMount(){
        // 自动获取定位
        this.onLoadAddress()
    }
   
     //高德地图获取 地理位置 
    onLoadAddress() {
        let that = this;
      //ADDRESS_KEY 是申请的高德地图的key
        let myAmapFun = new amapFile.AMapWX({key:ADDRESS_KEY});
        myAmapFun.getRegeo({
            success: function(data){  //成功回调
                let res = data[0] && data[0].regeocodeData.addressComponent
                res.formatted_address = data[0] && data[0].regeocodeData.formatted_address
                let {province,city,district} = res
                if(typeof city === 'object'){
                    city = province.substr(0,2) + '城区'
                }
                that.setState({
                    gaodeMapAds:`${province} ${city} ${district}`
                })
            },
            fail: function(info){
                // wx.onLocationChange
                //失败回调
                console.log(info)
            }
        })
    }
    
    render () {
        const {gaodeMapAds} = this.state
        return (
            <View className='index'>
               <AtInput      
                    editable={false}
                    title='地理位置:'
                    value={gaodeMapAds}
                    > </AtInput> 
            </View>
        )
    }
}

export default Index

调取获取定位的接口返回的结果如下:


image.png

如要获取省市区的code,直接从regeocode.addressComponent.adcode字段获取。

// 省份的code
   provinceCode = parseInt(adcode / 1000) * 1000 + ''
// 城市的code
   citysCode = parseInt(adcode / 100) * 100 + ''
//区域的code
   areasCode = adcode

6.picker省市区位置选择器

微信小程序提供了省市区选择器picker,只需要设置picker的mode属性为'region',省市区的内容就会自动填充。

 <picker mode="region" bindchange="bindRegionChange" value="{{region}}" custom-item="{{customItem}}">
    <view class="picker">
      当前选择:{{region[0]}},{{region[1]}},{{region[2]}}
    </view>
  </picker>
picker

具体使用说明可参考微信小程序picker使用说明

若对省市区没有特别要求,直接用微信提供的api即可。但是我们对省市区有特殊要求,需从接口获取省市区数据,因此这个mode='region' 属性不可用,需使用mode='multiSelector'属性。
这里需要注意的是,我们限制了用户必须省市区都要选中才行,因为若不满足条件就给出提示,但是因为picker点击确定就会关闭弹框没有阻止弹框关闭的api,所以这里一定要记录你上次选中的数据,在数据不满足条件弹框关闭时候,根据上次记录的数据恢复省市区的数据和上次选中数据的坐标,确保用户下次再打开弹框时候,选中的是上次的数据。

// store.js
import Taro from "@tarojs/taro";
import { observable } from "mobx";
import { WXRequest } from '../pages/api/request';
// 获取省市区接口的封装
import {getProvinces, getCities, getDistrict} from '../pages/api/buss'

export class WXLoginClass {
    @observable province = ''
    @observable city = ''
    @observable area = ''
    @observable provinces =[]
    @observable citys = []
    @observable areas = []
    @observable value = [0, 0, 0]   
    // 三级地址
    @observable.shallow ranges = [[], [], []]
    nextSureVal = [0, 0, 0]

    async addressChange() {
        let newarr = [],newNameArr =[]
        let res = await getProvinces();
        if (res.code == 200) {
            for (let i = 0; i < res.data.length; i++) {
                const resItem = res.data[i]
                const item = {
                    name: resItem.name,
                    value: resItem.citycode,
                }
                newarr.push(item)
                newNameArr.push(resItem.name)
            }
            this.ranges[0] = [...newNameArr]
            this.provinces = [...newarr]
            const cityCode = res.data[0] && res.data[0].citycode
            if(!cityCode) {
                this.citys = []
                this.ranges[1] = []
                this.areas = []
                this.ranges[2] = []
                return 
            }
            await this.getCities(cityCode)
        }
    }

    async getCities(id){
        if(!id) {
            this.ranges[1] = []
            return
        }
        let res = await getCities(id)
        let newarr = [],newNameArr =[]
        this.citys = []
        this.ranges[1] = []
        if (res.code == 200) {
            for (let i = 0; i < res.data.length; i++) {
                const resItem = res.data[i]
                const item = {
                    name: resItem.name,
                    value: resItem.citycode,
                }
                newarr.push(item)
                newNameArr.push(resItem.name)
            }
            this.ranges[1] = ['请选择',...newNameArr]
            this.citys = [option,...newarr]
        }
    }

    async getDistrict(id){
        if(!id) {
            this.ranges[2] = []
            return
        }
        let res = await getDistrict(id)
        let newarr = [],newNameArr =[]
        this.areas = []
        this.ranges[2] = []
        if (res.code == 200) {
            for (let i = 0; i < res.data.length; i++) {
                const resItem = res.data[i]
                const item = {
                    name: resItem.name,
                    value: resItem.citycode,
                }
                newarr.push(item)
                newNameArr.push(resItem.name)
            }
            this.ranges[2] = ['请选择',...newNameArr]
            this.areas = [option,...newarr]
        }
    }

    async columnChange(e) {
        const { provinces, citys, value } = this
        const column = e.detail.column
        const evalue = e.detail.value
        let provinceNum = value[0]
        let cityNum = value[1]
        let countyNum = value[2]
        // 记录上次的位置
        if (column == 0) { //选则了省份
            provinceNum = evalue
            const id = provinces[provinceNum].value
            await this.getCities(id)
            this.areas = []
            this.ranges[2] = []
            this.value = [provinceNum, 0, 0]
        } else if (column == 1) { //选则了城市
            cityNum = evalue
            const id = citys[cityNum].value
            this.getDistrict(id)
            this.value = [provinceNum, cityNum, 0]
        } else {
            // 滑动选择了区
            countyNum = evalue
            this.value = [provinceNum, cityNum, countyNum]
        }
    }

    // 不合乎规范或者取消时候,让rang和value的值都复原到上一次的状态
    async handlePickerHide(){
        const {provinces, citys, areas} = this
        this.value = this.nextSureVal
        const idCity =  provinces[this.value[0]].value 
        const eareCity =  citys[this.value[1]].value 
        idCity ? await this.getCities(idCity) : null
        eareCity ? await this.getDistrict(eareCity) : null
    }

    //  params true代表传递地址,false不传递
    async handlePickerShow(params: boolean) {
        const changeVal = params.detail.value
        const { provinces, citys, areas, value } = this
        if (params) {
            const p = provinces[value[0]] && provinces[value[0]].name
            const c = citys[value[1]] && citys[value[1]].name
            if(c==='请选择') { //city必须有
                Taro.showToast({
                    title:'城市必须选择',
                    duration:2000, //   持续时间
                    icon:'none',//  'success'、'loading'、'none'
                    mask:false,  //是否显示透明蒙层,防止触摸穿透
                })
                this.value = this.nextSureVal
                const idCity = this.provinces[this.value[0]].value 
                if(idCity){
                    await this.getCities(idCity)
                }
                const eareCity = this.citys[this.value[1]].value
                if(eareCity){
                    await this.getDistrict(eareCity)
                }
                return 
               
            }
            let a = areas[value[2]] && areas[value[2]].name
            if(!a || a === '请选择') {
                Taro.showToast({
                    title:'区域必须选择',
                    duration:2000, //   持续时间
                    icon:'none',//  'success'、'loading'、'none'
                    mask:false,  //是否显示透明蒙层,防止触摸穿透
                })
                this.value = this.nextSureVal
                const eareCity = this.citys[this.value[1]].value 
                if(eareCity){
                    this.getDistrict(eareCity)
                }
                return 
            }
            this.isAmapAddress = false //用户手动选择地址
            this.province = p || ''
            this.city = c || ''
            this.area = a || ''
            this.nextSureVal = this.value
        }
    }
}
export default new WXLoginClass();
// picker.js
@inject('Login')
@observer
class MyPicker extends Component {
  componentWillMount(){
      // 省份信息可以异步就获取
     this.props.Login.addressChange()
  }
    // 位置滑动触发
    columnChange(e) {
       this.props.Login.columnChange(e)
    }
       
    //  点击确定时候触发 params true代表传递地址,false不传递
    handlePickerShow(params: boolean) {
        this.props.Login.handlePickerShow(params)
    }

    handlePickerHide(){
        this.props.Login.handlePickerHide()
    }

    render() {
        let {ranges,value} = this.props
        ranges = ranges.slice()
        value = value.slice()
        return (
            <View className='mypicker' >
                <picker 
                    mode='multiSelector' 
                    range={ranges} 
                    value = {value}
                    onChange={this.handlePickerShow.bind(this)} 
                    onColumnChange={this.columnChange.bind(this)}
                    onCancel = {this.handlePickerHide.bind(this)}
                >
                    <View>
                        <AtIcon value='add-circle' size='20'></AtIcon>
                    </View>
                </picker>
            </View>
        )
    }
}
export default MyPicker

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容