web-dev-qa-db-ja.com

ネイティブ(エキスポ)ロードマークダウンファイルに反応する

マークダウンファイル(.md)をreactネイティブ(非分離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ファイルしか受け入れないということです

11
Norfeldt

@Filipeの応答のおかげで、私はいくつかのガイダンスを受け取り、あなたのニーズに合う実用的な例を得ました。

私の場合、_.md_フォルダーに_assets/markdown/_ファイルがあり、このファイルは_test-1.md_と呼ばれています

コツは、ファイルのローカルurlを取得し、fetch AP​​Iを使用してそのコンテンツを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()を少なくとも1回呼び出します開くたびに。したがって、更新されたファイルの表示に問題はありません。

また、fetchを使用した場合とExpo.FileSystem.readAsStringAsync()を使用した場合の速度をテストするのにも少し時間がかかりましたが、平均は同じでした。多くの場合、_Expo.FileSystem.readAsStringAsync_は200ミリ秒高速でしたが、私の意見では、それは取引ブレーカーではありません。

同じファイルを取得するための3つの異なる方法を作成しました。

_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>
    );
  }
}
_

ログの違いを確認するには、3つを交互に切り替えます。

9
Luis Rizo

私が知っていることから、これは博覧会の中で行うことはできません。私はreact-nativeを使用し、開発用にモバイルで実行します。

_react-native_デフォルトのバンドラーとしてMetroを使用しますが、これも同様の問題を抱えています。代わりに haul bundlerを使用する必要があります。

_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構成をサポートしているため、必要に応じてカスタムバベル変換を追加できます。

2

問題がどこにあるのか正確にはわかりませんが、htmlファイルをプロジェクトに追加しましたが、非常に似ていると思います。

App.json内で、次のフィールドを追加してみてください。

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

packagerOptsを使用すると、スタンドアロンで.mdファイルがバンドルされます。すでにアセットフォルダがあると思いますが、持っていない場合に備えて、必要になります。

次に、AppLoadingで_Asset.loadAsync_を使用してアセットをロードする必要はないかもしれませんが、除外することをお勧めします。使い方は documentation をご覧ください。

ファイルをインポートする場合、3つの方法があり、環境によって異なります。この抜粋を私の 中程度の記事 からコピーします:

シミュレータでは、プロジェクト内の任意のファイルにアクセスできます。したがって、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です。そのため、それがその一部であるかどうかを確認することにしました)

それはおそらくあなたの問題を解決するはずです。表示されない場合は、問題点をコメントしてください。さらにサポートさせていただきます。乾杯!

2
Filipe