uni-app完整实战
背景
小程序具有跨平台、体验好、高灵活性以及即用即走、无需下载安装诸多优势。随着微信团队推出微信小程序,国内各大互联网团队也相继推出了各自的小程序,手机厂商也联合推出了快应用。但由于各种原因,各团队的小程序,并没有形成统一的标准或联盟,导致部分平台差异化大,要实现多个平台的小程序就得写多套小程序代码,这给开发者和企业带来不少额外的负担。这种情况下,一个多端统一的解决方案就显得尤为重要。统一多端标准后,让一处代码,多处运行成为了可能。
多端构建方案选型
目前多端构建有不少的解决方案,通常在技术选型方面,我们会考虑以下几个维度:
- 框架的生态及其社区的大小
- 框架的性能
- 框架的学习成本与开发成本
- 框架支持构建的应用端(如是否支持快应用和QQ小程序)
- 团队的技术栈的匹配程度
从社区上的各种实战案例和测试案例来看,目前Taro
和uni-app
是相对比较成熟、完善的多端解决方案。uni-app
作为国内小程序的开创者,也制定了一套基于Vue.js
的解决方案,并跻身前列,成为多端构建解决方案的优秀者之一。本文将就uni-app
作为多端方案,讲解如何使用uni-app
多端构建框架一步一步搭建一个完整的项目,并就搭建过程中遇到的一些问题给出一些解决方案。
参考快递100小程序的案例,快递100算是国内小程序中服务类小程序一直排名靠前的小程序了,其目前也是基于uni-app
实现的多端统一的小程序,从效果来看,使用uni-app
作为多端统一的方案,不会有啥大的问题。
项目搭建
创建项目
uni-app
支持通过 可视化界面、vue-cli
命令行 两种方式快速创建项目。本文将通过以HBuilderX
可视化界面创建项目为例,从零到一搭建起一个完整的项目。安装的方法请查看uni-app
官网文档之快速上手
使用uni-app
内置组件模板创建后的目录结构如下:
各目录和文件说明如下:
components
: 公共组件目录,默认内置了uni-app
的官方扩展组件,应用的公共组件也将放置在该目录下pages
:业务页面目录,一个页面对应小程序的一个页面路由static
: 存放应用引用静态资源(如图片、视频等)的目录,静态资源只能存放在此目录,该目录下的内容不会经过编译App.vue
: 全局应用文件,用于配置全局样式、全局生命钩子等main.js
:应用初始化文件,一般全局的扩展均在该文件实现mainfest.json
: 应用配置文件,主要包含各个小程序的appid、版本号等信息pages.json
:路由配置,主要配置应用的页面路由、外观等uni.scss
:全局的scss变量文件,在该文件定义的scss变量全局可用
安装后通过HBuilderX
运行到微信小程序(需要配置微信开发者工具的路径),效果如下:
接下来让我们一步一步来完善整个应用。
自定义全局API
在开发的过程中,我们经常需要重复的使用一些方法,如http请求相关的方法,通常我们会将这些方法封装为统一的模块。在这里,我们把公共的方法统一放到项目根目录下的utils
目录下。
反馈类UI相关API封装
用户操作的过程中,经常需要一些操作反馈,如toast
,modal
等。不同的应用一般都有不同的默认反馈(如文案、字体颜色等),我们首先来封装一下这些方法,相对比较简单。
(1)toast & modal
在utils
目录下创建interactiveFeedback.js
文件
第一步,新建showToast
和modal
方法,如下:
// toast反馈
export function showToast(content = '', duration = 1500, icon = 'none') {
uni.showToast({
title: content,
icon: icon,
mask: true,
duration: duration
})
}
// 弹窗反馈
export function modal(content = '', opts = { showCancel: true }) {
return new Promise((resolve, reject) => {
uni.showModal({
title: opts.title || '提示',
content: content,
cancelText: (opts.cancelText || '取消').slice(0, 4),
confirmText: (opts.confirmText || '确定').slice(0, 4),
cancelColor: opts.cancelColor || '#bebebe',
confirmColor: opts.confirmColor || '#317ee7',
showCancel: opts.showCancel !== false,
success: res => {
if (res.confirm) {
resolve()
} else if (res.cancel) {
opts.handleCancel === true && reject()
}
}
})
})
}
在小程序中,confirmText
和cancelText
长度最长为4个字符,以上modal
方法遵循promise
规范
第二步,定义install
方法
function install(Vue) {
Vue.prototype.$toast = showToast
Vue.prototype.$modal = modal
}
可以根据实际情况定义更多的方法,如loading
,hideLoading
等
第三步,导出相关的方法
export default { install }
第四步,打开main.js
文件,安装模块
import interactiveFeedback from '@/utils/interactiveFeedback'
Vue.use(interactiveFeedback)
安装后,我们就可以用以下的方式使用:
this.$toast("这是一个吐司提示")
this.$modal("这里是弹窗的提醒内容", {
confirmText: "我知道了",
handleCancel: true
}).then(() => {
console.log("确定了")
}).catch(() => {
console.log('取消了')
})
http请求封装
http模块专门用来做接口请求,通常一个应用中接口都会有公共的参数、请求头、授权信息等,将这些信息统一封装非常有必要。由于项目上经常要用到一些常量等,因此,我们先在项目根目录下创建config.js
文件,用于配置项目上使用的一些常量(如请求超时、接口服务器地址、小程序appid
等),这里不一一列举,参照项目源码的config.js
文件,接着在utils
目录下创建request.js
文件
第一步,引入常量和其他公共方法配置
import store from '@/store'
import { showToast } from './interactiveFeedback.js'
import {
API_BASE,
NETWORK_TIMEOUT,
DEFAULT_ERR_MESSAGE,
PLATFORM,
NETWORK_ERR_MESSAGE
} from '@/config'
第二步,定义两个核心处理方法
/**
* 基本的http请求
* @param {url: String} 请求的链接
* @param {opts.data: Object} 请求的参数
* @param {opts.needAuth: Boolean } 是否需要登录
* @param {opts.handleFail: Boolean} 是否处理错误信息,设置为false将忽略请求时的错误
* @param {opts.accessToken: Boolean} 是否将token覆盖为提供商token
* */
export async function request(url, opts = {}) {
opts.data = opts.data || {}
if (opts.needAuth) {
const userinfo = await store.dispatch('user/getUserInfo') // 获取用户信息
opts.data.openid = store.getters.openid
opts.data.token = store.getters.token
opts.data.unionid = store.getters.unionid
}
return handleRequest(url, opts)
}
/**
* 请求处理
*/
async function handleRequest(url, opts, isUpload) {
opts.data.platform = PLATFORM.platform
opts.data.appId = PLATFORM.appid
let err = null
let res = null
if (isUpload) {
[err, res = {}] = await uni.uploadFile({
url: url,
filePath: opts.file,
name: opts.name,
formData: opts.data
})
} else {
[err, res = {}] = await uni.request({
url: `${API_BASE}${url}`,
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
method: opts.method || 'POST',
timeout: opts.timeout || NETWORK_TIMEOUT,
data: opts.data || {}
})
}
err && console.warn(`${url}接口错误:`, err)
opts.debug && console.info('接口结果:', res)
return new Promise((resolve, reject) => {
if (err) {
if (opts.handleFail !== false) {
showToast(NETWORK_ERR_MESSAGE)
return reject(res)
}
} else {
// 404, 502等服务器错误信息
if (typeof res.data !== 'object') res.data = { status: res.statusCode, message: DEFAULT_ERR_MESSAGE, data: res.data }
if (res.data.status === '200') {
return resolve(res.data)
} else if (res.data.status === '403' && opts.handleLogin !== false) { // 未登录或者登录失效
showToast('请先登录', 1000)
return uni.navigateTo({
url: '/pages/login/login'
})
} else {
if (opts.handleFail !== false) {
showToast(res.data.message || DEFAULT_ERR_MESSAGE)
}
return reject(res)
}
}
})
}
说明,以上store
为基于vuex
的数据管理,下文会实现
第三步,实现get
,post
以及upload
方法
export function get(url, opts = {}) {
opts.method = 'GET'
return request(url, opts)
}
export function post(url, opts = {}) {
opts.method = 'POST'
return request(url, opts)
}
/**
* @param {url} 上传地址
* @param {file} 文件路径
* @param {name} 文件的表单名称
* @param {formData} 额外需要提交的字段
* */
export async function upload(url, file, name = 'file', formData = {}) {
if (!file) return
formData.openid = formData.openid || store.getters.openid
if (formData.needAuth) {
const userinfo = await store.dispatch('user/getUserInfo')
formData.openid = userinfo.openid || formData.openid
formData.token = userinfo.token
}
return handleRequest(url, {
file: file,
name: name,
data: formData
}, true)
}
第四步,导出响应的方法:
function install(Vue) {
Vue.prototype.$request = request
Vue.prototype.$get = get
Vue.prototype.$post = post
Vue.prototype.$upload = upload
}
export default { install }
第五步,安装方法
打开main.js
文件,安装对应的方法
import request from '@/utils/request'
Vue.use(request)
做完以上工作后,我们可以像下面的方式在页面上调用http
相关的方法
export default {
onLoad(){
this.getOrders()
},
methods: {
getOrders(){
this.$get('orders', {
needAuth: true,
data: {
userid: this.$store.getters.userinfo.id
}
}).then(res => {
console.log(res)
}).catch(res => {
console.warn(res)
})
}
}
}
小结
以上通过Vue.use
安装的方式,在Vue
原型上扩展公共的方法,从而达到可以在页面(Vue
实例)上调用对应的方法来实现调用接口等功能,如果需要扩展更多的方法和属性,均可以以该种方式进行扩展。
路由拦截
路由拦截几乎是每一个应用都需要用到的功能,比如页面跳转前的登录判断。在Vue框架开发的单页应用中,我们会结合Vue-Router
来实现响应的功能,Vue-Router
提供了丰富的路由守卫,实现路由的拦截十分便利。然而,在小程序上实现路由拦截是相对比较麻烦的。uni-app
社区上也有一些解决方案,比如一个完全相似Vue-router的路由插件,该插件重写了uni-app
的一些方法以及生命钩子,从而实现了绝大部分的Vue-Router
的钩子,有兴趣的可以使用该插件。
实际情况是,我们往往不需要像Vue-Router
那样完整的路由拦截,有时我们仅仅只需要登录前的一层简单的校验,这样如果引入一个完整的路由拦截解决方案会显得有点冗余,同时,也会让应用的包更加大(小程序均对单个包有大小的限制)。面对这种情况,我们仅需要做一些简单的扩展即可达到我们的需要。以下,以登录拦截为例,简单的实现一下路由的拦截。我们的需求是,对于需要登录才可以进入的页面,我们先做一下登录校验,如果未登录,则跳转前先跳转至登录页面。
在utils
目录下创建extends.js
文件,该文件专门用来对uni-app
框架做一些额外的扩展。添加如下代码:
import store from '@/store'
export default function() {
['navigateTo', 'redirectTo', 'switchTab', 'navigateBack'].map(item => {
const nativeFunc = uni[item]
uni[item] = function(opts, needAuth) {
if (needAuth) {
store.dispatch('user/getUserInfo').then((res) => {
nativeFunc.call(this, opts)
})
} else {
return nativeFunc.call(this, opts)
}
}
})
}
代码解读:对于路由相关的几个方法进行遍历并缓存原方法,在uni
对象上重写方法,每个方法增加needAuth
参数,表示跳转前是否需要登录
如果需要登录,则首先获取用户信息,如果获取不到用户信息,将会自动跳转到登录页(store
里面的user
模块做的),否则获取到信息就调用缓存起来的原方法,实现跳转。如果不需要登录,则直接调用原方法
打开main.js
,引入该文件并执行
import uniExtend from '@/utils/extends'
uniExtend()
通过一个简单的扩展,我们就实现了一个跳转前的登录拦截。除了登录拦截外,针对uni-app
上提供原生方法,均可以以该方式进行扩展。
由于小程序的限制,拦截必须调用
uni-app
上对应的路由方法,通过其他途径跳转页面,无法拦截。包括以上社区上提供的拦截方案一样受这个限制
状态管理器Vuex
一个集中式的状态管理方案在一个应用中显得尤为重要,而uniapp本身是支持整合Vuex
的。因此,我们在该项目中,也集成了Vuex
。
首先,在根目录下创建store
目录,专门用于存放状态管理相关的文件。为了让所有的状态结构更加清晰,更好维护,我们采用vuex
的modules
来管理各个模块的数据。我们在store
目录下创建modules
目录和index.js
文件,前者用于管理各个数据模块,后者用于导出相关的数据。此时,项目的目录结构如下:
接下来,我们以用户模块为例,来实现一个基于vuex
的状态管理器。
打开store/index.js
文件,写入以下代码
import Vue from 'vue'
import Vuex from 'vuex'
const modules = require.context('./modules', true, /.js$/)
let moduleArr = []
modules.keys().map(key => {
moduleArr[key.replace(/(\.\/)|(\.js)/ig, '')] = modules(key).default
})
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
...moduleArr
}
})
export default store
说明:此处读取modules
目录下的所有文件,获取所有的module
,这要求module
目录下的文件都是导出一个vuex
模块。可以按照实际情况,修改以上代码
接着,打开main.js
文件,添加如下代码:
import store from './store'
const app = new Vue({
store,
...App
})
到这里,已经简单的实现了在应用上使用Vuex
。接下来,添加用户模块,在modules
目录下创建user.js
文件。通常一个module
包含state
,mutations
和actions
。首先我们实现一下state
,代码如下:
const KEYS = ['token', 'openid', 'unionid']
const getDefaultState = () => {
const result = {}
KEYS.map(key => {
result[key] = uni.getStorageSync(key) || ''
})
return result
}
const state = getDefaultState()
定义一个state
函数,用于设置进入页面时默认的state
,我们会优先读取本地缓存里的用户授权信息,每次登录授权后,会将授权信息存入本地缓存。在这里,我们授权的信息包含token
,openid
和unionid
三个信息(正常应该走小程序的授权等操作,这里仅作数据模拟)。接着实现一下mutations
,代码如下:
const mutations = {
SET_AUTH: (state, info) => { // 设置token等信息
KEYS.map(key => {
if (typeof info[key] !== 'undefined') {
state[key] = info[key]
uni.setStorageSync(key, info[key])
}
})
}
}
在这里,我只定义了一个设置授权信息的mutation
,传入授权后的信息,如果对应的授权信息不为undefined
,则将信息设置到state
并且缓存在本地。然后再实现一下actions
。actions
负责登录以及用户信息的获取。主要包括登录、登出、获取用户信息和授权信息等,实现分别如下:
- 获取授权信息
如果已经授权过了,则直接返回授权信息,否则的话会跳转至登录页,进行登录
getAuth({ state }) { // 获取用户授权信息
return new Promise(resolve => {
if (state.token) { // 已授权,直接返回
resolve({
openid: state.openid,
token: state.token,
unionid: state.unionid
})
} else { // 未授权,跳转到登录页
uni.navigateTo({
url: `/pages/login/login`
})
}
})
}
- 登录与登出
login({state, commit, dispatch}, data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(data.name === 'test' && data.password === 'test') {
const data = {
token: "testToken",
openid: "testOpenId",
unionid: "testUnionId"
}
commit('SET_AUTH', data)
resolve(data)
} else {
reject({
message: "账号或密码错误",
code: '100401'
})
}
}, 1000)
})
},
logout({ commit, dispatch }) { // 退出登录
commit('SET_AUTH', {
openid: '',
token: '',
unionid: ''
})
return Promise.resolve()
}
以上模拟了一个账号登录,当登录后,返回token
等授权信息,并调用commit('SET_AUTH', data)
更新state
以及本地缓存;登出时则清空所有的登录授权信息。最后再实现用户信息获取的action
:
getUserInfo({state, commit, dispatch}) { // 本地获取用户信息
return new Promise(resolve => {
dispatch('getAuth').then(res => {
resolve({
username: "test",
age: 18,
email: "test@test.test"
})
})
})
}
当获取用户信息时,首先会进行登录授权,授权成功后则模拟返回用户信息。到这里一个简单的与用户相关的Vuex
模块就完成了。最后就是导出模块供应用使用:
export default {
namespaced: true,
state,
mutations,
actions
}
为了方便页面访问state
数据,通常我们还会创建一些getters
。在store
目录下新建文件getters.js
,添加如下代码:
const getters = {
openid: state => state.user.openid,
token: state => state.user.token,
unionid: state => state.user.unionid
}
export default getters
打开store/index.js
,修改代码如下:
import getters from './getters'
const store = new Vuex.Store({
modules: {
...moduleArr
},
getters
})
这样,就可以在页面上使用this.$store.getters.token
以及使用Vuex
的mapGetters
方法来访问我们的getters
了。
mapGetters
和mapState
等辅助函数均可以使用
到这里,一个简单的数据管理器就算完成了,如果需要添加更多的数据状态,可以按照用户模块的组织方式对数据进行管理。有了数据管理以及数据请求等基础模块,就可以实现具体的业务页面。下面我们以用户登录与信息获取为例,实现一个简单的业务页面。
一个简单的例子--登录与信息获取
(1)修改pages/index/index
为如下代码
<template>
<view class="container">
<view>用户名:{{userinfo.username}}</view>
<view>年龄:{{userinfo.age}}</view>
<view>邮箱:{{userinfo.email}}</view>
<button @tap="logout" type="warn" style="margin-top: 30rpx;" v-if="userinfo.username">退出登录</button>
<button @tap="getUserinfo" type="primary" style="margin-top: 30rpx;" v-else>获取用户信息</button>
</view>
</template>
export default {
data() {
return {
userinfo: {
username: "",
email: "",
age: ""
}
}
},
methods: {
getUserinfo() {
this.$store.dispatch('user/getUserInfo').then(res => {
this.userinfo = res
})
},
logout() {
this.$store.dispatch('user/logout').then(() =>{
this.userinfo.username = ""
this.userinfo.age = ""
this.userinfo.email = ""
})
}
},
onUnload() {
uni.$off("loginSuccess", this.getUserinfo)
},
onLoad() {
uni.$on("loginSuccess", this.getUserinfo)
}
}
uni.$off
和uni.$on
为uni-app
的通信方案,后文会讲述
加上样式后,效果如下:
(2)接着实现登录页面。在pages
目录下新建login/login.vue
文件。添加如下代码:
<template>
<view class="wrap">
<view class="form-item">
<input v-model.trim="name" type="text" placeholder="请输入用户名" class="input">
</view>
<view class="form-item">
<input v-model.trim="password" type="password" placeholder="请输入密码" class="input">
</view>
<view class="button" @tap="submit">登录</view>
</view>
</template>
export default {
data() {
return {
name: '',
password: ''
}
},
methods: {
valid() {
if (!this.name) {
return this.$toast('请输入用户名')
}
if (!this.password) {
return this.$toast('请输入密码')
}
return true
},
submit: function() {
if (!this.valid()) return
uni.showLoading({
title: "正在登录..."
})
this.$store.dispatch('user/login', {
name: this.name,
password: this.password
}).then(res => {
console.log(res)
uni.$emit("loginSuccess", res)
uni.navigateBack()
}).catch(res => {
this.$toast(res.message)
}).finally(() => {
uni.hideLoading()
})
}
},
onLoad() {
this.$store.dispatch('user/logout')
}
}
逻辑比较简单,进入页面后使用this.$store.dispatch('user/logout')
清空原有的登录状态,登录成功后发布事件loginSuccess
并返回上一页,效果如下:
以上两个页面完成后,完整的操作演示如下:
数据通信方案
在uni-app
中,数据的通信通常包含组件之间的通信以及页面之间的通信。同样,在小程序中一般也包含这两种通信。在这里我们只讨论页面之间的数据通信(组件之间的通信参照Vue官方的组件通信)。一般情况下,在uni-app
中我们有以下的几种方式来实现页面之间的通信:
- 使用
url
传参 - 使用本地缓存
- 使用全局数据(如小程序的
globalData
) - 使用
getCurrentPage
方法获取对应的页面进行通信 - 使用
eventBus
- 使用
uni-app
自带的事件发布订阅 - 使用
Vuex
以上几种方案中,前4中方案在应用变得越来越复杂的时候,会使页面之间的耦合度变得越来越高,从而不利于应用的维护。因此,通常页面之间的数据通信我们会采用后三种方案。比如前面的登录操作例子中,则是使用了uni-app
自带的事件发布订阅通信的方案。其本质上也是一个eventBus
。
当然,这不是说前4中方案就不能在应用中使用,某些场景使用前四种方案可能会更简洁、方便、甚至是必须的。比如通过url参数区分渠道,记住某些状态(本地缓存)等。在实际操作的过程中,应该灵活选择、灵活应用。
uni-app
的事件发布订阅,请参考官方文档:页面通信
使用的时候需要注意以下几点:
- 订阅了事件后在页面销毁时记得取消订阅,使用
uni.$off
- 使用
uni.$off
建议传递第二个参数,取消当前页面的订阅,而不是取消所有的订阅。这需要在订阅的时候也传入第二个参数,两个回调应指向同一个引用
自定义组件
组件化是Vue
的一个核心之一,使用uni-app
同样支持自定义组件。但是,自定义组件在各小程序中,由于实现的方案并不一致,所以自定义组件并不总是能完美的运行在各个端的小程序中。这需要在实现的过程做一些特殊的兼容处理。查看uni-app
官方的组件库源码,同样也会为各个平台写一些兼容代码。本文不详细介绍如何一步一步的写一个自定义的组件。按照官方组件库的规范,实战中我们通常会遵循以下几个原则:
- 与业务无关的通用组件放置在根目录的下的
compoments
目录下,同时,需要放置在额外的目录,方便统一管理,如components/my-component
- 业务组件放置在对应页面模块的
components
目录下,如pages/login/components/phoneLogin/phoneLogin.vue
- 组件的命名方式采用横线连接的方式,如
my-dialog
- 组件需要放置在同名目录下,如
components/my-components/my-dialog/my-dialog.vue
- 组件的样式类型最好采用
BEM
规范的命名方式 - 如果需要扩展
uni-app
官方的组件,不应该修改源码,采用拷贝的方式拷贝到自定义的组件目录,同时,组件命名不要以uni-
开头
在使用自定义组件时,编译到各个端时并不是一帆风顺的,这主要是不同平台的小程序自定义组件实现的方式不同。如果在支付宝下,可以通过设置编译方式为
component2
来尝试解决。具体参考常见问题
常见问题与解决方案
- 使用
@import
引入外部样式无效或者报错
uni-app
官方给出的引用外部的样式方式为@import url('./common.scss')
,但应用时发现编译报错或者不生效,此时改为@import './common.scss'
即可。
placeholder-class
不起作用
在小程序中,可以使用placeholder-class
来修改页面的输入框占位符的样色、字体大小等。使用时会发现自定义组件不起作用,页面样式如果加了scoped
属性也会不起作用。解决办法为:
- 自定义组件中输入框使用
placeholder-style
替代 - 非自定义组件中的输入框可以在
App.vue
全局设置,并增加important
关键字(百度小程序无效)
textarea
的处理方法一样
v-show
不起作用
使用v-show
指令时,某些情况下(如基于数组的length
属性来判断),在小程序下不会正常工作,因为小程序的hidden
属性是不响应该变化的,可使用v-if
代替
$refs
不起作用
uni-app
中可以使用$refs
获取页面对应的组件,但是前提是在小程序中组件为自定义组件,原生的组件无法获取。另外,在支付宝小程序中,使用$refs
只能获取到一级的组件引用,嵌套的自定义组件无法获取,有两种解决方案:
(1)可以通过嵌套的$refs
来获取到,如下:
<template>
<popup ref="popup">
<view class="picker">
<region ref="picker" />
</view>
</popup>
</template>
mounted() {
this.popup = this.$refs.popup
// 支持宝不支持嵌套的ref
this.picker = this.$refs.picker || this.popup.$refs.picker
}
(2)通过将支付宝的编译模式改为component2
编译模式,详情可查看这里,代码如下:
"mp-alipay": {
"uniStatistics": {
"enable": true
},
"component2": true
}
- 包大小过大,无法上传
小程序对包的大小有限制,不同的小程序限制不一样。一般我们会采用分包的方式来处理。但是在开发模式下,由于uni-app
编译时会附带sourceMap
,导致分包也会很大,从而调试时无法上传到真机进行调试,如QQ小程序。此时,我们在编译时可以选择压缩代码,同时,在小程序开发者工具也选择上传时自动压缩即可。如下:
- 在
HBuilderX
选择运行
-运行到小程序模拟器
-勾选运行时压缩
- 在开发者工具选择
详情
-本地设置
-勾选上传代码时自动压缩混淆
textarea
穿透和错位问题
textarea
穿透以及滚动的问题属于小程序的问题。textarea
属于原生组件,在放置到具有fixed
定位的弹窗之类的容器时就会出现这样那样的问题。通常我们有两种解决方案:
- 弹窗展示前先隐藏掉
textarea
或者使用其他的元素代替,弹窗展示完毕后再显示textarea
,这样可以避免弹窗位置错位的问题 - 输入组件失去焦点时替换为
view
组件,获得焦点时恢复为textarea
组件
- 提示子组件不能修改父组件的
props
编译后,在支付宝下会提示子组件不能修改父组件的props
,如Avoid mutating a prop directly since the value will be overwritten ....Prop being mutated "value"
。这个错误就算是没有在子组件修改父组件的属性也会报出来。原因是支付宝以及钉钉等支付宝系的小程序开发者工具的一个bug
,实际上组件是正常工作的,但是会错误的报出这条信息,这个忽略即可,后续版本官方应该会修复。
inject/provide
不起作用
在自己写一些自定义组件或者使用uni-ui
扩展组件(如swipe-action
)的时候,在支付宝下会报Injection "xxxx" not found
,如Injection "swipeaction" not found
的错误,这个是因为没有开启component2
编译模式的原因,开启了即可解决。参考常见问题第4点
总结
多端构建的解决方案让我们写一套代码运行到多个端成为了可能,大大减低了开发者负担,也节省了企业的资源。但由于平台不可避免的差异性,仍然存在不少问题需要开发者自行去解决。另外,快应用目前支持的程度非常弱,uni-app
官方已经支持了基于webview
渲染的快应用,但是依然存在不少问题,同时,该模式目前仅华为/OPPO/Vivo支持,其他联盟厂商并不支持。基于原生快应用模式的渲染,uni-app
计划由社区来实现,目前仍旧没有好的解决方案。而从Taro
官方文档来看,Taro
的支持应该也比较有限(未测试,欢迎告知测评结果),可能不能用于比较复杂的应用。
无论是Taro
也好,uni-app
也罢,目前社区都在不断的完善和壮大。相信在国内小程序厂商、快应用联盟以及社区的努力下,多端构建的方案会日趋成熟,并能够形成一套更加统一的、优秀个解决方案。
源码:https://github.com/prianyu/uniapp
本文转载至:https://juejin.im/post/6868484714979098638
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/18/uni-app-complete-combat/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论