Inhaltsverzeichnis


Nichts verpassen?

react ÔÇó basics | Max Stoiber ÔÇó | 13 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>

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:

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.

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.