Как структурировать свой проект и управлять статическими ресурсами в React Native

kak strukturirovat svoj proekt i upravlyat staticheskimi resursami v react

от Khoa Pham

bJ4t6m7wdDX72qohmdL43MPFLox3ZH3djClN
Источник: stmed.net

React и React Native – это только фреймворки, и они не диктуют, как нам структурировать наши проекты. Все зависит от вашего личного вкуса и проекта, над которым вы работаете.

В этой публикации мы расскажем, как структурировать проект и управлять местными активами. Это, конечно, не написано на камне, и вы можете использовать только те части, которые вам подходят. Надеюсь, ты чему научишься.

Для проекта, загруженного с react-native init получаем только основную структуру.

Существует ios папка для проектов Xcode, файл android папку для проектов Android и index.js и an App.js файл для исходной точки React Native.

ios/
android/
index.js
App.js

Как человек, работавший с native на Windows Phone, iOS и Android, я считаю, что структурирование проекта сводится к разделению файлов с помощью типа или особенность

тип против функции

Разделение по типу означает, что мы организуем файлы по их типу. Если это компонент, существуют файлы-контейнеры и презентационные файлы. Если это Redux, то есть файлы действия, редуктор и файлы хранения. Если это просмотр, то есть файлы JavaScript, HTML и CSS.

Сгруппировать по типу

redux
  actions
  store
  reducers
components
  container
  presentational
view
  javascript
  html
  css

Таким образом мы можем увидеть тип каждого файла и легко запустить сценарий для определенного типа файла. Это всеобщее для всех проектов, но оно не отвечает на вопрос «о чем этот проект?» Это новостная программа? Это программа лояльности? Речь идет об отслеживании питания?

Организация файлов по типу предназначена для машины, а не для человека. Много раз мы работаем над функцией, и найти файлы, которые нужно исправить, в нескольких каталогах — хлопоты. Это тоже неприятно, если мы планируем создать фреймворк из нашего проекта, поскольку файлы распространены во многих местах.

Группировать по признаку

Более разумным решением является упорядочение файлов по признакам. Файлы, связанные с объектом, должны быть размещены вместе. И тестовые файлы должны оставаться рядом с исходными файлами. Просмотрите эту статью, чтобы узнать больше.

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

Моя типичная структура проекта на основе функций выглядит следующим образом:

index.js
App.js
ios/
android/
src
  screens
    login
      LoginScreen.js
      LoginNavigator.js
    onboarding
      OnboardingNavigator    
      welcome 
        WelcomeScreen.js
      term
        TermScreen.js
      notification
        NotificationScreen.js
    main
      MainNavigator.js
      news
        NewsScreen.js
      profile
        ProfileScreen.js
      search
        SearchScreen.js
  library
    package.json
    components
      ImageButton.js
      RoundImage.js
    utils
      moveToBottom.js
      safeArea.js
    networking
      API.js
      Auth.js
  res
    package.json
    strings.js
    colors.js
    palette.js
    fonts.js
    images.js
    images
      logo@2x.png
      logo@3x.png
      button@2x.png
      button@3x.png
scripts
  images.js
  clear.js

Кроме традиционных файлов App.js и index.js и ios1 и android папки, я поместил все исходные файлы внутрь src папку. Внутри src я имею res за ресурсы, library для распространенных файлов, используемых различными функциями, и screens для экрана содержимого.

Как можно меньше зависимостей

Поскольку React Native сильно зависит от множества зависимостей, я стараюсь быть в курсе, прибавляя больше. В своем проекте я использую just react-navigation для навигации. И я не поклонник redux поскольку это придает ненужную сложность. Добавляйте зависимость только тогда, когда она вам действительно нужна, иначе вы просто создаете для себя больше проблем, чем ценность.

