Winzige Komponenten: Was kann schief gehen? Wir verwenden den Grundsatz der alleinigen Verantwortung

Ursprünglicher Autor: Scott Domes
  • Übersetzung
Wir stellen Ihnen die Übersetzung des Artikels "Scott Domes" vor, der auf blog.bitsrc.io veröffentlicht wurde. Finden Sie unter dem Schnitt heraus, warum Komponenten so klein wie möglich sein sollten und wie der Grundsatz der alleinigen Verantwortung die Qualität der Anwendungen beeinflusst.


Foto von Austin Kirk mit Unsplash

Der Vorteil des React-Komponentensystems (und ähnlicher Bibliotheken) besteht darin, dass Ihre Benutzeroberfläche in kleine Teile unterteilt ist, die leicht wahrgenommen werden und wieder verwendet werden können.

Diese Komponenten sind kompakt (100 bis 200 Zeilen), sodass andere Entwickler sie leicht verstehen und modifizieren können.

Obwohl die Komponenten in der Regel versuchen, kürzer und klarer zu machen, gibt es keine strikte Begrenzung ihrer Länge. Wenn Sie sich dafür entscheiden, Ihre Anwendung in eine erschreckend große Komponente zu integrieren, die aus 3.000 Zeilen besteht, wird React nichts dagegen haben.

... aber mach es nicht. Die meisten Ihrer Komponenten sind wahrscheinlich zu umfangreich - oder sie führen vielmehr zu viele Funktionen aus.

In diesem Artikel werde ich beweisen, dass die meisten Komponenten (selbst bei der üblichen Länge von 200 Zeilen) stärker fokussiert sein sollten. Sie müssen nur eine Funktion erfüllen und sie gut ausführen. Diese wunderbare sagt Eddie Osmani ist hier .

Tipp : Wenn Sie in JS arbeiten, verwenden Sie Bit , um Komponenten wie Lego-Details zu organisieren, zusammenzubauen und wiederzuverwenden. Bit ist ein äußerst effektives Werkzeug für dieses Unternehmen. Es hilft Ihnen und Ihrem Team, Zeit zu sparen und den Aufbau zu beschleunigen. Probieren Sie es einfach aus.

Lassen Sie uns genau zeigen, wie beim Erstellen von Komponenten etwas schief gehen kann .

Unsere anwendung


Stellen Sie sich vor, wir haben eine Standardanwendung für Blogger. Und das ist was auf dem Hauptbildschirm ist:

class Main extends React.Component {
  render() {
    return (
      <div>
        <header>
          // Header JSX
        </header>
        <aside id="header">
          // Sidebar JSX
        </aside>
        <div id="post-container">
          {this.state.posts.map(post => {
            return (
              <div className="post">
                // Post JSX
              </div>
            );
          })}
        </div>
      </div>
    );
  }
}

(Dieses Beispiel sollte, wie viele nachfolgende, als Pseudocode betrachtet werden.)

Hier werden das obere Feld, das Seitenfeld und die Liste der Beiträge angezeigt . Es ist einfach

Da wir auch Beiträge hochladen müssen, können wir dies tun, während die Komponente bereitgestellt wird:

class Main extends React.Component {
  state = { posts: [] };
  componentDidMount() {
    this.loadPosts();
  }
  loadPosts() {
    // Load posts and save to state
  }
  render() {
    // Render code
  }
}

Wir haben auch eine Logik, durch die die Sidebar aufgerufen wird. Wenn der Benutzer auf die Schaltfläche im oberen Bereich klickt, wird die Seitenleiste verlassen. Sie kann sowohl von oben als auch von der eigentlichen Seitenwand aus geschlossen werden.

class Main extends React.Component {
  state = { posts: [], isSidebarOpen: false };
  componentDidMount() {
    this.loadPosts();
  }
  loadPosts() {
    // Load posts and save to state
  }
  handleOpenSidebar() {
    // Open sidebar by changing state
  }
  handleCloseSidebar() {
    // Close sidebar by changing state
  }
  render() {
    // Render code
  }
}

Unsere Komponente ist etwas schwieriger geworden, aber dennoch leicht wahrnehmbar.

Es kann argumentiert werden, dass alle seine Teile einem Zweck dienen: die Hauptseite der Anwendung anzuzeigen. Wir folgen also dem Grundsatz der alleinigen Verantwortung.

Das Prinzip der ausschließlichen Verantwortung besagt, dass eine Komponente nur eine Funktion erfüllen sollte. Wenn wir die Definition von wikipedia.org umformulieren , stellt sich heraus, dass jede Komponente nur für einen Teil der Funktion [Anwendung] verantwortlich sein sollte.

