Inhaltsverzeichnis

react basics | Max Stoiber | 12 Minuten

Info: Wir haben den Begriff “Children” -Kinder, bewusst nicht übersetzt.

Nehmen wir an, wir haben eine <Grid /> Komponente, die <Row /> Komponenten enthalten kann. Diese würdest du so verwenden:

<Grid>
  <Row />
  <Row />
  <Row />
</Grid>

Diese drei Row Komponenten werden an die Grid Komponente als props.children übergeben. Mit einem expression container (das ist der technische Begriff für diese geschweiften Klammern in JSX) können Parents ihre Children rendern.

class Grid extends React.Component {
  render() {
    return <div>{this.props.children}</div>
  }
}

Parents können auch entscheiden, keine Children zu rendern oder zu verändern. Diese <Fullstop /> Komponente zum Beispiel rendert ihre Children überhaupt nicht.

class Fullstop extends React.Component {
  render() {
    return <h1>Hello world!</h1>
  }
}

Es spielt keine Rolle, welche Children du an diese Komponente übergibst, sie wird immer „Hello world!“ zeigen und nichts anderes.

Hinweis: Das <h1> in dem Beispiel oben (ähnlich wie alle HTML-Primitiven) rendert seine Children nicht. In diesem Fall „Hello World!“.

Alles kann ein Child sein

Children Zum Beispiel können wir unserer Komponente von oben einen Text übergeben und es wird problemlos funktionieren.

<Grid>Hello world!</Grid>

JSX entfernt automatisch weiße Lücken am Anfang und Ende einer Zeile sowie auch Leerzeilen. Außerdem kürzt es leere Zeilen in die Mitte von Strings.

Das bedeutet, dass folgende Beispiele das Gleiche machen werden:

<Grid>Hello world!</Grid>

<Grid>
  Hello world!
</Grid>

<Grid>
  Hello
  world!
</Grid>

<Grid>

  Hello world!
</Grid>

Du kannst auch mehrere Arten von Children ohne Probleme mischen und anpassen:

<Grid>
  Here is a row:
  <Row />
  Here is another row:
  <Row />
</Grid>

Funktion als Child

Wir können jeden JavaScript-Ausdruck als Child ausführen:

class Executioner extends React.Component {
  render() {
    // See how we're calling the child as a function?
    //                        ?
    return this.props.children()
  }
}

Diese Komponente würdest du dann so verwenden:

<Executioner>
  {() => <h1>Hello World!</h1>}
</Executioner>

Dieses Beispiel ist nicht sonderlich sinnvoll, aber es zeigt die Idee.

Stell dir vor, du müsstest Daten von einem Server abrufen. Du könntest dies auf viele Arten tun, aber es eben auch mit dem Functin-as-a-child-Muster in folgender Weise:

<Fetch url="api.myself.com">
  {(result) => <p>{result}</p>}
</Fetch>

Mach dir keine Sorgen, wenn das erstmal zu viel erscheint. Alles was ich will ist, dass du nicht überrascht bist, wenn du dies in fremdem Code siehst. Mit Children ist alles möglich.

Children manipulieren

Wenn du einen Blick auf die React-Dokumentation wirfst, wirst du sehen, dass „Children eine intransparente Datenstruktur sind“. Was sie uns im wesentlichen sagen, ist, dass props.children jeder Typ sein kann - eine Funktion, ein Objekt, ein Feld, etc. Da du alles übergeben kannst, weißt du es nie sicher. React bietet eine Reihe von Helfer-Funktionen, um das Bearbeiten von Children einfach und schmerzfrei zu machen. Diese sind bei React.Children erhältlich.

Über Children iterieren

Die beiden offentsichtlichsten Helfer sind React.Children.map und React.Children.forEach. Sie arbeiten genau wie Ihre Feld-Counterparts, außer dass sie auch funktionieren, wenn eine Funktion, Objekt oder irgendetwas als Children übergeben wird.

class IgnoreFirstChild extends React.Component {
  render() {
    const children = this.props.children
    return (
      <div>
        {React.Children.map(children, (child, i) => {
          // Ignore the first child
          if (i < 1) return
          return child
        })}
      </div>
    )
  }
}

Die <IgnoreFirstChild /> Komponente katalogisiert alle ihre Children, ignoriert das erste child und gibt alle anderen zurück.

<IgnoreFirstChild>
  <h1>First</h1>
  <h1>Second</h1> // <- Only this is rendered
</IgnoreFirstChild>

In diesem Fall hätten wir auch this.props.children.map benutzt. Aber was wäre passiert, wenn jemand eine Funktion als Child übergeben hätte? `this.props.children’ wäre eine Funktion statt eines Felds gewesen, und wir hätten eine Fehlermeldung!

Mit der React.Children.map Funktion allerdings, ist das gar kein Problem:

<IgnoreFirstChild>
  {() => <h1>First</h1>} // <- Ignored ?
</IgnoreFirstChild>

Children zählen

Da this.props.children jeder Typ sein kann, ist es relativ schwer zu überprüfen, wie viele Children eine Komponente hat! Naiv this.props.children.length auszuführen würde nicht funktionieren, wenn ein String oder eine Funktion übergeben würde. Wir hätten nur ein Child, “Hello World!“, aber die Anzahl würde stattdessen 12 zeigen.

class ChildrenCounter extends React.Component {
  render() {
    return <p>React.Children.count(this.props.children)</p>
  }
}

Es gibt die Anzahl der Children wieder, egal welcher Art sie sind:

// Renders "1"
<ChildrenCounter>
  Second!
</ChildrenCounter>

// Renders "2"
<ChildrenCounter>
  <p>First</p>
  <ChildComponent />
</ChildrenCounter>

// Renders "3"
<ChildrenCounter>
  {() => <h1>First!</h1>}
  Second!
  <p>Third!</p>
</ChildrenCounter>
class Sort extends React.Component {
  render() {
    const children = React.Children.toArray(this.props.children)
    // Sort and render the children
    return <p>{children.sort().join(' ')}</p>
  }
}
<Sort>
  // We use expression containers to make sure our strings
  // are passed as three children, not as one string
  {'bananas'}{'oranges'}{'apples'}
</Sort>

Das Beispiel oben rendert die Strings, aber sortiert.

Children für jeden Bereich konvertieren

Als letzten Ausweg, falls keine der oben gezeigten Methoden für dich passt, kannst du Children zu einem Array mit React.Children.toArray konvertieren. Das wäre nützlich, wenn du z.B. sortieren müsstest:

Ein einzelnes Children durchsetzen

Wenn du an unsere <Executioner /> Komponente oben denkst, wird nur ein einziges Child erwartet, das eine Funktion sein muss.

class Executioner extends React.Component {
  render() {
    return this.props.children()
  }
}

Wir könnten versuchen das mit propTypes zu erzwingen, das würde so aussehen:

Executioner.propTypes = {
  children: React.PropTypes.func.isRequired,
}

Das würde eine Nachricht an die Konsole im Browser ausgeben, was Entwickler ignorieren können. Stattdessen könnten wir auch React.Children.only in unserer render Methode zu nutzen!

class Executioner extends React.Component {
  render() {
    return React.Children.only(this.props.children)()
  }
}

Das gibt dann ein einziges Child in this.props.children zurück. Wenn es mehr als ein child gibt, gibt es einen Fehler aus - die Anwendung bricht ab. Dies hindert faule Entwickler daran, Unsinn mit unserer Komponente zu treiben.

Children editieren

Wir können beliebige Komponenten als Children rendern, aber sie immer noch von dem Parent kontrollieren lassen, anstelle von der Komponente von der wir sie gerendert haben. Um dies zu verdeutlichen, nehmen wir eine RadioGroup Komponente, die eine Anzahl von RadioButton Komponenten beinhalten kann. (die eine <input type="radio"> eine in ein <label> rendern

Die RadioButton s werden nicht von der RadioGroup selbst gerendert, sie werden als Children benutzt. Das bedeutet irgendwo in unserer Anwendung haben wir diesen Code:

render() {
  return(
    <RadioGroup>
      <RadioButton value="first">First</RadioButton>
      <RadioButton value="second">Second</RadioButton>
      <RadioButton value="third">Third</RadioButton>
    </RadioGroup>
  )
}

Es gibt jedoch ein Problem mit diesem Code. Die Eingabefelder sind nicht gruppiert. Um input Tags zu gruppieren, brauchen sie alle das gleiche name-Attribut. Wir könnten natürlich fortfahren und jedem einzelnen RadioButton ein name-Attribut zuordnen:

<RadioGroup>
  <RadioButton name="g1" value="first">First</RadioButton>
  <RadioButton name="g1" value="second">Second</RadioButton>
  <RadioButton name="g1" value="third">Third</RadioButton>
</RadioGroup>

Aber das ist a) langweilig und b) fehleranfällig. Wir haben doch die ganze Macht von JavaScript in unseren Händen! Können wir das nutzen, um unserer RadioGroup das name-Attribut zuzuweisen, das allen Children übergeben wird?

Children-Gerüst ändern

In unserer RadioGroup fügen wir eine neue Methode namens renderChildren hinzu, wo wir die Children-props editieren:

class RadioGroup extends React.Component {
  constructor() {
    super()
    // Bind the method to the component context
    this.renderChildren = this.renderChildren.bind(this)
  }

  renderChildren() {
    // TODO: Change the name prop of all children
    // to this.props.name
    return this.props.children
  }

  render() {
    return (
      <div className="group">
        {this.renderChildren()}
      </div>
    )
  }
}

Beginnen wir mit einem .map() über die children, um jedes individuelles Child zu bekommen:

renderChildren() {
  return React.Children.map(this.props.children, child => {
    // TODO: Change the name prop to this.props.name
    return child
  })
}

Wie können wir ihre Eigenschaften bearbeiten?

Unveränderbare klonende Elemente

Hier kommt die letzte Hilfs-Methode von heute ins Spiel. Wie der Name schon sagt - React.cloneElement klont ein Element. Wir geben ihm das Element, das wir als erstes klonen wollen als erstes Argument. Als zweites Argument können wir dann ein Objekt von props übergeben, das wir auf das geklonte Element setzen wollen:

const cloned = React.cloneElement(element, {
  new: 'yes!'
})

Das cloned-Element wird jetzt die new prop auf "yes!" gesetzt haben.

Das ist genau was wir brauchen, um unsere RadioGroup zu vollenden. Wir klonen jedes Child und setzen das name-Prop des geklonten childs auf this.props.name:

renderChildren() {
  return React.Children.map(this.props.children, child => {
    return React.cloneElement(child, {
      name: this.props.name
    })
  })
}

Der letzte Schritt ist es, einen eindeutiges name-Attribut an unsere RadioGroup zu übergeben:

<RadioGroup name="g1">
  <RadioButton value="first">First</RadioButton>
  <RadioButton value="second">Second</RadioButton>
  <RadioButton value="third">Third</RadioButton>
</RadioGroup>

Es funktioniert! Anstatt manuell jedes name-Attribut auf jeden RadioButton zu setzen, sagen wir einfach unserer Radiogroup, welchen Namen wir wollen und es wird sich drum gekümmert.

Credits

Original gepostet von Max Stoiber auf mxstbr.blog.