Что мне нравится в React, это компоненты. Компонент – это место, где мы определяем взгляд, стиль и поведение. React имеет встроенный стиль – это как использование JavaScript для определения сценария, HTML и CSS. Это соответствует подходу к функциям, к которому мы стремимся. Вот почему я не использую styled-components. Поскольку стили – это только объекты JavaScript, мы можем просто поделиться стилями комментариев. library .

src

Мне очень нравится Android, поэтому я называю src и res чтобы соответствовать условиям папки.

react-native init настраивает для нас babel. Но для типичного проекта JavaScript хорошо организовать файлы в src папку. В моем electron.js приложение IconGenerator, я поместил исходные файлы внутрь src папку. Это не только помогает с точки зрения организации, но и помогает babel перенести всю папку одновременно. Просто команда, и у меня есть файлы src перенесен в dist мгновенно.

babel ./src --out-dir ./dist --copy-files

Экран

React основан на компонентах. Да. Есть контейнерные и презентационные компоненты, но мы можем составлять компоненты для создания более сложных компонентов. Обычно они отображаются во весь экран. Это называется Page в Windows Phone, ViewController в iOS и Activity в Android. Руководство React Native очень часто вспоминает экран как нечто, охватывающее все пространство:

Мобильные приложения редко состоят из одного экрана. Управление представлением нескольких экранов и переходом между ними обычно производится так называемым навигатором.

index.js или нет?

Каждый экран считается точкой входа для каждой функции. Вы можете переименовать LoginScreen.js к index.js с помощью функции модуля Node:

Модули не должны являться файлами. Мы также можем создать a find-me папка под node_modules и разместить an index.js файл туда. Так же require('find-me') строка будет использовать эту папку index.js файл

Поэтому вместо import LoginScreen from './screens/LoginScreen' мы можем просто сделать import LoginScreen from './screens'.

Использование index.js приводит к инкапсуляции и обеспечивает общедоступный интерфейс функции. Это все личный вкус. Я сам предпочитаю четкое именование файла, отсюда и название LoginScreen.js.

react-navigation представляется самым популярным выбором для обработки навигации в программе React Native. Для такой функции, как onboarding, вероятно, существует много экранов, управляемых стеком навигации, поэтому есть OnboardingNavigator .

Вы можете думать о Navigator как о чем-то, группирующем подэкраны или функции. Поскольку мы группируем по функциям, целесообразно разместить Navigator в папке функций. В основном это выглядит так:

import { createStackNavigator } from 'react-navigation'
import Welcome from './Welcome'
import Term from './Term'

const routeConfig = {
  Welcome: {
    screen: Welcome
  },
  Term: {
    screen: Term
  }
}

const navigatorConfig = {
  navigationOptions: {
    header: null
  }
}

export default OnboardingNavigator = createStackNavigator(routeConfig, navigatorConfig)

библиотека

Это более противоречивая часть структуризации проекта. Если вам не нравится название libraryвы можете назвать это utilities, common, citadel , whatever

Это не предназначено для бездомных файлов, но именно там мы размещаем обычные утилиты и компоненты, используемые многими функциями. Такие вещи, как атомарные компоненты, обертки, функция быстрого исправления, сетевые вещи и информация для входа, часто используются, и их трудно переместить в определенную папку функций. Иногда нам просто нужно быть практичными и выполнять работу.

В React Native нам часто нужно реализовать кнопку с фоном изображения на многих экранах. Вот простой, который остается внутри library/components/ImageButton.js . The components папка для многократных компонентов, иногда называемых атомарными компонентами. Согласно условиям React имен, первая буква должна быть прописной.

import React from 'react'
import { TouchableOpacity, View, Image, Text, StyleSheet } from 'react-native'
import images from 'res/images'
import colors from 'res/colors'

export default class ImageButton extends React.Component {
  render() {
    return (
      <TouchableOpacity style={styles.touchable} onPress={this.props.onPress}>
        <View style={styles.view}>
          <Text style={styles.text}>{this.props.title}</Text>
        </View>
        <Image
          source={images.button}
          style={styles.image} />
      </TouchableOpacity>
    )
  }
}

