随着小程序用户增多,越来越多的平台开始扩大本人的小程序能力。如传统微信小程序,我厂的京东小程序,字节和百度等泛滥平台都在退出。然而以后的开发大环境,却是 ” 小步快跑,疾速迭代 ”。那么,” 一套代码,多端运行 ” 成为很多团队实际的幻想。咱们在最近的京采云挪动端商城我的项目中应用 Taro + Vue3 + NutUI3 , 实际了一下多端开发之路。
开发背景(我的项目背景)
京采云将需要治理、寻源投标、供应商治理、自助式商城、履约协同、财务结算“六大治理能力”集于一体,可能高效实现多供应商的引入和全生命周期治理,并通过智能化洽购剖析帮忙企业将洽购需要和洽购计划主动匹配,实现便捷、高效的供应链治理。同时,产品心愿我的项目不只能在 PC 端运行,同时还要求开发 H5 页面不便嵌入客户 APP 中,满足客服自建电商商城的需要,且在下一阶段还心愿我的项目可能在小程序中疾速利用。
技术选型和介绍
技术比照
刚接到需要,咱们着手考察了市面上目前存在的多端框架,例如 Taro , uni-app , chameleon 等。
为了寻找更适宜咱们的框架,从以下几个方面做了比照:
- 开发工具
uni-app 应该是一骑绝尘,它的文档内容最为翔实丰盛,还自带了 IDE 图形化开发工具(HBuilderX),鼠标点点点就能编译测试公布。其它的框架都是应用 CLI 命令行工具,但值得注意的是 chameleon 有独立的语法查看工具,Taro 则独自写了 ESLint 规定和规定集。
- 技术栈
mpvue、uni-app、Taro 均反对 TypeScript,也都能通过 typing 实现编辑器主动补全。除了 API 补全之外,得益于 TypeScript 对于 JSX 的良好反对,Taro 也能对组件进行主动补全。然而 Taro 对 React 和 Vue 都能残缺的提供反对,其余都只能反对一种前端框架。
而 CSS 语法方面,所有框架均反对 SCSS、LESS、Stylus,Taro 则多一个 CSS Modules 的反对。
- 多端反对
目前 uni-app、Taro 和 chameleon 都能反对市面上常见的小程序。
- 性能比照
不论是 Taro 还是 uni-app,setData
的优化都是小程序性能优化中最为重要之事,且优化次要有两个方向:
- 尽可能减少
setData
调用的频次 - 尽可能减少单次
setData
传输的数据
咱们本人入手写了一个长列表测试,别离写了 Taro 版、uni-app 版、原生小程序版,前几页数据滚动时效率都差不多,然而 7、8 页过来后,发现 uni-app 加载新页面时有变慢的感觉。
揣测 uni-app 的长列表没有 recycle 机制,花了点工夫把 demo 改良了下,滚动上面时把后面几页的数据干掉,而后再滚动就感触不到晦涩度的差异了。
所以得出结论:Taro 在性能优化上做的更粗疏,应用 uni-app 须要本人留神代码优化。
综上所述,Taro 和 uni-app 作为市面上最为优良的多端框架。在各项指标上都并驾齐驱。然而 Taro 对开发者更加凋谢且更加多元化,同时配合更合乎京东格调的 NutUI 组件库。所以 Taro + NutUI3 成为咱们首选。
咱们先介绍一下咱们将要应用的工具
Taro 介绍
Taro 是一个开放式跨端跨框架解决方案,反对应用 React/Vue/Nerv 等框架来开发 微信 / 京东 / 百度 / 支付宝 / 字节跳动 / QQ 小程序 / H5 / RN 等利用。
以后 Taro 已进入 3.x 时代,一套更加高效,精简的 DOM/BOM API 包(taro-runtime)能够实现同时对齐 React 和 Vue 2/3 的开发。
@tarojs/runtime 是 Taro 的运行时适配器外围,它实现了精简的 DOM、BOM API、事件零碎、Web 框架和小程序框架的桥接层等
这时 Web 框架就能够应用 Taro 模仿的 API 渲染出一颗 Taro DOM 树,然而这所有都运行在小程序的逻辑层。而小程序的 xml 模板须要提前写死,Taro 如何应用一个动态的模板文件去渲染这颗动静的 Taro DOM 树呢?
Taro 抉择了利用小程序 能够援用其它 的个性,把 Taro DOM 树的每个 DOM 节点对应地一个个渲染。这时只须要把 Taro DOM 树的序列化数据进行 setData,就能触发数据的互相援用,从而渲染出最终的 UI。更多原理咱们在上面具体介绍。
NutUI 介绍
作为一个京东格调的轻量级挪动端 Vue 组件库,在 NutUI 3 的版本中,采纳了 Vite + Vue3 + ts 的架构,同时全面反对小程序的适配。Taro 官网也将 NutUI 作为 Vue 技术栈的举荐组件库。
NutUI 是如何实现反对小程序的?
针对每个组件,咱们在原有组件的目录构造中新增 .taro.vue
文件来专门解决 Taro 兼容。而在应用的时候,能够依据你的需要,装置惯例 Vue3 版本还是 Taro 版本。
#Vue3 我的项目
npm i @nutui/nutui@next -S
# NutUI 小程序多端我的项目
npm i @nutui/nutui@taro -S
而应用的时候,依据不同的源援用对应的组件即可。
import {createApp} from 'vue';
// vue
import {Button} from '@nutui/nutui';
// taro
import {Button} from '@nutui/nutui-taro';
这样,咱们就能够在 Taro 中应用这套组件库了。
业务开发
京采云商城为了满足企业客户自建电商商城,搭建了一套残缺的商城流程,包含首页(banner 图,导航,举荐等多个楼层),分类,搜寻,商品详情,店铺列表,店铺详情,购物车,订单,集体页面,地址 抉择等。同时针对大批量洽购,减少了订单提报和审批的流程。同时前期将会更加丰盛商城性能,为客户提供更高效,快捷的洽购服务。
先从视觉上感受一下~
#### 反对多平台嵌入
为了不便企业客户内 app 引入咱们商城,更好的引流商城商品。咱们只有客户的 app 内增加咱们的入口链接,应用 Cookie 买通登陆态, 即可完满嵌入。
咱们设置 Cookie 的 domain 参数,来实现不同域名之间拜访同一个 Cookie。
document.cookie = "Cookie=testcookie; domain=test.com;path=/";
主题定制
为了更好的服务各种接入平台,放弃与接入平台的格调统一,咱们必须实现主题可配置化,依据接入平台的主题色,传入不同的参数实现主题统一的成果。
首先,咱们在开发的时候,提取出公共的 SCSS 文件,其中蕴含咱们常见的主题款式变量。
// 主题色
$primary-color: #478ef2;
// 按钮色彩
$bg-color-selected: #fef4f3;
$bg-color-disabled: #fcd4cf;
// 边框色彩
$border-color: #ececec;
// 更多
......
咱们在开发过程中,咱们间接应用对应的款式变量,既不便咱们前期保护,也简略实现了主题一处批改,全局利用的要求。
然而这样咱们每次批改主题色都须要批改这个 SCSS 文件,咱们心愿通过一个接口来返回主题色,而后实现主题的渲染。如何实现呢?
得益于 Vue3 sfc 的性能,咱们能够间接在 Vue 文件中的 style 书写 css 款式的时候,能够应用 v-bind 来绑定变量,从而实现 css 款式中应用 JS 中的变量。
const colorState = reactive({
primaryColor: '#f0250f', // 默认色彩
borderColor:#fef4f3
});
// 在 App.vue 中获取全局主题
router.isReady().then(async () => {
...
const result = await getTheme(); // 获取主题的接口
if (result?.state === 0) {
colorState.primaryColor = result.primaryColor;
colorState.borderColor = result.borderColor;
}
});
<style lang="scss">
// 定义全局类
.primary-color {color: v-bind(primaryColor);
}
.border-color {color: v-bind(borderColor);
}
</style>
通过以上的定义,咱们能够在代码中间接应用 css 类 primary-color 等实现动静款式。
Taro + NutUI 的利用
随着 Vue 3 的正式公布,更好的性能,更小的体积,更好的 TypeScript 集成,更好的解决大规模用例的新 API , 所有的开发者开始拥抱 Vue3。
Taro 紧随潮流,迅速在 Taro3 中开始反对 Vue3 代码的转换,而 NutUI 也紧随其后,首先实现了所有组件 Vue2 到 Vue3 的降级,紧接着实现了对 Taro 的适配,更好的服务开发人员。
目前 Taro 仅提供一种开发方式:装置 Taro 命令行工具(Taro CLI)进行开发。通过在终端输出命令 npm i -g @tarojs/cli
装置 Taro CLI 之后就能够应用了。具体的 Taro 如何应用和开发,在官网上和都有残缺的教程,这里咱们就不做重点介绍。
上面咱们重点钻研一下 Taro 和 NutUI 是如何实现一套代码利用多端的。
咱们先来看一下小程序的架构。
微信小程序次要分为逻辑层和视图层,以及在他们之下的原生局部。逻辑层次要负责 JS 运行,视图层次要负责页面的渲染,它们之间次要通过 Event 和 Data 进行通信,同时通过 JSBridge 调用原生的 API。这也是以微信小程序为首的大多数小程序的架构。
小程序原生局部,各个平台是不一样的,而且这部分对于前端开发这就是一个黑盒。所以前端层面只须要关注逻辑层和视图层。
因而,只须要在逻辑层调用对应的 App()/Page()
办法,且在办法外面解决 data、提供生命周期 / 事件函数等,同时在视图层提供对应的模版及款式供渲染就能运行小程序了。这也是大多数小程序开发框架重点思考和解决的局部。
那么对前端来讲,无论什么框架 Vue or React,最终运行后果都是调用了浏览器的几个 BOM/DOM 的 API,如:createElement
、appendChild
、removeChild
等。因而,上文也提到过,Taro 应用 taro-runtime 实现了这套 API。
接下来,在 React 中实现了一个 taro-react 包,用来连贯 taro-runtime 和 React 保护 DOM 树的外围 react-reconciler,从而实现 render 函数,生成 Taro DOM Tree。Vue 同样解决,在 Vue 的 CreateVuePage 办法中对齐 一些运行时办法的解决,如生命周期或者 watch 等。
通过外部的 Taro 插件,Taro 就会生成一个 Taro DOM Tree,那么 Taro DOM Tree 如何对齐小程序,从而渲染到小程序页面上呢?
首先,咱们将小程序的所有组件挨个进行 模版化解决,从而失去小程序组件对应的模版,如下图就是小程序的 view 组件通过模版化解决后的样子:
<template>
<view
id="{{uid}}"
class="{{className}}"
style="{{style}}"
>
<block wx:for="{{children}}">
<template is="{{'tpl_'+item.nodeName}}" data="{{item}}">
</block>
</template>
接下来,咱们就去遍历咱们的 Taro DOM Tree 树,再依据每个子元素的类型抉择对应的小程序模板来渲染子元素,而后在每个模板中咱们又会去遍历以后元素的子元素,以此把整个节点树递归遍历进去。最初就能够在小程序上渲染胜利了。
// Taro 转换失去的 dom 树
{
1:[{type:REPLACE,node:ELEMENT}],
2:[{type:text,content:'文本',children:[ ...]
}],
3:[{type:PROPS,props:{class:"content",number:1},...
}],
...
10:[{type:ELEMENT,tage:"ul",childred:[{tag:"li",children:["item"] },...]
}]
}
当然,大家就可以看进去,这就是一个一般的虚构 dom 树。相似
const newVnode = h(
"div#container.two.classes",
{on: { click: anotherEventHandler} },
[
h(
"span",
{style: { fontWeight: "normal", fontStyle: "italic"} },
"This is now italic type"
),
"and this is still just normal text",
h("a", { props: { href: "/bar"} }, "I'll take you places!"),
]
);
那么,咱们动静递归解决这个 dom 树,同时转换成合乎小程序格局,这样就完满的在小程序上展现了。
<template>
<text>
文本 ...
</text>
...
<ul>
<li>...</li>
....
</template>
那么 NutUI 组件库又是如何实现对小程序的适配呢?
下面咱们提过,NutUI 针对每个组件独自设置了一个 Taro 文件,如 button.taro.vue。专门针对 Taro 的适配。其中间接应用 view 标签来作为常容器,更贴合小程序的转换。
咱们来看看这个文件内容:
<template>
<view :class="classes" :style="getStyle" @click="handleClick">
<view class="nut-button__warp">
<nut-icon class="nut-icon-loading" v-if="loading"></nut-icon>
<nut-icon :class="icon" v-if="icon && !loading" :name="icon"></nut-icon>
<view :class="{text: icon || loading}" v-if="$slots.default">
<slot></slot>
</view>
</view>
</view>
</template>
同时借助 tarojs/plugin-html实现对一些 dom 和事件的解决。
// 对 dom 转换
if(inlineElements.has(nodeName){return "text"}else if(specialElements.has(nodeName)){return "view"} else if(...)
// 对事件的解决
onAddEvent(type,_hader_,_options,node){if(!isHtmlTags(node.nodeName)) return;
//click 事件转换成 tap
if(type == "click"){defineMappedProp(node._handlers,type,"tap")
}else if(type == "input"){defineMappedProp ...}
}
我的项目优化和反思
开发过程中当然会遇到很多问题,同时也值得咱们记录下来,更好的服务下次开发。
路由
首先 Taro 在 Vue 中并没有实现真正的路由,而是通过管制组件事实 / 暗藏来模仿路由切换。所以在历史记录中,做了一个 10 层的限度。当应用 Taro.navigateTo 跳转的时候,很快就会冲破这个 10 层。这个须要咱们对 navigateTo 函数做了一层封装,避免溢出。
此外,因为没有真正的实现路由,所以当 A 页面跳转到 B 页面,其实 A 页面并没有隐没,只是通过 display:none 进行了暗藏,并给了 A 页面一个举世无双的随机 id。即便这样,咱们在代码中操作 DOM 元素或者一些 setInterval 办法,都要特地留神。Taro 导致不会每次进入页面就会刷新,也不会来到就销毁,刷新,清理数据的动作都须要本人在生命周期函数里被动触发。
页面内容缓存
接下面内容,应用 navigateTo 实现路由跳转,当 A 页面进入 B 页面 再回到 A 页面的时候,其实 A 页面并没有从新渲染。还会保留上次的数据。实现相似 Vue 中 keep-alive 组件的成果。但因为没有提供额定的 生命周期函数,所以会导致数据不更新。即便强制驱动数据更新,也因为上一次渲染的缓存,导致页面有一个闪动的变动,影响用户体验。所以倡议在每次的 beforeMount 时清理一下以后页面数据,防止闪动。同时慎用 navigateTo 跳转,多用 redirectTo 实现路由。
监听页面滚动事件
为了适配小程序,所以页面的滚动事件只能通过 onPageScroll 来监听,所以当我想在组件里进监听操作时,要将该局部的逻辑提前到 onPageScroll 函数,进步了形象老本。例如我须要开发一个滚动到某个地位就吸顶的 tab,原本能够在 tab 外部解决的逻辑被提前了,缩小了其可复用性。
优化
性能优化是每个我的项目的重点内容,咱们应用了在总结了一下几点常见的优化点。
总结
作为第一个应用 Taro + Vue3 + NutUI3 实现的我的项目,尽管开发中遇到很多问题,但也播种很多。” 一套代码,多端利用 ” 的需要场景将会越来越多,咱们将会在更多的业务中深刻实际,实现自我的晋升。