背景

通过一套代码,一套业务代码,构建成不同的端(H5/小程序),满足代理人平台多端的业务场景,并为东东企业家、京灵、bizNet 项目积累经验。

多端开发方式

  • 混合开发

cordova/uniapp

  • 原生体验开发

    1. flutter/react-native
  • 优缺点

    优点:可以快速开发跨ios和andriod两端的应用。降低开发成本<br />缺点:无法适配小程序端。
    

    Taro相比之下的优势

  • 跨框架

可以同时支持 vue/react/nerv 等框架

  • 跨端

可以编译成不同的端形式。H5/小程序/RN

需要注意的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—>服务提供方开发排期—>双方联调测试—>服务提供方发布生产。
    沟通成本略高,不可控因素较多【开发环境】【测试环境】…

    开发调试

    H5使用【vconsole】,小程序使用【微信开发者工具】

    代码优化

  • 设计模式【策略】【迭代器】… ```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>
);


基于Taro的多端(小程序 H5)开发实践 - 图1

基于Props的适配

可以将函数(“插值”)传递给已设置样式的组件的模板文本,以便根据其属性对其进行调整。

比如下面的组件可以更改其颜色的主状态。当将primary为true时,将切换其背景和文本颜色。

基于Taro的多端(小程序 H5)开发实践 - 图2

扩展样式

有时候我们需要多次使用某个组件,但样式不一定一样,这时候我们只需要将原有的组件用styled函数包装一下,就可以实现。

基于Taro的多端(小程序 H5)开发实践 - 图3

除此之外,还可以用as将组件A的特性表现为组件B的特性

基于Taro的多端(小程序 H5)开发实践 - 图4

props的传递

如果styled方法接受的目标是一个简单的html元素,styled-component会将与之对应的属性传递给它,如果目标是一个自定义的组件,那么会将我们定义的props传给它。

基于Taro的多端(小程序 H5)开发实践 - 图5

支持React Native

styled-components 可以用相同的写法同步React Native

基于Taro的多端(小程序 H5)开发实践 - 图6

这里引入的styled.View应该是对react-native的组件或者meterial-UI做了一层转化或者封装。

总结

了解了H5和小程序的差异以后,基于已有的设计规范使用web-component及styled-components可发一套框架依赖底,或能够适配ReactNative的UI库是可行的。

公众号

微信搜索《JavaScript高级程序设计》