反应本地(expo)加载降价文件

时间:2018-09-29 18:44:39

标签: reactjs react-native expo

在将markdown文件(.md)加载到我的react native(非独立的expo项目)中时遇到一些麻烦。

找到了这个很棒的程序包,可以让我渲染它。但是无法弄清楚如何将本地.md文件作为字符串加载。

import react from 'react';
import {PureComponent} from 'react-native';
import Markdown from 'react-native-markdown-renderer';

const copy = `# h1 Heading 8-)

| Option | Description |
| ------ | ----------- |
| data   | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext    | extension to be used for dest files. |
`;

export default class Page extends PureComponent {

  static propTypes = {};
  static defaultProps = {};

  render() {
    return (
        <Markdown>{copy}</Markdown>
    );
  }
}

顺便说一句:我尝试使用Google进行搜索,但无法获得建议的帮助

https://forums.expo.io/t/loading-non-media-assets-markdown/522/2?u=norfeldtconsulting

我在SO上尝试了对reactjs的建议答案,但问题似乎在于它仅接受.js.json文件

3 个答案:

答案 0 :(得分:6)

由于@Filipe的回复,我得到了一些指导,并得到了一个适合您需求的可行示例。

在我的情况下,我在.md文件夹中有一个assets/markdown/文件,该文件称为test-1.md

诀窍是获取文件的本地url,然后使用fetch API将其内容作为string

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Markdown from 'react-native-markdown-renderer';
const copy = `# h1 Heading 8-)

| Option | Description |
| ------ | ----------- |
| data   | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext    | extension to be used for dest files. |
`;

export default class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      copy: copy
    }
  }

  componentDidMount() {
    this.fetchLocalFile();
  }

  fetchLocalFile = async () => {
    let file = Expo.Asset.fromModule(require("./assets/markdown/test-1.md"))
    await file.downloadAsync() // Optional, saves file into cache
    file = await fetch(file.uri)
    file = await file.text()

    this.setState({copy: file});
  }


  render() {
    return (
        <Markdown>{this.state.copy}</Markdown>
    );
  }
}

编辑:为了摆脱错误

  

无法从“ App.js”解析“ ./assets/markdown/test-1.md”

您需要将@Filipe代码段的packagerOpts部分添加到您的app.json文件中。

app.json

{
  "expo": {
    ...
    "assetBundlePatterns": [
      "**/*"
    ],
    "packagerOpts": {
      "assetExts": ["md"]
    },
    ...
  }
}

编辑2: 回答@Norfeldt的评论: 尽管我在自己的项目上使用react-native init,因此对Expo不太熟悉,但我得到了这个Expo Snack,它可能为您提供一些答案:https://snack.expo.io/Hk8Ghxoqm

由于无法读取非JSON文件的问题,它在博览会小吃上不起作用,但是您可以根据需要在本地进行测试。

使用file.downloadAsync()将阻止应用程序对该应用程序会话中托管文件的服务器进行XHR调用(只要用户没有关闭并重新打开该应用程序即可)。

如果您更改文件或修改文件(通过调用Expo.FileSystem.writeAsStringAsync()模拟),只要您的组件重新提供并重新下载文件,它就应该显示更新的文件。

每次关闭并重新打开应用程序时都会发生这种情况,因为就我而言,file.localUri不会在每个会话中持续存在,因此您的应用程序至少会始终调用file.downloadAsync()每次打开一次。因此,显示更新文件应该没有问题。

我还花了一些时间来测试使用fetch与使用Expo.FileSystem.readAsStringAsync()的速度,它们的平均速度是相同的。通常Expo.FileSystem.readAsStringAsync的速度要快200毫秒左右,但在我看来这并不是一个大问题。

我创建了三种不同的方法来提取同一文件。

