de Khoa Pham

Cum să vă structurați proiectul și să gestionați resursele statice în React Native

Cum sa va structurati proiectul si sa gestionati resursele statice
Sursă: stmed.net

React și React Native sunt doar cadre și nu dictează modul în care ar trebui să ne structurăm proiectele. Totul depinde de gustul tău personal și de proiectul la care lucrezi.

În această postare, vom trece prin modul de structurare a unui proiect și modul de gestionare a activelor locale. Desigur, acest lucru nu este scris în piatră și sunteți liber să aplicați doar piesele care vi se potrivesc. Sper să înveți ceva.

Pentru un proiect bootstrapped cu react-native init , primim doar structură de bază.

Există ios folder pentru proiecte Xcode, android folder pentru proiecte Android și un index.js si un App.js fișier pentru punctul de plecare React Native.

ios/
android/
index.js
App.js

Ca cineva care a lucrat cu nativ atât pe Windows Phone, iOS, cât și pe Android, consider că structurarea unui proiect se reduce la separarea fișierelor prin tip sau caracteristică

tip vs caracteristică

Separarea după tip înseamnă că organizăm fișierele după tipul lor. Dacă este o componentă, există fișiere de tip container și de prezentare. Dacă este Redux, există fișiere de acțiune, de reducere și de stocare. Dacă este vizualizat, există fișiere JavaScript, HTML și CSS.

Grupați după tip

redux
  actions
  store
  reducers
components
  container
  presentational
view
  javascript
  html
  css

În acest fel, putem vedea tipul fiecărui fișier și putem rula cu ușurință un script către un anumit tip de fișier. Acest lucru este general pentru toate proiectele, dar nu răspunde la întrebarea „despre ce este acest proiect?” Este o aplicație de știri? Este o aplicație de loialitate? Este vorba de urmărirea nutriției?

Organizarea fișierelor după tip este pentru o mașină, nu pentru un om. De multe ori lucrăm la o caracteristică, iar găsirea fișierelor de remediat în mai multe directoare este o problemă. Este, de asemenea, dureros dacă intenționăm să realizăm un cadru din proiectul nostru, deoarece fișierele sunt răspândite în mai multe locuri.

Grupați după caracteristică

O soluție mai rezonabilă este organizarea fișierelor în funcție de caracteristică. Fișierele legate de o caracteristică ar trebui plasate împreună. Și fișiere de testare ar trebui să rămână aproape de fișierele sursă. Verifică Acest articol pentru a afla mai multe.

O caracteristică poate fi legată de autentificare, înregistrare, integrare sau profilul unui utilizator. O caracteristică poate conține sub-caracteristici atâta timp cât aparțin aceluiași flux. Dacă am dori să mutăm caracteristica secundară, ar fi ușor, deoarece toate fișierele conexe sunt deja grupate împreună.

Structura mea tipică de proiect bazată pe caracteristici arată astfel:

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

Pe lângă fișierele tradiționale App.js și index.js si ios1 și android foldere, am pus toate fișierele sursă în src pliant. Interior src eu am res pentru resurse, library pentru fișierele obișnuite utilizate în funcții și screens pentru un ecran de conținut.

Cât mai puține dependențe posibil

Deoarece React Native depinde în mare măsură de tone de dependențe, încerc să fiu destul de conștient când adaug mai multe. În proiectul meu folosesc doar react-navigation pentru navigare. Și nu sunt fan redux deoarece adaugă o complexitate inutilă. Adăugați o dependență numai atunci când aveți cu adevărat nevoie de ea, altfel doar vă configurați mai multe probleme decât valoare.

Lucrul care îmi place la React sunt componentele. O componentă este locul în care definim vizualizarea, stilul și comportamentul. React are un stil inline – este ca și cum ai folosi JavaScript pentru a defini script, HTML și CSS. Aceasta se potrivește abordării caracteristice pe care o vizăm. De aceea nu folosesc componente stilizate. Deoarece stilurile sunt doar obiecte JavaScript, putem împărtăși doar stiluri de comentarii în library .

src

Îmi place mult Android, așa că numesc src și res pentru a se potrivi convențiilor sale de dosare.

react-native init ne pregătește babel. Dar pentru un proiect JavaScript tipic, este bine să organizați fișiere în src pliant. In al meu electron.js cerere IconGenerator, Am pus fișierele sursă în interiorul src pliant. Acest lucru nu numai că ajută în ceea ce privește organizarea, dar ajută și babel să transpile întregul folder simultan. Doar o comandă și am fișierele în src transpus în dist într-o clipită.

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

Ecran

React se bazează pe componente. Da. Sunt container și componente de prezentare, dar putem compune componente pentru a construi componente mai complexe. De obicei, acestea se termină prin afișarea pe întregul ecran. Se numeste Page în Windows Phone, ViewController în iOS și Activity în Android. Ghidul React Native menționează ecran foarte des ca ceva care acoperă întregul spațiu:

