React Native使用echarts——@wuba/react-native-echarts

前言

平时写图表相关需求,用得最多的图表库就是echarts。echarts在web端的表现已经相当成熟,官方对小程序端也提供了解决方案,而在RN方面却没有相应支持。市面上搜到的,大多本质还是基于webview实现,而我更倾向于基于RN的方案,毕竟原生的体验会比Web的更好一些。
经过一番寻找发现 @wuba/react-native-echarts 满足需求,于是上手试了下,效果还不错 ~ 对实现原理感兴趣的可以看这里

Tips

1.如果你已经有APP包,可以忽略前面的打包流程,直接跳到第4步。
2.试用的完整代码放在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

tip: 注意,如果你是在已有工程中安装,安装完成后要重新打个新包,不然缺少原生依赖会报错;

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安卓
iOS截图安卓截图

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安卓
在这里插入图片描述在这里插入图片描述

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安卓
在这里插入图片描述在这里插入图片描述

渲染出来后,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很丝滑,安卓有时会出现掉帧的情况;
  • 性能上,官方报告显示优于react-native-echarts-pro。
    • 个人试了下,不是超大数据量就不会有什么问题,但是数据量太大的时候(比如画大数据量的热力图),渲染速度明显下降了很多,这是一个等待官方去优化的点。
    • 另外页面内图表多的话,真机调试时加载速度会变慢,建议先用模拟器。
  • 中文支持,Svg模式支持中文,但Skia模式目前还不可以。

以上仅代表个人观点,有问题欢迎交流。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在基于@shopify/shopify-app-express的应用程序中注册Shopify Webhook,可以使用该框架提供的webhook路由。下面是一个示例代码来注册一个Webhook: ```javascript const { default: createShopifyAuth } = require('@shopify/koa-shopify-auth'); const { default: Shopify, ApiVersion } = require('@shopify/shopify-api'); const { verifyRequest } = require('@shopify/koa-shopify-auth'); const Koa = require('koa'); const Router = require('koa-router'); const bodyParser = require('koa-bodyparser'); const app = new Koa(); const router = new Router(); const webhook = { topic: 'products/create', address: 'https://your-app.com/webhooks/products/create', format: 'json', }; app.use(bodyParser()); const shopifyAuth = createShopifyAuth({ // Your Shopify app API key and secret apiKey: process.env.SHOPIFY_API_KEY, secret: process.env.SHOPIFY_API_SECRET, // Your app URL appUrl: process.env.APP_URL, // Scopes to request on the merchant's behalf scopes: ['read_products', 'write_products', 'read_script_tags', 'write_script_tags'], // After authentication, redirect to the shop's home page afterAuth(ctx) { const { shop } = ctx.state.shopify; ctx.redirect(`https://${shop}/admin/apps/${process.env.SHOPIFY_API_KEY}`); }, }); // Register webhook router.post('/webhooks/products/create', verifyRequest({ returnHeader: true }), (ctx) => { console.log('New product created:', ctx.request.body); ctx.status = 200; }); (async function() { // Create an instance of Shopify const shopify = new Shopify({ apiKey: process.env.SHOPIFY_API_KEY, apiSecretKey: process.env.SHOPIFY_API_SECRET, shopName: ctx.session.shop, accessToken: accessToken, apiVersion: ApiVersion.October20, autoLimit: { calls: 2, interval: 1000, bucketSize: 35 }, }); // Register webhook await shopify.webhook.create(webhook); // Use the shopifyAuth middleware app.use(shopifyAuth); app.use(router.allowedMethods()); app.use(router.routes()); app.listen(process.env.PORT, () => { console.log(`Server listening on port ${process.env.PORT}`); }); })(); ``` 在上面的代码中,我们首先创建一个Shopify实例,并使用它来注册Webhook。然后,我们使用@shopify/shopify-app-express框架创建一个HTTP服务器,并为Webhook的URL路径创建一个POST路由。在路由处理程序中,我们可以处理接收到的Webhook数据。最后,我们使用Shopify API将Webhook注册到商店中。 注意,我们在Webhook地址中使用了公共URL,这意味着您需要在您的应用程序中设置公共URL,并将其用作Webhook地址。此外,您需要在Shopify后台中配置相应的Webhook主题,以便将Webhook发送到正确的URL地址。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值