Unsere Hauptkomponente erfüllt diese Anforderung. Was ist das problem

Dies ist eine andere Formulierung des Prinzips: Jede [Komponente] sollte nur einen Grund für eine Änderung haben .

Diese Definition stammt aus einem Buch von Robert Martin.„Schnelle Softwareentwicklung. Prinzipien, Beispiele, Praxis “ , und es ist von großer Bedeutung.

Durch die Fokussierung auf einen Grund für den Austausch unserer Komponenten können wir bessere Anwendungen erstellen, die zudem einfach zu konfigurieren sind.

Vereinfachen wir unsere Komponente.

Komplikation


Angenommen, einem Monat nach der Implementierung der Hauptkomponente wurde einem Entwickler aus unserem Team eine neue Funktion zugewiesen. Nun kann der Benutzer jeden Beitrag ausblenden (z. B. wenn er unangemessenen Inhalt enthält).

Es ist leicht zu machen!

class Main extends React.Component {
  state = { posts: [], isSidebarOpen: false, postsToHide: [] };
  // older methods
  get filteredPosts() {
    // Return posts in state, without the postsToHide
  }
  render() {
    return (
      <div>
        <header>
          // Header JSX
        </header>
        <aside id="header">
          // Sidebar JSX
        </aside>
        <div id="post-container">
          {this.filteredPosts.map(post => {
            return (
              <div className="post">
                // Post JSX
              </div>
            );
          })}
        </div>
      </div>
    );
  }
}

Unser Kollege hat es leicht gemeistert. Sie fügte nur eine neue Methode und eine neue Eigenschaft hinzu. Keiner derjenigen, die sich die kurze Liste der Änderungen angesehen haben, hatte Einwände.

Ein paar Wochen später wird eine weitere Funktion angekündigt - eine verbesserte Seitenleiste für die mobile Version. Anstatt sich mit CSS zu beschäftigen, entscheidet der Entwickler, mehrere JSX-Komponenten zu erstellen, die nur auf mobilen Geräten ausgeführt werden können.

class Main extends React.Component {
  state = {
    posts: [],
    isSidebarOpen: false,
    postsToHide: [],
    isMobileSidebarOpen: false
  };
  // older methods
  handleOpenSidebar() {
    if (this.isMobile()) {
      this.openMobileSidebar();
    } else {
      this.openSidebar();
    }
  }
  openSidebar() {
    // Open regular sidebar
  }
  openMobileSidebar() {
    // Open mobile sidebar
  }
  isMobile() {
    // Check if mobile device
  }
  render() {
    // Render method
  }
}

Eine weitere kleine Änderung. Ein Paar neu benannter Methoden und eine neue Eigenschaft.

Und hier haben wir ein Problem. Mainführt immer noch nur eine Funktion aus (Rendern des Hauptbildschirms), aber Sie betrachten alle diese Methoden, mit denen wir uns jetzt beschäftigen:

class Main extends React.Component {
  state = {
    posts: [],
    isSidebarOpen: false,
    postsToHide: [],
    isMobileSidebarOpen: false
  };
  componentDidMount() {
    this.loadPosts();
  }
  loadPosts() {
    // Load posts and save to state
  }
  handleOpenSidebar() {
    // Check if mobile then open relevant sidebar
  }
  handleCloseSidebar() {
    // Close both sidebars
  }
  openSidebar() {
    // Open regular sidebar
  }
  openMobileSidebar() {
    // Open mobile sidebar
  }
  isMobile() {
    // Check if mobile device
  }
  get filteredPosts() {
    // Return posts in state, without the postsToHide
  }
  render() {
    // Render method
  }
}

Unsere Komponente wird groß und unhandlich, sie ist schwer zu verstehen. Und mit der Erweiterung wird sich die Funktionssituation nur verschlechtern.

Was ist schief gelaufen?

Einziger Grund


Kehren wir zur Definition des Grundsatzes der alleinigen Verantwortung zurück: Jede Komponente sollte nur einen Grund für Änderungen haben .

Zuvor haben wir die Darstellung der Beiträge geändert, sodass wir unsere Hauptkomponente ändern mussten. Als Nächstes haben wir die Art und Weise geändert, in der die Seitenleiste geöffnet wurde - und erneut die Hauptkomponente.

Diese Komponente hat viele unabhängige Gründe für Änderungen. Das bedeutet, dass es zu viele Funktionen ausführt .