Aplicațiile mobile sunt rareori alcătuite dintr-un singur ecran. Gestionarea prezentării și tranziției între mai multe ecrane este de obicei gestionată de ceea ce este cunoscut ca navigator.

index.js sau nu?

Fiecare ecran este considerat punctul de intrare pentru fiecare caracteristică. Puteți redenumi LoginScreen.js la index.js prin folosirea Nodului modul caracteristică:

Modulele nu trebuie să fie fișiere. Putem crea și un find-me dosar sub node_modules și plasează un index.js fișier acolo. La fel require('find-me') linia va folosi folderul respectiv index.js fişier

Deci, în loc de import LoginScreen from './screens/LoginScreen' , putem doar să facem import LoginScreen from './screens'.

Folosind index.js are ca rezultat încapsularea și oferă o interfață publică pentru caracteristică. Acesta este tot gustul personal. Eu însumi prefer o denumire explicită pentru un fișier, de unde și numele LoginScreen.js.

navigare reactivă pare a fi cea mai populară alegere pentru gestionarea navigării într-o aplicație React Native. Pentru o caracteristică precum integrarea, există probabil multe ecrane gestionate printr-o navigare în stivă, așa că există OnboardingNavigator .

Vă puteți gândi la Navigator ca la ceva care grupează sub-ecrane sau caracteristici. Deoarece grupăm în funcție de caracteristică, este rezonabil să plasați Navigator în folderul de caracteristici. Practic, arată astfel:

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)

bibliotecă

Aceasta este cea mai controversată parte a structurării unui proiect. Dacă nu îți place numele library, îl puteți numi utilities, common, citadel , whatever

Acest lucru nu este destinat fișierelor fără adăpost, dar este locul în care plasăm utilități și componente comune care sunt utilizate de mai multe caracteristici. Lucruri precum componentele atomice, împachetările, funcția de remediere rapidă, lucrurile de rețea și informațiile de conectare sunt folosite foarte mult și este greu să le mutați într-un folder de caracteristici specifice. Uneori trebuie doar să fim practici și să facem treaba.

În React Native, de multe ori trebuie să implementăm un buton cu fundal de imagine în multe ecrane. Iată unul simplu care rămâne în interior library/components/ImageButton.js . components folderul este pentru componentele reutilizabile, uneori numite componente atomice. Conform convențiilor de denumire React, prima literă ar trebui să fie cu majuscule.

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

Și dacă dorim să plasăm butonul în partea de jos, folosim o funcție utilitară pentru a preveni duplicarea codului. Aici este 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

Utilizați package.json pentru a evita calea relativă

Apoi undeva în src/screens/onboarding/term/Term.js , putem importa folosind căi relative:

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

Acesta este un steag roșu mare în ochii mei. Este predispus la erori, deoarece trebuie să calculăm câte .. trebuie să performăm. Și dacă mutăm caracteristica, toate căile trebuie recalculate.

De cand library este menit să fie folosit în multe locuri, este bine să îl referiți ca o cale absolută. În JavaScript există de obicei 1000 de biblioteci pentru o singură problemă. O căutare rapidă pe Google relevă o mulțime de biblioteci pentru a aborda această problemă. Dar nu avem nevoie de o altă dependență, deoarece acest lucru este extrem de ușor de remediat.

Soluția este să te întorci library intr-o module asa de node o poate găsi. Se adaugă package.json către orice folder îl transformă într-un nod module . Adăuga package.json în interiorul library dosar cu acest conținut simplu:

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

Acum in Term.js putem importa cu ușurință lucruri din library pentru că acum este un 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
  }}
})

rez

S-ar putea să vă întrebați ce res/colors, res/strings , res/images și res/fonts sunt în exemplele de mai sus. Ei bine, pentru proiectele front-end, avem de obicei componente și le stilăm folosind fonturi, șiruri localizate, culori, imagini și stiluri. JavaScript este un limbaj foarte dinamic și este ușor de utilizat tipuri stringente peste tot. Am putea avea o grămadă de #00B75D color în mai multe fișiere sau Fira ca fontFamily in multe Text componente. Acesta este predispus la erori și greu de refactorizat.

Să încapsulăm utilizarea resurselor în interiorul res folder cu obiecte mai sigure. Arată ca exemplele de mai jos:

res / culori

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 https://github.com/onmyway133',
      button: 'Log in'
    },
    term: {
      heading: 'Terms and conditions',
      button: 'Read'
    }
  }
}

export default strings

res / fonturi

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

export default fonts

res / imagini

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

export default images

Ca library , res fișierele pot fi accesate de oriunde, așa că haideți să o facem module . Adăuga package.json la res pliant:

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

astfel încât să putem accesa fișiere de resurse, cum ar fi module normale:

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

Grupați culorile, imaginile, fonturile cu paleta

