14 Tipps zum Schreiben von sauberem React-Code. Teil 1

Das Schreiben von sauberem Code ist eine Fähigkeit, die zu einem bestimmten Zeitpunkt in der Karriere eines Programmierers obligatorisch wird. Diese Fähigkeit ist besonders wichtig, wenn der Programmierer versucht, seinen ersten Job zu finden. Dies macht den Entwickler im Wesentlichen zu einem Teamplayer und kann das Interview entweder „ausfüllen“ oder ihm helfen, erfolgreich zu bestehen. Arbeitgeber achten bei Personalentscheidungen auf den Code, den ihre potenziellen Mitarbeiter geschrieben haben. Der Code, den der Programmierer schreibt, sollte nicht nur von Maschinen, sondern auch von Menschen verstanden werden.



Das Material, dessen erster Teil der Übersetzung wir heute veröffentlichen, enthält Tipps zum Schreiben von sauberem Code für React-Anwendungen. Die Relevanz dieser Tipps ist umso größer, je größer das Projekt ist, in dem die darin enthaltenen Prinzipien angewendet werden. In kleinen Projekten können Sie wahrscheinlich auf die Anwendung dieser Prinzipien verzichten. Bei der Entscheidung, was in der jeweiligen Situation benötigt wird, lohnt es sich, sich vom gesunden Menschenverstand leiten zu lassen.

1. Eigenschaften zerstören


Die Zerstörung von Eigenschaften (in der englischen React-Terminologie werden sie als "Requisiten" bezeichnet) ist eine gute Möglichkeit, den Code sauberer zu gestalten und seine Unterstützungsfunktionen zu verbessern. Tatsache ist, dass Sie auf diese Weise klar ausdrücken oder deklarieren können, was eine Entität verwendet (wie die React-Komponente). Dieser Ansatz zwingt Entwickler jedoch nicht dazu, in die Implementierung der Komponente einzulesen, um die Zusammensetzung der damit verbundenen Eigenschaften herauszufinden.

Durch die Destrukturierung von Eigenschaften kann der Programmierer auch seine Standardwerte festlegen. Das ist ziemlich häufig:

import React from 'react' import Button from 'components/Button' const MyComponent = ({ placeholder = '', style, ...otherProps }) => {   return (     <Button       type="button"       style={{         border: `1px solid ${placeholder ? 'salmon' : '#333'}`,         ...style,       }}       {...otherProps}     >       Click Me     </Button>   ) } export default MyComponent 

Eine der angenehmsten Konsequenzen der Verwendung der Destrukturierung in JavaScript, die ich feststellen konnte, ist, dass Sie verschiedene Optionen für Parameter unterstützen können.

Zum Beispiel haben wir eine Funktionsauthentifizierung, die als Parameter das token verwendet, das zur Authentifizierung von Benutzern verwendet wird. Später musste die Entität jwt_token akzeptiert werden. Dieser Bedarf wurde durch eine Änderung in der Struktur der Serverantwort verursacht. Dank der Verwendung der Destrukturierung können Sie die Unterstützung für beide Parameter einfach organisieren, ohne den größten Teil des Funktionscodes ändern zu müssen:

 //   async function authenticate({ user_id, token }) {  try {    const response = await axios.post('https://someapi.com/v1/auth/', {      user_id,      token,    })    console.log(response)    return response.data  } catch (error) {    console.error(error)    throw error  } } //   async function authenticate({ user_id, jwt_token, token = jwt_token }) {  try {    const response = await axios.post('https://someapi.com/v1/auth/', {      user_id,      token,    })    console.log(response)    return response.data  } catch (error) {    console.error(error)    throw error  } } 

Die jwt_token wird ausgewertet, wenn der Code das token erreicht. Wenn sich herausstellt, dass jwt_token ein gültiges Token ist und sich die token Entität als undefined herausstellt, fällt der Wert jwt_token in das token . Wenn es im token bereits einen Wert gab, der nach JS-Regeln nicht falsch war (dh ein echtes Token), dann token es im token einfach das, was bereits vorhanden ist.

2. Platzieren Sie die Komponentendateien in einer durchdachten Ordnerstruktur


Schauen Sie sich die folgende Verzeichnisstruktur an:

  • src

    • Komponenten
    • Breadcrumb.js
    • CollapsedSeparator.js
  • Eingabe

    • index.js
    • Input.js
    • utils.js
    • focusManager.js
  • Karte

    • index.js
    • Card.js
    • CardDivider.js
  • Button.js
  • Typografie.js

