探讨uniapp的页面问题

1 新建页面

uni-app中的页面,默认保存在工程根目录下的pages目录下。

每次新建页面,均需在pages.json中配置pages列表;

未在pages.json -> pages 中注册的页面,uni-app会在编译阶段进行忽略。pages.json的完整配置参考:页面配置

通过HBuilderX开发 uni-app 项目时,在 uni-app 项目上右键"新建页面",HBuilderX会自动在pages.json中完成页面注册,开发更方便。

同时,HBuilderX 还内置了常用的页面模板(如图文列表、商品列表等),选择这些模板,可以大幅提升你的开发效率。

新建页面时,可以选择vue或nvue,还可以选择是否创建同名目录。创建目录的意义在于,如果你的页面较复杂,需要拆分多个附属的js、css、组件等文件,则使用目录归纳比较合适。如果只有一个页面文件,大可不必多放一层目录。

2 删除页面

删除页面时,需做两件工作:

  1. 删除.vue文件或.nvue文件
  2. 删除pages.json -> pages列表项中的配置(如使用HBuilderX删除页面,会在状态栏提醒删除pages.json对应内容)

pages.json是工程的页面管理配置文件,包括:页面路由注册、页面参数配置(原生标题栏、下拉刷新...)、首页tabbar等众多功能。

其篇幅较长,另见 pages.json

3 应用首页

pages.json -> pages 配置项中的第一个页面,作为当前工程的首页(启动页)。

java 复制代码
{
	"pages": [
		{
			"path": "pages/index/index", //名字叫不叫index无所谓,位置在第一个,就是首页
			"style": {
				"navigationBarTitleText": "首页" //页面标题
			}
		},
		{
			"path": "pages/my",
			"style": {
				"navigationBarTitleText": "我的"
			}
		},
	]
}

4 页面内容构成

uni-app 页面基于 vue 规范。一个页面内,有3个根节点标签 、

java 复制代码
<template>
	<view class="content">
		<button @click="buttonClick">{{title}}</button>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				title: "Hello world", // 定义绑定在页面上的data数据
			}
		},
		onLoad() {
			// 页面启动的生命周期,这里编写页面加载时的逻辑
		},
		methods: {
			buttonClick: function () {
				console.log("按钮被点了")
			},
		}
	}
</script>

<style>
	.content {
		width: 750rpx;
		background-color: white;
	}
</style>

4.1 template模板区

vue的template中写的全都是vue组件,每个组件支持属性、事件、vue指令,还可以绑定vue的data数据。

4.2 script脚本区

在vue的选项式(option)规范中,script下包含export default {}。(除了选项式,还有组合式)

页面级的代码大多写在export default {}中。写在里面的代码,会随着页面关闭而关闭。


export default 外的代码

先来介绍写在export default {}外面的代码,一般有几种情况:

  • import三方js模块
  • import非easycom的组件(一般组件推荐使用easycom,无需导入注册)
  • 定义作用域更大的变量
  • 在ts/uts中,对data的类型进行type定义
java 复制代码
<script>
	const TAB_OFFSET = 1; // 外层静态变量不会跟随页面关闭而回收
	import charts from 'charts.ts'; // 导入外部js/ts模块
	import swiperPage from 'swiper-page.vue'; //导入非easycom的组件
	type GroupType = {
		id : number,
		title : string
	} // 在ts中,为下面data数据的 groupList 定义类型
	export default {
		components: {
		    swiperPage
		}, // 注册非easycom组件
		data() {
			return {
				groupList: [
					{ id: 1, title: "第一组" },
					{ id: 2, title: "第二组" },
				] as GroupType[], // 为数据groupList定义ts类型
			}
		},
		onLoad() {},
		methods: {}
	}
</script>

开发者应谨慎编写export default {}外面的代码,这里的代码有2个注意事项:

  • 在应用启动时执行。也就是这里的代码执行时机是应用启动、而不是页面加载。如果这里的代码写的太复杂,会影响应用启动速度和内存占用。
  • 不跟随页面关闭而回收。在外层的静态变量不会跟随页面关闭而回收。

export default 里的代码

export default {} 里的内容,是页面的主要逻辑代码。包括几部分:

  • data:template模板中需要使用的数据。data 必须声明为返回一个初始数据对象的函数(注意函数内返回的数据对象不要直接引用函数外的对象)。
  • 页面生命周期:如页面加载、隐藏、关闭
  • methods方法,如按钮点击、屏幕滚动

