Kā strukturēt savu projektu un pārvaldīt statiskos resursus programmā React Native

React un React Native ir tikai ietvars, un tie nenosaka, kā mums būtu jāstrukturē mūsu projekti. Viss ir atkarīgs no jūsu personīgās gaumes un projekta, pie kura strādājat.

Šajā amatā mēs izskatīsim, kā strukturēt projektu un kā pārvaldīt vietējos aktīvus. Tas, protams, nav rakstīts akmenī, un jūs varat brīvi izmantot tikai tos gabalus, kas jums piemēroti. Ceru, ka jūs kaut ko iemācīsities.

Projekta sāknēšanas react-native initgadījumā mēs iegūstam tikai pamata struktūru.

Ir iosmape Xcode projektiem, androidmape Android projektiem index.jsun App.jsfails React Native sākuma punktam.

ios/ android/ index.js App.js

Kā cilvēks, kurš ir strādājis ar vietējiem lietotājiem gan Windows Phone, gan iOS, gan Android ierīcēs, es uzskatu, ka projekta strukturēšana ir saistīta ar failu atdalīšanu pēc veida vai funkcijas

tips vs funkcija

Atdalīšana pēc veida nozīmē, ka mēs kārtojam failus pēc to veida. Ja tas ir komponents, ir konteineru un prezentācijas faili. Ja tas ir Redux, ir darbības, reduktora un saglabāšanas faili. Ja tas ir skats, ir JavaScript, HTML un CSS faili.

Grupēt pēc veida

redux actions store reducers components container presentational view javascript html css

Tādā veidā mēs varam redzēt katra faila tipu un viegli palaist skriptu pret noteiktu faila tipu. Tas ir vispārīgi visiem projektiem, taču tas neatbild uz jautājumu “par ko ir šis projekts?” Vai tā ir ziņu aplikācija? Vai tā ir lojalitātes lietotne? Vai tas ir par uztura izsekošanu?

Failu kārtošana pēc veida ir paredzēta mašīnai, nevis cilvēkam. Daudzas reizes mēs strādājam pie funkcijas, un failu meklēšana, lai tos labotu vairākos direktorijos, ir apgrūtinoša. Ir arī sāpīgi, ja mēs plānojam izveidot mūsu projekta ietvaru, jo faili ir izplatīti daudzās vietās.

Grupēt pēc funkcijas

Saprātīgāks risinājums ir failu sakārtošana pēc pazīmēm. Faili, kas saistīti ar objektu, jāievieto kopā. Pārbaudes failiem jāpaliek tuvu avota failiem. Pārbaudiet šo rakstu, lai uzzinātu vairāk.

Funkcija var būt saistīta ar pieteikšanos, reģistrēšanos, iekļaušanu vai lietotāja profilu. Funkcija var saturēt apakšīpašības, ja vien tās pieder vienai plūsmai. Ja mēs vēlētos pārvietot apakšfunkciju apkārt, tas būtu viegli, jo visi saistītie faili jau ir grupēti kopā.

Mana tipiskā projekta struktūra, kas balstīta uz funkcijām, izskatās šādi:

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

Bez tradicionālajām failiem App.jsun index.jsun ios1un androidmapes, man visus avota failus iekšā srcmapē. Iekšpusē srcman ir resresursi, librarykopīgi faili, kas tiek izmantoti dažādās funkcijās, un screenssatura ekrāns.

Pēc iespējas mazāk atkarību

Tā kā React Native ir ļoti atkarīgs no daudzām atkarībām, es cenšos būt diezgan informēts, pievienojot vairāk. Savā projektā es izmantoju tikai react-navigationnavigāciju. Un es neesmu ventilators, reduxjo tas rada nevajadzīgu sarežģītību. Pievienojiet atkarību tikai tad, kad tā jums patiešām ir nepieciešama, pretējā gadījumā jūs vienkārši iestatāt sevi vairāk nepatikšanām nekā vērtībai.

Reakcijā man patīk komponenti. Komponents ir tas, kur mēs definējam skatu, stilu un uzvedību. Reakcijai ir iekļauts stils - tas ir tāpat kā JavaScript, lai definētu skriptu, HTML un CSS. Tas atbilst iezīmju pieejai, uz kuru mēs tiecamies. Tāpēc es neizmantoju stila komponentus. Tā kā stili ir tikai JavaScript objekti, mēs varam vienkārši kopīgot komentāru stilus library.

src

Man ļoti patīk Android, tāpēc es nosaucu srcun ressaskaņoju tā mapes.

react-native inituzstāda mums bābeli. Bet tipiskam JavaScript projektam ir labi organizēt failus srcmapē. Savā electron.jslietojumprogrammā IconGenerator avota failus ievietoju srcmapē. Tas palīdz ne tikai organizēt, bet arī palīdz Babel vienlaikus pārpētīt visu mapi. Tikai komanda, un faili tiek srcpārsūtīti uz mirkli dist.

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

Ekrāns

