一、技术栈
taro + react + taro-ui + mobx
最初因为内嵌有h5页面,h5是vue开发的,故选择了taro的vue版本进行开发。但是后续问了小程序开发经验丰富的同学,他说vue版的容易出问题,建议还是使用react版本的taro开发小程序。我就去网上查了下资料,还真是如此。官方虽然对vue,react语法都支持,但是其状态管理setData的设计思路明显更偏向于react,整理的设计思路更适合react。所以我们的项目最后将语法转换为了react版本的。
二、遇到的问题
1.页面报错:一个页面只能允许一个web-view
image.pngweb-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
网友评论