const styles = StyleSheet.create({
  view: {
    position: 'absolute',
    backgroundColor: 'transparent'
  },
  image: {
  
},
  touchable: {
    alignItems: 'center',
    justifyContent: 'center'
  },
  text: {
    color: colors.button,
    fontSize: 18,
    textAlign: 'center'
  }
})

И если мы хотим разместить кнопку внизу, мы используем функцию утилиты, чтобы предотвратить дублирование кода. Вот library/utils/moveToBottom.js:

import React from 'react'
import { View, StyleSheet } from 'react-native'

function moveToBottom(component) {
  return (
    <View style={styles.container}>
      {component}
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'flex-end',
    marginBottom: 36
  }
})

export default moveToBottom

Используйте package.json, чтобы избежать относительного пути

Потом где-то в src/screens/onboarding/term/Term.js мы можем импортировать, используя относительные пути:

import moveToBottom from '../../../../library/utils/move'
import ImageButton from '../../../../library/components/ImageButton'

Это большое красное знамя в моих глазах. Он подвержен ошибкам, поскольку нам нужно рассчитать, сколько .. нам нужно выполнить. И если мы перемещаем объект, то все пути нужно перечислить.

Так как library предназначен для использования во многих местах, хорошо ссылаться на него как на абсолютный путь. В JavaScript обычно существует 1000 библиотек для одной проблемы. Быстрый поиск в Google открывает множество библиотек для решения этой проблемы. Но нам не нужна другая зависимость, поскольку это очень легко исправить.

Решение – вернуть library в а module поэтому node можно найти. Добавление package.json в любую папку превращает ее в Node module . Добавить package.json внутри library папка с таким простым содержимым:

{
  "name": "library",
  "version": "0.0.1"
}

Теперь в Term.js мы можем легко импортировать вещи из library потому что сейчас это а module:

import React from 'react'
import { View, StyleSheet, Image, Text, Button } from 'react-native'
import strings from 'res/strings'
import palette from 'res/palette'
import images from 'res/images'
import ImageButton from 'library/components/ImageButton'
import moveToBottom from 'library/utils/moveToBottom'

export default class Term extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.heading}>{strings.onboarding.term.heading.toUpperCase()}</Text>
        {
          moveToBottom(
            <ImageButton style={styles.button} title={strings.onboarding.term.button.toUpperCase()} />
          )
        }
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center'
  },
  heading: {...palette.heading, ...{
    marginTop: 72
  }}
})

рез

Вы можете задаться вопросом, что res/colors, res/strings , res/images и res/fonts есть в приведенных выше примерах. Что ж, для проектов интерфейса мы обычно располагаем компонентами и стилируем их с помощью шрифтов, локализованных строк, цветов, изображений и стилей. JavaScript – это очень динамичный язык, и его легко использовать повсеместно. Мы могли бы иметь кучу #00B75D color во многих файлах, или Fira как fontFamily у многих Text компонентов. Это подвержено ошибкам и трудно поддается реорганизации.

Давайте инкапсулируем использование ресурсов внутри res папка с безопасными объектами. Они выглядят как следующие примеры:

res/colors

const colors = {
  title: '#00B75D',
  text: '#0C222B',
  button: '#036675'
}

export default colors

res/strings

const strings = {
  onboarding: {
    welcome: {
      heading: 'Welcome',
      text1: "What you don't know is what you haven't learn",
      text2: 'Visit my GitHub at 
      button: 'Log in'
    },
    term: {
      heading: 'Terms and conditions',
      button: 'Read'
    }
  }
}

export default strings

res/fonts

const fonts = {
  title: 'Arial',
  text: 'SanFrancisco',
  code: 'Fira'
}

export default fonts

res/изображение

const images = {
  button: require('./images/button.png'),
  logo: require('./images/logo.png'),
  placeholder: require('./images/placeholder.png')
}

export default images

Люблю library , res файлы могут быть доступны отовсюду, поэтому давайте сделаем это a module . Добавить package.json к res папка:

