【问题标题】:react native (expo) load markdown files反应原生(expo)加载降价文件
【发布时间】:2018-09-29 18:44:39
【问题描述】:

我在将 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>
    );
  }
}

顺便说一句:我尝试使用谷歌搜索,但无法获得建议

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

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

【问题讨论】:

标签: reactjs react-native expo


【解决方案1】:

感谢@Filipe 的回复,我得到了一些指导,并得到了一个可以满足您需求的工作示例。

就我而言,我在assets/markdown/ 文件夹中有一个.md 文件,该文件名为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 的 sn-p 的 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>
    );
  }
}

只需在三者之间交替以查看日志中的差异。

【讨论】:

  • 非常感谢!我显然不具备弄清楚如何从中获取和提取文本的技能。 file.downloadAsync() 是否意味着每次使用该应用程序时它都不会下载文件?如果我在部署后更改 markdown 的内容并进行 OTA - 使用缓存文件或检测到它已更改并缓存新文件,该怎么办?
  • 尽我所能在我更新的答案中回答你的问题
  • 为了让它工作,你还需要修改你的 metro.config.js 文件:const defaultAssetExts = require("metro-config/src/defaults/defaults").assetExts; module.exports = { resolver: { //... assetExts: [ ...defaultAssetExts, "md" ], //... };
  • 如果有人在 expo 40 上看到了这个答案,那它是行不通的 - 在下面查看我的答案。
【解决方案2】:

据我所知,这不能在世博会中完成。我使用 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 来捆绑文件。

要添加 markdown 文件,请安装 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')来导入markdown文件了

Haul 支持 webpack 配置,因此您可以添加任何您想要的自定义 babel 转换。

【讨论】:

  • 非常感谢您抽出宝贵时间尝试回答我的问题。我从来不知道haul - 看起来很有趣。我想避免退出世博会(我喜欢 OTA 功能),所以我会将问题保持更长时间,如果没有更好的答案出现 - 然后我会将您的答案标记为答案。
  • 感谢您,我从来不用编辑 haul.config.js 文件,现在我知道如何了 :)
  • 我很高兴你也从中有所收获。再次感谢您的回答。
【解决方案3】:

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

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

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

packagerOpts 使得独立版将捆绑 .md 文件。我想你已经有一个资产文件夹,但万一你没有,你需要一个。

然后,在AppLoading 上,可能不需要使用Asset.loadAsync 加载资产,但最好排除。查看documentation 了解如何使用它。

导入文件时,您可能希望通过三种方式进行导入,具体取决于环境。我将从我的Medium article 中复制这段摘录:

在模拟器中,您可以访问项目中的任何文件。因此,source={require(./pathToFile.html)} 有效。然而,当你构建一个独立的,它不会以同样的方式工作。我的意思是,至少对于 android 它没有。由于某种原因,android webView 无法识别 asset:/// uris。你必须得到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,这就是为什么我选择检查它是否是它的一部分)

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

【讨论】:

  • 我可能很笨,但我不知道如何读取文件内容。我尝试使用的包没有源道具,但希望我将其作为字符串删除。执行console.log(Expo.Asset.fromModule(require('./pathToFile.html'))) 不会显示任何原始内容..
  • 提取uri 允许我通过Linking.openURL(uri) 在浏览器中“打开”文件
  • downloadCallbacks: [] downloaded: false downloading: false hash: "43d299a4986521da6b28a00eb764b2a7" localUri: null name: "C05" type: "md" uri: "http://localhost:19001/assets/assets/markdown/C05.md?platform=ios&amp;hash=43d299a4986521da6b28a00eb764b2a7"
  • 检查我的答案,它应该回答你的问题。
  • 是的,很抱歉,我没有查看您使用的库。 @Luiz Rizo 的回答应该可以。
猜你喜欢
  • 2017-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-02
相关资源
最近更新 更多