摘要:我们参考小程序的设计思路进行了优化升级,为每一个需要特有化配置的页面添加一个格式的配置文件,配置文件包括导航栏的配置页面级别的配置跳转的配置等,将配置工程化标准化。设置导航栏按钮包含按钮样式的数组通过完成按钮事件的回调。
一、背景1.为什么是Weex在公司快速发展的大环境下,App的更新迭代高速、高频,技术团队平均两周便可诞生一款中型App,但App团队只有6个人(iOS 、Android各3人),在确保效率、质量的前提下,单纯依靠Native的能力显得步履蹒跚——我们亟需提升团队效率,希望单人可完成原本2~3人的工作量。
其一,接入Web页面,一个页面适配两端;
其二,选择Weex、React Native、Flutter、Chameleon等跨平台开发框架,主流框架对比如下:
对比内容 | React Native | Flutter | Weex |
上手难度 | 一般 | 一般 | 容易 |
接入特点 | 适合开发整体App | 适合开发整体App | 适合单页面 |
维护难度 | 一般 | 一般 | 容易 |
开发语言 | React | Dart | Vue、Rax |
框架体量 | 较重 | 重 | 较轻 |
Bundle大小 | 较大 | 不需要 | 较小 |
社区 | 丰富 | 新起之秀 | 不够完善 |
支持终端 | Android、iOS | Android、iOS、Web等 | Android、iOS、Web |
引擎 | JSCore、V8 | Flutter Engine | JSCore、V8 |
通过对比,最终选择了Weex,有以下几个主要原因:
Weex的上手成本较低,且单页面的支持更符合项目规划;
Vue框架,契合团队的大前端环境;
Weex承接了淘宝、飞猪等App的大量页面,给予外界充足的信心。
虽然Web页面的上手、维护成本更低,但与Weex相比,同等页面的Web包体积要比Weex大,Web页面无法做到纯Native的体验,且页面加载速度很难极致化,在某些设备上容易出现白屏。Weex所具备的下列优势,足以让一个追求极致的团队所青睐。
二、Weex基本原理
Weex支持 Vue 和 Rax两个前端框架,由于前端团队使用Vue进行日常开发,为了降低上手成本,我们选择Vue框架进行Weex开发。Weex的基本工作流程如下:
Weex we 文件 --------------前端(we源码)
↓ (转换) ------------------前端(构建过程)
JS Bundle -----------------前端(JS Bundle代码)
↓ (部署) ------------------服务器或本地
在服务器或本地上的JS bundle ----服务器或本地
↓ (编译) ------------------ 客户端(JS引擎)
虚拟 DOM 树 --------------- 客户端(Weex JS Framework)
↓ (渲染) ------------------ 客户端(渲染引擎)
Native视图 --------------- 客户端(渲染引擎)
来自官网
除去前端页面的编写,Weex可划分为
DOM
、Render
、JSBridge
三大块(线程)。DOM
负责JSBundle
的解析、绑定、映射等处理,并通知Render
线程进行UI的更新。而Render
线程,即UI线程,负责Component
的渲染,例如前端编写的TextComponent。JSBridge
负责JS
端与Native
端的双向通信,通信的具体逻辑处理则由原生实现的一个个Module来完成
。下图是一个页面被渲染的完整流程:三、客户端系统架构
依托于Native,借助于前端,演变成大前端的架构,整体结构如下图。我们希望App作为终端,提供容器的能力,做好底层服务,完美融合Weex、Web等跨平台技术。
四、实践与解决方案以下记录了实践中遇到的一些较为重要、常见的问题,及其解决方案,它们贯穿了Weex页面的生命周期。
页面间通信
路由
Weex自身提供的navigator只支持简单的在线资源,而不支持本地文件的加载,同时无法满足动态化的呈现方式和复杂参数传递,需要自行实现一套完整的页面跳转规则。
在大量使用了
Web、Weex
技术的前提下,为了保证页面跳转、页面间参数传递、回调等的统一和便捷性,以及模块间的解耦,跳转目标可配置化,采用了路由的形式来作为页面间跳转的技术方案。基本的路由形式以及完整的过程如下:// 打开Web页面 scheme:/web/open");
Weex中发起一次页面跳转的示例:
navigator.openUrl({
url: "scheme://weex/open",
params: {
bundleUrl: "/dist/about.js",
name: "这是下一个页面所需的参数",
},
})
// 备注: 而下一个页面只需要在data中声明一个与参数同名的属性来接收具体的参数即可。
反向传值
我们在考虑反向传值的问题时,主要涉及两种场景,一是Weex页面反向传值给Weex页面,二是Native反向传值给Weex页面。我们可以使用Weex提供的基于W3C 规范的
BroadcastChannel
来轻松满足第一种场景。但是目前并没有现成的API能满足第二种场景,我们需要不断的尝试:在页面跳转时将
WXModuleKeepAliveCallback
传入下一个页面,然后在合适的时候执行该回调。这对于iOS客户端很容易实现,但由于Android在页面间传值时需要将参数序列化到内存中,然后在对应的页面从内存中反序列化出来,这会导致生成一个新的对象,从而无法完成回调。我们有尝试借鉴
BroadcastChannel
的实现,通过Weex项目全局的JSContext对象来触发广播完成反向传值,但最后无疾而终。我们最后选择使用
fireGlobalEvent
,步骤如下:1.Weex添加事件监听
const globalEvent = weex.requireModule("globalEvent");
globalEvent.addEventListener("eventName", (e) => {
// ...
});
2.原生页面发送事件
weexInstance.fireGlobalEventCallback("eventName", params); // Android
[weexInstance fireGlobalEvent:seventName params:params]; // iOS
代码中的weexInstance,可以理解为一个页面的实例对象,该流程需要在发送监听的页面需要获取上一个
Weex
页面对应的weexInstance
,可以以一种相对优雅的方式告知下一级----
在跳转的路由中添加一个needListen
来告知下一个页面:navigator.openUrl({
url: "scheme://weex/open",
params: {
bundleUrl: "/dist/about.js",
needListen: true,
},
})
2. 页面配置文件
起初Weex页面导航栏、跳转方式等配置,均在自建Module中进行处理,例如一个页面要设置导航栏,我们需要在mounted方法中加入如下代码:
created () {
navigator.setTitle("导航栏标题")
navigator.setItems([{
title: "按钮",
}])
}
这种不够工程化、标准化的方式给后期的维护、跨项目移植、架构升级造成了极大的干扰。
我们参考小程序的设计思路进行了优化升级,为每一个需要特有化配置的Weex页面添加一个json格式的配置文件,配置文件包括导航栏的配置、页面级别的配置、跳转的配置等,将配置工程化、标准化。
例如我为about页面添加了配置文件,json文件的类容为:
{
"navigationBarTitle": "导航栏标题",
"navigationItems": [{
"title": "按钮"
}]
}
打包后的资源文件目录如下,about.js、about.json文件在同级目录:
那么一个完整的页面打开步骤如下:
经过扩展,配置文件变得更加丰富,先前麻烦的跳转处理、弹框等都可以通过配置文件实现,下面是一些常用的属性介绍:
1.部分属性属性 | 类型 | 默认值 | 描述 |
backgroundColor | HexColor | 同项目配置 | 窗口背景色 |
navigationBarBackgroundColor | HexColor | 同项目配置 | 导航栏背景色 |
navigationBarHidden | Bool | false | 隐藏导航栏 |
navigationBarTitle | String | | 导航栏标题 |
navigationBarTitleColor | HexColor | 同项目配置 | 导航栏标题颜色 |
针对于iOS客户端存在页面需要Present等情况,可以设置以下属性:
present | Bool | false | Present页面 |
presentWithNavigationBar | Bool | false | Present页面,并携带导航栏 |
transition | Map | false | 以转场的形式呈现,并规定转场的动画表达式(默认背景alpha从0到1) |
优先级:transition > presentWithNavigationBar > present |
transition中需定义动画的表达式,Native则需要解析该表达式,并按照表达式执行动画。
3.设置导航栏按钮navigationItems | Array | [] | 包含按钮样式的数组 |
通过fireEvent完成按钮事件的回调。 |
按钮样式说明:
{
"type": "TEXT", // "TEXT", "IMG", "TEXT_IMG",必传
"title": "标题", // 文字标题或者
"image": "刷新", // 是图片地址,图片地址支持本地图片和网络图片
"textColor": "FFFFFF", // 16进制, 默认为白色,可不传
"backgroundColor": "", // 16进制, 默认透明色,可不传
"borderColor": "", // 16进制, 默认无边框,可不传
"borderWidth": 1, // 默认无边框,可不传
"cornerRadius": 1, // 默认无圆角,可不传
"font": 16, // 默认16号字体,可不传
"position": 0, // 默认0,可不传, 0-左侧显示,1-右侧显示
"imagePosition": 0, // 0-图片在左,文字在右,默认, 1- 图片在右,文字在左, 2-图片在上,文字在下,
4.自定义导航栏
例如满足导航栏分段选择的需求:
navigationBarTitleComponent | String | 无 | 对应的自定义Component的名称 |
Component由原生自行实现,并暴露API与Weex进行交互 |
Weex对于iOS的支持比较友好,然而Android 端无法显示阴影。 虽然文档有明确指出此问题,但是Android sdk却提供相关方法。也许是阿里的工程师尝试解决,但效果并不理想。比较明显的一点是,如果列表中的item使用阴影, 在列表滑动的时候会把阴影残留在最初绘制的位置。Android的同学一直在尝试解决此问题,最终也没达到一个理想的效果。最后的降级方案是通过图片来替代阴影,以下是Weex官方的注释:
目前仅 iOS 支持 box-shadow 属性,Android 暂不支持,可以使用图片代替。
每个元素只支持设置一个阴影效果,不支持多个阴影同时作用于一个元素。
4. 网络请求
Weex提供了
Stream
模块来完成网络请求,如果依赖于该模块,请求头、签名等配置以及请求结果校验都必须在Weex端完成,这对于一个全量Weex App而言无可厚非。但很多App的核心业务是使用Native完成,甚至会嵌套很多Web页面,我们必须将所有的请求统一至Native,让Weex更关心的是UI的呈现而非底层业务。因此我们提供了自己的网络请求模块,Weex端调用Native提供的方法,并通过参数来决定请求,一些可选的参数:
参数 | 类型 | 必填 | 描述 |
path | String | 是 | 请求的路径 |
method | String | 否 | 请求的方式,默认为’GET‘,支持’POST‘、’DELETE‘等 |
params | Map | 否 | 请求所需的参数 |
timeout | Number | 否 | 请求超时时间 |
customHost | String | 否 | 自定义请求的Host |
callback | Function | 是 | 请求的回调 |
请求示例:
// 1. 获取原生Module
const nativeStream = weex.requireModule("nativeStream")
// 2. 设置基本参数
const options = {
path:"/....",
method: "POST",
params: {
id: "123"
}
}
// 3. 发起请求
nativeStream.fetch(options, (res) => {
if (res.code === 0) {
succesCallback(res)
return
}
failCallback(res)
}
5. 图片加载
通过上图我们可以知道,一个简单的图片显示流程,其实并不简单,其中最为关键的是在进行第5步时所选择方案。先上传图片对于程序员而言是最为便捷的方案,但是比较影响用户体验,图片本应该在需要上传的时候进行上传,而非因为技术隘口而干扰业务。
转换为Base64可以提升用户体验,但是却比较影响性能。在iOS端,将一张1M的图片转换为Base64所需要的时间≥45ms,第6、7步所消耗的时间则是30ms左右,这种时间消耗随图片大小以倍数增加。
综上所述,我们设计了一种本地化方案,为每一个添加的图片生成一个唯一性的ID,Native负责图片的存储、加载。
6. 刷新组件
由于Weex所提供的
1.属性
属性 | 类型 | 必填 | 描述 |
showRefresh | Bool | 否 | 是否添加下拉刷新 |
showLoading | Bool | 否 | 是否添加上拉刷新 |
refreshAtCreated | Bool | 否 | 是否在初次显示的时候,自动进行下拉刷新 |
refresh 事件:当 、
loading 事件:当 、
"list"
c
:show-refresh="true"
class="list"
@refresh="refreshList"
@loading="loadMoreList">
beginRefresh 事件:开始下拉刷新,由Weex调用。
beginLoading 事件:开始上拉刷新,由Weex调用。
endRefresh 事件:结束下拉刷新,由Weex调用。
endLoading 事件:结束上拉刷新,由Weex调用。
this.$refs.list.beginRefresh()
this.$refs.list.endRefresh()
3.refreshAtCreated属性如果不使用该属性,则需要在created或者mounted函数中手动调用刷新的方法以触发下拉刷新,然而在某些Android设备上面出现了白屏的情况。Weex对此做出了解释:
和浏览器不同的是,Weex 的渲染流程是异步的,而且渲染出来的结果都是原生系统中的 View,这些数据都无法被 javascript 直接获取到。因此在 Weex 上,Vue 的 mounted 生命周期在当前组件的 virtual-dom (Vue 里的 VNode) 构建完成后就会触发,此时相应的原生视图未必已经渲染完成。
7. 截屏
Weex中完成截屏,只需要获取到对应的视图层即可,Weex页面在渲染时会为每一个组件生成一个唯一的ID,在JavaScript中更直观的体现是ref,尽管Weex并不存在真正的DOM,但其依然支持ref的使用。具体的做法如下:
// 1. 标签生命ref
"poster">
// 2. 获取到该element,实际上是一个Map
const poster = this.$refs.poster
// 3. 获取Map中对应的ref
saveViewShot({ ref: poster.ref })
// 4. 获取Component
// iOS
WXComponent *component = [weexInstance componentForRef:ref];
// Android
WXComponent component = WXSDKManager.getInstance().getWXRenderManager().getWXComponent(mWXSDKInstance.getInstanceId(), ref);
// 5. 获取View,进行
8. 优雅的弹窗
这是一个很简单的弹框需求,视图渐渐变大最后全屏展示。然而基于Weex现有的能力是无法实现的,第一点:Weex页面默认的布局是从导航栏下面开始的,第二点:路由的跳转方式并不能直接支持此种弹出方式,页面默认是从右向左推出。
为了实现弹框的功能,我们需要四个步骤:
1. Native定义PopView组件
1. Weex搭建页面,并以PopView为基础进行布局
2. 全屏呈现页面并隐藏导航栏
3. 执行动画
原生定义好PopView组件后,Weex页面可以这样布局:
"pop-view"> 测试弹框
结合第三点所提出的配置文件,我们将2、3步骤的控制放在配置文件当中,最后写出的配置文件为:
{
navigationBarHidden: true, // 隐藏导航栏
transition: {
property:"scale", // popView的尺寸将由(0,0)变为显示大小
duration: 2, // 动画时间,单位(秒)
},// 转场显示,并规定popView的显示动画
}
这只是最为简单的例子,更复杂的动画需要客户端支持即可。
五、To Be Continued
以上概括了Weex接入的心路历程,以及在实践中遇到的基本问题,表明了Weex在团队中的运用已经畅通并日趋规范化,但是更深入的性能优化、热更新等需要我们继续前行,以下是下一期文章将涉及的知识点:
热更新
资源预加载
配置文件动态化
Weex资源打包自动化,自动加入终端仓库
加推科学院公众号
mp.weixin.qq.com/s/LxdQ6Eq2R…
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/6889.html
摘要:问题,你可以在中文讨论板块提交问题,地址。文字展现必须使用标签关于端的点透事件需要在上层视图上加上,如果上层视图有事件,多加一个中间层,把加在空事件视图上关于事件注意仅支持和,暂不支持。事件会在页面就要关闭时被触发。 好吧,我知道你来看这个文章,一定是遇到坑了,所以,把这几个放在最开始吧 现在,如果你的团队的技术栈是react,请尝试这个吧,跟react很像,如果你的团队一直使用rea...
阅读 3512·2021-08-02 13:41
阅读 2289·2019-08-30 15:56
阅读 1501·2019-08-30 11:17
阅读 1159·2019-08-29 15:18
阅读 564·2019-08-29 11:10
阅读 2644·2019-08-26 13:52
阅读 491·2019-08-26 13:22
阅读 2927·2019-08-23 15:41