React pamatā ir komponenti. Jā. Ir konteineru un prezentācijas komponenti, taču mēs varam komponēt komponentus, lai izveidotu sarežģītākus komponentus. Parasti tās tiek rādītas pilnekrāna režīmā. To sauc Pagepar Windows Phone, ViewControlleriOS un ActivityAndroid. React Native ceļvedī ekrāns bieži tiek minēts kā kaut kas tāds, kas aptver visu telpu:

Mobilās lietotnes reti veido viens ekrāns. Vairāku ekrānu prezentācijas un pārejas starp tiem pārvaldību parasti veic tas, kas ir pazīstams kā navigators.

index.js vai nē?

Katrs ekrāns tiek uzskatīts par katras funkcijas ieejas punktu. Varat pārdēvēt LoginScreen.js, lai index.js, piesaistot mezglu moduļa funkciju:

Moduļiem nav jābūt failiem. Mēs varam arī izveidot find-memapi zem node_modulesun ievietot tajā index.jsfailu. Tajā pašā require('find-me')rindā tiks izmantots šīs mapes index.jsfails

Tā vietā import LoginScreen from './screens/LoginScreen'mēs varam vienkārši darīt import LoginScreen from './screens'.

Izmantojot index.jsrezultātus hermetizāciju un nodrošina publisku saskarni funkciju. Šī ir visa personīgā gaume. Es pats dodu priekšroku faila skaidram nosaukšanai, tāpēc arī nosaukums LoginScreen.js.

Navigators

reakcijas navigācija, šķiet, ir vispopulārākā navigācijas apstrādes izvēle React Native lietotnē. Tādai funkcijai kā onboarding, iespējams, ir daudz ekrānu, ko pārvalda kaudzes navigācija, tāpēc ir OnboardingNavigator.

Jūs varat iedomāties Navigator kā kaut ko, kas grupē apakšekrānus vai funkcijas. Tā kā mēs grupējamies pēc funkcijām, ir lietderīgi novietot Navigator funkciju mapē. Tas būtībā izskatās šādi:

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)

bibliotēka

Šī ir visstrīdīgākā projekta strukturēšanas daļa. Ja jums nepatīk nosaukumu library, jūs varat nosaukt to 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/" ] }

Piekļuve fontiem pēc nosaukuma ir pakļauta kļūdām, mēs varam izveidot skriptu, kas līdzīgs tam, ko esam darījuši ar attēliem, lai radītu drošāku pievienošanos. Pievienot fonts.jsmūsu scriptsmapei

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

Tagad jūs varat izmantot pielāgotu fontu, izmantojot Rvārdu vietu.

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

R nosaukumvieta

Šis solis ir atkarīgs no personīgās gaumes, taču, manuprāt, tas ir organizētāk, ja ieviešam R nosaukumvietu tāpat kā to, kā Android rīkojas aktīviem ar ģenerēto R klasi.

Kad esat noņēmis ārā lietotnes resursus, varat tiem piekļūt, izmantojot resursu ID, kas tiek ģenerēti jūsu projekta Rklasē. Šajā dokumentā ir parādīts, kā grupēt resursus savā Android projektā un nodrošināt alternatīvus resursus konkrētām ierīču konfigurācijām un pēc tam tiem piekļūt no lietotnes koda vai citiem XML failiem.

Tādā veidā, pieņemsim veikt failu ar nosaukumu R.js, kas 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

Un piekļūstiet tam ekrānā:

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

Aizstāt stringsar R.strings, colorsar R.colorsun imagesar R.images. Izmantojot R anotāciju, ir skaidrs, ka statiskajiem aktīviem mēs piekļūstam no lietotņu paketes.

Tas atbilst arī Airbnb konvencijai par singletonu, jo mūsu R tagad ir kā globāla konstante.

23.8 Izmantojiet PascalCase, kad eksportējat konstruktoru / klasi / singletonu / funkciju bibliotēku / tukšu objektu.
const AirbnbStyleGuide = { es6: { }, } export default AirbnbStyleGuide

Kurp doties no šejienes

Šajā amatā es jums parādīju, kā, manuprāt, jums vajadzētu strukturēt mapes un failus React Native projektā. Mēs esam iemācījušies arī pārvaldīt resursus un piekļūt tiem drošāk. Es ceru, ka jums tas šķita noderīgs. Šeit ir vēl daži resursi, lai turpinātu izpētīt:

  • React Native projekta organizēšana
  • Projektu strukturēšana un komponentu nosaukšana
  • Izmantojot index.js jautrībai un publiskām saskarnēm

Tā kā jūs esat šeit, jūs varat izbaudīt citus manus rakstus

  • React Native izvietošana uz Bitrise, Fabric, CircleCl
  • Novietojiet elementu ekrāna apakšdaļā, izmantojot React Native Flexbox
  • ESLint un EditorConfig iestatīšana React Native projektos
  • Firebase SDK ar Firestore for React Native lietotnēm 2018. gadā

Ja jums patīk šī ziņa, apsveriet iespēju apmeklēt citus manus rakstus un lietotnes?