一次失败的尝试,h5+Api 结合 react,webpack,同时生成android 、ios、h5端代码
vue 項(xiàng)目寫多了,覺得不能一成不變,想去外面的世界看看。所以嘗試了一把react開發(fā),嗯~ o( ̄▽ ̄)o 就在想做一個(gè)webApp吧,腳手架也自己搭一個(gè)吧。然后腳手架搭建完,項(xiàng)目可以正式開始了,自己又出幺蛾子,為什么不能打包成App呢,之前接觸過cordova平臺(tái)打包App,這次決定用HBuilder h5+api 開發(fā)一個(gè)同時(shí)打包多頁面App應(yīng)用 和 SPA單頁面應(yīng)用。(小程序,哎 野心太大,但是實(shí)力不允許),在抹平平臺(tái)差異后,可以愉快的寫代碼了,但是在我看了uni-app文檔后,覺得自己寫的好原始。哎。雖然寫的嘍了點(diǎn),但是對(duì)HBuilder h5+api 有了一定的了解,在看uni-app 文檔時(shí)可以在腦海里模擬它接口,功能的實(shí)現(xiàn)了。還有他封裝的功能為了實(shí)現(xiàn)多平臺(tái)對(duì)h5+api的簡化。
項(xiàng)目地址:github.com/wangyaxinon…
由于是同時(shí)開發(fā)多頁面應(yīng)用和單頁面應(yīng)用,所以我再開發(fā)之前考慮到了如下問題:
開始解決上面提到的問題
app端跳轉(zhuǎn)代碼
import allRouter from "@/utils/route.js" import utils from "@/utils/init.js" var _openw;export function push({path,titleViewOptions,AnimationType}){if(_openw || !path){return;} // 防止快速點(diǎn)擊if(path==="/login"){if(isLogin()){return;} }if(path==='/' ||path==='/index' ){path = `index.html`}else if(path[0]==='/'){var pathArr = path.split('/');var newpath = pathArr[pathArr.length-1]path = `/pages/${newpath}.html`}utils.changePage(path).then(()=>{_openw=null;})} export function go(num){utils.go() } function isLogin() {var userDetail = utils.getItem("userDetail");if(userDetail &&userDetail.token){return true;}else{return false;} }復(fù)制代碼web端跳轉(zhuǎn)代碼
import { createHashHistory } from 'history' var history = createHashHistory(); var push = function (data){console.log(arguments);return history.push(data.path) } var go = (num)=>{return history.go(num) } export {push,go } 復(fù)制代碼2.h5+api App 支持離線應(yīng)用,在離線狀態(tài)如何獲取上次有網(wǎng)的數(shù)據(jù),以及離線提交。react webApp 不支持離線。解決方案: APP端離線的一些靜態(tài)資源如 html css js img font 都是打包在應(yīng)用內(nèi)的可以直接離線訪問,但是比如一個(gè)商品列表的數(shù)據(jù)是從后臺(tái)請(qǐng)求過來的。在離線的情況下是肯定拿不到數(shù)據(jù)的。但是我們可以借助h5+api (sqlite本地?cái)?shù)據(jù)庫實(shí)現(xiàn)此功能 )。原理是在初始化的時(shí)候創(chuàng)建一個(gè)表,第一次請(qǐng)求的時(shí)候?qū)⒄?qǐng)求接口和數(shù)據(jù)插入表中,以后的每次請(qǐng)求都是跟新表中當(dāng)前接口的數(shù)據(jù)。
var qs = require('qs') import config from "./config.js" import SQLite from "@/platform/storage/app.js"var types = {}; types['0'] = "未知"; types['1'] = "未連接網(wǎng)絡(luò)"; types['2'] = "有線網(wǎng)絡(luò)"; types['3'] = "WiFi網(wǎng)絡(luò)"; types['4'] = "2G蜂窩網(wǎng)絡(luò)"; types['5'] = "3G蜂窩網(wǎng)絡(luò)"; types['6'] = "4G蜂窩網(wǎng)絡(luò)";function get(options){if(!options.url){return }if(!options.type){options.type = 'get';}if(Object.prototype.toString.call(options.data)!=="[object String]"){options.data = qs.stringify(options.data)}return new Promise((resolve,reject)=>{var xhr = new plus.net.XMLHttpRequest();xhr.onreadystatechange = function () {if(xhr.readyState==4){if ( xhr.status == 200 ) {resolve(xhr.responseText );} else {reject(xhr.readyState );}}}xhr.open( options.type, `${options.url}?${options.data}` );xhr.send();})} function post(options){if(!options.url){return }if(Object.prototype.toString.call(options.data)!=="[object String]"){options.data = JSON.stringify(options.data)}return new Promise((resolve,reject)=>{var xhr = new plus.net.XMLHttpRequest();xhr.onreadystatechange = function () {if(xhr.readyState==4){if ( xhr.status == 200 ) {resolve(xhr.responseText );} else {reject(xhr.readyState );}}}xhr.open( options.type, `${options.url}?${options.data}` );xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');xhr.send();}) }export default function (options){options.url = config.baseUrl+options.url;var CurrentType = types[plus.networkinfo.getCurrentType()];options.cache = options.cache || true;//無網(wǎng)絡(luò)時(shí)或者cache,讀取數(shù)據(jù)庫中上一次請(qǐng)求成功的數(shù)據(jù)if(CurrentType==='未知' || CurrentType==='未連接網(wǎng)絡(luò)' && options.cache){return SQLite.selectSQL(`select * from database WHERE key = '${options.url}'`).then((data)=>{var nowData;if(data && data.length){nowData = data[0].data;}try{nowData = JSON.parse(nowData)}catch{}return nowData || {};})}else{if(options.type==='get' || !options.type){return Promise.race([get(options),new Promise((resolve, reject) => {setTimeout(() => reject('request timeout'), config.timeout ? config.timeout : 30 * 1000);})]).then((res)=>{try {res = JSON.parse(res);}catch(err) {}setsqLite(`UPDATE database SET data = '${JSON.stringify(res)}', date = '${new Date()/1} WHERE key = '${options.url}'`)return res;})}else{return Promise.race([post(options),new Promise((resolve, reject)=>{setTimeout(() => reject('request timeout'), config.timeout ? config.timeout : 30 * 1000);})]).then((res)=>{try {res = JSON.parse(res);}catch(err) {}setsqLite({res,options})return res;})}}function setsqLite({options,res}) {SQLite.selectSQL(`select key from database WHERE key = '${options.url}'`).then((data)=>{if(data && data.length){//跟新表中數(shù)據(jù)SQLite.executeSql(`UPDATE database SET data = '${JSON.stringify(res)}', time = '${new Date()/1}' WHERE key = '${options.url}'`)}else{//第一次請(qǐng)求數(shù)據(jù)SQLite.executeSql(`insert into database values('${options.url}','${JSON.stringify(res)}','${new Date()/1}')`)}})} }復(fù)制代碼3.區(qū)分不同的平臺(tái),我是借助webpack 實(shí)現(xiàn)的,在package.json scripts 傳入一個(gè)參數(shù) --ios --wx --android --web,同時(shí)在根目錄下的 config/webpack.config.base.js 文件中獲取這些參數(shù),在webpack.DefinePlugink中設(shè)置全局變量
"scripts": {"build-ios": "cross-env NODE_ENV=production webpack --ios --config configMulti/webpack.config.js","build-Android": "cross-env NODE_ENV=production webpack --Android --config configMulti/webpack.config.js","build-web": "cross-env NODE_ENV=production webpack --web --config configSPA/webpack.config.js","build-wx": "cross-env NODE_ENV=production webpack --wx --config configSPA/webpack.config.js","dev-web": "cross-env NODE_ENV=development webpack-dev-server --web --inline --host 0.0.0.0 --config configSPA/webpack.config.dev.js","dev-ios": "cross-env NODE_ENV=development webpack --w --ios --inline --host 0.0.0.0 --config configMulti/webpack.config.dev.js","dev-Android": "cross-env NODE_ENV=development webpack-dev-server --Android --inline --host 0.0.0.0 --config configMulti/webpack.config.dev.js","dev-wx": "cross-env NODE_ENV=development webpack-dev-server --wx --inline --host 0.0.0.0 --config configSPA/webpack.config.dev.js"}, 復(fù)制代碼//讀取命令行傳入的參數(shù) var parms = process.argv; var DefinePlugin = null if(parms.includes('--ios')){DefinePlugin = {'process.env': {platform: '"ios"'}} } if(parms.includes('--Android')){DefinePlugin = {'process.env': {platform: '"Android"'}} } if(parms.includes('--wx')){DefinePlugin = {'process.env': {platform: '"wx"'}} } if(parms.includes('--web')){DefinePlugin = {'process.env': {platform: '"web"'}} } // DefinePlugin.NODE_ENV = '"development"' config.plugins.push(new webpack.DefinePlugin(DefinePlugin), ) module.exports = config 復(fù)制代碼項(xiàng)目的核心來了:
單頁面還好,多頁面視圖的切換,底部的導(dǎo)航,頂部的titleNView 子視圖的創(chuàng)建 這些調(diào)用的都是原生功能。所以我做了一個(gè)配置,不必每次都該源代碼,按規(guī)則修改配置視圖也跟著去變化。這些配置在初始化的時(shí)候去創(chuàng)建。
比如初始化頁面
initSubPages() {if(routeConfig){for(var key in routeConfig){var children = routeConfig[key].children;var parentConfig = routeConfig[key];if(children && children.length){//默認(rèn)打開的第一個(gè)首頁if(key==='index'){var self = plus.webview.currentWebview();var titleNView = self.getTitleNView();console.log('titleNView')console.log(JSON.stringify(titleNView))children.forEach((item,idx)=>{var page = item.MultiPath;var meta = item.meta || {};if(!plus.webview.getWebviewById(page)){// 初始化第一個(gè)子頁面if(idx ==0 ){utils.setStatusBar(item);var sub = plus.webview.create( page, page, item.WebviewStyles,meta);// append到當(dāng)前父webviewself.append(sub);//添加第一個(gè)子頁面進(jìn)入棧utils.setItem('pagesList',[page])}}})}else{//其他在需要顯示的時(shí)候創(chuàng)建// var parentPage = routeConfig[key].MultiPath;// var parent = plus.webview.create( parentPage, parentPage);// children.forEach((item)=>{// var page = item.MultiPath;// var meta = item.meta// if(!plus.webview.getWebviewById(page)){// var sub = plus.webview.create( page, page, utils.subPageStyle,meta);// // append到父webview// parent.append(sub);// // 初始化隱藏// sub.hide();// }// })}}else{//其他在需要顯示的時(shí)候創(chuàng)建// var parentPage = routeConfig[key].MultiPath;// var parent = plus.webview.create( parentPage, parentPage);// parent.hide();}}}}, 復(fù)制代碼初始化所有路由頁面配置的底部按鈕
//遞歸路由配置,創(chuàng)建原生底部導(dǎo)航 initAllTabBar() {if(routeConfig){drawAllNative(routeConfig);}function drawAllNative(routeConfig) {if(Object.prototype.toString.call(routeConfig)==="[object Object]"){for(var key in routeConfig){var View = routeConfig[key].View;if(View && View.length){View.forEach((item,idx)=>{var nowView = new plus.nativeObj.View(item.id, item.styles, item.tags);var parentWebview = plus.webview.getWebviewById(routeConfig[key].MultiPath==='/index.html'?utils.indexId:routeConfig[key].MultiPath);if(parentWebview){parentWebview.append(nowView)}else{//未創(chuàng)建頁面在切換時(shí)加載View}})}var children = routeConfig[key].children;if(children && children.length){drawAllNative(children);}} }else if(Object.prototype.toString.call(routeConfig)==="[object Array]"){routeConfig.forEach((item,idx)=>{var View = item.View;if(View && View.length){View.forEach((item,idx)=>{var nowView = new plus.nativeObj.View(item.id, item.styles, item.tags);var parentWebview = plus.webview.getWebviewById(item.MultiPath);if(parentWebview){parentWebview.append(nowView)}else{//未創(chuàng)建頁面在切換時(shí)加載View}})}var children = item.children;if(children && children.length){drawAllNative(children);}})} }}, 復(fù)制代碼h5+api切換頁面
//切換頁面changePage(targetPage) {return new Promise((resolve,reject)=>{var pagesList = utils.getItem('pagesList')var activePage = pagesList[pagesList.length-1];if(targetPage===activePage){return;}if($.isEmptyObject(utils.MuLti)){utils.MuLti = getMuLtiConfig(routeConfig)}else{}var targetPageWebview = plus.webview.getWebviewById(targetPage)if(targetPageWebview){plus.webview.show(targetPage , (utils.MuLti[targetPage].AnimationTypeShow || 'auto'), 300,()=>{hidePage()});console.log('已存在');}else{// plus.webview.open(targetPage, targetPage, {}, 'slide-in-right', 200);var nowConfig = utils.MuLti[targetPage];var meta = nowConfig.meta || {};console.log('parentPath : '+nowConfig.parentPath)if(nowConfig.parentPath){var parentView = plus.webview.getWebviewById(nowConfig.parentPath=="/index.html"?utils.indexId:nowConfig.parentPath);var sub = plus.webview.create( nowConfig.MultiPath, nowConfig.MultiPath, nowConfig.WebviewStyles,meta);// append到當(dāng)前父webviewparentView.append(sub);addNowPageView();plus.webview.show(sub, (nowConfig.AnimationTypeShow || 'auto'), 300,()=>{hidePage()});}else{var ws = plus.webview.create( targetPage, targetPage, nowConfig.WebviewStyles ,meta);addNowPageView();plus.webview.show(ws, (nowConfig.AnimationTypeShow || 'auto'), 300,()=>{hidePage()});}console.log('初次創(chuàng)建');}utils.setStatusBar(utils.MuLti[targetPage]);function addNowPageView(){var nowConfig = utils.MuLti[targetPage];if(nowConfig.View && nowConfig.View.length){nowConfig.View.forEach((item)=>{var nowView = new plus.nativeObj.View(item.id, item.styles, item.tags);var parentWebview = plus.webview.getWebviewById(nowConfig.MultiPath);if(parentWebview){parentWebview.append(nowView)}})}}//隱藏當(dāng)前 除了第一個(gè)父窗口function hidePage() {resolve('success')var pagesList = utils.getItem('pagesList')if(utils.MuLti[targetPage] && utils.MuLti[targetPage].meta && utils.MuLti[targetPage].meta.ignore){// activePage = pagesList[pagesList.length-1] //activePage = 上一次打開的頁面}else{}pagesList.push(targetPage)utils.setItem('pagesList',pagesList)activePage = pagesList[pagesList.length-2] //activePage = 上一次打開的頁面if(activePage !== plus.webview.getLaunchWebview().id) {var AnimationTypeClose = utils.MuLti[activePage] ? utils.MuLti[activePage].AnimationTypeClose :nullif(utils.MuLti[activePage] && utils.MuLti[activePage].meta && utils.MuLti[activePage].meta.leaveClose) {plus.webview.close(activePage,AnimationTypeClose || 'auto');}else{plus.webview.hide(activePage,AnimationTypeClose || 'auto');}}}})}, 復(fù)制代碼寫的廢話有點(diǎn)多。
轉(zhuǎn)載于:https://juejin.im/post/5d00ab626fb9a07ef90c9209
總結(jié)
以上是生活随笔為你收集整理的一次失败的尝试,h5+Api 结合 react,webpack,同时生成android 、ios、h5端代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Cloud 基于Consu
- 下一篇: Linux中硬盘转速查看