跨端开发 Taro 应用篇:多端复用实战
一 前言
Taro 是跨端开发的经典解决方案,可以支持使用 React/Vue/Nerv 等框架,运行小程序和 H5 应用。有了 Taro, 前端开发同学,可以使用自己擅长的框架,跨端开发 h5 小程序。接下来让我们看一下如何快速上手 Taro 框架。
快速上手:
首先就是安装 Taro 的脚手架 @tarojs/cli,在终端执行命令:
npm install -g @tarojs/cli
成功之后会如下的样子:
成功之后可以运行 taro -v
可以看到 taro 脚手架是否安装成功了。
接下来就可以通过 taro init myApp
来初始化项目。
如上可以选择项目的配置,在运行时框架上,可以选择 Vue,Vue3 和 React 等; 在语言层面上可以选择 TypeScript 或者 JavaScript;还可以自由的选择 CSS 预处理器,编译工具和包管理工具等。
选择完工程化配置后,接下来 Taro 可以自定义从远程下载模版,然后自动安装依赖,接下来项目初始化完成了:
接下来可以运行不同的终端命令,构建不同端的产物。来看一下 myApp 工程项目中的 package.json 文件,里面存在各种的命令,比如运行 h5 端,小程序端,React Native 端。
如果我们想把代码运行到 h5 端,那么终端执行 npm run build:h5 ,这样会把我们的业务代码打包到 dist 文件夹下面,打包产物如下所示:
如上可以打包的产物都是浏览器 html js ,css 三大件这一套。
如果想运行到小程序端,那么只需要终端运行 npm run build:weapp, 接下来就可以看到产物了。
这样就可以实现,一套代码可以运行多端,真正的实现了跨端多端复用了。 接下来我们用 React 构建的 H5 引用,和 Vue 构建的小程序应用为切入点,详细介绍一下 Taro 应用实践。
二 Taro React 构建 H5 应用
Taro 构建 H5 的应用适合什么场景?
Taro 构建 H5 应用,非常适合 App 应用内部嵌入的 h5 活动页面,或者本身是 h5 构建的移动 端 Hybrid 应用。
作者之前在某家大厂,用 Taro 构建了一个 h5 应用,主要场景就是某一个知名电商 App 内部的 h5 落地页。
Taro 构建 h5 优势是十分明显的,Taro 本身做了移动端适配,无需开发者在关心适配成本,并且 Taro 生态体系比较完善,比如移动端的 UI 库有 taro-ui,里面提供了很多移动端组件。比如,移动端网页期望更快的打开速度,那么就需要 SSR 服务端渲染,提升首屏实现,那么可以用 Taro 配套的 SSR 服务端渲染方案—tarojs-plugin-ssr 。
应用入口:
接下来我们用 React 构建一个 h5 应用,选择 ts 开发,并且使用 mobx 作为状态管理工具:
import Taro, { Component, Config } from '@tarojs/taro'
import { Provider } from '@tarojs/mobx'
import Index from './pages/index'
import eventListener from './store/eventListener'
import './app.less'
const store = {
eventListener
}
class App extends Component {
constructor(){
super()
}
componentDidMount(){
/* 可以获取路由信息,比如路由 path 和路由参数等 */
const { path } = this.$router as any
console.log(path)
}
/* 这里约定配置路由页面 */
config: Config = {
pages: [
'pages/index', //首页
'pages/fail', //失败页面
],
}
render () {
return (
/* 使用 mobx */
<Provider store={store}>
<Index />
</Provider>
)
}
}
/* Taro 提供的渲染器 */
Taro.render(<App />, document.getElementById('app'))
如上用 Taro 提供的 render 渲染器来负责视图渲染,config 里面约定好路由页面,这个应用有首页和失败页两个页面 ,@tarojs/mobx 能够让 Taro React 使用 mobx 进行状态管理。
可以在 $router 中可以获取路由信息。在 Taro v3.6 版本中,运行时引入 History & Location 对象,且尽可能与 Web 端规范对齐,你可以在 window 对象上访问到 history 和 location 对象。同时,也支持监听 hashchange 和 popstate 事件,这为使用路由库提供技术基础。
使用 DOM Api
在 Taro h5 应用中,有一点比较好的就是有 DOM 以及对应的 API , 所以可以使用一些基于 DOM 的操作,比如获取元素真实 DOM ,或者 DOM 事件监听等。
useEffect(()=>{
document.addEventListener('scroll', handerScroll)
return function () {
document.removeEventListener('scroll', handerScroll)
}
},[])
如上在可以用 hooks 和事件监听器完成滚动事件的监听。
与 app 完成协议通信
在 Taro h5 本质上运行在 Native 的 webview 容器里,所以会有很多安卓和 ios 差异,可以读取 navigator.userAgent
中的设备状态,根据不同的端做不同的处理。
对于一些和 Native 简单交互,比如跳转,返回,刷新,可以按照容器 App 指定的 scheme 协议处理,如果有差异,就可以走差异化逻辑。
const browerEnv = (window as any).__browerEnv__
if(browerEnv==='IOS'){
window.location.href = "openApp://webview?refresh=true";
}else{
window.location.href = 'openApp.Mobile://virtual?params={"category":"jump","des":"HomePage","sourceValue":"","sourceType":""}'
}
如上用 window.location.href
来完成大多数交互逻辑。还可以通过之前第三章中讲到的桥通信来解决与 Native 的交互问题。
工程化配置
在 Taro 项目中,可以在根目录中的 config 文件中,做不同环境下配置环境,如下所示:
如上 index.js 中是 Taro 工程化的公共配置,dev.js 是在本地调试环境下的配置文件,prod.js 是生产环境下的配置文件。
module.exports = {
env: {
NODE_ENV: '"development"' // 当前环境参数
},
defineConstants: {
},
weapp: {}, // 微信环境下的配置
h5: { // h5 环境下的配置
devServer:{
host:'localhost',
port:10086,
historyApiFallback: true,
inline: true,
publicPath: '/',
proxy:{ // 配置本地服务器代理
'/':{
target:'https://xxx.xxx.com',
changeOrigin:true,
}
}
},
}
}
如上在 module.exports
导出的对象中,h5 属性代表 h5 环境下的配置,weapp 代表微信环境下的配置,如上本地化配置中,可以通过配置 proxy 来完成本地服务器代理,解决本地调试跨域问题。
设计模式应用
用 Taro React 构建的 h5 应用,可以灵活运用 React 多种设计模式,比如组合模式,Hoc 高阶组件模式,自定义 Hooks 模式 , render props 等方式,通过这些设计模式,可以提升项目的架构能力,增加代码健壮性和可维护性。
三 Taro Vue 构建小程序应用
讲完 Taro 构建 web 应用,我们来看一下用 Taro Vue 构建一个小程序应用,Taro 有 2.0 升到了 3.0, 对于小程序的处理,由轻运行时重编译时变成了轻编译时重运行时,新版本 Taro 小程序更多的是保存了原生框架的活性,比如 Vue 中的响应式,React 的 Reconciler 渲染器,这些都是在运行时执行的,也就又可能会带来一些性能负担。但是在运行时和编译时之间寻找均衡点,可能也是这种跨端框架发展的一项重要指标。
入口文件
来看一下 Vue 构建的小程序的入口文件。
import Vue from 'vue';
import Taro from '@tarojs/taro'
import store from './store';
import './app.scss';
import navCustomBar from './components/xl-nav-bar';
import XlPicker from './components/xl-picker';
/* 使用程序的虚拟列表 */
import VirtualList from '@tarojs/components/virtual-list'
/* 注册小程序的公共组件 */
Vue.use(VirtualList)
Vue.component('xl-picker', XlPicker);
Vue.component('nav-custom-bar', navCustomBar);
/* 这里可以做一些初始化的事情 */
const App = new Vue({
store, // 使用状态管理工具
onShow(options) { // 小程序生命周期
console.log('----App onShow');
},
onLaunch() {
console.log('----App Launch');
},
render(h) {
// this.$slots.default 是将要会渲染的页面
return h('block', this.$slots.default);
},
});
export default App;
Vue 构建的小程序应用,和 Vue web 应用类似,在入口文件中,可以完成公共组件的注册,比如可以使用 Taro 小程序的虚拟列表组件 VirtualList ,还可以做一些初始化的事情,比如
1 初始化一些状态,比如环境变量,请求参数等;
2 判断小程序是否有新版本;
3 业务处理;
在 Vue 的实例化过程中,有小程序特有的生命周期,比如 onShow ,onLaunch 等,还可以注册状态管理工具,比如 Vuex 。
Taro 和原生小程序结合
taro 小程序本质上用的 React 或者 Vue 语法,小程序用的是独有的语法,在 Taro 小程序中,可以使用原生小程序和 taro 小程序混合开发的模式。比如在 Taro 构建的 Vue 小程序中使用小程序原生的 tab 组件。如下所示:
但是会暴露一个问题,就是如何实现原生小程序和 Taro Vue 小程序的通信呢?这里交给大家一个方法,可以用 EventBus 来解决,就可以实现 Taro 小程序和原生小程序的通信了。比如
attached() {
/* 绑定方法 */
BusService.on("getMenuList", this.changeData.bind(this), false);
},
methods:{
changeData(tabbar) {}
}
如上通过 EventBus 绑定了一个 getMenuList 的事件,绑定的方法是小程序的 changeData 方法。这样绑定之后,就可以在 Taro Vue 小程序中使用触发 getMenuList 事件。
<template>
</template>
<script>
export default {
onLoad(options, e) {
/* 触发 EventBus 中的 getMenuList */
BusService.emit('getMenuList',this.menuList)
}
}
</script>
如上在 vue 文件中,在 onLoad 中触发 getMenuList 方法,实现了原生小程序 -> Taro 小程序的通信。
请求封装
在 Taro 小程序中,默认使用了 Taro.request 的固定方法,这个方法底层使用 wx.request 方法,但是对比 axios 等请求库,wx.request 很多功能都缺失,比如请求拦截,响应拦截等,所以可以封装一些请求方法,补上一些核心功能。
import Taro from '@tarojs/taro'
import { compose } from "../base/utils/index";
import { commonPath } from "./common-url";
const requestInterceptorQueue = []; /* 请求拦截器数组 */
const responseInterceptorQueue = []; /* 响应拦截器 */
const request = Taro.request;
export const interceptorsRequest = {
use: (cb) => {
const symBolKey = Symbol("");
cb.interceptorslKey = symBolKey;
requestInterceptorQueue.push(cb);
return symBolKey;
},
eject: (symBolKey) => {
const index = requestInterceptorQueue.findIndex(
(item) => item.interceptorslKey === symBolKey
);
if (index >= 0) requestInterceptorQueue.splice(index, 1);
},
};
/* 封装响应拦截器 */
export const interceptorsResponse = {
use: (cb) => {
const symBolKey = Symbol("");
cb.interceptorslKey = symBolKey;
responseInterceptorQueue.push(cb);
return symBolKey;
},
eject: (symBolKey) => {
const index = responseInterceptorQueue.findIndex(
(item) => item.interceptorslKey === symBolKey
);
if (index >= 0) responseInterceptorQueue.splice(index, 1);
},
};
/**
* @param res 请求成功响应内容
* @param hander 处理函数resolve
*/
function proxyResole(res, handleResolve, handleReject) {
const newRes = compose(responseInterceptorQueue, res);
handleResolve(newRes);
}
/**
* @param err 请求失败
* @param hander reject
*/
function proxyReject(err, handleReject) {
handleReject();
}
/**
* @param param 请求
*/
export function httpRequset(param) {
const newParam = compose(requestInterceptorQueue, param);
const url = `${commonPath}${param.url}`;
return new Promise((resole, reject) => {
request({
...newParam,
url,
success: (res) => proxyResole(res, resole, reject),
fail: (err) => proxyReject(err, reject),
});
});
}
/**
* @param method 请求方法
*/
function httpFactory(method) {
return function (options) {
return httpRequset({
method,
...options,
});
};
}
export const get = httpFactory("get");
export const post = httpFactory("post");
export default httpFactory;
如上就是请求库的封装,这些统一处理的响应成功,和响应失败的场景,对于响应失败的情况,也可以集中处理。这样业务就可以直接使用:
function getScreenConfigQuery(data){
return new Promise((resolve,reject)=>{
get(commonUrl + '/screen/config/query',resolve,data, reject ) // 使用 get 方法
})
}
当然也可以使用请求拦截器:
/* 请求拦截 */
interceptorsRequest.use((req) => {
const { header = {} } = req;
const token = wx.getStorageSync("token");
req.header = {
...header,
Authorization: `Bearer ${token}`,
};
return req;
});
如上通过自定封装的请求拦截器,将 token 注入到请求头中。
四 总结
本章节介绍了 Taro 应用的上手,从 h5 和小程序两个角度介绍了 Taro 的应用,感兴趣的读者可以尝试用 Taro 构建一个 web 或者小程序应用,“纸上得来终觉浅,绝知此事要躬行” 只有尝试了,才会有更深刻的收获。
在下一章节中,我将介绍 Taro 背后的运转原理。