沉寂了大半年许久,终于准备写下这个篇纯技术干货,结合项目,由于是公司项目,只能用demo作为讲解
# 项目预览
这个性能效果接近原生
image image image image image# 项目搭建
FB提供的脚手架各创建项目一套:
create-react-native-app //rn app 脚手架
create-react-app //web app 脚手架
# 知识点
## RN:
native app 只需要加载一个全屏的webview即可,牵扯到的坑也只有native和js的通讯了,等会项目会有介绍,这里暴露一下package.json
image## React:
web app其实页面不多,技能要求也算中规中矩吧
-
RSA MD5 加密
-
文件的上传
-
二维码识别
-
socket通讯
-
indexedDB操作
-
js 和native 相互调用
这里也暴露一下package.json
image# web app 开发
## 路由配置****:
class App extends Component {
render() {
return (
<div className="App">
<BrowserRouter>
<div>
<Route path="/" exact component={Page2} />
<Route path="/index" component={Index} />
</div>
</BrowserRouter>
</div>
);
}
}
如果需要重定向指定页面把Route换成Protected包起来
const Protected = ({component: _comp, ...rest}) => {
//判断条件 重定向指定路由
let isLogin = true
return <Route { ...rest} render = {props => isLogin ? < _comp /> : <Redirect to ="index"/>}/>
}
## 跨域配置
最简单的代理,在package.json增加:
"proxy": {
"/yours": {
"target": "http://your api server/",
"changeOrigin": true
}
}
## 封装个****axios****请求基类
axios很强大,简单配置下
import axios from 'axios'
const AUTH_TOKEN = ""
//全局配置
// axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
// http request 请求拦截器,有token值则配置上token值
axios.interceptors.request.use(
config => {
let token = ""
if (token) { // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
config.headers.Authorization = token;
}
return config;
},
err => {
return Promise.reject(err);
});
// http response 服务器响应拦截器,这里拦截401错误,并重新跳入登页重新获取token
axios.interceptors.response.use(
response => {
return response;
},
error => {
if (error.response) {
switch (error.response.status) {
case 401:
}
}
return Promise.reject(error.response.data)
});
export default axios;
## RSA md 示例
把请求的params传过来,加签后返回string(ps:我们项目里只做加签)
export function encryptData(params) {
if (_.isEmpty(params)) {
return ""
}
//TODO: 数组转json字符
if (_.get(params,'fixqrs')){
params = {
...params,
fixqrs:JSON.stringify(params.fixqrs)
}
}
let keyArray = []
for (let key in params) {
keyArray.push(key);
}
keyArray.sort();
let sb = "";
for (let i = 0; i < keyArray.length; i++) {
sb += keyArray[i] + "=" + params[keyArray[i]] + "&";
}
let pkcs8key = "your key";
let rsaPrivateKey = new jsrsasign.RSAKey();
/*由于java后台生成的key格式是pkcs8格式 而前端js插件是pkcs1格式解析,故使用KEYUTIL.getKey(pkcs8key)获取私钥*/
rsaPrivateKey = jsrsasign.KEYUTIL.getKey(pkcs8key);
let sigval = rsaPrivateKey.sign(sb.substring(0, sb.length - 1), "sha1");
return sigval
}
用户名密码比较隐私,md5+salt
export function mdPassword(pw) {
return md5(pw+'your salt')
}
## 文件上传
/**
* 上传文件
* @param url
* @param params
* @param file
* @returns {AxiosPromise<any>}
*/
export function n_uploadFile(url,params,file) {
//header-sign-params
let param = new FormData()
_.forEach(params,(value,key) => {
param.append(key, value)
})
_.forEach(file,(value,key) => {
param.append(key, value)
})
let config = {
headers: {
'Content-Type': 'multipart/form-data',
}
}
return axios.post(url, param, config)
}
## 二维码识别
获取的file需要转一下url,调用tool里getObjectURL()方法
qrcode.qrcode.decode(getObjectURL(file))
qrcode.qrcode.callback = (d, status) =>{
console.log('',d)
}
## socket****通讯
傻瓜式操作
connect = () => {
let url = `your socket server ip`
this.ws = new Sockette(url, {
timeout: 2e3,
maxAttempts: 10,
onopen: e => console.log('Connected!', e),
onmessage: e => console.log('onmessage!', e),
});
}
close = () => {
if (this.ws){
this.ws.close()
}
}
## indexedDB****操作
关于indexedDB,这个不多说,取代sqlite,高性能储存查询,鉴于js db操作全是异步,各种回调有点恶心,不过dexie利用promise,大大解决了回调地狱,下面看看,如何用这个利器吧,
import Dexie from 'dexie';
const db = new Dexie("Cache_Image_DB");
db.version(1).stores({ file: '++id' });
export const findFiles = async(file) => {
return db.file
.filter(item => item.name === file.name)
.toArray()
}
export const addFile = async(file) => {
return db.file
.add(file)
}
export const deleteFile = async(id) => {
return db.file
.delete(id)
}
## 比较坑的****js native****通讯
先说Android吧,在RN官网的组件WebView,如果你h5里有用到选取相册,那WebView绝对的不支持,因为在Android设备需要你自己去实现,iOS则正常,发消息都是window.postMessage(),收消息也是在onMessage回调里,
再说iOS,官网的WebView组件是基于UIWebView,性能烂的一比,通讯也只能js注入,无法达到WKWebView的高性能,上下文注入的快捷,
综上,rn app就装了两个库
所以呢,js && native 相互通讯的方式也有点差异,
//native ==> js
//react-native-webview-android
this.refs.WEBVIEW_REF.postMessage(data);
//react-native-wkwebview
this.refs.WK_WebView.evaluateJavaScript(`receivedMessageFromReactNative(${data})`)
//js
//android
window.onload = () => {
document.addEventListener('message', (e) => {
this.handleMessage(e.data)
});
}
//ios
window.receivedMessageFromReactNative && window.receivedMessageFromReactNative(data => {
this.handleMessage(data)
})
// js ==> native
export const postMessage = (message) => {
//react-native-webview-android
window.webView&&window.webView.postMessage(message)
//react-native-wkwebview
!_.isEmpty(window,'webkit.messageHandlers.reactNative')&&window.webkit.messageHandlers.reactNative.postMessage(message);
}
//native
//react-native-webview-android
onMessage =(event) => {
console.log('android_webview on message',e)
}
//react-native-wkwebview
onMessage = (e) => {
console.log('wk_webview on message',e.nativeEvent)
}
# RN App****开发
## 按设备加载****webview****组件
只需要判断一下设备,如果是Android就加载react-native-webview-android这个库,iOS就加载react-native-wkwebview,(ps:这里我各自封装了两个库)
render() {
return (
<View style={styles.container}>
{
this.platform === 'ios'?
<WebViewIOS ref="web_ios" url={SITE_URL}/>
:
<WebViewANDROID ref="web_android" url={SITE_URL}/>
}
</View>
);
}
## android****设备的返回虚拟键返回处理
android设备有个返回虚拟键,所有拦截一下事件,稍作处理
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBack)
AppState.addEventListener('change', this.handleAppStateChange);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBack)
}
handleBack = () => {
this.refs.web_android.goBack();
return true;
}
# 打包发布
## rn Android 打包部署
1.Android studio 创建keystore
image2.your project/android/gradle.properties 声明一下变量,
MYAPP_RELEASE_STORE_FILE=your-keystore.keystore
MYAPP_RELEASE_KEY_ALIAS=your alias name
MYAPP_RELEASE_STORE_PASSWORD=your keystore password
MYAPP_RELEASE_KEY_PASSWORD=your key password
在your project/android/app/build.gradle 引用
signingConfigs {
release {
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
}
}
3.写个shell 自动打包出apk
#!/bin/sh
cd ~/your rn project name/
curl "localhost:8081/index.android.bundle?platform=android&dev=false&minify=true" -o "android/app/src/main/assets/index.android.bundle"
cd android && ./gradlew assembleRelease
cd app/build/outputs/apk/release
open .
## rn iOS 打包部署
ios打包就相对简单了,前提需要在developer.apple.com 配好provisioning profile 和开发生产证书,这里我就不多说了
同样,写个shell 创建一个生产环境的jsboundle
#!/bin/sh
cd ~/your rn project name/
react-native bundle --entry-file index.js --platform ios --dev false --bundle-output release_ios/main.jsbundle --assets-dest release_ios/
把release_ios/main.jsbundle,拖到ios项目里, 顺便改一下代码:
//这个是本地jsboundle
// jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
//生产环境的jsboundle,名字要对应哦
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
然后你就可以一劳永逸的直接achieve,然后直接上传appstore,或者打出ADHOC包内测一下,
#****中间遇到的错
关于中间的遇到的报错,大概Stack Overflow或者某度都可以,如果你有遇到什么错,也可以在下面评论一下
# demo