So strukturieren Sie Ihr Projekt und verwalten statische Ressourcen in React Native

React und React Native sind nur Frameworks und bestimmen nicht, wie wir unsere Projekte strukturieren sollen. Es hängt alles von Ihrem persönlichen Geschmack und dem Projekt ab, an dem Sie arbeiten.

In diesem Beitrag erfahren Sie, wie Sie ein Projekt strukturieren und lokale Assets verwalten. Dies ist natürlich nicht in Stein gemeißelt, und Sie können nur die Teile anwenden, die zu Ihnen passen. Hoffe du lernst etwas.

Für ein Projekt, mit dem gebootet wird react-native init, erhalten wir nur die Grundstruktur.

Es gibt den iosOrdner für Xcode-Projekte, den androidOrdner für Android-Projekte sowie eine index.jsund eine App.jsDatei für den Startpunkt von React Native.

ios/ android/ index.js App.js

Als jemand, der sowohl unter Windows Phone als auch unter iOS und Android mit Native gearbeitet hat, kommt es bei der Strukturierung eines Projekts darauf an, Dateien nach Typ oder Funktion zu trennen

Typ vs Funktion

Nach Typ zu trennen bedeutet, dass wir Dateien nach ihrem Typ organisieren. Wenn es sich um eine Komponente handelt, gibt es Container- und Präsentationsdateien. Wenn es sich um Redux handelt, gibt es Aktions-, Reduzierungs- und Speicherdateien. Wenn es sich um eine Ansicht handelt, gibt es JavaScript-, HTML- und CSS-Dateien.

Nach Typ gruppieren

redux actions store reducers components container presentational view javascript html css

Auf diese Weise können wir den Typ jeder Datei anzeigen und problemlos ein Skript für einen bestimmten Dateityp ausführen. Dies ist allgemein für alle Projekte, beantwortet jedoch nicht die Frage „Worum geht es in diesem Projekt?“. Ist es eine Nachrichtenanwendung? Ist es eine Treue-App? Geht es um Ernährungsverfolgung?

Das Organisieren von Dateien nach Typ gilt für eine Maschine, nicht für einen Menschen. Oft arbeiten wir an einer Funktion, und es ist mühsam, Dateien zu finden, die in mehreren Verzeichnissen repariert werden können. Es ist auch ein Schmerz, wenn wir planen, aus unserem Projekt ein Framework zu machen, da Dateien über viele Orte verteilt sind.

Nach Feature gruppieren

Eine vernünftigere Lösung besteht darin, Dateien nach Funktionen zu organisieren. Dateien, die sich auf ein Feature beziehen, sollten zusammen platziert werden. Und Testdateien sollten in der Nähe der Quelldateien bleiben. In diesem Artikel erfahren Sie mehr.

Eine Funktion kann sich auf die Anmeldung, Anmeldung, Onboarding oder das Profil eines Benutzers beziehen. Ein Feature kann Unter-Features enthalten, solange sie zum selben Flow gehören. Wenn wir das Unterfeature verschieben wollten, wäre dies einfach, da alle zugehörigen Dateien bereits zusammen gruppiert sind.

Meine typische Projektstruktur basierend auf Features sieht folgendermaßen aus:

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 [email protected]x.png [email protected] [email protected] [email protected] scripts images.js clear.js

Neben den traditionellen Dateien App.jsund index.jsund ios1und androidOrdnern, habe ich alle Quelldateien in dem srcOrdner. Im Inneren habe srcich resRessourcen, libraryallgemeine Dateien , die für verschiedene Funktionen verwendet werden, und screenseinen Inhaltsbildschirm.

So wenig Abhängigkeiten wie möglich

Da React Native stark von unzähligen Abhängigkeiten abhängig ist, versuche ich, beim Hinzufügen weiterer Abhängigkeiten ziemlich bewusst zu sein. In meinem Projekt verwende ich nur react-navigationzur Navigation. Und ich bin kein Fan von, reduxda es unnötige Komplexität hinzufügt. Fügen Sie eine Abhängigkeit nur hinzu, wenn Sie sie wirklich benötigen, andernfalls stellen Sie sich nur auf mehr Probleme als Wert ein.