Brotkrumen können Separatoren enthalten. Die CollapsedSeparator Komponente wird in die Datei Breadcrumb.js importiert. Dies gibt uns das Wissen, dass sie bei der Umsetzung des betreffenden Projekts miteinander verbunden sind. Jemand, der diese Informationen nicht besitzt, kann jedoch vorschlagen, dass Breadcrumb und CollapsedSeparator ein Paar völlig unabhängiger Komponenten sind, die in keiner Weise miteinander verbunden sind. Insbesondere, wenn der CollapsedSeparator keine eindeutigen Anzeichen dafür aufweist, dass diese Komponente der Breadcrumb Komponente zugeordnet ist. Unter solchen Zeichen kann beispielsweise ein Breadcrumb Präfix im Komponentennamen verwendet werden, das den Namen in etwas wie BreadcrumbCollapsedSeparator.js verwandeln kann.

Da wir wissen, dass Breadcrumb und CollapsedSeparator miteinander verwandt sind, fragen wir uns möglicherweise, warum sie nicht in einem separaten Ordner wie Input und Card abgelegt werden. Gleichzeitig können wir verschiedene Annahmen darüber treffen, warum die Projektmaterialien eine solche Struktur haben. Nehmen wir an, Sie können hier darüber nachdenken, welche Komponenten auf der obersten Ebene des Projekts platziert wurden, damit sie diese Komponenten schnell finden und sich um diejenigen kümmern können, die mit dem Projekt arbeiten. Infolgedessen erscheint die Beziehung zwischen den Teilen des Projekts für den neuen Entwickler ziemlich vage. Die Verwendung sauberer Code-Schreibtechniken sollte genau den gegenteiligen Effekt haben. Der Punkt ist, dass der neue Entwickler dank ihnen die Möglichkeit erhält, den Code eines anderen zu lesen und sofort das Wesentliche der Situation zu erfassen.

Wenn wir in unserem Beispiel eine durchdachte Verzeichnisstruktur verwenden, erhalten wir ungefähr Folgendes:

  • src

    • Komponenten
  • Brotkrume

    • index.js
    • Breadcrumb.js
    • CollapsedSeparator.js
  • Eingabe

    • index.js
    • Input.js
    • utils.js
    • focusManager.js
  • Karte

    • index.js
    • Card.js
    • CardDivider.js
  • Button.js
  • Typografie.js

Jetzt spielt es keine Rolle, wie viele Komponenten, die der Breadcrumb Komponente zugeordnet sind, erstellt werden. Solange sich ihre Dateien im selben Verzeichnis wie Breadcrumb.js , wissen wir, dass sie der Breadcrumb Komponente zugeordnet sind:

  • src

    • Komponenten
  • Brotkrume

    • index.js
    • Breadcrumb.js
    • CollapsedSeparator.js
    • Expander.js
    • BreadcrumbText.js
    • Breadcrumbhothotog.js
    • Breadcrumbfishes.js
    • Breadcrumbleftft.js
    • Breadcrumbhead.js
    • Breadcrumbaddict.js
    • Breadcrumbdragon0814.js
    • Breadcrumbcontext.js
  • Eingabe

    • index.js
    • Input.js
    • utils.js
    • focusManager.js
  • Karte

    • index.js
    • Card.js
    • CardDivider.js
  • Button.js
  • Typografie.js

So sieht die Arbeit mit ähnlichen Strukturen im Code aus:

 import React from 'react' import Breadcrumb, {  CollapsedSeparator,  Expander,  BreadcrumbText,  BreadcrumbHotdog,  BreadcrumbFishes,  BreadcrumbLeftOvers,  BreadcrumbHead,  BreadcrumbAddict,  BreadcrumbDragon0814, } from '../../../../../../../../../../components/Breadcrumb' const withBreadcrumbHotdog = (WrappedComponent) => (props) => (  <WrappedComponent BreadcrumbHotdog={BreadcrumbHotdog} {...props} /> ) const WorldOfBreadcrumbs = ({  BreadcrumbHotdog: BreadcrumbHotdogComponent, }) => {  const [hasFishes, setHasFishes] = React.useState(false)  return (    <BreadcrumbDragon0814      hasFishes={hasFishes}      render={(results) => (        <BreadcrumbFishes>          {({ breadcrumbFishes }) => (            <BreadcrumbLeftOvers.Provider>              <BreadcrumbHotdogComponent>                <Expander>                  <BreadcrumbText>                    <BreadcrumbAddict>                      <pre>                        <code>{JSON.stringify(results, null, 2)}</code>                      </pre>                    </BreadcrumbAddict>                  </BreadcrumbText>                </Expander>                {hasFishes                  ? breadcrumbFishes.map((fish) => (                      <>                        {fish}                        <CollapsedSeparator />                      </>                    ))                  : null}              </BreadcrumbHotdogComponent>            </BreadcrumbLeftOvers.Provider>          )}        </BreadcrumbFishes>      )}    />  ) } export default withBreadcrumbHotdog(WorldOfBreadcrumbs) 