Proiectarea aplicației ar trebui să fie consecventă. Anumite elemente ar trebui să aibă același aspect și să nu confunde utilizatorul. De exemplu, titlul Text ar trebui să utilizeze o singură culoare, font și dimensiunea fontului. Image componentă ar trebui să utilizeze aceeași imagine substituent. În React Native, folosim deja numele styles cu const styles = StyleSheet.create({}) deci să folosim numele palette.

Mai jos este paleta mea simplă. Acesta definește stilurile comune pentru titlu și Text:

res / paletă

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

Și apoi le putem folosi în ecranul nostru:

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

Aici folosim operator de răspândire a obiectelor a se contopi, uni palette.heading și obiectul nostru de stil personalizat. Aceasta înseamnă că folosim stilurile de la palette.heading dar specificați și mai multe proprietăți.

Dacă ar fi să refacem aplicația pentru mai multe mărci, am putea avea mai multe palete. Acesta este un model foarte puternic.

Generați imagini

Puteți vedea asta în /src/res/images.js avem proprietăți pentru fiecare imagine din src/res/images pliant:

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

export default images

Acest lucru este obositor de făcut manual și trebuie să ne actualizăm dacă există modificări în convenția de denumire a imaginilor. În schimb, putem adăuga un script pentru a genera fișierul images.js pe baza imaginilor pe care le avem. Adăugați un fișier la rădăcina proiectului /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()

Interesantul despre Node este că avem acces la fs modul, care este foarte bun la procesarea fișierelor. Aici pur și simplu traversăm imagini și le actualizăm /src/res/images.js în consecinţă.

Ori de câte ori adăugăm sau schimbăm imagini, putem rula:

node scripts/images.js

Și putem, de asemenea, să declarăm scriptul în interiorul principalului nostru 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"
}

Acum putem fugi npm run images și primim o actualizare images.js fișier resursă.

Ce zici de fonturi personalizate

React Native are unele fonturi personalizate asta poate fi suficient de bun pentru proiectele tale. De asemenea, puteți utiliza fonturi personalizate.

Un lucru de reținut este că Android folosește numele fișierului font, dar iOS folosește numele complet. Puteți vedea numele complet în aplicația Font Book sau inspectând aplicația care rulează

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

Pentru ca fonturile personalizate să fie înregistrate în iOS, trebuie să declarăm UIAppFonts în Info.plist folosind numele fișierului fonturilor, iar pentru Android, fonturile trebuie plasate la app/src/main/assets/fonts .

Este o bună practică să denumiți fișierul fontului la fel ca numele complet. Se spune că React Native încarcă dinamic fonturi personalizate, dar în cazul în care primiți „Familia de fonturi nerecunoscută”, pur și simplu adăugați acele fonturi pentru a viza în Xcode.

Pentru a face acest lucru manual, avem nevoie de timp, din fericire rnpm asta poate ajuta. Mai întâi adăugați toate fonturile din interior res/fonts pliant. Atunci pur și simplu declarați rnpm în package.json și fugi react-native link . Acest lucru ar trebui să se declare UIAppFonts în iOS și mutați toate fonturile în app/src/main/assets/fonts pentru Android.

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

Accesarea fonturilor după nume este predispusă la erori, putem crea un script similar cu ceea ce am făcut cu imaginile pentru a genera o accesare mai sigură. Adăuga fonts.js la noi scripts pliant

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

Acum puteți utiliza fonturi personalizate prin R spațiu de nume.

import R from 'res/R'

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

Spațiul de nume R.

Acest pas depinde de gustul personal, dar îl găsesc mai organizat dacă introducem spațiul de nume R, la fel cum procedează Android pentru activele generate Clasa R..

După ce vă externalizați resursele aplicației, le puteți accesa folosind ID-urile de resurse generate în proiectul dvs. Rclasă. Acest document vă arată cum să vă grupați resursele în proiectul dvs. Android și să furnizați resurse alternative pentru anumite configurații ale dispozitivului, apoi să le accesați din codul aplicației dvs. sau din alte fișiere XML.

În acest fel, să facem un fișier numit R.js în 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

Și accesați-l în ecran:

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

A inlocui strings cu R.strings, colors cu R.colors, și images cu R.images. Cu adnotarea R, este clar că accesăm active statice din pachetul de aplicații.

Acest lucru se potrivește și cu Airbnb convenţie pentru singleton, deoarece R-ul nostru este acum ca o constantă globală.

23.8 Utilizați PascalCase când exportați un constructor / clasă / singleton / bibliotecă de funcții / obiect gol.

const AirbnbStyleGuide = {
  es6: {
  },
}

export default AirbnbStyleGuide

Unde să merg de aici

În această postare, v-am arătat cum cred că ar trebui să structurați folderele și fișierele într-un proiect React Native. De asemenea, am învățat cum să gestionăm resursele și să le accesăm într-un mod mai sigur. Sper că ți s-a părut util. Iată câteva resurse pentru a explora în continuare:

Deoarece sunteți aici, vă puteți bucura de celelalte articole ale mele

Dacă vă place această postare, vă recomandăm să vizitați celelalte articole ale mele și aplicații ?