性能稳定,使用流畅,用户体验好、功能齐全,安全性有保证,兼容性好 ,可使用手机所有硬件功能等
开发周期长、维护成本高、迭代慢、部署慢、新版本必须重新下载应用
不支持跨平台,必须同时开发多端代码
可以跨平台,一套代码搞定iOS、Android、微信小程序、H5应用等
开发成本较低,开发周期比原生短
适用于跟系统交互少、页面不太复杂的场景
对开发者要求高,除了本身JS的了解,还必须熟悉一点原生开发
不适合做高性能、复杂用户体验,以及定制高的应用程序。比如:抖音、微信、QQ等
同时开发多端兼容和适配比较麻烦、调试起来不方便
都是接近原生的体验、打开即用、不需要安装
都可开发微信小程序、都有非常完善的官方文档
跨平台
uni-app : 支持跨平台,编写一套代码,可以发布到多个平台
微信小程序 : 不支持
工程化
uni-app : 纯Vue体验、高效、统一、工程化强
微信小程序 : 工程化弱、使用小程序开发语言
复杂程度
uni-app : 适合不太复杂的应用,因为需要兼容多端,多端一起兼容和适配增加了开发者心智负担
微信小程序 : 适合较复杂、定制性较高、兼容和稳定性更好的应用
需要跨平台、不太复杂的应用选 uni-app,复杂的应用使用uni-app反而增加了难度。
不需要跨平台、较复杂、对兼容和稳定性要求高的选原生微信小程序
Hbuilder X : 是通用的前端开发工具,但为 uni-app 做了特别强化
网址 : hbuilderx
ps : 用Vue3的Composition API 建议用 HBuilder X最新Alpha版,旧版有兼容问题
点工具栏里的文件 -> 新建 -> 项目 ( 快捷键Ctrl+N )
选择uni-app类型,输入工程名,选择模板,选择Vue版本,点击创建即可
点击工具栏的运行 -> 运行到浏览器 -> 选择浏览器
如果自动启动失败,可用微信开发者工具手动打开项目
项目在unpackage/dist/dev/mp-weixin路径下
微信开发者工具需要开启服务端口:
小程序开发工具设置 -> 安全(目的是让HBuilder可以启动微信开发者工具)
这里是mac电脑 + ios手机环境
ps : 之所以要用XCode新建一个项目,先打开模拟器,再用hbuilderx打开相同的模拟器
是为了减少bug,不这么做容易有奇奇怪怪的小问题
可不配 : 不配会自动去寻找默认的
HBuilderX正式版adb目录位置 :
windows : 安装路径下的 tools/adbs 目录
mac : HBuilderX.app/Contents/tools/adbs目录;
HBuilderX Alpha版的adb目录位置:
windows : 安装路径下的 plugins/launcher/tools/adbs 目录
mac下 : /Applications/HBuilderX-Alpha.app/Contents/HBuilderX/plugins/launcher/tools/adbs目录
在adbs目录下运行./adb ,即可使用adb命令(Win和Mac一样)
有个很严重的问题,我的这样没有热更新,每次改完得重新运行,我要崩溃,有没有大佬指点迷津
为了实现多端兼容,综合考虑编译速度、运行性能等因素,uni-app 约定了如下开发规范:
页面文件遵循
Vue 单文件组件 (SFC) 规范
组件标签靠近小程序规范
uni-app 组件规范 => 使用小程序的标签
接口能力(JS API)
靠近微信小程序规范 => 但需将前缀 wx 替换为 uni
uni-app接口规范
数据绑定及事件处理
同 Vue.js 规范 => 例如使用@click
生命周期
可用小程序的
也可用vue的
为兼容多端运行
建议使用flex布局
推荐使用rpx单位
uni-app的官网文档
uni-app官网文档
main.js是 uni-app 的入口文件,主要作用是:
初始化vue实例
定义全局组件
定义全局属性
安装插件,如:pinia、vuex 等
import { createSSRApp } from 'vue'
//引入组件
import myComponent from './components/my-component/my-component.vue'
export function createApp() {
const app = createSSRApp(App)
//调用app.component方法全局注册组件
app.component('my-component', myComponent)
return {
app
}
}
// vue2
Vue.prototype.$http = () => {}
// vue3
export function createApp() {
const app = createSSRApp(App);
app.config.globalProperties.$http = () => {}
return {
app
}
}
import { createSSRApp } from 'vue'
// 当然这里要自己先封一下 Pinia
import pinia from '.stores'
export function createApp() {
const app = createSSRApp(App);
app.use(pinia);
return {
app
Pinia // 此处必须将 Pinia 返回
}
}
App.vue : 入口组件
App.vue是uni-app的入口组件,所有页面都是在App.vue下进行切换
App.vue本身不是页面,这里不能编写视图元素,也就是没有元素
App.vue的作用是 :
应用的生命周期
编写全局样式
定义全局数据 globalData
注意:应用生命的周期仅可在App.vue中监听,在页面监听无效。
全局样式 : App.vue 中style的样式,作用于每一个页面(style标签不支持scoped)
App.vue 中通过 @import 语句可以导入外联样式,一样作用于每一个页面
局部样式 : 在 pages 目录下 的 vue 文件的style中的样式为局部样式,只作用对应的页面,并会覆盖 App.vue 中相同的选择器
style标签不支持scoped,默认就是局部样式
// 页面使用
onShow() {
// getApp() => 全部页面自带,都可以使用
console.log('page', getApp().globalData);
}
uni.scss 全局样式文件
为了方便整体控制应用风格,默认定义了uni-app框架内置全局变量,也可以存放自定义的全局变量等
在uni.scss中定义的变量,无需 @import 就可以在任意组件中直接使用。
使用uni.scss中的变量,需在 HBuilderX 里面安装 scss 插件(dart-sass插件),
然后在该组件的 style 上加 lang=“scss”,重启即可生效
注意事项 :
这里的uni-app框架内置变量和后面uni-ui组件库的内置变量是不一样的。
uni.scss定义的变量是全局可以直接使用,App.vue定义的变量只能在当前组件中使用
uni.scss的作用 :
定义自定义的全局的样式变量
重写uni-app内置的样式变量
重写uni-un内置的样式变量
page.json : 全局页面配置(兼容h5、weapp、app )
pages.json 文件用来对 uni-app 进行全局配置,类似微信小程序中app.json
决定页面的路径、窗口样式、原生的导航栏、底部的原生tabbar 等
{
"pages": [
// 页面窗口的配置,优先级比全局的要高
// 首页
{
"path": "pages/index/index",
"style": {
// 相当于配置页面样式
"navigationBarTitleText": "空我,核弹踢!"
}
}
],
// 相当于整个小程序的顶部窗口
"globalStyle": {
// 导航栏标题的颜色 black/white
"navigationBarTextStyle": "black",
// 导航栏标题的文字,整体的,会被上面的单个页面配置的覆盖
"navigationBarTitleText": "迪迦奥特曼,冲啊",
// 顶部背景颜色
"navigationBarBackgroundColor": "#ff8198",
// 下拉刷新时可以看到的那个
"backgroundColor": "#F8F8F8"
}
}
manifest.json : 应用配置
Android平台相关配置
iOS平台相关配置
Web端相关的配置
微信小程序相关配置
......
uni-ui是DCloud提供的一个UI组件库,一套基于Vue组件、flex布局的跨全端UI框架
uni-ui不包括uni-app框架提供的基础组件,而是基础组件的补充
网址 : uni-ui
通过 uni_modules(插件模块化规范)单独安装组件
1. 官网找到扩展组件清单,然后将所需要的组件导入到项目,导入后直接使用,无需import和注册
2. 若想切换应用风格,可以在uni.scss导入uni-ui提供的内置scss变量,然后重启应用
ps :需要登录 DCloud 账号才能安装
ps: 该方式使得全部组件都下载了
安装dart-sass插件(一般都会提示,并自动安装)
在项目根目录的uni.scss文件中引入uni-ui组件库的variable.scss变量文件
然后就可以使用或修改对应的scss变量
变量主要定义的是主题色
// 小程序 和 app 有效果
.uni-forms-item_label{
color:red !important;
}
// 小程序 和 app 和 h5
:deep(.uni-forms-item_label){
color:red !important;
}
// 小程序 和 app 和 h5
:global(.uni-forms-item_label){
color:red !important;
}
uni-app能实现一套代码、多端运行,核心是通过编译器 + 运行时实现的:
编译器:将uni-app统一代码编译生成每个平台支持的特有代码
如在小程序平台,编译器将.vue文件拆分生成wxml、wxss、js等。
运行时:动态处理数据绑定、事件代理,保证 Vue和对应宿主平台 数据的一致性
跨平台存在的问题:
uni-app 已将常用的组件、JS API 封装到框架中,按照 uni-app 规范开发即可保证多平台兼容
大部分业务均可直接满足。
但每个平台有自己的一些特性,因此会存在一些无法跨平台的情况
大量写 if else,会造成代码执行性能低下和管理混乱
编译到不同的工程后二次修改,会让后续升级变的很麻烦
跨平台兼容解决方案:
在 C 语言中,通过 #ifdef、#ifndef 的方式,为 windows、mac 等不同 os 编译不同的代码
uni-app 参考这个思路,为 uni-app 提供了条件编译手段,在一个工程里优雅的完成了平台个性化实现
// #ifdef %PLATFORM%
平台特有的API实现
// #endif
页面!!!!
Android 和 iOS 平台不支持条件编译
如需区分 Android、iOS 平台,请通过调用 uni.getSystemInfo 来获取平台信息
可直接新建页面,不用新建目录,会带同名目录
一般会自动生成,如果没有,手写即可
uni-app 有两种页面路由跳转方式: 和小程序一模一样
使用navigator组件跳转
调用API跳转(类似小程序,与vue-router不同)
跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面
关闭当前页面,返回上一页面或多级页面
可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层
// 此处是A页面
uni.navigateTo({
url: 'B?id=1'
});
// 此处是B页面
uni.navigateTo({
url: 'C?id=1'
});
// 在C页面内 navigateBack,将返回A页面
uni.navigateBack({
delta: 2,
success: (res) => {
console.log('success:', res);
},
fail: (err) => {
console.log('fail:', err);
},
complete: (res) => {
console.log('complete:', res);
},
});
关闭当前页面,跳转到应用内的某个页面
uni.redirectTo({
url:'test?id=1',
success: (res) => {
console.log('success:', res);
},
fail: (err) => {
console.log('fail:', err);
},
complete: (res) => {
console.log('complete:', res);
},
});
关闭所有页面,打开到应用内的某个页面
uni.reLaunch({
url:'test?id=1',
success: (res) => {
console.log('success:', res);
},
fail: (err) => {
console.log('fail:', err);
},
complete: (res) => {
console.log('complete:', res);
},
});
open-type="switchTab" 对应 uni.switchTab 的功能
跳转到个人
若有特殊字符
传递时 : encodeURIComponent 编码
接受时 : decodeURIComponent 解码
jumpFn() {
uni.navigateTo({
// 需要跳转的应用内非 tabBar 的页面的路径 , 路径后可以带参数
url: '/pages/detail/detail?name=coder&age=18',
// 仅支持在app端看到运动 效果
animationType: 'fade-in',
animationDuration: 2000,
success: (res) => {
console.log('success:', res);
},
fail: (err) => {
console.log('fail:', err);
},
complete: (res) => {
console.log('complete:', res);
},
})
}
export default {
// 可在这获取到首页传递过来的值
onLoad(option) {
console.log(option); // {name: 'coder', age: '18'}
},
/**
* 写在这里的理由 : 用户可能点击顶部状态栏中的返回键,写在这不管怎么返回,都会触发
*/
// 页面被注销时调用
onUnload() {
// 1. 获取到已经存在的所有页面
const pages = getCurrentPages()
// 2. 拿到上一个页面的实例 当前页面为最后一个,所以上一个页面是减2
const prePage = pages[pages.length - 2]
/**
* 3. 设置值
*/
// H5端
// #ifdef H5
prePage.arr[1].name = 'ccc'
// #endif
// 微信端,有个$vm
// #ifdef MP-WEIXIN
prePage.$vm.arr[1].name = 'ccc'
// #endif
// app端,上述两种都可满足,奇怪
// #ifdef APP-PLUS
// prePage.arr[1].name = 'ccc'
prePage.$vm.arr[1].name = 'ccc'
// #endif
}
}
只有 uni.navigateTo 才能使用这个哦
jumpFn() {
uni.navigateTo({
// 需要跳转的应用内非 tabBar 的页面的路径 , 路径后可以带参数,如果有特殊字符记得编码
url: `/pages/detail/detail?name=${encodeURIComponent('coder')}&age=18`,
events: {
// 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据
acceptDataFromOpenedPage: (data) => {
console.log('acceptDataFromOpenedPage',
data) // acceptDataFromOpenedPage {name: 'coder'}
},
someEvent: (data) => {
console.log('someEvent', data) // someEvent {age: 10}
}
},
success: (res) => {
console.log('success:', res);
// 通过eventChannel向被打开页面传送数据,用此方式可不用在url上拼接参数
res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'data from starter page' })
},
fail: (err) => {
console.log('fail:', err);
},
complete: (res) => {
console.log('complete:', res);
},
})
}
export default {
// 可在这获取到首页传递过来的值
onLoad(option) {
console.log(option, decodeURIComponent(option.name)); // {name: 'coder', age: '18'}
},
/**
* 写在这里的理由 : 用户可能点击顶部状态栏中的返回键,写在这不管怎么返回,都会触发
*/
// 页面被注销时调用
onUnload() {
// 1. 拿到eventChanner渠道
const eventChanner = this.getOpenerEventChannel()
// 2. 通过渠道,传递数据给上一个页面
eventChanner.emit('acceptDataFromOpenedPage', {
name: 'coder'
})
eventChanner.emit('someEvent', {
age: 10
})
// 3. 监听上一个页面传递过来的数据
eventChanner.on('acceptDataFromOpenerPage', function(data) {
console.log(data) // {data: 'data from starter page'}
})
}
}
detail-view
需先监听,再触发事件,比如:你在A界面触发,然后跳转到B页面后才监听是不行的
通常on 和 off 是同时使用,可以避免多次重复监听
适合页面返回传递参数、适合跨组件通讯,不适合界面跳转传递参数
触发全局的自定义事件,附加参数都会传给监听器回调
uni.$emit('emitFn',{msg:'页面更新'})
监听全局的自定义事件
事件可以由 uni.$emit 触发,回调函数会接收所有传入事件触发函数的额外参数
uni.$on('emitFn',(data) => {
console.log('监听到事件来自 emitFn ,携带参数 msg 为:' + data.msg);
})
监听全局的自定义事件
事件可以由 uni.$emit 触发,但是只触发一次,在第一次触发之后移除监听器
uni.$once('emitFn',(data) => {
console.log('监听到事件来自 emitFn ,携带参数 msg 为:' + data.msg);
})
移除全局自定义事件监听器
// 注 : 这样并不能清除监听过的emitFn
uni.$off('emitFn',(data) => {
console.log('监听到事件来自 emitFn ,携带参数 msg 为:' + data.msg);
})
需先监听,再触发事件 ,比如:你在A界面触发,然后跳转到B页面后才监听是不行的
A监听,B发射,A再监听才能拿到值
detail-view
tabBar的页面不会销毁,不会触发onUnload
export default {
/**
* 都会触发
* */
// 1.页面的生命周期
onLoad(options) {
console.log('onLoad');
},
onShow() {
console.log('onShow');
console.log(this);
},
onReady() {
console.log('onReady');
},
onHide() {
console.log('onHide');
},
onUnload() {
console.log('onUnload');
},
onPullDownRefresh() {
console.log('onPullDownRefresh');
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
},
onReachBottom() {
console.log('onReachBottom');
},
// 2.Vue组件的生命周期
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeMount() {
console.log('beforeMount');
},
mounted() {
console.log('mounted');
},
beforeDestroy() {
console.log('beforeDestroy');
},
destroyed() {
console.log('destroyed');
}
}
偷个懒,文档上复制的~
uni.request({
url: 'https://www.example.com/request', //仅为示例,并非真实接口地址。
data: {
text: 'uni.request'
},
header: {
'custom-header': 'hello' //自定义请求头信息
},
success: (res) => {
console.log(res.data);
this.text = 'request success';
}
});
/**
* 封装请求
*/
import { BASE_URL, TIME_OUT } from './config.js'
// 封装成类
class StarRequest {
// 设定公共URL + 超时时间
constructor(url, timeout) {
this.baseUrl = url
this.timeout = timeout
}
request({ url, method = 'GET', data } = {}) {
// 返回一个promise
return new Promise((resolve, reject) => {
uni.request({
url: this.baseUrl + url,
timeout: this.timeout,
method,
data,
success: (res) => {
resolve(res.data)
},
fail: reject
})
})
}
get() {
return this.request(option)
}
post() {
return this.request({ ...option, method: 'POST' })
}
}
// 可创建多个实例,请求不同的地址
export const sRequest = new StarRequest(BASE_URL, TIME_OUT)
/**
* 设定常量
*/
export const BASE_URL = 'http://www.baidu.com/api'
export const TIME_OUT = 10000
/**
* 封装公共逻辑
*/
import { sRequest } from '@/service/request/index.js'
// 获取公共的字典数据
export const getAllDics = () => {
return sRequest.request({
url: '/common/dic'
})
}
/**
* 作统一导出
*/
// 公共数据
export { getAllDics } from './modules/common'
// ...
import { onLoad, onUnload } from "@dcloudio/uni-app";
import { getAllDics } from '@/service/index.js'
// 封装一下请求,可以写在utils中,这里为了方便写在这
const awaitWrap = (promise) => {
// 就不用写try...catch了
return promise.then(res => [res, null]).catch(err => [null, err])
}
onLoad(async () => {
// 使用请求
const [res, err] = await awaitWrap(getAllDics())
// 如果不为空,说明抛出了异常,返回即可
if (err !== null) return console.log('请求错误');
// 拿到数据
console.log(res);
})
sync : 同步,不存储完,不会执行后续代码
可看我的另一篇文章 : 云原生 之 docker
待更新,未操作~