3. Benennen Sie Komponenten mit Standard-Namenskonventionen


Die Verwendung bestimmter Standards beim Benennen von Komponenten erleichtert es jemandem, der nicht der Autor des Projekts ist, den Code für dieses Projekt zu lesen.

Beispielsweise wird den Namen von Komponenten höherer Ordnung (HOCs) normalerweise ein Präfix vorangestellt. Viele Entwickler sind an folgende Komponentennamen gewöhnt:

 import React from 'react' import hoistNonReactStatics from 'hoist-non-react-statics' import getDisplayName from 'utils/getDisplayName' const withFreeMoney = (WrappedComponent) => {  class WithFreeMoney extends React.Component {    giveFreeMoney() {      return 50000    }    render() {      return (        <WrappedComponent          additionalMoney={[            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),          ]}          {...this.props}        />      )    }  }  WithFreeMoney.displayName = `withFreeMoney(${getDisplayName(    WrappedComponent,  )}$)`  hoistNonReactStatics(WithFreeMoney, WrappedComponent)  return WithFreeMoney } export default withFreeMoney 

Angenommen, jemand beschließt, von dieser Praxis zurückzutreten und Folgendes zu tun:

 import React from 'react' import hoistNonReactStatics from 'hoist-non-react-statics' import getDisplayName from 'utils/getDisplayName' const useFreeMoney = (WrappedComponent) => {  class WithFreeMoney extends React.Component {    giveFreeMoney() {      return 50000    }    render() {      return (        <WrappedComponent          additionalMoney={[            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),            this.giveFreeMoney(),          ]}          {...this.props}        />      )    }  }  WithFreeMoney.displayName = `useFreeMoney(${getDisplayName(    WrappedComponent,  )}$)`  hoistNonReactStatics(WithFreeMoney, WrappedComponent)  return WithFreeMoney } export default useFreeMoney 

Dies ist ein perfekt funktionierender JavaScript-Code. Die Namen hier sind aus technischer Sicht richtig zusammengesetzt. Das use ist jedoch in anderen Situationen üblich, insbesondere beim Benennen von React-Hooks . Wenn jemand ein Programm schreibt, das er jemand anderem zeigen möchte, sollte er daher vorsichtig mit den Namen der Entitäten sein. Dies gilt insbesondere für Fälle, in denen jemand nach seinem Code fragt und ihm bei der Lösung eines Problems hilft. Tatsache ist, dass jemand, der möglicherweise den Code eines anderen liest, bereits an ein bestimmtes Entitätsnamensschema gewöhnt ist.

Abweichungen von allgemein anerkannten Standards erschweren das Verständnis des Codes eines anderen.

4. Vermeiden Sie Boolesche Fallen


Der Programmierer sollte äußerst vorsichtig sein, falls einige Ausgaben von primitiven logischen Werten abhängen und einige Entscheidungen auf der Grundlage der Analyse dieser Werte getroffen werden. Dies deutet auf die schlechte Qualität des Codes hin. Dies zwingt Entwickler, den Code zum Implementieren von Komponenten oder anderen Mechanismen zu lesen, um eine genaue Vorstellung davon zu erhalten, was genau das Ergebnis dieser Mechanismen ist.

Angenommen, wir haben eine Typography , die die folgenden Optionen akzeptiert: 'h1' , 'h2' , 'h3' , 'h4' , 'h5 ', 'h6' , 'title' , 'subheading' .

Was genau wirkt sich auf die Ausgabe einer Komponente aus, wenn die Optionen in der folgenden Form an sie übergeben werden?

 const App = () => (  <Typography color="primary" align="center" subheading title>    Welcome to my bio  </Typography> ) 

Diejenigen, die Erfahrung mit React (oder besser gesagt mit JavaScript) haben, gehen möglicherweise bereits davon aus, dass die Option title subheading aufgrund der Funktionsweise des Systems subheading . Die letzte Option überschreibt die erste.

Das Problem hierbei ist jedoch, dass wir ohne einen Blick in den Code nicht genau sagen können, inwieweit die Titeloption oder die subheading angewendet wird.

Zum Beispiel:

 .title {  font-size: 1.2rem;  font-weight: 500;  text-transform: uppercase; } .subheading {  font-size: 1.1rem;  font-weight: 400;  text-transform: none !important; } 

