react-native 中简单使用 echarts(下)

543 阅读5分钟

昨天使用模拟器的时候,居然不行,也就是直接使用 html 的方式,今天我到公司一测试,发现可以。我就测试了一下发现是每次都需要重新打开才可以。由于要兼容 ios 和 android , require 的方式在 ios 上可以,但是在 android 上不行, android 需要在 assets 文件夹中才可以,而 android 这种需要重新打包才可以,我们是热更新,所以这种方式也不行。最终发现只有下面这种最好,首先定义好 html 字符串:

export default `
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- webView ios适配 start -->
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <!-- webView ios适配 end -->
    <meta name="viewport" content="initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no">
    <script type="text/javascript" src="https://cdn.bootcss.com/echarts/4.2.1/echarts.min.js"></script>
    <title>Document</title>
    <style type="text/css">
      html,
      body {
        height: 100%;
        width: 100%;
        margin: 0;
        padding: 0;
        background-color: rgba(0, 0, 0, 0);
      }
      #main {
        height: 100%;
        background-color: rgba(0, 0, 0, 0);
      }
    </style>
  </head>
  <body>
    <div id="main"></div>
    <script>
      const main = document.getElementById('main')
      let myChart;
    </script>
  </body>
</html>
`;

这里我使用了类型检查,看文件开头,同时也定义了 props ,这样我在外面调用就都有有代码提示了,如果调用的文件也使用 @ts-check 的话,那么传入的 props 如果类型不正确就会报错,我觉得这种方式非常的好,以后我都尽量这样写。

我发现第一次加载的时候不会出现,这个时候我想到等页面加载完成再去设置 echarts ,以后的每次更新就通过 useEffect 就可以了,但是我又发现这样会冲突,我也发现页面加载开始的回调只会在第一次调用,所以我就在这个地方判断一下,这样第一次的时候 useEffect 就不会被调用。

//@ts-check
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {View, ActivityIndicator, StyleSheet} from 'react-native';
import WebView from 'react-native-webview';
import html from './html';

/**
 * 专门负责显示处理 echarts
 * @typedef {{
 *  option: echarts.EChartOption,
 *  width?: number | string,
 *  height?: number | string,
 *  containerStyle?: import('react-native').ViewStyle,
 *  indicatorProps?: import('react-native').ActivityIndicatorProps,
 *  HeaderComponent?: () => React.ReactElement | null
 * }} EChartsProps
 *
 * @param {EChartsProps} props
 */
const ECharts = (props) => {
  const [refreshing, setRefreshing] = useState(true);
  const webviewRef = useRef();
  useEffect(() => {
    // @ts-ignore
    if (!webviewRef.current.firstLoad) {
      return;
    }
    // @ts-ignore
    webviewRef.current?.injectJavaScript(
      `
      myChart = echarts.init(main);
      myChart.setOption(${JSON.stringify(props.option)})`,
    );
  }, [props.option]);
  const onMessage = useCallback(({nativeEvent: {data}}) => {
    console.log(data);
  }, []);
  const onError = useCallback((error) => {
    console.log(error);
  }, []);
  const onLoadStart = useCallback(() => {
    // @ts-ignore
    webviewRef.current.firstLoad = true;
  }, []);
  const onLoadEnd = useCallback(() => {
    // @ts-ignore
    webviewRef.current?.injectJavaScript(
      `
      myChart = echarts.init(main);
      myChart.setOption(${JSON.stringify(props.option)})`,
    );
    setRefreshing(false);
  }, [props.option]);
  return (
    <View
      style={[
        styles.containerStyle,
        {width: props.width, height: props.height},
        props.containerStyle,
      ]}>
      <WebView
        source={{html, baseUrl: ''}}
        useWebKit={true}
        onMessage={onMessage}
        onError={onError}
        ref={webviewRef}
        onLoadStart={onLoadStart}
        onLoadEnd={onLoadEnd}
        originWhitelist={['*']}
        javaScriptEnabled={true}
        allowUniversalAccessFromFileURLs={true}
      />
      {refreshing && (
        <View style={styles.refreshContainer}>
          <ActivityIndicator
            animating={refreshing}
            size={'small'}
            color={'blue'}
            {...props.indicatorProps}
          />
        </View>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  containerStyle: {
    backgroundColor: '#f5f5f5',
  },
  refreshContainer: {
    position: 'absolute',
    width: '100%',
    height: '100%',
    alignItems: 'center',
    justifyContent: 'center',
    zIndex: 9,
  },
});

export default ECharts;

今天的记录就到这里了,明天我会做一个效果,我也不知道那是什么效果,像是悬浮。

我会把工作中封装的都尽量记录下来,因为我发现以前我做过的,由于没有记录导致现在还是得重新写,而且基本上都忘了,还是得去官网看一会儿的资料才能想起来,如果我记录了,我隔一段时间就翻阅一下我记录的,就像我的QQ空间一样,慢慢的回忆。

补充 1

这里补充一下我使用这个所遇到的问题和对应的解决方案。我会把上面的代码也修改,以免看到的人看错了。

我在使用中发现 android 有一个奇怪的现象,第一次能成功渲染,当同一个页面再次进去的时候就会出现白屏,而 ios 并没有这个问题;于是我就从我封装的组件入手。

  1. 首先看当进入的时候有没有重新渲染或者页面退出的时候有没有卸载组件,经过调试发现这些都没什么问题,此时我想其实 ios 的可以的话,至少组件的渲染是没有问题的;
  2. 于是我就开始在网络上搜寻答案,但是找了一圈并没有发现有跟我相同的问题,于是我就从我的使用上下功夫,如果大家都没有遇到过,很大程度是因为我使用的方式不好导致的,但我又想如果是 WebView 我使用不好,应该有警告之类的,但是并没有发现;在这个过程中我用大部分都使用的方式,那就是用远程加载的方式,我试了试发现没有问题了,按道理到这里这个问题就算解决了;
  3. 但是我和后台一致觉得把 html 放到服务器并不好,于是继续找答案,这次我们再次从问题出发:不同页面第一次进去能出现,相同页面进去就没有,于是我们觉得应该是数据没有发生改变导致的,我一直觉得即便如此应该也是渲染上一次的结果,于是我尝试添加随机参数来验证这个结论,结果如我所说,并没有解决这个问题,看来并不是数据的问题。然后我就反复看我的代码,突然想到,也许只是缓存了 html 的那段代码,可是为啥打开不同页面能正常显示呢,因为按道理只要加载过一次,那么以后的任何一次都不会正常渲染。我能想到的就是,echarts 在渲染相同的情况下,就不会重新触发页面的重新渲染。这里还是没完全搞清楚,只不过按照上面的是正常的,以后我会学习 androidWebView 来彻底解决这个疑惑。

补充 2

在实际使用中的又一个发现,我发现我切换数据的时候不会改变,这里的不会改变是如果从有数据的情况跳转到有数据,那么就会报错,但是在手机上看不出报错的原因。这下我就郁闷了,因为从有数据切换到空数据再到有数据,这样是可以的,只是从有数据到有数据才会出现仍然渲染以前的数据,根据上一次的经验,我首先聚焦到 echarts 上,发现我定义好了 echarts 实例对象,如果在切换的过程中页面只是刷新数据而不是刷新整个 html ,那么就会出现重新定义变量的情况,由于已经定义过变量,再次定义肯定会触发错误,于是我改成现在上面的情况。