作者:iambool
前言
平時寫圖表相關需求,用得最多的圖表庫就是echarts。echarts 在 web 端的表現已經相當成熟,官方對小程式端也提供瞭解決方案,而在 RN 方面卻沒有相應支援。市面上搜到的,大多本質還是基於 webview 實現,而我更傾向於基於 RN 的方案,畢竟原生的體驗會比 Web 的更好一些。
所以我們釋出了@wuba/react-native-echarts 來滿足需求。對實現原理感興趣的可以看這裡
接下來我將使用 @wuba/react-native-echarts來做一個實際專案中的應用,截圖如下:
小提示
- 如果你已經有 APP 包,可以忽略前面的打包流程,直接跳到第 4 步。
- 試用的完整程式碼放在 github 上了,地址:https://github.com/iambool/TestApp
詳細使用過程如下
1、開發環境搭建
本地搭好 RN 開發環境,搭建過程網上一抓一大把,就不贅述了。
2、準備 RN 工程
因為是試用,所以我用 expo 新初始化了一個 rn 工程,叫 TestApp。
npx create-expo-app TestApp
3、build App 包
用命令列生成包 ios android app 包。這裡 ios 建議用模擬器(不需要配證照),安卓我是連的真機
yarn android
yarn ios
生成包後,手機看到已經安裝了這個應用,就代表成功啦。
4、 安裝相關依賴
yarn add @wuba/react-native-echarts echarts
yarn add @shopify/react-native-skia
yarn add react-native-svg
注意,如果你是在已有工程中安裝,安裝完成後要重新打個新包,不然缺少原生依賴會報錯;
5、試用 Skia 模式
@wuba/react-native-echarts 支援兩種渲染模式(Skia 和 Svg),先用 Skia 試一個簡單的圖表。大致分為這幾個小步驟:
- 引入 echarts、圖表元件等依賴
- 註冊圖表元件
- 建立圖表例項,並設定圖表的配置(option)
- 頁面銷燬時要記得同步銷燬圖表例項
具體程式碼如下:
import { useRef, useEffect } from 'react';
import { View } from 'react-native';
/**
* 一、引入echarts依賴,這裡先試下折線圖
*/
import * as echarts from 'echarts/core';
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { SVGRenderer, SkiaChart } from '@wuba/react-native-echarts';
/**
* 二、註冊需要用到的元件
* SVGRenderer: 是必須註冊的
* LineChart: 因為用的折線圖,所以要引入LineChart(如果不知道該引入哪些元件,就直接看報錯,報錯說缺什麼就加什麼)
* GridComponent: 這個就是報錯的時候提示,然後我加的hhh
*/
echarts.use([SVGRenderer, LineChart, GridComponent]);
export default () => {
const skiaRef = useRef(null); // Ref用於儲存圖表例項
useEffect(() => {
/**
* 四、圖表配置
*/
const option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line',
},
],
};
let chart;
if (skiaRef.current) {
/**
* 五、初始化圖表,指定下寬高
*/
chart = echarts.init(skiaRef.current, 'light', {
renderer: 'svg',
width: 400,
height: 400,
});
chart.setOption(option);
}
/**
* 六、頁面關閉後要銷燬圖表例項
*/
return () => chart?.dispose();
}, []);
return (
<View className='index'>
<SkiaChart ref={skiaRef} />
</View>
);
};
寫完搖一搖手機,reload bundle 包時出現了報錯:
ERROR Invariant Violation: requireNativeComponent: "SkiaDomView" was not found in the UIManager.
google 了一下,說是需要降級解決。其實是要跟 expo 版本對應,在安裝依賴的時候也會有類似這樣的提示,安裝提示的版本就可以了
於是按照提示做了版本降級:
@shopify/react-native-skia@0.1.157
react-native-svg@13.4.0
重新構建 app 後載入出來了,針不戳;(安卓遮住了點,看來應該自適應螢幕寬度)
iOS | Android |
---|---|
6、試用 Svg 模式
寫個複雜點的動態排序柱狀圖,試試 Svg 模式,給 Svg 和 Skia 做個對比,完整程式碼看這裡。
// ...此處省略一些不重要的程式碼
// 註冊需要用到的元件,BarChart-柱狀圖 LegendComponent-圖例
echarts.use([SVGRenderer, BarChart, LegendComponent, GridComponent]);
export default () => {
const skiaRef = useRef(null);
const svgRef = useRef(null);
useEffect(() => {
// Skia模式
const skiaChartData = getData(); // 生成圖表柱狀圖資料
let skiaChart;
let skiaInter;
if (skiaRef.current) {
skiaChart = echarts.init(skiaRef.current, 'light', {
renderer: 'svg',
width: 300,
height: 300,
});
skiaChart.setOption(getDefaultOption(skiaChartData));
setTimeout(function () {
run(skiaChart, skiaChartData);
}, 0);
skiaInter = setInterval(function () {
run(skiaChart, skiaChartData);
}, 3000);
}
// Svg模式
const svgChartData = getData();
let svgChart;
let svgInter;
if (svgRef.current) {
svgChart = echarts.init(svgRef.current, 'light', {
renderer: 'svg',
width: 300,
height: 300,
});
svgChart.setOption(getDefaultOption(svgChartData));
setTimeout(function () {
run(svgChart, svgChartData);
}, 0);
svgInter = setInterval(function () {
run(svgChart, svgChartData);
}, 3000);
}
return () => {
skiaChart?.dispose();
svgChart?.dispose();
// 定時器得清理掉,不然退出頁面後還會執行
clearInterval(skiaInter);
clearInterval(svgInter);
};
}, []);
return (
<View>
<Text>skia如下</Text>
<SkiaChart ref={skiaRef} />
<Text>svg如下</Text>
<SvgChart ref={svgRef} />
</View>
);
};
Skia 和 Svg 模式,肉眼看不出明顯差別
iOS | Android |
---|---|
7、封裝 Chart 元件
效果不錯,不過每次使用都要把一堆東西引進去好煩,先簡單封裝下吧
import { useRef, useEffect } from 'react';
import * as echarts from 'echarts/core';
import { BarChart, LineChart, PieChart } from 'echarts/charts';
import {
DataZoomComponent,
GridComponent,
LegendComponent,
TitleComponent,
ToolboxComponent,
TooltipComponent,
} from 'echarts/components';
import {
SVGRenderer,
SvgChart as _SvgChart,
SkiaChart as _SkiaChart,
} from '@wuba/react-native-echarts';
import { Dimensions } from 'react-native';
// 註冊需要用到的元件
echarts.use([
DataZoomComponent,
SVGRenderer,
BarChart,
GridComponent,
LegendComponent,
ToolboxComponent,
TooltipComponent,
TitleComponent,
PieChart,
LineChart,
]);
// 圖表預設寬高
const CHART_WIDTH = Dimensions.get('screen').width; // 預設用手機螢幕寬度
const CHART_HEIGHT = 300;
const Chart = ({
option,
onInit,
width = CHART_WIDTH,
height = CHART_HEIGHT,
ChartComponent,
}) => {
const chartRef = useRef(null);
useEffect(() => {
let chart;
if (chartRef.current) {
chart = echarts.init(chartRef.current, 'light', {
renderer: 'svg',
width,
height,
});
option && chart.setOption(option);
onInit?.(chart);
}
return () => chart?.dispose();
}, [option]);
return <ChartComponent ref={chartRef} />;
};
const SkiaChart = (props) => <Chart {...props} ChartComponent={_SkiaChart} />;
const SvgChart = (props) => <Chart {...props} ChartComponent={_SvgChart} />;
// 對外只暴露這哥倆就行
export { SkiaChart, SvgChart };
8、多個圖表使用
封裝好了,我們就寫個多圖表同時使用的頁面看看效果。這裡寫了個“電商資料分析”頁面,分別有折線圖、柱狀圖、餅圖。下方是主要程式碼,用的 svg 模式,詳細程式碼見這裡。
// 頁面程式碼
import { SkiaChart } from '../../components/Chart';
import { ScrollView, Text, View } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { useCallback, useEffect, useState } from 'react';
import {
defaultActual,
lineOption,
salesStatus,
salesVolume,
userAnaly,
getLineData,
} from './contants';
import styles from './styles';
// 開啟圖表loading
const showChartLoading = (chart) =>
chart.showLoading('default', {
maskColor: '#305d9e',
});
// 關閉圖表loading
const hideChartLoading = (chart) => chart.hideLoading();
export default () => {
const [actual, setActual] = useState(defaultActual); // 記錄實時資料
useEffect(() => {
// 假設迴圈請求資料
const interv = setInterval(() => {
const newActual = [];
for (let it of actual) {
newActual.push({
...it,
num: it.num + Math.floor((Math.random() * it.num) / 100),
});
}
setActual(newActual);
}, 200);
return () => clearInterval(interv);
}, [actual]);
const onInitLineChart = useCallback((myChart) => {
showChartLoading(myChart);
// 模擬資料請求
setTimeout(() => {
myChart.setOption({
series: getLineData,
});
hideChartLoading(myChart);
}, 1000);
}, []);
const onInitUserChart = useCallback((myChart) => {
// 模擬資料請求,跟onInitLineChart類似
}, []);
const onInitSaleChart = useCallback((myChart) => {
// 模擬資料請求,跟onInitLineChart類似
}, []);
const onInitStatusChart = useCallback((myChart) => {
// 模擬資料請求,跟onInitLineChart類似
}, []);
const chartList = [
['訂單走勢', lineOption, onInitLineChart],
['使用者統計', userAnaly, onInitUserChart],
['各品類銷售統計', salesVolume, onInitSaleChart],
['訂單狀態統計', salesStatus, onInitStatusChart],
];
return (
<ScrollView style={styles.index}>
<StatusBar style='light' />
<View>
<View style={styles.index_panel_header}>
<Text style={styles.index_panel_title}>實時資料</Text>
</View>
<View style={styles.index_panel_content}>
{actual.map(({ title, num, unit }) => (
<View key={title} style={styles.sale_item}>
<View style={styles.sale_item_cell}>
<Text style={styles.sale_item_text}>{title}</Text>
</View>
<View style={[styles.sale_item_cell, styles.num]}>
<Text style={styles.sale_item_num}>{num}</Text>
</View>
<View style={[styles.sale_item_cell, styles.unit]}>
<Text style={styles.sale_item_text}>{unit}</Text>
</View>
</View>
))}
</View>
</View>
{chartList.map(([title, data, callback]) => (
<View key={title}>
<View style={styles.index_panel_header}>
<Text style={styles.index_panel_title}>{title}</Text>
</View>
<View style={styles.index_panel_content}>
<SkiaChart option={data} onInit={callback} />
</View>
</View>
))}
</ScrollView>
);
};
重新載入 bundle,看看效果圖
iOS | Android |
---|---|
渲染出來後,iOS 上互動很絲滑,安卓上互動時感覺偶爾會有卡頓(不會是因為我手機太差吧…)。
再換 Skia 模式看看
emmm 雖然可以,但是好像中文不能正常顯示,安卓上中文都沒有顯示,iOS 則是亂碼。看了下文件,目前 skia 在安卓端還不支援中文,在 iOS 端可以透過設定字型為 'PingFang SC'顯示中文,比如:
const option = {
title: {
text: '我是中文',
textStyle: {
fontFamily: 'PingFang SC', // 指定字型型別
},
},
};
但是每個顯示中文的地方都要設定字型……那還是先用 svg 吧,我懶。
總結
使用了一段時間後,我總結了下:
- 支援度上,@wuba/react-native-echarts 除了 GL 系列、地圖類圖表還不支援外,其餘型別的圖表都支援,對於日常業務來說已經非常 enough 了。echarts 各種型別的圖表實現,都可以在taro-playground上找到;
- 互動上,iOS 很絲滑,安卓有時會出現掉幀的情況;
效能上,還挺好的。
- 個人試了下,不是超大資料量就不會有什麼問題,但是資料量太大的時候(比如畫大資料量的熱力圖),渲染速度明顯下降了很多,這是一個等待官方去最佳化的點。
- 另外頁面內圖表多的話,真機除錯時載入速度會變慢,建議先用模擬器。
- 中文支援,Svg 模式支援中文,但 Skia 模式目前還不可以。
以上僅代表個人觀點,有問題歡迎交流。