如下页面代码的逻辑是:

  1. 在data中定义了title,初始值是"点我"
  2. 在页面中放置了一个button组件,按钮文字区使用{{}}模板写法,里面写title,把data里的title绑定到按钮的文字区,即按钮的初始文字是"点我"
  3. 按钮的点击事件@click,指向了methods里的一个方法buttonClick,点击按钮即触发这个方法的执行
  4. buttonClick方法里通过this.title的方式,访问data数据,并重新赋值为"被点了"。由于vue中data和界面是双向绑定,修改data中的title后,因为按钮文字绑定了title,会自动更新按钮的文字。

整体效果就是,刚开始按钮文字是"点我",点一下后按钮文字变成了"被点了"

5 页面生命周期

uni-app 页面除支持 Vue 组件生命周期外还支持下方页面生命周期函数.

5.1 页面加载时序介绍

接下来我们介绍onLoad、onReady、onShow的先后关系,页面加载的详细流程。

  1. uni-app框架,首先根据pages.json的配置,创建页面 。所以原生导航栏是最快显示的。页面背景色也应该在这里配置。
  2. 根据页面template里的组件,创建dom。
    这里的dom创建仅包含第一批处理的静态dom,对于通过js更新data,然后通过v-for再创建的列表数据,不在第一批处理。要注意一个页面静态dom元素过多,会影响页面加载速度。在uni-app x Android版本上,可能会阻碍页面进入的转场动画。 因为此时,页面转场动画还没有启动。
  3. 触发onLoad
    此时页面还未显示,没有开始进入的转场动画,页面dom还不存在 。所以这里不能直接操作dom(可以修改data,因为vue框架会等待dom准备后再更新界面);
    在 app-uvue 中获取当前的activity拿到的是老页面的activity,只能通过页面栈获取activity。
    onLoad比较适合的操作是:接受上页的参数,联网取数据,更新data。
    手机都是多核的,uni.request 或云开发联网,在子线程运行,不会干扰UI线程的入场动画,并行处理可以更快的拿到数据、渲染界面。
    但onLoad里不适合进行大量同步耗时运算,因为此时转场动画还没开始。 尤其uni-app x 在 Android上,onLoad里的代码(除了联网和加载图片)默认是在UI线程运行的,大量同步耗时计算很容易卡住页面动画不启动。除非开发者显式指定在其他线程运行。
  4. 转场动画开始
    新页面开始进入的转场动画,动画默认耗时300ms,可以在路由API中调节时长。
  5. 页面onReady
    第2步 创建dom是虚拟dom,dom创建后需要经历一段时间,UI层才能完成了页面上真实元素的创建,即触发了onReady。
    onReady后,页面元素就可以自由操作了,比如ref获取节点。同时首批界面也渲染了。
    注意:onReady和转场动画开始、结束之间,没有必然的先后顺序,完全取决于dom的数量和复杂度。
    如果元素排版和渲染够快,转场动画刚开始就渲染好了;
    大多情况下,转场动画走几格就看到了首批渲染内容;
    如果元素排版和渲染过慢,转场动画结束都没有内容,就会造成白屏。
    联网进程从onLoad起就在异步获取数据更新data,如果服务器速度够快,第二批数据也可能在转场动画结束前渲染。
  6. 转场动画结束
    再次强调,5和6的先后顺序不一定,取决于首批dom渲染的速度。

5.2 页面加载常见问题

了解了页面加载时序原理,我们就知道如何避免页面加载常见的问题:

优化白屏的方法:

  1. 页面dom太多,注意有的组件写的不好,会拖累整体页面。uni-app x 里减少dom数量的策略,详见
  2. 联网不要在onReady里,那样太慢了,在onLoad里早点联网
  3. 在pages.json里配置原生导航栏和背景色
  4. 有的页面template内容非常少,整页就是一个需要联网加载的列表,这会造成虽然首批dom飞快渲染了,但页面其实还是白的,联网后才能显示字和图。
    此时需要在template里做一些简单占位组件,比如loading组件、骨架屏,让本地先显示一些内容。

卡住动画不启动的原因:

  1. 页面dom太多,注意有的组件写的不好,会拖累整体页面。uni-app x 里减少dom数量的策略,详见
  2. onLoad里执行了耗时的同步计算

