react native (expo) загрузка файлов уценки

у меня возникли проблемы с загрузкой файлов markdown (.md) в my 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

я попробовал предложенные ответы для reactjs на SO, но проблема, похоже, в том, что он принимает только .js и .json файлы

3 ответов


благодаря ответу @Filipe я получил некоторые рекомендации и получил рабочий пример, который будет соответствовать вашим потребностям.

в моем случае, у меня было на 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>
    );
  }
}

EDIT: чтобы избавиться от ошибки

невозможно разрешить ". /активы / уценка / тест-1.md "from" App.js"

вам нужно будет добавить packagerOpts часть фрагмента @Filipe в ваш .

app.в JSON

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

редактирование 2: Ответ на комментарий @Norfeldt: Хотя я использую react-native init при работе над собственными проектами, и поэтому я не очень хорошо знаком с Expo, я получил эту закуску Expo, которая может иметь некоторые ответы для вас:https://snack.expo.io/Hk8Ghxoqm.

это не будет работать на EXPO snack из-за проблем с чтением файлов, отличных от 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>
    );
  }
}

просто чередуйте между тремя, чтобы увидеть разницу в журналах.


из того, что я знаю, это не может быть сделано в рамках expo. Я использую react-native и запускаю его на своем мобильном телефоне для разработки.

react-native используйте Metro в качестве пакета по умолчанию, который также страдает от подобных проблем. Вы должны использовать перегона bundler вместо этого.

npm install --save-dev haul

npx haul init

npx haul start --platform android

в отдельном терминале react-native run-android. Это будет использовать haul вместо metro объединить файлы.

чтобы добавить файл markdown, установите raw-loader редактировать . 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;
 }
};

теперь вы можете импортировать файл Markdown с помощью const example = require('./example.md')

Haul поддерживает конфигурацию webpack, поэтому вы можете добавить любое пользовательское преобразование babel.


я точно не знаю, в чем проблема, но я добавил html-файлы в проект, и я бы предположил, что это будет очень похоже.

внутри вашего приложения.json, попробуйте добавить эти поля:

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

на packagerOpts делает его таким образом, автономный будет связывать .файлы md. Я бы предположил, что у вас уже есть папка assets, но на всякий случай вам она понадобится.

затем на AppLoading загрузка активов с Asset.loadAsync возможно, не понадобится, но это хорошая идея, чтобы исключить. Проверьте документация о том, как использовать его.

при импорте файла есть три способа, которые вы можете захотеть сделать, это изменение в зависимости от среды. Я скопирую этот отрывок из моего средне-статьи:

в симуляторе, вы можете получить доступ к файлам в проекте. Таким образом, 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, вот почему я решил проверить, было ли это частью этого)

это, вероятно, должно решить вашу проблему. Если это не так, прокомментируйте, что происходит не так, и я попытаюсь помочь вам дальше. Ура!