Was ich an React mag, sind die Komponenten. In einer Komponente definieren wir Ansicht, Stil und Verhalten. React hat einen Inline-Stil - es ist wie mit JavaScript, um Skript, HTML und CSS zu definieren. Dies passt zu dem von uns angestrebten Feature-Ansatz. Deshalb verwende ich keine gestylten Komponenten. Da Stile nur JavaScript-Objekte sind, können wir nur Kommentarstile in freigeben library.

src

Ich mag Android sehr, deshalb benenne ich die Ordnerkonventionen srcund respasse sie an.

react-native initrichtet babel für uns ein. Für ein typisches JavaScript-Projekt ist es jedoch gut, Dateien im srcOrdner zu organisieren . In meiner electron.jsAnwendung IconGenerator habe ich die Quelldateien in den srcOrdner gelegt. Dies hilft nicht nur beim Organisieren, sondern auch beim gleichzeitigen Transpilieren des gesamten Ordners. Nur ein Befehl , und ich habe die Dateien in srctranspiled zu distin einem Blinzeln.

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

Bildschirm

Die Reaktion basiert auf Komponenten. Jep. Es gibt Container- und Präsentationskomponenten, aber wir können Komponenten zusammenstellen, um komplexere Komponenten zu erstellen. Sie werden normalerweise im Vollbildmodus angezeigt. Es wird Pagein Windows Phone, ViewControllerin iOS und Activityin Android aufgerufen . Der React Native-Leitfaden erwähnt den Bildschirm sehr oft als etwas, das den gesamten Raum abdeckt:

Mobile Apps bestehen selten aus einem einzigen Bildschirm. Das Verwalten der Darstellung und des Übergangs zwischen mehreren Bildschirmen wird normalerweise von einem sogenannten Navigator durchgeführt.

index.js oder nicht?

Jeder Bildschirm wird als Einstiegspunkt für jede Funktion betrachtet. Sie können das in umbenennen LoginScreen.js, index.jsindem Sie die Funktion des Knotenmoduls nutzen:

Module müssen keine Dateien sein. Wir können auch einen find-meOrdner unter erstellen node_modulesund dort eine index.jsDatei ablegen. In derselben require('find-me')Zeile wird die index.jsDatei dieses Ordners verwendet

Stattdessen import LoginScreen from './screens/LoginScreen'können wir es einfach tun import LoginScreen from './screens'.

Die Verwendung von index.jsErgebnissen bei der Kapselung bietet eine öffentliche Schnittstelle für die Funktion. Das ist alles persönlicher Geschmack. Ich selbst bevorzuge die explizite Benennung einer Datei, daher der Name LoginScreen.js.

Navigator

React-Navigation scheint die beliebteste Wahl für die Navigation in einer React Native-App zu sein. Für eine Funktion wie Onboarding gibt es wahrscheinlich viele Bildschirme, die von einer Stapelnavigation verwaltet werden OnboardingNavigator.

Sie können sich Navigator als etwas vorstellen, das Unterbildschirme oder Funktionen gruppiert. Da wir nach Funktionen gruppieren, ist es sinnvoll, Navigator im Funktionsordner zu platzieren. Es sieht im Grunde so aus:

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)

Bibliothek

Dies ist der umstrittenste Teil der Strukturierung eines Projekts. Wenn Sie nicht wie der Name zu tun library, können Sie es nennen utilities, common, citadel, whatever...

Dies ist nicht für obdachlose Dateien gedacht, aber hier platzieren wir allgemeine Dienstprogramme und Komponenten, die von vielen Funktionen verwendet werden. Dinge wie atomare Komponenten, Wrapper, Schnellkorrekturfunktionen, Netzwerkmaterial und Anmeldeinformationen werden häufig verwendet, und es ist schwierig, sie in einen bestimmten Funktionsordner zu verschieben. Manchmal müssen wir nur praktisch sein und die Arbeit erledigen.

