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

 

posted @   非常007  阅读(4796)  评论(4编辑  收藏  举报
编辑推荐:
· 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语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示