{
  "name": "res",
  "version": "0.0.1"
}

поэтому мы можем получить доступ к файлам ресурсов, как обычные модули:

import strings from 'res/strings'
import palette from 'res/palette'
import images from 'res/images'

Сгруппируйте цвета, изображения, шрифты с палитрой

Дизайн программы должен быть последовательным. Некоторые элементы должны иметь одинаковый вид и чувство, чтобы они не запутывали пользователя. Например, заголовок Text следует использовать один цвет, шрифт и размер шрифта. The Image компонент должен использовать то же изображение-заполнитель. В React Native мы уже используем это название styles с const styles = StyleSheet.create({}) поэтому давайте используем название palette.

Ниже приведена моя простая палитра. Он определяет общие стили для заголовков и Text:

res/палитра

import colors from './colors'

const palette = {
  heading: {
    color: colors.title,
    fontSize: 20,
    textAlign: 'center'
  },
  text: {
    color: colors.text,
    fontSize: 17,
    textAlign: 'center'
  }
}

export default palette

И тогда мы можем использовать их на нашем экране:

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center'
  },
  heading: {...palette.heading, ...{
    marginTop: 72
  }}
})

Здесь мы используем оператор распространения объекта для слияния palette.heading и наш объект специального стиля. Это означает, что мы используем стили из palette.heading но также укажите больше свойств.

Если бы мы переработали приложение для нескольких брендов, мы могли бы иметь несколько палитр. Это действительно мощный шаблон.

Создание изображений

Вы можете увидеть это в /src/res/images.js у нас есть свойства для каждого изображения в src/res/images папка:

const images = {
  button: require('./images/button.png'),
  logo: require('./images/logo.png'),
  placeholder: require('./images/placeholder.png')
}

export default images

Это скучно делать вручную, и мы должны обновить, если есть изменения в условии наименования изображений. Вместо этого мы можем добавить скрипт для создания images.js на основе изображений, которые у нас есть. Добавьте файл в корень проекта /scripts/images.js:

const fs = require('fs')

const imageFileNames = () => {
  const array = fs
    .readdirSync('src/res/images')
    .filter((file) => {
      return file.endsWith('.png')
    })
    .map((file) => {
      return file.replace('@2x.png', '').replace('@3x.png', '')
    })
    
return Array.from(new Set(array))
}

const generate = () => {
  let properties = imageFileNames()
    .map((name) => {
      return `${name}: require('./images/${name}.png')`
    })
    .join(',\n  ')
    
const string = `const images = {
  ${properties}
}

export default images
`

fs.writeFileSync('src/res/images.js', string, 'utf8')
}

generate()

Прекрасная вещь в Node заключается в том, что мы имеем доступ к fs модуль действительно хорошо обрабатывает файлы. Здесь мы просто проходим через изображение и обновляем /src/res/images.js соответственно.

Каждый раз, когда мы добавляем или изменяем изображение, мы можем запускать:

node scripts/images.js

И мы также можем объявить скрипт внутри нашего main package.json :

"scripts": {
  "start": "node node_modules/react-native/local-cli/cli.js start",
  "test": "jest",
  "lint": "eslint *.js **/*.js",
  "images": "node scripts/images.js"
}

Теперь мы можем просто бежать npm run images и мы получаем актуальную информацию images.js файл ресурса

Как насчет пользовательских шрифтов

React Native имеет некоторые специальные шрифты, которые могут быть достаточно хороши для ваших проектов. Также можно использовать собственные шрифты.

Следует отметить, что Android использует имя шрифта файла, но iOS использует полное имя. Вы можете увидеть полное название в программе Font Book или проверив его в запущенном приложении

for (NSString* family in [UIFont familyNames]) {
  NSLog(@"%@", family);
  
for (NSString* name in [UIFont fontNamesForFamilyName: family]) {
    NSLog(@"Family name:  %@", name);
  }
}

