换换项目三端化的小试牛刀
背景
在换换项目列表页、详情页用React-native实现了Android、iOS的基础上,要继续上web端,为节省开发成本,本着一套代码三端使用的原则,并且在此之前我们的RN小团队经过调研,基于以上业务的基础上并产出了一套WubaRN-web库,以支持三端化的需求,因此也为该需求的接入奠定了基础。
方案
React-native + React-native-web + WubaRN-web。
这里首先看下WubaRN-web的主要工作,因为React-native代码编译出来的bundle.js文件目前只能在App里运行,无法直接在web上运行。那么要生成可以直接在web运行的js需要一套react-native-web库,通过webpack将react-native库替换为react-native-web库进行打包,做下如下配置: 1. 在web/configs/webpack.config.js文件里
{
resolve: {
alias: { 'react-native': 'react-native-web' },
},
}
也就是把指向react-native的地方指向react-native-web
2. 针对react-native-web提供的API有些不能满足业务需求,在自定义文件中将不满足需求的API组件等替换为自定义的实现,如:
/src/lib/react-native-web/index.js
import ReactNative, {
// top-level API
createElement,
findNodeHandle,
render,
...
// Text,
...
PointPropType
} from 'react-native-web';
import Text from './Text'
ReactNative.Text = Text;
import DeviceEventEmitter from './DeviceEventEmitter';
ReactNative.DeviceEventEmitter = DeviceEventEmitter;
...
export {
// top-level API
createElement,
findNodeHandle,
render,
...
Text,
...
PointPropType
};
export default ReactNative;
如上代码,直接转换的Text控件,不能满足我们的业务需求,所以需要我们引入我们自定义的
RN页面有依赖NativeModule的功能以及组件,可以由以下方式方便的添加自定义组件,modules.config.js配置文件
//自定义API配置,根据NativeModules调用的 const WBWebModulesConfig = { WBShare: require('./WBShare').default, /** 分享 */ WBUserLog: require('./WBUserLog').default, /** 埋点*/ WBLocation: require('./WBLocation').default,/** 定位*/ WBInitialParams: require('./WBInitialParams').default,/** 初始化参数 */ WBToast: require('./WBToast').default, /** Toast提示 */ WBLoadingBar: require('./WBLoadingBar').default, }; //自定义view配置,根据requireNativeComponent调用的 const WBCustomWebViewsConfig = { WBErrorView: require('./WBErrorView').default }; exports.WBWebModulesConfig = WBWebModulesConfig; exports.WBCustomWebViewsConfig = WBCustomWebViewsConfig;
入口index.js文件添加注册
import {NativeModules} from 'react-native-web' import {WBComponentsConfig} from './lib/react-native-web/requireNativeComponent' import {WBWebModulesConfig,WBCustomWebViewsConfig} from '../wb_modules/modules.config'; //往NativeModules中注册自定义API Object.keys(WBWebModulesConfig).forEach(function (key){ NativeModules[key] = WBWebModulesConfig[key]; }); //往requireNativeComponent中注册自定义View组件 Object.keys(WBCustomWebViewsConfig).forEach(function (key){ WBComponentsConfig[key] = WBCustomWebViewsConfig[key]; });
webpack配置文件如下:
resolve: { alias: { 'react-native':path.resolve(__dirname, '../src/lib/react-native-web/index.js'), } }
以上主要是WubaRN-web的主要工作,接下来,看业务层如何接入。
实现
接入流程很简单,直接导入wubaRN-web库即可
1.在换换列表、详情页RN项目的基础上引入wubaRN-web库,所以项目结构如下:---| root ---| |-node_modules ---| |-src //存放共用RN代码 ---| |-App ---| |-index.js ---| |-web //web的相关配置 ---| |-configs ---| |-webpack.config.js //webpack打包配置文件 ---| |-node_modules ---| |-src ---| |-index.js ---| |-package.json //针对web的依赖配置文件 基于react-
wubaRN-web就是以上的web目录里面的东西。 2.运行项目 * cd web 进入web目录 * npm start 启动服务 * 浏览器输入 http://localhost:3000/
问题
tab栏没法固定
解决(业务层):需要在style中加入以下属性:
position: 'fixed',
top: 0,
left: 0,
zIndex: 8,
2.FlatList的onRefresh、onEndReached等相关属性不起作用解决(业务层):直接把FlatList组件替换为普通的View,并监听Scroll方法及解析数据拼装item等方法
IS_WEB ?
:
window.addEventListener('scroll', function () {
...此处省略 滑动距离的判断
that.store.loadMore();
}, false);
....
3.切换tab时,列表页没有自定滚动到最顶部原因:RN端使用flatlist的scrollToIndex()实现,普通的View没有该属性解决(业务层):使用window.scrollBy()处理
4.列表页滑动时,误点击进入详情页原因:列表页item最外层使用TouchableOpacity实现,在onPress执行点击,当直接使用web库转换后,容易出发它的点击事件。解决(业务层):把TouchableOpacity替换收到替换为标签来处理,这样就避免了误点击问题。
5.调用naive的API没法直接使用,比如: * WBAPP.initialParams(‘WB_JUMPER’)获取跳转协议携带的参数原因:web端跳转方式与RN端不样,跳转协议也一样解决(wubaRN-web库):下兼容处理,从跳转的URL里获取
initialParams(action, callback) {
setTimeout(() => {
var json = searchToObject(location.search);
callback({
'WB_JUMPER': {"params":{},"content":json}
})
})
},
WBAPP.getLocation(localData) 获取native 定位SDK返回的参数原因:web无法使用native提供的定位SDK解决(wubaRN-web库):引入web端的定位SDK
getLocation: function (callback) { if (typeof callback === 'function') { var geolocation = new BMap.Geolocation(); geolocation.getCurrentPosition(function (r) { if (r && r.point) { // // 创建地理编码实例 var myGeo = new BMap.Geocoder(); // 根据坐标得到地址描述 myGeo.getLocation(new BMap.Point(r.point.lng, r.point.lat), function (result) { if (result) { let business = result.business; callback && callback({ "statusCode": 0, "content": { "lon": r.point.lng, "lat": r.point.lat, "businessName": business, } }) } }
WBAPP.setWebLog()调用native的埋点框架原因:APP端与M端埋点框架不一样解决(wubaRN-web库):兼容转换为M端格式的埋点
export function setWebLog({pagetype = '',actiontype = '',cate = '', paramsArray,extraParams }) { let protocol = location.protocol || 'http:';//协议头,由页面协议确定 let trackURL = JSON.stringify({ page: actiontype, cate: cate, area: '', pagetype: pagetype, ...extraParams, ...paramsArray }) ... }
6.详情页返回列表页,列表没有保留现场原因:列表页,详情页分别都是单独的一个js文件,所以当返回列表页的时候,浏览器会重新执行一遍js文件的流程,以至没有保留现场,而重新加载了。解决(业务层):跳转时,使用sessionStorage缓存当前页面的item数据,当前显示的tab等相关数据以及各标识,下次加载时,优先判断下。
{
sessionStorage.setItem(comStore.BACK_FROM_JUMP, 1);
sessionStorage.setItem(comStore.MORE_URL, moreUrl);
sessionStorage.setItem(comStore.LIST_CHOOSE, listchoose);
}
}>
7.网络请求跨域问题原因:跨域名请求服务器解决(业务层):$.ajax处理
......
还有一些各式各样的问题,这里就不一一列举出来了,而且目前主要是在业务层处理的。
总结
因为WubaRN-web开始就是以换换需求为技术背景产出的,所以前期解决了部分问题,但是真正接入时,还是还有各式各样的问题存在,挖坑填坑的方式直到到达上线要求,其中也走了不少弯路,不过整体来看,确实减少了开发量,大概评估了下,该需求如果纯web端开发的话,假如10天的工作量,直接转换加上解决以上问题,一共用了5天左右,如果后期WubaRN-web把以上问题中业务层处理的公共的部分都解决掉了话,相信开发量会骤减。 最后,感谢前期,@蒋宏伟、@朴惠姝、@于卫国的支持。
参考
http://wxc.58corp.com/pages/viewpage.action?pageId=25730228