1.工作中遇到的問題
我們在使用react-native肯定遇到過各種奇葩的問題,比如引入Echarts時候莫名報錯,但是Echarts官網明顯告訴我們可以懶加載的,這是因為基本上js大部分原生的組件庫都不支持React-Native,直接引用都會報"undefined is not an object (evaluating 'ua.match')" when importing an incompatible (browser) library.
2.經過調研得知react-native的WebView可以解決這個問題
有時候可以使用 WebView 彌補一些 ReactNative 內置的組件實現不了的東西,我們可以借助 HTML 來完成,畢竟 HTML 有豐富的工具可以用。例如要想在 ReactNative 里展示圖表,原生自帶的組件則沒辦法實現,其他的圖表組件都是基於 react-native-svg 實現的,展示效果目前還不足人意,如果僅僅是展示,不在乎圖表的各項數據和動態操作,這里也介紹幾個小巧的圖表插件,react-native-pathjs-charts,victory-native ( 展示效果豐富,極力推薦,名字有點隨意,導致很多人不知道這個插件 )。但是如果需要echarts或者highChart這些豐富的功能,這個時候 HTML 則有一大堆圖表工具可以使用。
那我們接下就教大家如何一步一步封裝自己的echarts組件。
3.封裝echarts
假設我有一個本地的react-native目錄如下
Demo/ android/ ios/ App.js index.js packege.json src/ components/ chart/ view ...
我們在src/components/chart目錄下新建兩個文件,一個叫chart.html,一個叫chartComponent.js
編寫chart.html如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <h1>測試文件</h1> </body> </html>
然后編寫chartComponent.js
import React ,{Component} from 'react'; import { View, Text, ScrollView , WebView, Dimensions, StyleSheet, Platform } from 'react-native'; export default class SelfEChart extends Component { render() { return ( <WebView source={require('./chart.html')} //加載的html資源 /> ) } }
接下來引入你的SelfEchart組件展示到你的頁面中,如果出現剛才的測試文件,那么你的webview就是起效果了。
我們開始改造我們的chart.html使它正式成為平時我們寫echarts的樣子
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style type="text/css"> html,body { height: 100%; width: 100%; margin: 0; padding: 0; } #main { height: 100%; } </style> </head> <body> <div id="main"></div> <script> /* echarts.min.js代碼拷貝到這里 */ </script> </body> </html>
在使用html加載好我們的html文件以及文件內部的echarts.min.js后需要初始化echarts插件,這個時候需要用到webview的 injectedJavaScript 屬性,但是該屬性必須是一段js的字符串,我們先將需要執行的js字符串編寫好如下:
/*在WebView加載外部html后執行的js,主要是初始化echart圖表*/ function renderChart(props) { const height = `${props.height || 400}px`; const width = props.width ? `${props.width}px` : 'auto'; return ` document.getElementById('main').style.height = "${height}"; document.getElementById('main').style.width = "${width}"; var myChart = echarts.init(document.getElementById('main')); myChart.setOption(${toString(props.option)});
//這個自定義的message主要是監聽webview組件傳遞來的數據變化的,假設圖表數據變化,我們需要更新echart的option,使的
//圖表的變化不間斷,可以實現實時監控的效果,以至於不閃屏
window.document.addEventListener('message', function(e) { var option = JSON.parse(e.data); myChart.setOption(option); }); ` }
我們注意到上述代碼有個toString方法,主要是將option對象轉成字符串,因為JSON.stringify()方法本身會忽略函數屬性,所以toString隊JSON.stringify做了判斷,代碼如下
function toString(obj) { let result = JSON.stringify(obj, function(key, val) { if (typeof val === 'function') { return `~--demo--~${val}~--demo--~`; } return val; }); do { result = result.replace('\"~--demo--~', '').replace('~--demo--~\"', '').replace(/\\n/g, '').replace(/\\\"/g,"\""); } while (result.indexOf('~--demo--~') >= 0); return result; }
所要的東西都准備好了,家下來開始在webview組件內引入在chart.html加載后需要執行的js代碼了
export default class SelfEChart extends Component { render() { return ( <WebView source={require('./chart.html')} //加載的html資源 injectedJavaScript = {renderChart(this.props)} //在html內執行js代碼,必須是字符串 /> ); } }
option當然是父組件傳遞過來的,我們也可以指定該圖標顯示的高度和寬度,以及一些其他的屬性,還有就是webview還有一些其他的輔助屬性,可以幫組我們優化組件的功能,接下來我們看看完整的charComponent.js的代碼
import React ,{Component} from 'react'; import { View, Text, ScrollView , WebView, Dimensions, StyleSheet, Platform } from 'react-native'; /*獲取設備的屏幕寬度和高度*/ const {width, height} = Dimensions.get('window'); function toString(obj) { let result = JSON.stringify(obj, function(key, val) { if (typeof val === 'function') { return `~--demo--~${val}~--demo--~`; } return val; }); do { result = result.replace('\"~--demo--~', '').replace('~--demo--~\"', '').replace(/\\n/g, '').replace(/\\\"/g,"\""); } while (result.indexOf('~--demo--~') >= 0); return result; } /*在WebView加載外部html后執行的js,主要是初始化echart圖表*/ function renderChart(props) { const height = `${props.height || 400}px`; const width = props.width ? `${props.width}px` : 'auto'; return ` document.getElementById('main').style.height = "${height}"; document.getElementById('main').style.width = "${width}"; var myChart = echarts.init(document.getElementById('main')); myChart.setOption(${toString(props.option)}); window.document.addEventListener('message', function(e) { var option = JSON.parse(e.data); myChart.setOption(option); }); ` } /** * 通過WebView封裝react-native不支持的插件,本次封裝echarts * * 該組件需要的props * option 必填,為ECharts配置屬性option,詳細配置參考官網EChartshttp://echarts.baidu.com/option.html#title * width 不必填,為圖表的寬度 * height 不必填,為圖表的高度 * * */ export default class SelfEChart extends Component { constructor(props) { super(props); this.setNewOption = this.setNewOption.bind(this); } componentWillReceiveProps(nextProps) { if(nextProps.option !== this.props.option) { this.refs.chart.reload(); } } setNewOption(option) {
//postMessage會觸發剛才js中的message監聽方法,使得圖表刷新option配置 this.refs.chart.postMessage(JSON.stringify(option)); } render() { /**在安卓下加載的資源跟ios不同,需要做兼容處理, * 就是將當下的chart.html拷貝到android/app/src/main/assets */ const source = (Platform.OS == 'ios') ? require('./chart.html') : { uri: 'file:///android_asset/chart.html' } return ( <View style={{width:this.props.width || width,flex: 1, height: this.props.height || 400,}}> <WebView ref="chart" scrollEnabled = {false} style={{ height: this.props.height || 400, backgroundColor: this.props.backgroundColor || 'transparent' }} source={source} //加載的html資源 scalesPageToFit={Platform.OS !== 'ios'} injectedJavaScript = {renderChart(this.props)} //在html內執行js代碼,必須是字符串 /> </View> ); } }
到此為止,我們的echar組件已經封裝好了,接下來我們看看怎么使用
import React ,{Component} from 'react'; import { View, Text,ScrollView } from 'react-native'; import SelfEChart from '../../components/chart/chart' export default class ListScreen extends React.Component { componentDidMount(){ /** * 連續不間斷刷新圖標demo */ setInterval(()=>{ let data = [5, 20, 36, 10, 10, 20].map((v)=>{ return Math.random()*v }) var option = { title: { text: 'ECharts 入門示例' }, tooltip: {}, legend: { data:['銷量'] }, xAxis: { data: ["襯衫","羊毛衫","雪紡衫","褲子","高跟鞋","襪子"] }, yAxis: {}, series: [{ name: '銷量', type: 'bar', data: data }] }; /**普通圖表刷新通過改變state內部的option實現,缺點就是組件不斷更新,導致圖表組件重頭開始渲染,沒有連貫效果 * 在chartComponent里面封裝的setNewOption方法, * 目的是為了調用myChart.setOption(option) * 達到不抖屏不更新state刷新圖表 * */ this.refs.charts.setNewOption(option) },2000) } render() { var option = { title: { text: 'ECharts 入門示例' }, tooltip: {}, legend: { data:['銷量'] }, xAxis: { data: ["襯衫","羊毛衫","雪紡衫","褲子","高跟鞋","襪子"] }, yAxis: {}, series: [{ name: '銷量', type: 'bar', data: [5, 20, 36, 10, 10, 20] }] }; return ( <ScrollView> <View style={ { flex: 1, justifyContent: 'center', alignItems: 'center'} }> <Text>ListScreen!</Text> <SelfEChart ref="charts" option={option} /> </View> </ScrollView> ); } }
最好附上已經全部搭建好的react-native框架地址:https://github.com/jiangzhenfei/react-native-demo