React Native:如何在WebView内加载SPA或本地静态HTML页面?
React Native WebView允许你使用uri属性加载可公开访问的资源,就像程序内的一个浏览器。但是,当你要加载本地html或者SPA应用时,它并不是那么简单。
通常在APP内部接入WebView时,我们可以有两种选择,一是通过uri指向一个公开的网页地址,从而加载远程网页。二是指向一个本地的静态HTML文件路径,从而加载本地网页。两种方式各有优劣,方法一接入简单但不可离线且加载时间不可控,方法二接入复杂但可离线访问资源都在本地加载速度快。
选泽这两种接入方式需要视情况而定,当需要接入网络的资源大而多,如一些SPA应用,可以考虑使用本地接入, 若只是接入一些简单且常变的活动页时,可以考虑远程接入。 本篇文章是针对第二种本地接入方式进行分享。
目标要求
所有的静态资源如 css,js,img等都应该存储在本地。
同时支持android和ios
所有平台都从一个目录中读取文件,避免冗余。
设计方案
第一步、静态资源目录
在React Native项目根目录中添加Static.bundle文件夹。
将你的SPA应用或者本地HTML文件移动到此文件夹中。
静态资源文件夹以.bundle结尾的原因在于,iOS对于以.bundle结尾的文件夹在打包后能够保持内部的资源引用路径。
第二步、iOS静态资源设置
打开xcode项目并选中以项目名称命名的第一个文件夹,并右键添加文件
找到第一步设置的Static.bundle文件夹并添加,注意不要勾选Copy items if needed。
Copy items if needed如果勾选则会将资源拷贝一份到iOS目录下,与初始目标:所有平台都从一个目录中读取文件相悖
第三步、Android资源路径设置
打开android/app/build.gradle文件,修改如下
android {
...
sourceSets {
main {
asset.srcDirs = ['src/main/assets', '../../Static.bundle']
}
}
}
WebView访问本地HTML
设置HTML路径
let source = (Platform.OS === 'android' ? 'file:///android_asset/' : '') + `Static.bundle/index.html`;
ref={ref => (this.webViewRef = ref)}
onMessage={this.onMessage}
originWhitelist={['*']}
allowFileAccess={true}
source={{uri: source}}
javaScriptEnabled={true}
decelerationRate='normal'
scrollEnabled={true}
useWebKit={true}
mediaPlaybackRequiresUserAction={true}
mixedContentMode="compatibility"
allowingReadAccessToURL="*"
// onLoadEnd={() => this.setState({ loading: false })}
/>
至此,接入本地HTML已大致完毕, 可在iOS和Android平台测试。
传递参数
在React Native中打开本地HTML应用该如何传递参数呢?
现有解决方案是通过WebView提供injectedJavaScript参数传递。
let injectJs = `(function(){location.href="#url?access_token=${access_token}&user_id=${user_id}"}())`
injectedJavaScript={injectJs}
/>
Loading
若是本地HTML中的内容需要ajax请求获取,则一般会配置Loading动画,而我们加载WebView本身亦是需要Loading动画, 若是不做处理,则会出现两个Loading交替现象,用户体验下降。
解决方案是通过WebView的事件传递,在WebView中通过ajax将数据加载完毕后,通过window上的事件通知 React Native端解除Loading.
// html
window.ReactNativeWebView.postMessage(JSON.stringify({
type: "loadEnd"
}));
// react native
onMessage = event => {
try {
const action = JSON.parse(event.nativeEvent.data);
switch(action.type) {
case "loadEnd":
this.setState({loading: false});
break;
} catch (error) {
console.log(error);
}
}
单页应用异步路由处理
若是本地SPA应用中包含一些异步路由渲染的页面则不能直接接入使用, 原因在于在浏览器中,异步路由的实现是通过ajax请求异步获取页面内容, 但是在本地的单页应用中,由于跨域问题,是不支持以ajax获取内容的。
解决: 将所有异步文件在index.html中导入。
调试
由于WebView是使用本地绝对路径作为uri,最终访问的是App内容文件,所有React Native的热更新不会起作用,若HTML文件发生改变,则需要重新build。
总结
通过方案设计和接入设计,最终完成了我们的三点目标要求
通过.bundle文件夹保持了xcode打包后的资源结构,可以放心将所有资源存储到本地
通过对Android资源路径的设置,解决了冗余问题
通过对平台的判断,添加前缀兼容了所有平台