react-native中使用Echarts,自己使用WebView封装Echarts经验
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如下:
1 2 3 4 5 6 7 8 9 10 11 12 | <! 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的样子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <! 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字符串编写好如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /*在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做了判断,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 | 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代码了
1 2 3 4 5 6 7 8 9 10 | export default class SelfEChart extends Component { render() { return ( <WebView source={require( './chart.html' )} //加载的html资源 injectedJavaScript = {renderChart( this .props)} //在html内执行js代码,必须是字符串 /> ); } } |
option当然是父组件传递过来的,我们也可以指定该图标显示的高度和宽度,以及一些其他的属性,还有就是webview还有一些其他的辅助属性,可以帮组我们优化组件的功能,接下来我们看看完整的charComponent.js的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | 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) {<br> //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组件已经封装好了,接下来我们看看怎么使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构