5.3 onShow和onHide

  • 注意页面显示,是一个会重复触发的事件。
  • a页面刚进入时,会触发a页面的onShow。
  • 当a跳转到b页面时,a会触发onHide,而b会触发onShow。
  • 但当b被关闭时,b会触发onUnload,此时a再次显示出现,会再次触发onShow。
  • 在tabbar页面(指pages.json里配置的tabbar),不同tab页面互相切换时,会触发各自的onShow和onHide。

5.4 onInit

不建议使用

5.5 onReachBottom


java 复制代码
onPageScroll : function(e) { //nvue暂不支持滚动监听,可用bindingx代替
	console.log("滚动距离为:" + e.scrollTop);
},
  • 可在pages.json里定义具体页面底部的触发距离onReachBottomDistance,比如设为50,那么滚动页面到距离底部50px时,就会触发onReachBottom事件。
  • 如使用scroll-view导致页面没有滚动,则触底事件不会被触发。scroll-view滚动到底部的事件请参考scroll-view的文档
  • onPageScroll里不要写交互复杂的js,比如频繁修改页面。因为这个生命周期是在渲染层触发的,在非h5端,js是在逻辑层执行的,两层之间通信是有损耗的。如果在滚动过程中,频发触发两层之间的数据交换,可能会造成卡顿。

5.6 onBackPress


  • onBackPress上不可使用async,会导致无法阻止默认返回

当用户进行以下操作时,会触发该函数:

  • Android 实体返回键 (from = backbutton)
  • 顶部导航栏左边的返回按钮 (from = backbutton)
  • 返回 API,即 uni.navigateBack() (from = navigateBack)

注意事项:

  • 只有在该函数中返回值为 true 时,才表示不执行默认的返回,自行处理此时的业务逻辑。

场景示例:

页面中的遮罩处于显示状态时,点击返回不希望直接关闭页面,而是隐藏掉遮罩。遮罩隐藏后,继续点击返回再执行默认的逻辑。

自定义遮罩 通常自定义的遮罩/弹出层,都会做成组件,这样方便复用。新建 uni-app 项目->components->mask.vue 文件,代码如下:

java 复制代码
<template>  
    <view>  
        <view class="cpt-mask">  
        </view>  
    </view>  
</template>  

<script>  
    export default {}  
</script>  

<style>  
    .cpt-mask {  
        position: fixed;  
        top: 0;  
        left: 0;  
        width: 100%;  
        height: 100%;  
        background-color: #000000;  
        opacity: 0.5;  
        z-index: 99;  
    }  
</style>

引用遮罩组件 在页面中引入 mask 自定义组件后,通过一个状态值来控制其隐藏/显示。

java 复制代码
<mask v-if="showMask"></mask>

这里用 v-if,不要用 v-show,自定义组件存在一些问题待优化。

在 onBackPress 中,判定当前遮罩是否处于显示状态。如果显示,则关闭遮罩并返回 true。

java 复制代码
		onBackPress() {
			if (this.showMask) {
				console.log('onBackPress close mask');
				this.showMask = false;
				return true;
			} else {
				uni.showModal({
					title: '提示',
					content: '自定义是否退出uni-app?',
					success: function(res) {
						if (res.confirm) {
							// 退出当前应用,改方法只在App中生效
							plus.runtime.quit();
						} else if (res.cancel) {
							console.log('用户点击取消');
						}
					}
				});
				return true;
			}
		},

5.7 onTabItemTap

  • onTabItemTap常用于点击当前tabitem
java 复制代码
onTabItemTap : function(e) {
	console.log(e);
	// e的返回格式为json对象: {"index":0,"text":"首页","pagePath":"pages/index/index"}
},

5.8 onNavigationBarButtonTap

点击导航栏 buttons 时触发


java 复制代码
{
    "fontSrc": "/static/yticon.ttf",
    "text": "",
    "fontSize": "26",
    "color": "#303133",
    "float": "left",
    "background": "rgba(0,0,0,0)",
    "onclick": "function() { [native code] }",
    "__cb__": {
        "id": "plus71692953577889",
        "htmlId": "__uniapp__service"
    },
    "index": 0
}

6 组件生命周期

7 页面调用接口

7.1 getApp()

getApp() 函数用于获取当前应用实例VUE ,一般用于获取globalData。也可通过应用实例调用 App.vue methods 中定义的方法。

7.2 getCurrentPages()