In React Native müssen wir häufig eine Schaltfläche mit einem Bildhintergrund in vielen Bildschirmen implementieren. Hier ist eine einfache, die drinnen bleibt library/components/ImageButton.js. Der componentsOrdner ist für wiederverwendbare Komponenten vorgesehen, die manchmal als atomare Komponenten bezeichnet werden. Gemäß den Namenskonventionen von React sollte der erste Buchstabe in Großbuchstaben geschrieben werden.

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 (   {this.props.title}    ) } } const styles = StyleSheet.create({ view: { position: 'absolute', backgroundColor: 'transparent' }, image: { }, touchable: { alignItems: 'center', justifyContent: 'center' }, text: { color: colors.button, fontSize: 18, textAlign: 'center' } })

Und wenn wir die Schaltfläche unten platzieren möchten, verwenden wir eine Dienstprogrammfunktion, um das Duplizieren von Code zu verhindern. Hier ist library/utils/moveToBottom.js:

import React from 'react' import { View, StyleSheet } from 'react-native' function moveToBottom(component) { return (  {component}  ) } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'flex-end', marginBottom: 36 } }) export default moveToBottom

Verwenden Sie package.json, um einen relativen Pfad zu vermeiden

Then somewhere in the src/screens/onboarding/term/Term.js , we can import by using relative paths:

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

This is a big red flag in my eyes. It’s error prone, as we need to calculate how many .. we need to perform. And if we move feature around, all of the paths need to be recalculated.

Since library is meant to be used many places, it’s good to reference it as an absolute path. In JavaScript there are usually 1000 libraries to a single problem. A quick search on Google reveals tons of libraries to tackle this issue. But we don’t need another dependency as this is extremely easy to fix.

The solution is to turn library into a module so node can find it. Adding package.json to any folder makes it into a Node module . Add package.json inside the library folder with this simple content:

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

Now in Term.js we can easily import things from library because it is now a 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 (  {strings.onboarding.term.heading.toUpperCase()} { moveToBottom(  ) }  ) } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center' }, heading: {...palette.heading, ...{ marginTop: 72 }} })

res

You may wonder what res/colors, res/strings , res/images and res/fonts are in the above examples. Well, for front end projects, we usually have components and style them using fonts, localised strings, colors, images and styles. JavaScript is a very dynamic language, and it’s easy to use stringly types everywhere. We could have a bunch of #00B75Dcolor across many files, or Fira as a fontFamily in many Text components. This is error-prone and hard to refactor.

Let’s encapsulate resource usage inside the res folder with safer objects. They look like the examples below:

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 //github.com/onmyway133', 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/images

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

Like library , res files can be access from anywhere, so let’s make it a module . Add package.json to the res folder:

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

so we can access resource files like normal modules:

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

Group colors, images, fonts with palette

The design of the app should be consistent. Certain elements should have the same look and feel so they don’t confuse the user. For example, the heading Text should use one color, font, and font size. The Image component should use the same placeholder image. In React Native, we already use the name styles with const styles = StyleSheet.create({}) so let’s use the name palette.

Below is my simple palette. It defines common styles for heading and Text:

res/palette

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

And then we can use them in our screen:

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

Here we use the object spread operator to merge palette.heading and our custom style object. This means that we use the styles from palette.heading but also specify more properties.

If we were to reskin the app for multiple brands, we could have multiple palettes. This is a really powerful pattern.

Generate images

You can see that in /src/res/images.js we have properties for each image in the src/res/images folder:

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

This is tedious to do manually, and we have to update if there’s changes in image naming convention. Instead, we can add a script to generate the images.js based on the images we have. Add a file at the root of the project /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()

The cool thing about Node is that we have access to the fs module, which is really good at file processing. Here we simply traverse through images, and update /src/res/images.js accordingly.

Whenever we add or change images, we can run:

node scripts/images.js

And we can also declare the script inside our 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" }

Now we can just run npm run images and we get an up-to-date images.js resource file.

How about custom fonts

React Native has some custom fonts that may be good enough for your projects. You can also use custom fonts.

One thing to note is that Android uses the name of the font file, but iOS uses the full name. You can see the full name in Font Book app, or by inspecting in running app

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

For custom fonts to be registered in iOS, we need to declare UIAppFonts in Info.plist using the file name of the fonts, and for Android, the fonts need to be placed at app/src/main/assets/fonts .

It is good practice to name the font file the same as full name. React Native is said to dynamically load custom fonts, but in case you get “Unrecognized font family”, then simply add those fonts to target within Xcode.

Doing this by hand takes time, luckily we have rnpm that can help. First add all the fonts inside res/fonts folder. Then simply declare rnpm in package.json and run react-native link . This should declare UIAppFonts in iOS and move all the fonts into app/src/main/assets/fonts for Android.

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

Der namentliche Zugriff auf Schriftarten ist fehleranfällig. Wir können ein Skript erstellen, das dem ähnelt, was wir mit Bildern gemacht haben, um einen sichereren Zugang zu erzielen. In fonts.jsunserem scriptsOrdner

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()

Jetzt können Sie benutzerdefinierte Schriftarten über den RNamespace verwenden.

import R from 'res/R' const styles = StyleSheet.create({ text: { fontFamily: R.fonts.FireCodeNormal } })

Der R-Namespace

Dieser Schritt hängt vom persönlichen Geschmack ab, aber ich finde ihn besser organisiert, wenn wir den R-Namespace einführen, genau wie Android es für Assets mit der generierten R-Klasse tut.

Sobald Sie Ihre App-Ressourcen ausgelagert haben, können Sie mithilfe von Ressourcen-IDs, die in der RKlasse Ihres Projekts generiert werden, auf sie zugreifen . In diesem Dokument erfahren Sie, wie Sie Ihre Ressourcen in Ihrem Android-Projekt gruppieren, alternative Ressourcen für bestimmte Gerätekonfigurationen bereitstellen und dann über Ihren App-Code oder andere XML-Dateien darauf zugreifen.

Auf diese Weise wollen wir eine Datei machen genannt R.jsin 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

Und greifen Sie auf dem Bildschirm darauf zu:

import R from 'res/R' render() { return (    {R.strings.onboarding.welcome.title.toUpperCase()} ) }

Ersetzen Sie stringsdurch R.strings, colorsmit R.colorsund imagesmit R.images. Mit der R-Annotation wird deutlich, dass wir über das App-Bundle auf statische Assets zugreifen.

Dies entspricht auch der Airbnb-Konvention für Singleton, da unser R jetzt wie eine globale Konstante ist.

23.8 Verwenden Sie PascalCase, wenn Sie einen Konstruktor / eine Klasse / einen Singleton / eine Funktionsbibliothek / ein nacktes Objekt exportieren.
const AirbnbStyleGuide = { es6: { }, } export default AirbnbStyleGuide

Wohin von hier aus?

In diesem Beitrag habe ich Ihnen gezeigt, wie Sie Ordner und Dateien in einem React Native-Projekt strukturieren sollten. Wir haben auch gelernt, wie Sie Ressourcen verwalten und sicherer darauf zugreifen können. Ich hoffe, Sie fanden es nützlich. Hier sind einige weitere Ressourcen, die Sie weiter erforschen können:

  • Organisation eines React Native-Projekts
  • Strukturieren von Projekten und Benennen von Komponenten in React
  • Verwenden von index.js für unterhaltsame und öffentliche Schnittstellen

Since you are here, you may enjoy my other articles

  • Deploying React Native to Bitrise, Fabric, CircleCI
  • Position element at the bottom of the screen using Flexbox in React Native
  • Setting up ESLint and EditorConfig in React Native projects
  • Firebase SDK with Firestore for React Native apps in 2018

If you like this post, consider visiting my other articles and apps ?