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