getCurrentPages() 函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,数组中的元素为页面实例,第一个元素为首页,最后一个元素为当前页面。

getCurrentPages()仅用于展示页面栈的情况,请勿修改页面栈,以免造成页面状态错误。页面关闭时,对应页面实例会在页面栈中删除。

java 复制代码
			var pages = getCurrentPages();
			var page = pages[pages.length - 1];

8 页面通讯

8.1 uni.$emit(eventName,OBJECT)

触发全局的自定义事件。附加参数都会传给监听器回调。

java 复制代码
	uni.$emit('update',{msg:'页面更新'})

8.2 uni.$on(eventName,callback)

监听全局的自定义事件。事件可以由 uni.$emit 触发,回调函数会接收所有传入事件触发函数的额外参数。

java 复制代码
	uni.$on('update',function(data){
		console.log('监听到事件来自 update ,携带参数 msg 为:' + data.msg);
	})

8.3 uni.$once(eventName,callback)

监听全局的自定义事件。事件可以由 uni.$emit 触发,但是只触发一次,在第一次触发之后移除监听器。

java 复制代码
	uni.$once('update',function(data){
		console.log('监听到事件来自 update ,携带参数 msg 为:' + data.msg);
	})

8.3 uni.$off([eventName, callback])

移除全局自定义事件监听器。

  • 如果没有提供参数,则移除所有的事件监听器;
  • 如果只提供了事件,则移除该事件所有的监听器;
  • 如果同时提供了事件与回调,则只移除这个回调的监听器;
  • 提供的回调必须跟$on的回调为同一个才能移除这个回调的监听器;

$emit、$on、$off常用于跨页面、跨组件通讯,这里为了方便演示放在同一个页面

8.4 如何使用uni.$emit()和uni.$on() 进行页面间通讯:

监听事件

java 复制代码
// 我的页面  
onLoad(){  
    // 监听事件  
    uni.$on('login',(usnerinfo)=>{  
        this.usnerinfo = usnerinfo;  
    })  
},  
onUnload() {  
    // 移除监听事件  
        uni.$off('login');  
    },

因为事件监听是全局的,所以使用 uni.$on ,需要使用 uni.$off 移除全局的事件监听,避免重复监听。

触发事件

java 复制代码
// 登陆页面  
uni.$emit('login', {  
                avatarUrl: 'https://img-cdn-qiniu.dcloud.net.cn/uploads/nav_menu/10.jpg',  
                token: 'user123456',  
                userName: 'unier',  
                login: true  
            });

使用 uni.$emit 触发事件后,对应的 uni.$on 就会监听到事件触发,在回调中去执行相关的逻辑。

9 路由

uni-app 有两种页面路由跳转方式:使用navigator组件跳转、调用API跳转。

  • 页面返回时会自动关闭 loading 及 toast, modal 及 actionSheet 不会自动关闭。
  • 页面关闭时,只是销毁了页面实例,未完成的网络请求、计时器等副作用需开发者自行处理

10 页面栈

框架以栈的形式管理当前所有页面, 当发生路由切换的时候,页面栈的表现如下:

相关推荐
变色龙云1 小时前
uni-app开发完成app上传OPPO认定为马甲包如何处理?
uni-app
用户480622604141515 小时前
使用uniapp开发微信小程序-框架搭建
微信小程序·uni-app
TttHhhYy17 小时前
uniapp+vue开发app,蓝牙连接,蓝牙接收文件保存到手机特定文件夹,从手机特定目录(可自定义),读取文件内容,这篇首先说如何读取,手机目录如何寻找
开发语言·前端·javascript·vue.js·uni-app
Funky_oaNiu17 小时前
uniapp实现按钮防重复点击(防抖)完整解决方案
uni-app
原克技术18 小时前
uniapp验证码
uni-app
web150850966412 天前
在uniapp Vue3版本中如何解决webH5网页浏览器跨域的问题
前端·uni-app
何极光2 天前
uniapp小程序样式穿透
前端·小程序·uni-app
User_undefined3 天前
uniapp Native.js 调用安卓arr原生service
android·javascript·uni-app
流氓也是种气质 _Cookie3 天前
uniapp blob格式转换为video .mp4文件使用ffmpeg工具
ffmpeg·uni-app
爱笑的眼睛113 天前
uniapp 极速上手鸿蒙开发
华为·uni-app·harmonyos