本文基於 React Native 的實踐項目進行總結, 該項目基於 React Native 和 H5 在開發效率、功能性能、用戶體驗等方面的差別性,對功能模塊進行精心設計,主要基於咱們如今實際項目的業務,結合移動端特有的特性。javascript
本文圍繞 React Native 項目的環境配置、運行,React Native 介紹,項目的主要功能,React Native 開發存在的坑等多個方面進行展開。若是你尚未 React Native 開發經驗,那麼這篇文章將很好的向你展現 React Native 的各方面,包括官方文檔、生態、兼容性等等,但願你在這篇文章中找到你想要的答案。html
辛苦整理良久,還望手動點贊鼓勵~java
博客 github地址爲:github.com/fengshi123/… ,彙總了做者的全部博客,也歡迎關注及 star ~node
本項目 github 地址爲:github.com/fengshi123/…react
配套的服務端 express 項目 github 地址爲:github.com/fengshi123/…android
在這個 React Native App 開發中,個人開發環境相關配置以下:ios
工具名稱 | 版本號 |
---|---|
node.js | 11.12.0 |
npm | 6.7.0 |
yarn | 1.17.3 |
Android Studio | 3.4.1 |
JDK | 1.8 |
react | 16.8.6 |
react-native | 0.60.5 |
(1)安裝 yarn、react-native 命令行工具git
$ npm install -g yarn react-native-cli
複製代碼
(2)設置 yarn 鏡像源github
$ yarn config set registry https://registry.npm.taobao.org --global
$ yarn config set disturl https://npm.taobao.org/dist --global
複製代碼
(3)安裝第三方插件web
進入到 react_native_project 目錄底下,安裝第三方插件:
$ yarn
複製代碼
(4)Android Studio 配置
Android Studio 的配置這裏再也不作介紹,能夠參考 react-native 官網;
(5)編譯並運行項目
$ react-native run-android
複製代碼
(6)啓動項目
第 5 步後,若是真機或模擬器提示,Metro 沒有啓動,可關閉第 5 步開啓的 node 窗口,再重啓 Metro:
npm start
複製代碼
(7)服務端配套項目
記得 clone 本項目配套的服務端 express 項目,並啓動它。
「 Learn once, write anywhere 」,React Native 的定義就像是:學習 React ,同時掌握 web 與 app 兩種開發技能。 React Native 使用 React 的設計模式,開發者編寫 js 代碼,經過 React Native 的中間層轉化爲原生控件和操做,擁有接近原生開發的用戶體驗。下面引用官網上 4 條特性:
(1)使用 JavaScript 和 React 編寫原生移動應用
React Native 使你只使用 JavaScript 也能編寫原生移動應用。 它在設計原理上和 React 一致,經過聲明式的組件機制來搭建豐富多彩的用戶界面。
(2)React Native 應用是真正的移動應用
React Native 產出的並非「網頁應用」, 或者說「HTML5應用」,又或者「混合應用」。 最終產品是一個真正的移動應用,從使用感覺上和用 Objective-C 或 Java 編寫的應用相比幾乎是沒法區分的。 React Native 所使用的基礎 UI 組件和原生應用徹底一致。 你要作的就是把這些基礎組件使用 JavaScript 和 React 的方式組合起來。
(3)別再傻等編譯了
React Native 讓你能夠快速迭代開發應用。 比起傳統原生應用漫長的編譯過程,如今你能夠在瞬間刷新你的應用。開啓 Hot Reloading 的話,甚至能在保持應用運行狀態的狀況下熱替換新代碼!
(4)可隨時呼叫原生外援
React Native 完美兼容使用 Objective-C、Java 或是 Swift 編寫的組件。 若是你須要針對應用的某一部分特別優化,中途換用原生代碼編寫也很容易。 想要應用的一部分用原生,一部分用 React Native 也徹底沒問題。
考慮到更好的體驗 React Native 和 H5 在開發效率、功能性能、用戶體驗等方面的差別性,咱們對功能模塊進行精心設計,主要基於咱們如今實際項目的業務,結合移動端特有的特性。相關的模塊功能設計以下圖所示。
截取一些功能展現以下:
咱們的項目目錄結構以下:
> ├─ .vscode 編輯器配置
> ├─ android android 原生目錄
> ├─ ios ios 原生目錄
> ├─node_modules 項目依賴包
> ├─ src 代碼主目錄
> │ ├─assets 存放樣式文件
> │ │ ├─images 存放圖片
> │ │ └─styles 樣式文件的 js 目錄
> │ │ ├─index.js 存放圖片路徑,能夠參照主頁面模塊寫法
> │ ├─components 存放塊級組件
> │ ├─navigation 存放導航配置
> │ │ ├─ index.js 導航配置主文件
> │ ├─pages 存放頁面級組件,不一樣模塊不一樣目錄
> │ └─utils 存放工具方法
> │ │ ├─ constant.js 一些常量配置,例如:服務器 IP 端口等
> │ │ ├─ globalVar.js 一些全局變量
> │ │ └─ request.js ajax 請求
> ├─.eslintrc.js eslint 配置
> ├─.gitignore.js git 忽略配置
> ├─index.js 項目入口
> ├─package.json 項目依賴包配置
複製代碼
此模塊包含功能:文件夾建立、重命名、文件上傳、下載、側滑操做、長按列表操做、下拉刷新操做、文件預覽(包含圖片)等。
(1) 使用插件
react-native-popup-menu
複製代碼
(2)功能實現
yarn add react-native-popup-menu
複製代碼
在 react_native_project/src/pages/netDisk/NetDisk.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import {
Menu,
MenuProvider,
MenuOptions,
MenuOption,
MenuTrigger,
} from 'react-native-popup-menu';
// render
<MenuProvider>
<Menu>
<MenuTrigger
onAlternativeAction={() => this.getDirFile(rowData.item)}
triggerOnLongPress={true}
customStyles={triggerStyles}>
<Image
source={ rowData.item.icon }
style={styles.thumbnail}
/>
<View>
<Text>{rowData.item.name}</Text>
<Text>{dayjs(rowData.item.time).format('YYYY-MM-DD HH:mm:ss')}</Text>
</View>
<View>
{
rowData.item.type === 'dir'?
<NBIcon type="AntDesign" name="right"/> : null
}
</View>
</MenuTrigger>
<MenuOptions customStyles={optionsStyles}>
<MenuOption value={1} text='重命名' onSelect={() => {this.setState({
modalVisible: true,
fileItem: rowData.item,
dialogType: 'Rename',
hasInputText: true,
inputVal: rowData.item.name,
isSideSlip: false
});}}/>
<MenuOption value={2} text='刪除' onSelect={() => {
this.setState({
modalVisible: true,
fileItem: rowData.item,
dialogType: 'Delete',
confirmText: '肯定刪除?',
hasInputText: false,
isSideSlip: false
});
}}/>
<MenuOption value={3} text='下載'
onSelect={() => this.downloadFile(rowData.item)} disabled={rowData.item.type === 'dir'}/>
</MenuOptions>
</Menu>
</MenuProvider>
複製代碼
(3)注意事項
(4)參考文檔
(1)使用插件
react-native-swipe-list-view
複製代碼
(2)功能實現
yarn add react-native-swipe-list-view
複製代碼
在 react_native_project/src/pages/netDisk/NetDisk.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import { SwipeListView } from 'react-native-swipe-list-view/lib/index';
// render
<SwipeListView
style={styles.list}
data={this.state.filesList}
renderItem={ (rowData) => (
<TouchableHighlight style={styles.rowFront} underlayColor={'#AAA'} > <View style={{flexDirection:'row',flex: 1,alignItems:'center'}}> <Text>{rowData.item.name}</Text> </View> </TouchableHighlight>
)}
renderHiddenItem={ (rowData, rowMap) => {
return (
<View style={styles.standaloneRowBack} key={rowData.item.time}> <NbButton style={[styles.backRightBtn, styles.backRightBtnLeft]} onPress={() =>{ this.setState({ modalVisible: true, fileItem: rowData.item, fileIndex: rowData.item.key, fileRowMap: rowMap, dialogType: 'Rename', hasInputText: true, inputVal: rowData.item.name, isSideSlip: true }); }}> <Text style={styles.backTextWhite}>重命名</Text> </NbButton> <NbButton style={[styles.backRightBtn, styles.backRightBtnRight]} onPress={() => { this.setState({ modalVisible: true, fileItem: rowData.item, fileIndex: rowData.item.key, fileRowMap: rowMap, dialogType: 'Delete', confirmText: '肯定刪除?', hasInputText: false, isSideSlip: true }); }}> <Text style={styles.backTextWhite}>刪除</Text> </NbButton> </View>
);}
}
rightOpenValue={-150}
stopRightSwipe={-150}
disableRightSwipe={true}
swipeToOpenPercent={20}
swipeToClosePercent={0}
/>
複製代碼
(3)注意事項
// 關閉側滑
closeRow(rowMap, rowKey) {
if (rowMap[rowKey]) {
rowMap[rowKey].closeRow();
}
}
複製代碼
(4)參考文檔
(1) 使用插件
rn-fetch-blob
複製代碼
(2)功能實現
yarn add rn-fetch-blob
複製代碼
由於該插件涉及到 Android 原生功能,因此配置完該插件,須要從新編譯 Android。
在 react_native_project/src/pages/netDisk/NetDisk.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import RNFetchBlob from 'rn-fetch-blob';
// 下載方法
async actualDownload(item) {
let dirs = RNFetchBlob.fs.dirs;
const android = RNFetchBlob.android;
RNFetchBlob.config({
fileCache : true,
path: `${dirs.DownloadDir}/${item.name}`,
// android only options, these options be a no-op on IOS
addAndroidDownloads : {
// Show notification when response data transmitted
notification : true,
// Title of download notification
title : '下載完成',
// File description (not notification description)
description : 'An file.',
mime : getMimeType(item.name.split('.').pop()),
// Make the file scannable by media scanner
mediaScannable : true,
}
})
.fetch('GET', `${CONSTANT.SERVER_URL}${item.path}`)
.then(async(res) => {
await android.actionViewIntent(res.path(), getMimeType(item.name.split('.').pop()));
});
}
複製代碼
(3)注意事項
// 問題
So basically this needs to be added to line 122-123 of file android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:
// 解決辦法
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
If above is not working do to the below step: overwrite the 121 line in android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 121 line
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 122 line
複製代碼
(4)參考文檔
(1)使用插件
// 獲取本機文件
react-native-file-selector
複製代碼
(2)功能實現
yarn add react-native-file-selector
複製代碼
由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。
在 react_native_project/src/pages/netDisk/NetDisk.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import RNFileSelector from 'react-native-file-selector';
// 選擇文件並上傳
RNFileSelector.Show(
{
title: '請選擇文件',
onDone: (filePath) => {
let data = new FormData();
let file = { uri: 'file://' + filePath, type: 'multipart/form-data', name: escape(path.basename(filePath))};
data.append('file', file);
let options = {
url: '/files/uploadFile', // 請求 url
data: data,
tipFlag: true, // 默認統一提示,若是須要自定義提示,傳入 true
};
request(options).then(async (res) => {
if (res.status == 200) {
await this.fetchData();
ToastAndroid.show(
'上傳成功',
ToastAndroid.SHORT,
ToastAndroid.CENTER
);
}
});
},
onCancel: () => {
ToastAndroid.show(
'取消上傳',
ToastAndroid.SHORT,
ToastAndroid.CENTER
);
}
}
);
複製代碼
(3)注意事項
(4)參考文檔
(1) 使用插件
react-native-doc-viewer
複製代碼
(2)功能實現
yarn add react-native-doc-viewer
複製代碼
由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。
在 react_native_project/src/pages/netDisk/NetDisk.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import OpenFile from 'react-native-doc-viewer';
// 文件預覽
OpenFile.openDoc([{
url: `${CONSTANT.SERVER_URL}${item.path}`,
fileName: item.name.split('.').shift(),
cache: false,
fileType: item.name.split('.').pop()
}], (error) => {
if (error) {
this.setState({ animating: false });
console.log(error);
ToastAndroid.show('請先安裝相關應用軟件', ToastAndroid.SHORT);
} else {
this.setState({ animating: false });
// ToastAndroid.show('該文件不支持預覽', ToastAndroid.SHORT);
}
});
複製代碼
(3)注意事項
node_modules/react-native-doc-viewer/android/src/main/java/com/reactlibrary/RNReactNativeDocViewerModule.java
文件中 刪除 import com.facebook.react.views.webview.ReactWebViewManager;
(4)參考文檔
(1) 使用插件
react-native-image-zoom-viewer
複製代碼
(2)功能實現
react-native-image-zoom-viewer
複製代碼
在 react_native_project/src/pages/netDisk/NetDisk.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import ImageViewer from 'react-native-image-zoom-viewer';
// 圖片預覽方法
saveImg(url) {
let promise = CameraRoll.saveToCameraRoll(url);
promise.then((result) => {
console.log(result);
ToastAndroid.show('已保存到相冊', ToastAndroid.SHORT);
}).catch((error) => {
console.log(error);
ToastAndroid.show('保存失敗', ToastAndroid.SHORT);
});
}
// render
<Modal
transparent={true}
visible={imgModalVisible}
onRequestClose={() => this.props.closeImg()}>
<ImageViewer onCancel={()=> this.props.closeImg()} onClick={(onCancel) => {onCancel();}} onSave={(url) => this.saveImg(url)} saveToLocalByLongPress={true} imageUrls={images} index={imgIndex} doubleClickInterval={1000} menuContext={{ 'saveToLocal': '保存到相冊', 'cancel': '取消' }}/> </Modal>
複製代碼
(3)注意事項
(4)參考文檔
此模塊包含功能:音/視頻上傳、下載、刪除、判斷網絡、播放、全屏播放、轉向全屏播放、評論、分享等功能,其中上傳、下載、刪除功能在網盤模塊和試題模塊已說明。
(1)使用插件
react-native-video
複製代碼
(2)功能實現
yarn add react-native-video
複製代碼
由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。
在 react_native_project/src/pages/video/VideoPlayer.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import Video from 'react-native-video';
// 視頻進度時間方法
function formatTime(second) {
let h = 0, i = 0, s = parseInt(second);
if (s > 60) {
i = parseInt(s / 60);
s = parseInt(s % 60);
}
// 補零
let zero = function (v) {
return (v >> 0) < 10 ? '0' + v : v;
};
return [zero(h), zero(i), zero(s)].join(':');
}
// render
// 自帶參數和方法請看 api
<Video
ref={(ref) => this.videoPlayer = ref}
source={{uri: CONSTANT.SERVER_URL + '/' + this.state.videoUrl}}
rate={this.state.playRate}
volume={this.state.volume}
muted={this.state.isMuted}
paused={!this.state.isPlaying}
resizeMode={'contain'}
playWhenInactive={false}
playInBackground={false}
ignoreSilentSwitch={'ignore'}
progressUpdateInterval={250.0}
onLoadStart={this._onLoadStart}
onLoad={this._onLoaded}
onProgress={this._onProgressChanged}
onEnd={this._onPlayEnd}
onError={this._onPlayError}
onBuffer={this._onBuffering}
style={{ width: this.state.videoWidth, height: this.state.videoHeight}}
/>
複製代碼
(3)參考文檔
(1) 使用插件
react-native-orientation
複製代碼
(2)功能實現
yarn add react-native-orientation
複製代碼
由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。
在 react_native_project/src/pages/video/VideoPlayer.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import Orientation from 'react-native-orientation';
// 點擊工具欄上的全屏按鈕
onControlShrinkPress() {
if (this.state.isFullScreen) {
Orientation.lockToPortrait();
} else {
Orientation.lockToLandscapeRight();
}
}
// 屏幕旋轉時寬高會發生變化,能夠在onLayout的方法中作處理,比監聽屏幕旋轉更加及時獲取寬高變化
_onLayout = (event) => {
//獲取根View的寬高
let {width, height} = event.nativeEvent.layout;
// 通常設備橫屏下都是寬大於高,這裏能夠用這個來判斷橫豎屏
let isLandscape = (width > height);
if (isLandscape && !this.showKeyboard){
this.setState({
videoWidth: width,
videoHeight: height,
isFullScreen: true,
});
} else {
this.setState({
videoWidth: width,
videoHeight: width * 9/16,
isFullScreen: false,
});
}
Orientation.unlockAllOrientations();
};
複製代碼
(3)參考文檔
(1) 使用插件
react-native-wechat
複製代碼
(2)功能實現
yarn add react-native-wechat
複製代碼
由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。
在 react_native_project/src/components/video/VideoShare.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import * as WeChat from 'react-native-wechat';
// const wxAppId = ''; // 微信開放平臺註冊的app id
// const wxAppSecret = ''; // 微信開放平臺註冊獲得的app secret
// WeChat.registerApp(wxAppId);
// 分享
shareItemSelectedAtIndex(index) {
// this.props.onShareItemSelected && this.props.onShareItemSelected(index);
WeChat.isWXAppInstalled().then((isInstalled) => {
this.setState({
isWXInstalled: isInstalled
});
if (isInstalled && index === 0) {
WeChat.shareToSession({
title: this.state.videoTitle,
type: 'video',
videoUrl: CONSTANT.SERVER_URL + '/' + this.state.videoUrl
}).catch((error) => {
console.log(error.message);
});
} else if (isInstalled && index === 1) {
WeChat.shareToTimeline({
title: this.state.videoTitle,
type: 'video',
videoUrl: CONSTANT.SERVER_URL + '/' + this.state.videoUrl
}).catch((error) => {
console.log(error.message);
});
} else {
console.log('微信未安裝');
}
});
}
複製代碼
(3)參考文檔
(1)使用插件
react-native-image-crop-picker
複製代碼
(2)功能實現
yarn add react-native-image-crop-picker
複製代碼
由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。
在 react_native_project/src/components/exam/ImageAudioTab.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import ImagePicker from 'react-native-image-crop-picker';
// 從相冊選擇圖片
ImagePicker.openPicker(paramObj).then(image => {
this.props.handleImage(qsIndex, image);
}).catch(err => {
console.log(err);
});
// 調用攝像頭功能
openCamera(qsIndex) {
ImagePicker.openCamera({
width: 300,
height: 400,
cropping: true,
}).then(image => {
this.props.handleImage(qsIndex, image);
}).catch(err => {
console.log(err);
});
}
複製代碼
(3)注意事項
(4)參考文檔
(1) 使用插件
react-native-audio // 語音錄入
react-native-sound // 語音播放
react-native-spinkit // 動畫效果
複製代碼
(2)功能實現
yarn add react-native-audio react-native-sound react-native-spinkit
複製代碼
由於語音錄入插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。
在 react_native_project/src/components/exam/ImageAudioTab.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import { AudioRecorder, AudioUtils } from 'react-native-audio';
import Sound from 'react-native-sound';
import Spinkit from 'react-native-spinkit';
// 音頻路徑配置
prepareRecordingPath = (path) => {
const option = {
SampleRate: 44100.0, //採樣率
Channels: 2, //通道
AudioQuality: 'High', //音質
AudioEncoding: 'aac', //音頻編碼
OutputFormat: 'mpeg_4', //輸出格式
MeteringEnabled: false, //是否計量
MeasurementMode: false, //測量模式
AudioEncodingBitRate: 32000, //音頻編碼比特率
IncludeBase64: true, //是不是base64格式
AudioSource: 0, //音頻源
};
AudioRecorder.prepareRecordingAtPath(path, option);
}
// 開始錄音
startSoundRecording(qsIndex, stemAudio) {
if (stemAudio.length >= 5) {
ToastAndroid.show('每道題最多 5 段語音哦', ToastAndroid.SHORT);
return;
}
console.log('startSoundRecording....');
// 請求受權
AudioRecorder.requestAuthorization()
.then(isAuthor => {
if (isAuthor) {
this.prepareRecordingPath(this.audioPath + qsIndex + '_' + stemAudio.length + '.aac');
// 錄音進展
AudioRecorder.onProgress = (data) => {
this.recordTime = Math.floor(data.currentTime);
};
// 完成錄音
AudioRecorder.onFinished = (data) => {
// data 返回須要上傳到後臺的錄音數據;
this.isRecording = false;
if (!this.recordTime) {
ToastAndroid.show('錄音時間過短...', ToastAndroid.SHORT);
return;
}
this.props.handleAudio(qsIndex, data.audioFileURL, this.recordTime);
// 重置爲 0
this.recordTime = 0;
};
// 錄音
AudioRecorder.startRecording();
this.isRecording = true;
}
});
}
// 結束錄音
stopSoundRecording() {
console.log('stopSoundRecording....');
// 已經被節流操做攔截,沒有在錄音
if (!this.isRecording) {
return;
}
AudioRecorder.stopRecording();
}
// 播放錄音
playSound(qsIndex, index, stemAudio, audioFlag, path) {
this.props.changeAudioState(qsIndex, index, 2);
let whoosh = new Sound(path.slice(7), '', (err) => {
if (err) {
return console.log(err);
}
whoosh.play(success => {
if (success) {
console.log('success - 播放成功');
} else {
console.log('fail - 播放失敗');
}
this.props.changeAudioState(qsIndex, index, 1);
});
});
}
複製代碼
(3)注意事項
(4)參考文檔
(1) 使用插件
victory-native // 圖標繪製插件
react-native-svg // svg 圖片繪製
複製代碼
(2)功能實現
yarn add victory-native react-native-svg
複製代碼
由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。
在 react_native_project/src/pages/exam/ResultStatistics.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 插件引入
import {
VictoryPie,
VictoryLegend,
VictoryTooltip
} from 'victory-native';
// 圖形繪製組件使用
<VictoryLegend
orientation="vertical"
data={[
{
name: '不及格 < 60 分',
symbol: { fill: colorScale[0], type: 'square' },
},
{
name: '及格 60 - 75 分',
symbol: { fill: colorScale[1], type: 'square' },
},
{
name: '良好 75 - 85 分',
symbol: { fill: colorScale[2], type: 'square' },
},
{
name: '優秀 > 85 分',
symbol: { fill: colorScale[3], type: 'square' },
}
]}
width={180}
height={125}
/>
<VictoryPie
colorScale={colorScale}
data={[
{ y: this.state.result[3], label: '不及格:' + this.state.result[3] + '人'},
{ y: this.state.result[2], label: '及格:' + this.state.result[2] + '人' },
{ y: this.state.result[1], label: '良好:' + this.state.result[1] + '人' },
{ y: this.state.result[0], label: '優秀:' + this.state.result[0] + '人' }
]}
innerRadius={60}
height={300}
width={345}
animate={{
duration: 2000
}}
labelComponent={
<VictoryTooltip
active={({ datum }) => datum.y === 0 ? false : true}
constrainToVisibleArea={true}
flyoutHeight={30}
flyoutStyle={{ strokeWidth: 0.1}}
/>
}
/>
複製代碼
(3)注意事項
(4)參考文檔
(1)使用插件
Linking // react native 自帶的插件
複製代碼
(2)功能實現
在 react_native_project/src/components/user/ListItem.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
// 撥打電話功能 or 短信功能
call(flag) {
let tel = flag === 1 ? 'tel:10086' : 'smsto:10086';
Linking.canOpenURL(tel).then(supported => {
if (!supported) {
ToastAndroid.show.show('您未受權通話和短信權限');
} else {
return Linking.openURL(tel);
}
}).catch(err => console.error('An error occurred', err));
}
複製代碼
(3)注意事項
(4)參考文檔
(1) 使用插件
(2)功能實現
在 react_native_project/android/app/src/main/java/com/react_native_project/module
目錄中建立實現類 LocationModule.java,須要注意的是這個類須要實現 ReactContextBaseJavaModule 這個類:
public class LocationModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext mContext;
public LocationModule(ReactApplicationContext reactContext) {
super(reactContext);
mContext = reactContext;
}
/** * @return js調用的模塊名 */
@Override
public String getName() {
return "LocationModule";
}
/** * 使用ReactMethod註解,使這個方法被js調用 */
@ReactMethod
public void getLocation(Callback locationCallback) {
// 省略一些邏輯實現 ...
locationCallback.invoke(lat,lng,country,locality);
}else{
locationCallback.invoke(false);
}
}
}
複製代碼
對剛剛實現定位功能的模塊進行註冊,在 react_native_project/android/app/src/main/java/com/react_native_project/module
目錄中建立註冊包管理類 LocationReactPackage .java,相關邏輯以下:
public class LocationReactPackage implements ReactPackage {
/** * @param reactContext 上下文 * @return 須要調用的原生控件 */
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
/** * @param reactContext 上下文 * @return 須要調用的原生模塊 */
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new LocationModule(reactContext));
return modules;
}
}
複製代碼
在 react_native_project/android/app/src/main/java/com/react_native_project/MainApplication.java
中添加包管理類,相關邏輯以下:
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new LocationReactPackage());
return packages;
}
複製代碼
咱們在 react_native_project/src/components/user/ListItem.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
import { NativeModules } from 'react-native';
// 獲取地理位置
showLocation() {
NativeModules.LocationModule.getLocation((lat, lng, country, locality) => {
let str = '獲取位置信息失敗,您可能手機位置信息沒有開啓!';
if (lat && lng) {
str = country + ',' + locality + ',緯度:' + lat + ',' + '經度:' + lng;
}
ToastAndroid.show(str, ToastAndroid.SHORT);
});
}
複製代碼
(3)注意事項
(4)參考文檔
(1) 使用插件
rn-fetch-blob
複製代碼
(2)功能實現
yarn add rn-fetch-blob
複製代碼
由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。
咱們實如今線升級功能的大概邏輯是,在 app 管理端上傳 apk 安裝包,而後點擊發布,這時服務端會經過 websocket 將最新發布的版本號通知 app,app 收到最新版本號,會跟當前的 app 版本比較,若是當前版本號小於最新版本號,則會彈窗提示有最新版本,詢問用戶是否下載安裝,用戶若是確認安裝最新版本,則會從服務器下載最新的 apk,並進行安裝。在 react_native_project/src/components/user/ListItem.js
組件中實現相應邏輯,關鍵代碼及註釋以下:
import RNFetchBlob from 'rn-fetch-blob';
checkUpdate = () => {
const android = RNFetchBlob.android;
//下載成功後文件所在path
const downloadDest = `${ RNFetchBlob.fs.dirs.DownloadDir }/app_release.apk`;
RNFetchBlob.config({
//配置手機系統通知欄下載文件通知,下載成功後點擊通知可運行apk文件
addAndroidDownloads: {
useDownloadManager: true,
title: 'RN APP',
description: 'An APK that will be installed',
mime: 'application/vnd.android.package-archive',
path: downloadDest,
mediaScannable: true,
notification: true
}
}).fetch(
'GET',
CONSTANT.SERVER_URL+'/appVersion/download?path='+this.newVersionInfo.path
).then(res => {
//下載成功後自動打開運行已下載apk文件
android.actionViewIntent(
res.path(),
'application/vnd.android.package-archive'
);
});
}
複製代碼
(3)注意事項
(4)參考文檔
4.一、運行 react-native run-android 出現錯誤:Task :app:mergeDebugAssets FAILED OR Task :app:processDebugResources FAILED 。
解決:
cd android && ./gradlew clean
cd .. && react-native run-android
複製代碼
4.二、若是手機真機出現鏈接不上開發開發服務器的狀況。
解決:
命令窗口運行如下命令:
adb reverse tcp:8081 tcp:8081
複製代碼
4.三、kotlin 相關 jar 包沒法下載。
解決:
對應的插件的 android/build.gradle 配置阿里雲倉庫(例如遇到這個問題時,是在插件 react-native-webview)
// Maven中心倉庫牆內版
maven { url "https://maven.aliyun.com/repository/central" }
// jCenter中心倉庫牆內版
maven { url "https://maven.aliyun.com/repository/jcenter" }
maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'}
複製代碼
4.四、文件預覽插件:react-native-doc-viewer安裝完 run-android 編譯失敗。
解決:
Could be fixed by removing the import in node_modules/react-native-doc-viewer/android/src/main/java/com/reactlibrary/RNReactNativeDocViewerModule.java
Remove the ununsed import:
import com.facebook.react.views.webview.ReactWebViewManager;
複製代碼
4.五、第三方插件 rn-fetch-blob 下載文檔沒法打開。
解決:
So basically this needs to be added to line 122-123 of file android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
If above is not working do to the below step: overwrite the 121 line in android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 121 line
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 122 line
複製代碼
本文主要基於 React Native 框架的實踐進行總結,分享了 React Native 理念、React Native 項目的功能介紹、React Native 項目編譯以及 React Native 存在的一些坑,但願對徹底閱讀完的你有啓發和幫助,若是有不足,歡迎批評、指正、交流!
姐妹篇《 Weex 實踐總結 》,能夠進行 React Native 和 Weex 的對比。
辛苦整理良久,還望手動點贊鼓勵~
博客 github地址爲:github.com/fengshi123/… ,彙總了做者的全部博客,也歡迎關注及 star ~
本項目 github 地址爲:github.com/fengshi123/…
配套的服務端 express 項目 github 地址爲:github.com/fengshi123/…