背景
通过一套代码,一套业务代码,构建成不同的端(H5/小程序),满足代理人平台多端的业务场景,并为东东企业家、京灵、bizNet 项目积累经验。
多端开发方式
- 混合开发
cordova/uniapp
可以同时支持 vue/react/nerv 等框架
- 跨端
需要注意的H5和小程序的区别
H5和小程序的运行环境有本质上的差别,导致两者在很多方面有不同的地方。
H5主要与运行在浏览器及web-view的浏览器环境。简单来说,浏览器环境主要有window对象,Bom, Dom 等API构成,同时可操作的系统API不多,这里的系统API比如: 拍照,图片上传,位置服务等等。
小程序主要运行在以微信为代表的平台环境中。平台环境提供了一套能够操作系统权限的API,同时由于小程序架构的特点,小程序的js运行在平台封装的jsCore中,并没有一个完整浏览器对象,因而缺少浏览器中相应的Dom 及Bom操作。
另外,H5的渲染机制和小程序也有所不同。H5的js和dom是在一个线程中同时进行,小程序则分了渲染层和逻辑层两个线程,两个线程通过原生的消息机制进行通信。渲染层在IOS中使用【WKWebView】进行渲染,在Android中使用【chromium定制内核】进行渲染。
因此,在开发过程中我们需要对这些差异有所了解,以及能够预判这些差异给我们带来的影响。这些差异主要有以下几点:
- 客户端环境
- 运行机制
- 系统权限
- 更新机制
-
客户端环境
H5的宿主环境是浏览器以及web-view中的浏览器环境。小程序的宿主环境是特定的应用平台(微信/支付宝…)。
导致小程序脚本中无法使用浏览器常用的window对象及dom,bom相关的api,以及两者的缓存机制略有不同,H5可以使用cookie进行设置,小程序虽然有Storage但是没有cookie。所以,在使用Taro进行开发时,需要注意以下问题: 客户端运行宿主环境判断
-
客户端运行环境判断
以往基于H5的移动端开发时,通常时用navigator.userAgent这个API来获取浏览器信息,判断是ios/android/浏览器。
let browser={ versions:function(){ var u = navigator.userAgent, app = navigator.appVersion; return { trident: u.indexOf('Trident') > -1, //IE内核 presto: u.indexOf('Presto') > -1, //opera内核 webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核 gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,//火狐内核 mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端 ios: !!u.match(/(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端 android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1, //android终端 iPhone: u.indexOf('iPhone') > -1 , //是否为iPhone或者QQHD浏览器 iPad: u.indexOf('iPad') > -1, //是否iPad webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部 weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增) qq: u.match(/sQQ/i) == " qq" //是否QQ }; }(), language:(navigator.browserLanguage || navigator.language).toLowerCase() }
但是在Taro中,Taro提供了getEnv()方法,可以直接获取所在端信息。比如:
const isWeb = Taro.getEnv() === 'WEB' const isWeapp = Taro.getEnv() === 'WEAPP'
登录流程的控制
小程序的登录机制是,wx.login获取code传给服务端, 服务端接收code同时调用微信的凭证校验接口,微信服务返回seccion_key/openid给服务端,服务端根据返回的信息生成一个token返给前端,前端缓存token,以后的每次请求都写到请求头中。
H5的登录情况有两种,一种是调用login接口后,服务端根据接受到的用户信息生成token/cookie返回给前端,前端缓存后,每次请求都写到请求头中。另外一种是服务端直接设置cookie。
注意:本次开发时,H5的登录是由服务端直接设置cookie, 在测试环境调试的时候需要手动将cookie设置到对应的域名下。同时小程序的登录用了中台的登录插件,登录成功后可以直接使用回调函数plugin.loginSuccess((data)=>{ // 登录成功后的操作 ... })
假设小程序登录不用插件,H5登录也不是服务daunt直接设置cookie,那么可以做如下封装:
const loginMethods = { WEB:()=>{ // h5登录流程 ... }, WEAPP:()=>{ // 小程序登录流程 ... } } const currentEnv = Taro.getEnv() // 执行登录方法 loginMethods[currentEnv]()
运行机制
H5的运行机制/运行过程通常是加载Dom树,加载渲染树Css,绘制页面这样一个过程。小小程序的运行机制分为热启动和冷启动。【热启动】:如果用户已经打开过某小程序,在一定时间内再次打开该小程序,此时无需重新启动,只需将后台态的小程序切换到前台。【冷启动】: 如果用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动。
这里对我们造成的影响主要是:【数据请求时机】。数据请求时机
H5的请求通常在document.ready即可调用。vue框架在created()函数中,react在componentDidMount()中。
小程的热启动在调用数据请求时可能会遇到以下问题:小程序的onShow函数,页面每次展示时都会请求数据,如果请求的数据是列表。手机息屏,重新点亮屏幕会出现重复数据,需要对这个现象进行处理。系统权限
小程序可以直接调用系统的【保存图片】【拍照】【音频】【位置】【转发】等权限。H5则无法直接获取这写权限。这里的差异主要表现在:
-
分享转发
小程序可以直接使用wx.shareAppMessage方法及
小程序上传图片及图片预览可以直接调用相应的Api从相册中获取图片进行上传。
H5则需要特定的上传组件,或借用jsBridge使用原生应用的能力。
更新机制
小程序每次 【冷启动】时,都会检查有无更新版本,如果发现有新版本,会异步下载新版本代码包,并同时用客户端本地包进行启动,所以新版本的小程序需要等下一次 冷启动 才会应用上,当然微信也有wx.getUpdateManager 可以做检查更新。这个对我们的影响主要在发布项目的过程。
发布项目
H5打包后可以直接发布实时更新。小程序需要打包后上传到小程序后台进行审核后,才能正常发布。
其他开发过程中需要注意的问题
除了以上列举的差异外,还有几个问题需要注意。
组件化
- 多端组件
- 接入中台服务
- 开发调试
-
组件化开发
前端项目基本上都采用组件化开发的形式。组件化开发的优势明显,将复杂业务逻辑拆分,解耦。组件内逻辑清晰。可以复用性强。同时便于后期维护。
多端组件
组件的封装过程中可能遇到不同端展示的效果不一致。这时候可以用环境判断进行相应的布局。
// 多端组件 renderName(){ const {name,role} = props return ( <View className="nickName"> <View>{name}<View> {Taro.getEnv()==='WEAPP'?null:<View className="role">{role}<View>} </View> }
虽然Taro提供了根据文件后缀名编译不同端页面的能力,但是基于组件进行开发,就没必要创建那么多不同后缀名的文件了。
接入中台服务
接入流程一般是: 申请业务ID—>服务提供方开发排期—>双方联调测试—>服务提供方发布生产。
沟通成本略高,不可控因素较多【开发环境】【测试环境】…开发调试
代码优化
设计模式【策略】【迭代器】… ```javascript // 上传文件 // 例如上传文件
const uploadH5 = () => {…}
const uploadWx = () => {…}
const uploadNative () => {…}
export const uploadFile = () => { let uploadlist = [uploadH5,uploadWx,uploadNative] for(let i in uploadList){ let success = uploadListi if(success){ return ‘upload success’ } } }
- 复用代码
某些代码需要多处用到,则可以将之封装起来,甚至直接添加到全局对象上或者Taro的实例上。比如实名状态判断
<a name="fplqJ"></a>
## 其他扩展
- 跨框架组件
- 开发真正的跨端组件
<a name="UIbqL"></a>
#### <br />
虽然Taro支持将代码打包成不同的端,同时也有相应的trao-ui组件库,但是改UI库目前只支持react框架,假如我们使用vue进行迭代,则需要引入基于vue的另一个组件库。 同时目前的Taro-UI不支持ReactNative。
有没有什么方法,可以同时兼容两种框架?有没有什么方法可以同时支持ReactNative? 简单介绍下 web-component和styled-component
<a name="Xenx1"></a>
### web-component
web-component是一种自定义可重用元素的技术。其主要概念有以下三个:
- 自定义元素
- temlate / slot 模板和插槽
- shadow Dom
<a name="UJWOZ"></a>
#### 自定义元素
我们可以使用customElements.define()方法,自定义一个元素。该方法接受两个参数:
- 自定义的元素名称
自定义元素名称需要用连字符 - 进行连接
- 一个用于定义元素行为的类
- 一个可选参数,个包含 extends 属性的配置对象,是可选参数。它指定了所创建的元素继承自哪个内置元素,可以继承任何内置元素。
<a name="Xtm6z"></a>
##### 自定义元素示例:
class MyButton extend HTMLElement { // …. } // 注册自定义元素 customElements.define(‘my-button’, HTMLElement);
// 使用自定义元素 和 平时写html一样
<a name="q4ayH"></a>
#### Shadow Dom
Shadow Dom的主要作用是将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。
Shadow DOM 都不是一个新事物——在过去的很长一段时间里,浏览器用它来封装一些元素的内部结构。以一个有着默认播放控制按钮的 <video> 元素为例。你所能看到的只是一个 <video> 标签,实际上,在它的 Shadow DOM 中,包含来一系列的按钮和其他控制器。Shadow DOM 标准允许你为你自己的元素(custom element)维护一组 Shadow DOM。
Shadow Dom有以下几个重要的概念:
- shadow host : 一个常规 DOM节点,Shadow DOM 会被附加到这个节点上。
- Shadow tree:Shadow DOM内部的DOM树。
- Shadow root: Shadow tree的根节点。
我们可以使用 Element.attachShadow() 方法来将一个 shadow root 附加到任何一个元素上。它接受一个配置对象作为参数,该对象有一个 mode 属性,值可以是 open 或者 closed:open 表示可以通过页面内的 JavaScript 方法来获取 Shadow DOM。
同时,可以使用模板字符串给shadow dom 添加样式。这里有几个伪类选择器
- :host 表示当前shadow dom的宿主节点
- : slotted 表示插槽内容中的dom节点
// web-components写法 换成图片 // footer 页脚 // 布局容器 const layout = `
class AppFooter extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: ‘open’ });
shadowRoot.innerHTML = ${layoutStyle.footer}
<slot></slot>
}
connectedCallback() {
}
attributeChangedCallback (name, oldValue, newValue) {
}
}
<a name="WHhep"></a>
### styled-component
简单介绍下如何使用styled-component
![](https://user-gold-cdn.xitu.io/2020/6/21/172d6e2b7ce771de?imageView2/1/w/1304/h/734/q/85/format/webp/interlace/1#id=PwWvB&originHeight=412&originWidth=730&originalType=binary&ratio=1&status=done&style=none)
组件时代的视觉原语。<br />Use the best bits of ES6 and CSS to style your apps without stress 💅🏾
它可以让我们毫无压力的使用ES6和CSS中最好的部分来设计应用程序。
<a name="hv5ZD"></a>
#### 创建附带样式的组件(react)
```javascript
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
// Create a Wrapper component that'll render a <section> tag with some styles
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
// Use Title and Wrapper like any other React component – except they're styled!
render(
<Wrapper>
<Title>
Hello World!
</Title>
</Wrapper>
);
基于Props的适配
可以将函数(“插值”)传递给已设置样式的组件的模板文本,以便根据其属性对其进行调整。
比如下面的组件可以更改其颜色的主状态。当将primary为true时,将切换其背景和文本颜色。
扩展样式
有时候我们需要多次使用某个组件,但样式不一定一样,这时候我们只需要将原有的组件用styled
函数包装一下,就可以实现。
除此之外,还可以用as
将组件A的特性表现为组件B的特性
props的传递
如果styled
方法接受的目标是一个简单的html元素,styled-component
会将与之对应的属性传递给它,如果目标是一个自定义的组件,那么会将我们定义的props传给它。
支持React Native
styled-components
可以用相同的写法同步React Native
这里引入的styled.View
应该是对react-native的组件或者meterial-UI做了一层转化或者封装。
总结
了解了H5和小程序的差异以后,基于已有的设计规范使用web-component及styled-components可发一套框架依赖底,或能够适配ReactNative的UI库是可行的。
公众号
微信搜索《JavaScript高级程序设计》