Чтобы специальные шрифты были зарегистрированы в iOS, нам нужно объявить UIAppFonts в Info.plist используя название файла шрифтов, а для Android шрифты нужно разместить по адресу app/src/main/assets/fonts .

Рекомендуется называть файл шрифта точно так же, как и полное имя. Говорят, что React Native динамически загружает специальные шрифты, но если вы получите «Семейство нераспознанных шрифтов», просто добавьте эти шрифты для таргетинга в Xcode.

Чтобы сделать это вручную, нужно время, к счастью, у нас есть rnpm, которое может помочь. Сначала добавьте все шрифты внутри res/fonts папку. Тогда просто заявите rnpm в package.json и бежать react-native link . Это должно заявить UIAppFonts в iOS и переместите все шрифты app/src/main/assets/fonts для Android.

"rnpm": {
  "assets": [
    "./src/res/fonts/"
  ]
}

Доступ к шрифтам по имени подвержен ошибкам, мы можем создать сценарий, подобный тому, что мы сделали с изображениями, чтобы создать более безопасный доступ. Добавить fonts.js к нашим scripts папку

const fs = require('fs')

const fontFileNames = () => {
  const array = fs
    .readdirSync('src/res/fonts')
    .map((file) => {
      return file.replace('.ttf', '')
    })
    
return Array.from(new Set(array))
}

const generate = () => {
  const properties = fontFileNames()
    .map((name) => {
      const key = name.replace(/\s/g, '')
      return `${key}: '${name}'`
    })
    .join(',\n  ')
    
const string = `const fonts = {
  ${properties}
}

export default fonts
`

fs.writeFileSync('src/res/fonts.js', string, 'utf8')
}

generate()

Теперь вы можете использовать собственный шрифт через R пространство имен.

import R from 'res/R'

const styles = StyleSheet.create({
  text: {
    fontFamily: R.fonts.FireCodeNormal
  }
})

Пространство имен R

Этот шаг зависит от личного вкуса, но я считаю его более организованным, если мы введем пространство имен R, равно как Android делает для активов со сгенерированным классом R.

После того, как вы передадите ресурсы программы, вы сможете получить к ним доступ с помощью идентификаторов ресурсов, созданных в вашем проекте Rкласс. В этом документе показано, как сгруппировать ресурсы в проекте Android и предоставить альтернативные ресурсы для некоторых конфигураций устройства, а затем получить к ним доступ из кода приложения или других файлов XML.

Таким образом, давайте создадим файл под названием R.js в src/res:

import strings from './strings'
import images from './images'
import colors from './colors'
import palette from './palette'

const R = {
  strings,
  images,
  colors,
  palette
}

export default R

И перейдите к нему на экране:

import R from 'res/R'

render() {
  return (
    <SafeAreaView style={styles.container}>
      <Image
        style={styles.logo}
        source={R.images.logo} />
      <Image
        style={styles.image}
        source={R.images.placeholder} />
      <Text style={styles.title}>{R.strings.onboarding.welcome.title.toUpperCase()}</Text>
  )
}

Заменить strings с R.strings, colors с R.colorsи images с R.images. С аннотацией R понятно, что мы получаем доступ к статическим ресурсам из пакета приложений.

Это также отвечает условиям Airbnb для singleton, поскольку наш R теперь похож на глобальную константу.

23.8 Используйте PascalCase, когда экспортируете конструктор/класс/синглтон/библиотеку функций/обнаженный объект.

const AirbnbStyleGuide = {
  es6: {
  },
}

export default AirbnbStyleGuide

Куда уходить отсюда

В этой публикации я показал вам, как, по-моему, вы должны структурировать папки и файлы в проекте React Native. Мы также научились управлять ресурсами и безопаснее получать к ним доступ. Надеюсь, вы нашли это полезно. Вот еще несколько ресурсов для дальнейшего изучения:

Поскольку вы здесь, вам могут понравиться другие мои статьи

Если вам нравится эта публикация, попробуйте посетить другие мои статьи и приложения?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *