Hogyan strukturálhatja a projektet és hogyan kezelheti a statikus erőforrásokat a React Native alkalmazásban

A React és a React Native csak keretek, és nem diktálják, hogyan kell strukturálnunk a projektjeinket. Minden a személyes ízlésen és a projekten dolgozik.

Ebben a bejegyzésben áttekintjük a projekt struktúráját és a helyi eszközök kezelését. Ez természetesen nem kőbe van írva, és szabadon alkalmazhatja csak a neked megfelelő darabokat. Remélem, megtanul valamit.

A rendszerrel indított projekt esetében react-native initcsak az alapstruktúrát kapjuk meg.

Van egy iosmappa az Xcode projektekhez, a androidmappa az Android projektekhez, valamint egy index.jsés egy App.jsfájl a React Native kiindulópontjához.

ios/ android/ index.js App.js

Mint aki mind a Windows Phone-on, mind az iOS-en és az Android-on együtt dolgozott natív nyelven, azt tapasztalom, hogy a projekt strukturálása a fájlok típusok vagy funkciók szerinti szétválasztására vezethető vissza.

type vs feature

A típus szerinti elválasztás azt jelenti, hogy a fájlokat típusuk szerint rendezzük. Ha ez egy összetevő, akkor vannak tároló és prezentációs fájlok. Ha ez Redux, akkor vannak műveletek, kicsinyítők és tároló fájlok. Ha nézet, akkor vannak JavaScript, HTML és CSS fájlok.

Csoportosítás típus szerint

redux actions store reducers components container presentational view javascript html css

Így láthatjuk az egyes fájlok típusát, és egyszerűen futtathatunk egy szkriptet egy bizonyos fájltípus felé. Ez minden projekt esetében általános, de nem válaszol a „miről szól ez a projekt” kérdésre. Híralkalmazás? Ez hűséges alkalmazás? A táplálkozás követéséről van szó?

A fájlok típus szerinti rendezése gépre vonatkozik, nem emberre. Sokszor dolgozunk egy funkción, és több könyvtárban javításra kerülő fájlokat találni gond. Az is fáj, ha tervezünk keretet készíteni a projektünkből, mivel a fájlok sok helyen el vannak terítve.

Csoportosítás jellemzők szerint

Ésszerűbb megoldás a fájlok jellemzők szerinti rendezése. A funkcióhoz kapcsolódó fájlokat együtt kell elhelyezni. A tesztfájloknak pedig a forrásfájlok közelében kell maradniuk. További információért olvassa el ezt a cikket.

A funkció kapcsolódhat a bejelentkezéshez, a regisztrációhoz, az onboardinghoz vagy a felhasználó profiljához. Egy szolgáltatás tartalmazhat részjellemzőket, amennyiben ugyanahhoz a folyamathoz tartoznak. Ha az alfunkciót át akarjuk mozgatni, akkor az egyszerű lenne, mivel az összes kapcsolódó fájl már össze van csoportosítva.

A jellemzők alapján a tipikus projektszerkezetem így néz ki:

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

A hagyományos fájlok App.jsés index.jsa ios1és androidmappák mellett az összes forrásfájlt a srcmappába tettem . Bent srcvan reserőforrásaim, librarya funkciók között használt közös fájlok és screensa tartalom képernyője.

A lehető legkevesebb függőség

Mivel a React Native nagymértékben függ a sokféle függéstől, igyekszem eléggé tudatos lenni, amikor további adom. Projektemben csak react-navigationa navigációt használom . És nem vagyok rajongója, reduxmivel ez szükségtelen bonyolultságot ad hozzá. Csak akkor adjon hozzá függőséget, amikor valóban szüksége van rá, különben csak több bajért, mint értékért állítja be magát.

Ami tetszik a React-ben, azok az alkatrészek. Komponens az, ahol meghatározzuk a nézetet, a stílust és a viselkedést. A React inline stílusú - ez olyan, mintha JavaScriptet használna a szkript, a HTML és a CSS meghatározásához. Ez megfelel az általunk megcélzott funkció megközelítésnek. Ezért nem használok stílusos alkatrészeket. Mivel a stílusok csak JavaScript objektumok, egyszerűen megoszthatjuk a megjegyzés stílusokat library.

src

Nagyon kedvelem az Androidot, ezért megnevezem srcés resmegfelelek annak mappakonvencióinak.

react-native initbabellát állít nekünk. De egy tipikus JavaScript projekt esetében jó, ha fájlokat rendez a srcmappába. Az electron.jsIconGenerator alkalmazásomban a forrásfájlokat a srcmappába tettem . Ez nem csak a szervezés szempontjából segít, hanem a babelnek a teljes mappa egyszerre történő áttelepítésében is. Csak egy parancs, és a fájlokat egy srcpillanat alatt áthelyezem dist.

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

Képernyő

A React az összetevők köré épül. Aha. Vannak tároló és prezentációs komponensek, de összetettebb komponensek felépítéséhez komponenseket is összeállíthatunk. Általában a teljes képernyőn történő megjelenítéssel végződnek. PageWindows Phone-ban ViewController, iOS-ben és ActivityAndroid- ban hívják . A React Native útmutató gyakran említi a képernyőt, mint valami, ami az egész teret lefedi:

A mobilalkalmazásokat ritkán egyetlen képernyő alkotja. A több képernyő megjelenítésének és az átmenetek kezelését általában az úgynevezett navigátor kezeli.

index.js vagy sem?

Minden képernyő az egyes szolgáltatások belépési pontja. Akkor nevezd át a LoginScreen.js, hogy index.jskihasználva a Node modul funkció:

A moduloknak nem feltétlenül fájloknak kell lenniük. Létrehozhatunk egy find-memappát is, node_modulesés elhelyezhetünk benne egy index.jsfájlt. Ugyanez a require('find-me')sor fogja használni a mappa index.jsfájlját

Tehát ehelyett import LoginScreen from './screens/LoginScreen'csak tehetünk import LoginScreen from './screens'.

Az index.jseredmények felhasználása a kapszulázásban és nyilvános felületet biztosít a szolgáltatáshoz. Ez mind személyes ízlés. Én magam inkább egy fájl egyértelmű megnevezését részesítem előnyben, ezért a nevet LoginScreen.js.

Navigátor

react-navigation seems to be the most popular choice for handling navigation in a React Native app. For a feature like onboarding, there are probably many screens managed by a stack navigation, so there is OnboardingNavigator .

You can think of Navigator as something that groups sub screens or features. Since we group by feature, it’s reasonable to place Navigator inside the feature folder. It basically looks like this:

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

This is the most controversial part of structuring a project. If you don’t like the name library, you can name it utilities, common, citadel , whatever

This is not meant for homeless files, but it is where we place common utilities and components that are used by many features. Things like atomic components, wrappers, quick fixes function, networking stuff, and login info are used a lot, and it’s hard to move them to a specific feature folder. Sometimes we just need to be practical and get the work done.

In React Native, we often need to implement a button with an image background in many screens. Here is a simple one that stays inside library/components/ImageButton.js . The components folder is for reusable components, sometimes called atomic components. According to React naming conventions, the first letter should be uppercase.

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' } })

And if we want to place the button at the bottom, we use a utility function to prevent code duplication. Here is 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

Use package.json to avoid relative path

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/" ] }

A betűtípusok név szerinti elérése hibára hajlamos, létrehozhatunk egy szkriptet, hasonlóan ahhoz, amit képekkel tettünk a biztonságosabb csatlakozás érdekében. Hozzáadás fonts.jsa scriptsmappánkhoz

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

Most már használhatja az egyedi betűtípust a Rnévtéren keresztül .

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

Az R névtér

Ez a lépés a személyes ízléstől függ, de én szervezettebbnek találom, ha bemutatjuk az R névteret, csakúgy, mint az Android hogyan csinál a létrehozott R osztályú eszközökhöz.

Miután kivezette az alkalmazás erőforrásait, hozzáférhet hozzájuk a projekt Rosztályában létrehozott erőforrás-azonosítók használatával . Ez a dokumentum bemutatja, hogyan csoportosíthatja erőforrásait az Android projektben, és hogyan adhat alternatív erőforrásokat az adott eszközkonfigurációkhoz, majd hozzáférhet hozzájuk az alkalmazáskódjából vagy más XML fájlokból.

Így, tegyük nevű fájlt R.jsa 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

És érje el a képernyőn:

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

Cserélje stringsaz R.strings, colorsa R.colors, és imagesa R.images. Az R kommentárral egyértelmű, hogy a statikus eszközökhöz az alkalmazáscsomagból férünk hozzá.

Ez egyezik az Airbnb szinglettre vonatkozó egyezményével is, mivel R-jünk most olyan, mint egy globális állandó.

23.8 A PascalCase használata konstruktor / class / singleton / function library / csupasz objektum exportálásakor.
const AirbnbStyleGuide = { es6: { }, } export default AirbnbStyleGuide

Innen merre lehet menni

Ebben a bejegyzésben megmutattam neked, hogy szerintem hogyan kell struktúrálnod a mappákat és fájlokat a React Native projektben. Megtanultuk az erőforrások kezelését és azok biztonságosabb elérését is. Remélem, hasznosnak találta. Íme néhány további forrás a további feltáráshoz:

  • React Native Project megszervezése
  • Structuring projects and naming components in React
  • Using index.js for Fun and Public Interfaces

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 ?