Mit anderen Worten: Wenn Sie einen Teil Ihrer Komponente erheblich ändern können und dies nicht zu Änderungen in einem anderen Teil der Komponente führt, hat die Komponente zu viel Verantwortung.

Effizientere Trennung


Die Lösung des Problems ist einfach: Es ist notwendig, die Hauptkomponente in mehrere Teile aufzuteilen. Wie kann man das machen?

Fangen wir von vorne an. Das Rendern des Hauptbildschirms bleibt in der Verantwortung der Hauptkomponente, wir reduzieren sie jedoch nur, um die zugehörigen Komponenten anzuzeigen:

class Main extends React.Component {
  render() {
    return (
      <Layout>
        <PostList />
      </Layout>
    );
  }
}

Großartig

Wenn wir plötzlich das Layout des Hauptbildschirms ändern (z. B. zusätzliche Abschnitte hinzufügen), ändert sich Main. In anderen Fällen haben wir keinen Grund, es anzufassen. Fein

Gehen wir zu Layout:

class Layout extends React.Component {
  render() {
    return (
      <SidebarDisplay>
        {(isSidebarOpen, toggleSidebar) => (
          <div>
            <Header openSidebar={toggleSidebar} />
            <Sidebar isOpen={isSidebarOpen} close={toggleSidebar} />
          </div>
        )}
      </SidebarDisplay>
    );
  }
}

Hier ist es etwas komplizierter. Es Layoutist für das Rendern der Markup-Komponenten (Seiten- / obere Platte) verantwortlich. Wir werden jedoch nicht in Versuchung geraten, und es wird uns nicht die LayoutVerantwortung übertragen, zu bestimmen, ob die Seitenleiste geöffnet ist oder nicht.

Wir weisen diese Funktion einer Komponente zu SidebarDisplay, die die erforderlichen Methoden oder Zustände an die Komponenten Headerund überträgt Sidebar.

(Das Obige ist ein Beispiel für das Muster " Rendern von Requisiten über Kinder" in "React". Wenn Sie nicht mit dem Muster vertraut sind, machen Sie sich keine Sorgen. Es ist wichtig, dass es eine separate Komponente gibt, die den Status "Öffnen / Schließen" der Seitenleiste steuert.)

Und dann Sidebarkann es ziemlich einfach sein, wenn es antwortet Nur zum Rendern der Seitenleiste rechts.

class Sidebar extends React.Component {
  isMobile() {
    // Check if mobile
  }
  render() {
    if (this.isMobile()) {
      return <MobileSidebar />;
    } else {
      return <DesktopSidebar />;
    }
  }
}

Wiederum widerstehen wir der Versuchung, JSX für Computer / Mobilgeräte direkt in diese Komponente einzufügen, da in diesem Fall zwei Gründe für die Änderung vorliegen.

Schauen wir uns eine andere Komponente an:

class PostList extends React.Component {
  state = { postsToHide: [] }
  filterPosts(posts) {
    // Show posts, minus hidden ones
  }
  hidePost(post) {
    // Save hidden post to state
  }
  render() {
    return (
      <PostLoader>
        {
          posts => this.filterPosts(posts).map(post => <Post />)
        }
      </PostLoader>
    )
  }
}

PostListändert sich nur, wenn wir die Darstellung der Liste der Beiträge ändern. Scheint offensichtlich, richtig? Genau das brauchen wir.

PostLoaderändert sich nur, wenn wir ändern, wie Beiträge geladen werden. Und schließlich Poständert sich dies nur, wenn wir die Art und Weise ändern, in der der Beitrag gezeichnet wird.

Fazit


Alle diese Komponenten sind winzig und erfüllen eine kleine Funktion. Die Gründe für die Änderungen sind leicht zu erkennen und die Komponenten selbst werden getestet und korrigiert.

Unsere Anwendung ist jetzt viel einfacher zu ändern - ordnen Sie die Komponenten neu an, fügen Sie eine neue hinzu und erweitern Sie die vorhandene Funktionalität. Sie müssen sich nur eine Komponentendatei ansehen, um herauszufinden, wozu sie dient.

Wir wissen, dass sich unsere Komponenten im Laufe der Zeit ändern und wachsen werden. Die Anwendung dieser universellen Regel wird Ihnen jedoch dabei helfen, technische Schulden zu vermeiden und die Geschwindigkeit des Teams zu erhöhen. Sie entscheiden, wie die Komponenten verteilt werden sollen, denken Sie jedoch daran, dass es nur einen Grund gibt, eine Komponente zu ändern .

Vielen Dank für Ihre Aufmerksamkeit und freuen uns auf Ihre Kommentare!

Jetzt auch beliebt: