共计 7531 个字符,预计需要花费 19 分钟才能阅读完成。
作者: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 模式目前还不能够。
以上仅代表个人观点,有问题欢送交换。