Dynamicznie dodawaj komponenty potomne w React

86

Moim celem jest dynamiczne dodawanie komponentów do strony / komponentu nadrzędnego.

Zacząłem od podstawowego przykładowego szablonu, takiego jak ten:

main.js:

var App = require('./App.js');
var SampleComponent = require('./SampleComponent.js');
ReactDOM.render(<App/>, document.body);
ReactDOM.render(<SampleComponent name="SomeName"/>, document.getElementById('myId'));

App.js:

var App = React.createClass({
    render: function() {
        return (
            <div>
                <h1>App main component! </h1>
                <div id="myId">myId div</div>
            </div>

        );
    }

});

SampleComponent.js:

var SampleComponent = React.createClass({
    render: function() {
        return (
            <div>
                <h1>Sample Component! </h1>
            </div>
        );
    }
});

Tutaj SampleComponentjest zamontowany do <div id="myId"></div>węzła, który jest wstępnie zapisany w App.jsszablonie. Ale co, jeśli muszę dodać nieokreśloną liczbę składników do składnika aplikacji? Oczywiście nie mogę mieć wszystkich wymaganych elementów div .

Po przeczytaniu kilku samouczków nadal nie rozumiem, w jaki sposób komponenty są tworzone i dynamicznie dodawane do komponentu macierzystego. Jak to zrobić?

Justin Trevein
źródło
2
Czy jest jakiś powód, dla którego oba komponenty są montowane na różnych elementach? 99% aplikacji React wywołuje tylko ReactDOM.renderraz, a wszystkie inne komponenty są elementami
podrzędnymi
1
Nie, teraz rozumiem, że to nie jest poprawny sposób :)
Justin Trevein

Odpowiedzi:

67

Musisz przekazać swoje komponenty jako dzieci, na przykład:

var App = require('./App.js');
var SampleComponent = require('./SampleComponent.js');
ReactDOM.render(
    <App>
        <SampleComponent name="SomeName"/> 
    <App>, 
    document.body
);

A następnie dołącz je do treści komponentu:

var App = React.createClass({
    render: function() {
        return (
            <div>
                <h1>App main component! </h1>
                {
                    this.props.children
                }
            </div>
        );
    }
});

Nie musisz ręcznie manipulować kodem HTML, React zrobi to za Ciebie. Jeśli chcesz dodać jakieś komponenty potomne, wystarczy zmienić właściwości lub stan, w którym to zależy. Na przykład:

var App = React.createClass({

    getInitialState: function(){
        return [
            {id:1,name:"Some Name"}
        ]
    },

    addChild: function() {
        // State change will cause component re-render
        this.setState(this.state.concat([
            {id:2,name:"Another Name"}
        ]))
    }

    render: function() {
        return (
            <div>
                <h1>App main component! </h1>
                <button onClick={this.addChild}>Add component</button>
                {
                    this.state.map((item) => (
                        <SampleComponent key={item.id} name={item.name}/>
                    ))
                }
            </div>
        );
    }

});
Nikolai Mavrenkov
źródło
2
Dzięki! Brakowało mi faktu, że komponenty można zdefiniować w render () na podstawie ich stanu. Twoja odpowiedź tutaj jest najbardziej kompletnym jeden (wspomina wypełniania stan z komponentami) i odpowiada dokładnie na pytanie :)
Justin Trevein
Jak przekazujesz dzieciom rekwizyty, które zostały dołączone?
BrightIntelDusk,
Aby więc kontynuować: jeśli chcę stworzyć aplikację na jedną stronę, która ma kilka zakładek i okien wyskakujących z przycisków, muszę kontynuować i renderować te komponenty (puste), ukryć je (w jakiś sposób), a następnie zmodyfikować stan, aby „pojawiają się” we właściwym czasie? Gdybyś spróbował stworzyć aplikację z wieloma kartami na jednej stronie w React, czy to najlepszy sposób, aby o tym pomyśleć? A może coś mi brakuje? Dziękuję Ci!
Elisabeth
1
@Elisabeth Yes, to prawda. W rzeczywistości masz dwie główne opcje: albo zawsze renderuj wszystko i po prostu ukrywaj rzeczy za pomocą CSS, warunkowo stosując klasy CSS lub style wbudowane; lub renderuj rzeczy warunkowo, zwracając element React lub wartość null w zależności od stanu aplikacji. Te dwa podejścia mają zalety i wady i często są używane w tej samej aplikacji dla różnych komponentów w zależności od potrzeb.
Nikolai Mavrenkov
1
Dziękuję Nikolai! To naprawdę pomaga mi utrwalić myśli o React. To zupełnie inny sposób projektowania aplikacji i myślenia o modelu DOM, niż w przypadku zwykłego JavaScript i jQuery.
Elisabeth
6