Obwohl der title gewinnt, gilt die CSS text-transform: uppercase Regel für text-transform: uppercase nicht. Dies liegt an der höheren Spezifität der text-transform: none !important Regel, die in der subheading . Wenn Sie in solchen Situationen keine Vorsicht walten lassen, kann das Debuggen solcher Fehler in Stilen äußerst schwierig werden. Insbesondere - in Fällen, in denen der Code keine Warnungen oder Fehlermeldungen in der Konsole anzeigt. Dies kann die Signatur der Komponente erschweren.

Eine mögliche Lösung für dieses Problem ist die Verwendung einer saubereren Version der Typography :

 const App = () => <Typography variant="title">Welcome to my bio</Typography> 

Hier ist der Typography Komponentencode:

 import React from 'react' import cx from 'classnames' import styles from './styles.css' const Typography = ({  children,  color = '#333',  align = 'left',  variant,  ...otherProps }) => {  return (    <div      className={cx({        [styles.h1]: variant === 'h1',        [styles.h2]: variant === 'h2',        [styles.h3]: variant === 'h3',        [styles.h4]: variant === 'h4',        [styles.h5]: variant === 'h5',        [styles.h6]: variant === 'h6',        [styles.title]: variant === 'title',        [styles.subheading]: variant === 'subheading',      })}    >      {children}    </div>  ) } 

Wenn wir nun in der App Komponente an die Typography Komponente variant="title" , können wir sicher sein, dass nur der title die Ausgabe der Komponente beeinflusst. Dies erspart uns die Analyse des Komponentencodes, um zu verstehen, wie diese Komponente aussehen wird.

Um mit Eigenschaften zu arbeiten, können Sie das einfache if/else :

 let result if (variant === 'h1') result = styles.h1 else if (variant === 'h2') result = styles.h2 else if (variant === 'h3') result = styles.h3 else if (variant === 'h4') result = styles.h4 else if (variant === 'h5') result = styles.h5 else if (variant === 'h6') result = styles.h6 else if (variant === 'title') result = styles.title else if (variant === 'subheading') result = styles.subheading 

Die Hauptstärke dieses Ansatzes besteht jedoch darin, dass Sie einfach das folgende saubere einzeilige Design verwenden und dem ein Ende setzen können:

 const result = styles[variant] 

5. Verwenden Sie die Pfeilfunktionen


Pfeilfunktionen stellen einen präzisen und klaren Mechanismus zum Deklarieren von Funktionen in JavaScript dar (in diesem Fall wäre es korrekter, über den Vorteil von Pfeilfunktionen gegenüber funktionalen Ausdrücken zu sprechen).

In einigen Fällen verwenden Entwickler jedoch keine Pfeilfunktionen anstelle von Funktionsausdrücken. Zum Beispiel, wenn es notwendig ist, das Erhöhen von Funktionen zu organisieren.

React verwendet diese Konzepte auf ähnliche Weise. Wenn ein Programmierer jedoch nicht daran interessiert ist, Funktionen zu erhöhen, ist es meiner Meinung nach sinnvoll, die Syntax von Pfeilfunktionen zu verwenden:

 //     function Gallery({ title, images = [], ...otherProps }) {  return (    <CarouselContext.Provider>      <Carousel>        {images.map((src, index) => (          <img align="center" src={src} key={`img_${index}`} />        ))}      </Carousel>    </CarouselContext.Provider>  ) } //         const Gallery = ({ title, images = [], ...otherProps }) => (  <CarouselContext.Provider>    <Carousel>      {images.map((src, index) => (        <img align="center" src={src} key={`img_${index}`} />      ))}    </Carousel>  </CarouselContext.Provider> ) 

Es ist zu beachten, dass es bei der Analyse dieses Beispiels schwierig ist, die Stärken der Pfeilfunktionen zu erkennen. Ihre Schönheit zeigt sich voll und ganz, wenn es um einfache einzeilige Designs geht:

 //     function GalleryPage(props) {  return <Gallery {...props} /> } //         const GalleryPage = (props) => <Gallery {...props} /> 

Ich bin sicher, dass solche einzeiligen Designs alle ansprechen werden.

6. Platzieren Sie unabhängige Funktionen außerhalb Ihrer eigenen Haken


Ich habe gesehen, wie einige Programmierer Funktionen in ihren eigenen Hooks deklarieren, aber diese Hooks benötigen solche Funktionen nicht besonders. Diese Art "bläst" den Hook-Code leicht auf und erschwert das Lesen. Schwierigkeiten beim Lesen des Codes ergeben sich aus der Tatsache, dass seine Leser möglicherweise Fragen dazu stellen, ob der Hook wirklich von der darin enthaltenen Funktion abhängt. Ist dies nicht der Fall, ist es besser, die Funktion außerhalb des Hakens zu verschieben. Dies gibt dem Codeleser ein klares Verständnis dafür, wovon der Hook abhängt und was nicht.

Hier ist ein Beispiel:

 import React from 'react' const initialState = {  initiated: false,  images: [], } const reducer = (state, action) => {  switch (action.type) {    case 'initiated':      return { ...state, initiated: true }    case 'set-images':      return { ...state, images: action.images }    default:      return state  } } const usePhotosList = ({ imagesList = [] }) => {  const [state, dispatch] = React.useReducer(reducer, initialState)  const removeFalseyImages = (images = []) =>    images.reduce((acc, img) => (img ? [...acc, img] : acc), [])  React.useEffect(() => {    const images = removeFalseyImages(imagesList)    dispatch({ type: 'initiated' })    dispatch({ type: 'set-images', images })  }, [])  return {    ...state,  } } export default usePhotosList 

Wenn wir diesen Code analysieren, können wir verstehen, dass die Funktionen removeFalseyImages tatsächlich nicht im Hook vorhanden sein müssen, sondern nicht mit seinem Status interagieren. removeFalseyImages bedeutet, dass er außerhalb des removeFalseyImages platziert und problemlos vom Hook aus aufgerufen werden kann.

7. Seien Sie beim Schreiben von Code konsistent


Ein konsistenter Ansatz zum Schreiben von Code wird häufig für diejenigen empfohlen, die in JavaScript programmieren.

Im Fall von React lohnt es sich, auf einen einheitlichen Ansatz bei der Verwendung der folgenden Designs zu achten:

  1. Import- und Exportteams.
  2. Benennung von Komponenten, Hooks, Komponenten höherer Ordnung, Klassen.

Beim Importieren und Exportieren von Komponenten verwende ich manchmal etwas Ähnliches wie das Folgende:

 import App from './App' export { default as Breadcrumb } from './Breadcrumb' export default App 

Aber ich mag auch die Syntax:

 export { default } from './App' export { default as Breadcrumb } from './Breadcrumb' 

Was auch immer der Programmierer wählt, er sollte es in jedem von ihm erstellten Projekt konsequent verwenden. Dies vereinfacht die Arbeit dieses Programmierers und das Lesen seines Codes durch andere Personen.

Es ist sehr wichtig, die Namenskonventionen von Entitäten einzuhalten.

Wenn beispielsweise jemand dem Hook den Namen useApp , ist es wichtig, dass die Namen anderer Hooks auf ähnliche Weise erstellt werden - unter Verwendung des Präfixes use. Beispielsweise kann der Name eines anderen useController mit diesem Ansatz wie useController aussehen.

Wenn Sie diese Regel nicht einhalten, kann sich der Code eines Projekts am Ende wie folgt herausstellen:

 //  #1 const useApp = ({ data: dataProp = null }) => {  const [data, setData] = React.useState(dataProp)  React.useEffect(() => {    setData(data)  }, [])  return {    data,  } } //  #2 const basicController = ({ device: deviceProp }) => {  const [device, setDevice] = React.useState(deviceProp)  React.useEffect(() => {    if (!device && deviceProp) {      setDevice(deviceProp === 'mobile' ? 'mobile' : 'desktop')    }  }, [deviceProp])  return {    device,  } } 

So sieht der Import dieser Hooks aus:

 import React from 'react' import useApp from './useApp' import basicController from './basicController' const App = () => {  const app = useApp()  const controller = basicController()  return (    <div>      {controller.errors.map((errorMsg) => (        <div>{errorMsg}</div>      ))}    </div>  ) } export default App 

Auf den ersten Blick ist es völlig offensichtlich, dass der basicController ein Hook ist, genau wie useApp . Dies zwingt den Entwickler, den Implementierungscode dessen zu lesen, was er importiert. Dies geschieht nur, um zu verstehen, womit der Entwickler genau zu tun hat. Wenn wir konsequent dieselbe Strategie für die Benennung von Entitäten einhalten, entsteht eine solche Situation nicht. Alles wird auf einen Blick klar:

 const app = useApp() const controller = useBasicController() 

Fortsetzung folgt…

Liebe Leser! Wie gehen Sie mit der Benennung von Entitäten in Ihren React-Projekten um?

Source: https://habr.com/ru/post/de465811/


All Articles