export default class MarkdownRenderer extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      copy: ""
    }
  }

  componentDidMount() {
    this.fetch()
  }

  fetch = () => {
    if (this.state.copy) {
      // Clear current state, then refetch data
      this.setState({copy: ""}, this.fetch)
      return;
    }
    let asset = Expo.Asset.fromModule(md)
    const id = Math.floor(Math.random()  * 100) % 40;
    console.log(`[${id}] Started fetching data`, asset.localUri)
    let start = new Date(), end;

    const save = (res) => {
      this.setState({copy: res})
      let end = new Date();
      console.info(`[${id}] Completed fetching data in ${(end - start) / 1000} seconds`)
    }

    // Using Expo.FileSystem.readAsStringAsync.
    // Makes it a single asynchronous call, but must always use localUri
    // Therefore, downloadAsync is required
    let method1 = () => {
      if (!asset.localUri) {
        asset.downloadAsync().then(()=>{
          Expo.FileSystem.readAsStringAsync(asset.localUri).then(save)
        })
      } else {
        Expo.FileSystem.readAsStringAsync(asset.localUri).then(save)
      }
    }

    // Use fetch ensuring the usage of a localUri
    let method2 = () => {
      if (!asset.localUri) {
        asset.downloadAsync().then(()=>{
          fetch(asset.localUri).then(res => res.text()).then(save)
        })
      } else {
        fetch(asset.localUri).then(res => res.text()).then(save)
      }
    }

    // Use fetch but using `asset.uri` (not the local file)
    let method3 = () => {
      fetch(asset.uri).then(res => res.text()).then(save)
    }

    // method1()
    // method2()
    method3()
  }

  changeText = () => {
    let asset = Expo.Asset.fromModule(md)
    Expo.FileSystem.writeAsStringAsync(asset.localUri, "Hello World");
  }

  render() {
    return (
        <ScrollView style={{maxHeight: "90%"}}>
          <Button onPress={this.fetch} title="Refetch"/>
          <Button onPress={this.changeText} title="Change Text"/>
            <Markdown>{this.state.copy}</Markdown>
        </ScrollView>
    );
  }
}

只需在这三个之间交替即可查看日志中的差异。

答案 1 :(得分:2)

据我所知,这无法在Expo内完成。我使用react-native并在我的手机上运行以进行开发。

react-native使用Metro作为默认捆绑器,它也存在类似的问题。您必须改用haul捆绑器。

npm install --save-dev haul

npx haul init

npx haul start --platform android

在单独的终端中运行react-native run-android。这将使用haul而不是metro来捆绑文件。

要添加降价文件,请安装raw-loader并编辑haul.config.js文件。 raw-loader将任何文件导入为字符串。

自定义您的haul.config.js,使其看起来像这样:

import { createWebpackConfig } from "haul";
export default {
 webpack: env => {
  const config = createWebpackConfig({
    entry: './index.js',
  })(env);
  config.module.rules.push({
      test: /\.md$/,
      use: 'raw-loader'
   })
  return config;
 }
};

现在您可以使用const example = require('./example.md')

导入降价文件

Haul支持Webpack配置,因此您可以添加任何所需的自定义babel转换。

答案 2 :(得分:2)

我不确切地知道问题出在哪里,但是我向项目中添加了html文件,并且我想它会非常相似。

在您的app.json中,尝试添加以下字段:

"assetBundlePatterns": [
  "assets/**",
],
"packagerOpts": {
  "assetExts": ["md"]
},

packagerOpts可以使独立文件捆绑.md文件。我以为您已经有一个资产文件夹,但是如果您没有,则需要一个。

然后,可能不需要在AppLoading上用Asset.loadAsync加载资产,但是排除掉它是一个好主意。查看documentation的使用方法。

导入文件时,可能需要三种方式,具体取决于环境。我将从我的Medium article中复制此摘录:

  

在模拟器中,您可以访问项目中的任何文件。因此,source={require(./pathToFile.html)}起作用。但是,在构建独立版本时,它的工作方式可能不同。我的意思是,至少对于android而言不是。 android webView由于某种原因无法识别asset:/// uri。您必须获得file:///路径。幸运的是,这很容易。资产捆绑在file:///android_asset内(小心,不要写资产),并且Expo.Asset.fromModule(require(‘./pathToFile.html')).localUri返回asset:///nameOfFile.html。但这还不是全部。对于前几次,此uri将是正确的。但是,过了一会儿,它变成了另一个文件方案,因此无法以相同的方式进行访问。相反,您必须直接访问localUri。因此,完整的解决方案是:

/* Outside of return */
const { localUri } = Expo.Asset.fromModule(require('./pathToFile.html'));
/* On the webView */
source={
  Platform.OS === ‘android’
  ? {
    uri: localUri.includes('ExponentAsset')
      ? localUri
      : ‘file:///android_asset/’ + localUri.substr(9),
  }
  : require(‘./pathToFile.html’)
}
  

(uri的恒定部分是ExponentAsset,这就是为什么我选择检查它是否是它的一部分)

那应该可以解决您的问题。如果没有,请评论出了什么问题,我会尽力帮助您。干杯!