Po pierwsze, nie użyłbym document.body . Zamiast tego dodaj pusty pojemnik:

index.html:

<html>
    <head></head>
    <body>
        <div id="app"></div>
    </body>
</html>

Następnie zdecyduj się renderować tylko swój <App />element:

main.js:

var App = require('./App.js');
ReactDOM.render(<App />, document.getElementById('app'));

Wewnątrz App.jsmożesz zaimportować inne komponenty i całkowicie zignorować kod renderujący DOM:

App.js:

var SampleComponent = require('./SampleComponent.js');

var App = React.createClass({
    render: function() {
        return (
            <div>
                <h1>App main component!</h1>
                <SampleComponent name="SomeName" />
            </div>
        );
    }
});

SampleComponent.js:

var SampleComponent = React.createClass({
    render: function() {
        return (
            <div>
                <h1>Sample Component!</h1>
            </div>
        );
    }
});

Następnie można programowo współdziałać z dowolną liczbą komponentów, importując je do niezbędnych plików komponentów za pomocą require.

Chris
źródło
Nie ma problemu, po prostu widziałem, jak ktoś kopiuje, wkleja i spędza na tym godziny ...
Neil Twist
6

Udostępniam tutaj moje rozwiązanie na podstawie odpowiedzi Chrisa. Mam nadzieję, że może pomóc innym.

Musiałem dynamicznie dołączyć elementy podrzędne do mojego JSX, ale w prostszy sposób niż warunkowe sprawdzenia w mojej instrukcji return. Chcę pokazać program ładujący w przypadku, gdy elementy potomne nie są jeszcze gotowe. Oto ona:

export class Settings extends React.PureComponent {
  render() {
    const loading = (<div>I'm Loading</div>);
    let content = [];
    let pushMessages = null;
    let emailMessages = null;

    if (this.props.pushPreferences) {
       pushMessages = (<div>Push Content Here</div>);
    }
    if (this.props.emailPreferences) {
      emailMessages = (<div>Email Content Here</div>);
    }

    // Push the components in the order I want
    if (emailMessages) content.push(emailMessages);
    if (pushMessages) content.push(pushMessages);

    return (
      <div>
        {content.length ? content : loading}
      </div>
    )
}

Teraz zdaję sobie sprawę, że mógłbym po prostu umieścić {pushMessages}i {emailMessages}bezpośrednio w moim return()poniżej, ale zakładając, że mam jeszcze więcej warunkowej zawartości, return()wyglądałbym po prostu na zagracony.


źródło
4

Po pierwsze ostrzeżenie: nigdy nie powinieneś majstrować przy DOM zarządzanym przez React, który robisz dzwoniąc ReactDOM.render(<SampleComponent ... />);

Z Reactem powinieneś używać SampleComponent bezpośrednio w głównej aplikacji.

var App = require('./App.js');
var SampleComponent = require('./SampleComponent.js');
ReactDOM.render(<App/>, document.body);

Zawartość twojego komponentu nie ma znaczenia, ale powinna być używana w następujący sposób:

var App = React.createClass({
    render: function() {
        return (
            <div>
                <h1>App main component! </h1>
                <SampleComponent name="SomeName"/>
            </div>
        );
    }
});

Następnie możesz rozszerzyć składnik aplikacji, aby używał listy.

var App = React.createClass({
    render: function() {
        var componentList = [
            <SampleComponent name="SomeName1"/>,
            <SampleComponent name="SomeName2"/>
        ]; // Change this to get the list from props or state
        return (
            <div>
                <h1>App main component! </h1>
                {componentList}
            </div>
        );
    }
});

Naprawdę poleciłbym przejrzenie dokumentacji Reacta, a następnie wykonanie instrukcji „Rozpocznij”. Czas, który na to poświęcisz, opłaci się później.

https://facebook.github.io/react/index.html

Neil Twist
źródło
W jaki sposób tablica zapełni komponent potomny bez mapowania?
solanki ...
1
@solanki ... Mapowanie jest wymagane tylko wtedy, gdy lista nie jest listą komponentów Reacta. Lista tutaj składa się z dwóch SampleComponents, co w związku z tym nie wymaga mapowania. Gdyby lista była listą nazwisk, wymagałaby odwzorowania na komponenty Reacta.
Neil Twist
@solanki ... Pamiętaj, że wynik mapowania to po prostu kolejna lista
Neil Twist
1
Dokładnie to, czego szukałem. Mój przypadek użycia wymagał opcjonalnego umieszczenia komponentów JSX w pustym miejscu <div>przypisanym do zmiennej, która została następnie wstawiona do JSX, jak {content}w mojej instrukcji return. Korzystanie z pustej tablicy było takie proste!