Jak przekazać rekwizyty do {this.props.children}

888

Próbuję znaleźć właściwy sposób zdefiniowania niektórych składników, które mogłyby być używane w ogólny sposób:

<Parent>
  <Child value="1">
  <Child value="2">
</Parent>

Istnieje oczywiście logika renderowania między komponentami nadrzędnymi i podrzędnymi, co można sobie wyobrazić <select>i <option>jako przykład tej logiki.

To jest fikcyjna implementacja na potrzeby pytania:

var Parent = React.createClass({
  doSomething: function(value) {
  },
  render: function() {
    return (<div>{this.props.children}</div>);
  }
});

var Child = React.createClass({
  onClick: function() {
    this.props.doSomething(this.props.value); // doSomething is undefined
  },
  render: function() {
    return (<div onClick={this.onClick}></div>);
  }
});

Pytanie brzmi: ilekroć użyjesz {this.props.children}do zdefiniowania elementu opakowania, w jaki sposób przekazujesz jakąś właściwość wszystkim jego potomkom?

plus-
źródło

Odpowiedzi:

955

Klonowanie dzieci za pomocą nowych rekwizytów

Możesz użyć React.Children do iteracji nad dziećmi, a następnie sklonować każdy element z nowymi rekwizytami (płytko scalone) za pomocą React.cloneElement, np .:

import React, { Children, isValidElement, cloneElement } from 'react';

const Child = ({ doSomething, value }) => (
  <div onClick={() => doSomething(value)}>Click Me</div>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    const childrenWithProps = Children.map(children, child => {
      // Checking isValidElement is the safe way and avoids a TS error too.
      if (isValidElement(child)) {
        return cloneElement(child, { doSomething })
      }

      return child;
    });

    return <div>{childrenWithProps}</div>
  }
};

ReactDOM.render(
  <Parent>
    <Child value="1" />
    <Child value="2" />
  </Parent>,
  document.getElementById('container')
);

Fiddle: https://jsfiddle.net/2q294y43/2/

Wywoływanie dzieci jako funkcja

Możesz także przekazywać rekwizyty dzieciom z rekwizytami renderującymi . W tym podejściu dzieci (które mogą być childreninnymi nazwami rekwizytów) to funkcja, która może zaakceptować argumenty, które chcesz przekazać, i zwraca dzieci:

const Child = ({ doSomething, value }) => (
  <div onClick={() =>  doSomething(value)}>Click Me</div>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    // Note that children is called as a function and we can pass args to it
    return <div>{children(doSomething)}</div>
  }
};

ReactDOM.render(
  <Parent>
    {doSomething => (
      <React.Fragment>
        <Child doSomething={doSomething} value="1" />
        <Child doSomething={doSomething} value="2" />
      </React.Fragment>
    )}
  </Parent>,
  document.getElementById('container')
);

Zamiast <React.Fragment>lub po prostu <>możesz również zwrócić tablicę, jeśli wolisz.

Fiddle: https://jsfiddle.net/ferahl/y5pcua68/7/

Dominik
źródło
7
To mi nie działa. nie jest to zdefiniowane w React.cloneElement ()
Patrick
12
Ta odpowiedź nie działa, valueprzekazana do doSomethingzostała utracona.
Dave
3
@DominicTobias Arg, przepraszam, przełączyłem console.log na alert i zapomniałem połączyć dwa parametry w jeden ciąg.
Dave
1
Ta odpowiedź była bardzo pomocna, ale natknąłem się na problem, który nie został tutaj wymieniony i zastanawiałem się, czy to coś nowego, co się zmieniło, czy też jest to coś dziwnego z mojej strony. Kiedy sklonowałem mój element potomny, jego potomek został ustawiony na stary element, dopóki nie dodałem this.props.children.props.children do trzeciego argumentu cloneElement.
fenen
7
Co się stanie, jeśli dziecko zostanie załadowane za pomocą trasy (v4) załadowanej z oddzielnej strony trasy?
blamb 13.03.18
394

Aby uzyskać nieco czystszy sposób, spróbuj:

<div>
    {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
</div>

Edycja: Aby używać z wieloma pojedynczymi dziećmi (dziecko musi być komponentem), możesz to zrobić. Testowane w 16.8.6

<div>
    {React.cloneElement(props.children[0], { loggedIn: true, testingTwo: true })}
    {React.cloneElement(props.children[1], { loggedIn: true, testProp: false })}
</div>
Andres F. Garcia
źródło
10
Użyłem najbardziej ocenianej odpowiedzi, ale ta jest o wiele prostsza! Tego rozwiązania używają również na stronie przykładów routera reagującego.
captDaylight
10
Czy ktoś mógłby wyjaśnić, jak to działa (lub co faktycznie robi)? Czytając dokumentację , nie widziałem, jak to zstąpiłoby na dzieci i dodałoby ten rekwizyt do każdego dziecka - czy właśnie to ma na celu? Jeśli tak, to skąd wiemy, że tak właśnie będzie? Nie jest wcale oczywiste, że nawet przekazywanie nieprzezroczystej struktury danych ( this.props.children) do cloneElement..., które oczekuje ... elementu.
GreenAsJade
51
Dokładnie nie wydaje się, aby działało to z więcej niż jednym dzieckiem.
Danita
17
Możesz więc napisać kod, który działa, gdy ktoś przekazuje tylko jedno dziecko do komponentu, ale gdy dodaje kolejne, powoduje awarię ... co nie brzmi świetnie na pierwszy rzut oka? Wydawałoby się to pułapką dla OP, który pytał konkretnie o przekazywanie rekwizytów wszystkim dzieciom.
GreenAsJade
10
@GreenAsJade jest w porządku, o ile Twój komponent spodziewa się jednego dziecka. Możesz zdefiniować za pomocą komponentów propTypes, że spodziewa się pojedynczego dziecka. React.Children.onlyfunkcja zwraca jedyne dziecko lub zgłasza wyjątek, jeśli jest ich wiele (nie istniałoby, gdyby nie było przypadku użycia).
cchamberlain
80

Spróbuj tego

<div>{React.cloneElement(this.props.children, {...this.props})}</div>

Działa dla mnie za pomocą Reag-15.1.

7 szt
źródło
3
Czy można zwrócić React.cloneElement()bezpośrednio, nie otaczając go <div>tagami? Bo co jeśli dziecko jest <span>(lub czymś innym) i chcemy zachować jego typ elementu tagu?
adrianmc
1
Jeśli jest to jedno dziecko, możesz pominąć opakowanie, a to rozwiązanie działa tylko dla jednego dziecka, więc tak.
ThaJay,
1
Pracuje dla mnie. Bez załączenia <div> jest w porządku.
Crash Override
4
Jeśli musisz wyraźnie wymusić, że otrzymujesz tylko jedno dziecko, możesz to zrobić, React.cloneElement(React.Children.only(this.props.children), {...this.props})co spowoduje błąd, jeśli zostanie przekazane więcej niż jedno dziecko. Więc nie musisz zawijać div.
itsananderson
1
Ta odpowiedź może wygenerować wartość obiektu Cykl: cykliczny obiekt. Chyba że chcesz, aby jeden z rekwizytów dziecka był sam, użyj, let {children, ...acyclicalProps} = this.propsa następnie React.cloneElement(React.Children.only(children), acyclicalProps).
Parabolord
68

Przekaż rekwizyty dzieciom.

Zobacz wszystkie pozostałe odpowiedzi

Przekaż wspólne, globalne dane przez drzewo komponentów poprzez kontekst

Kontekst służy do udostępniania danych, które można uznać za „globalne” dla drzewa komponentów React, takich jak bieżący uwierzytelniony użytkownik, motyw lub preferowany język. 1

Oświadczenie: To jest zaktualizowana odpowiedź, poprzednia użyła starego kontekstowego interfejsu API

Opiera się na zasadzie konsument / dostarcz. Najpierw utwórz kontekst

const { Provider, Consumer } = React.createContext(defaultValue);

Następnie użyj przez

<Provider value={/* some value */}>
  {children} /* potential consumers */
<Provider />

i

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>

Wszyscy konsumenci, którzy są potomkami dostawcy, będą renderować ponownie, ilekroć zmieni się wartość prop. Propagacja od dostawcy do jego potomków Konsumentów nie podlega metodzie shouldComponentUpdate, więc konsument jest aktualizowany nawet wtedy, gdy element nadrzędny wyskoczy z aktualizacji. 1

Pełny przykład, pseudo-kod.

import React from 'react';

const { Provider, Consumer } = React.createContext({ color: 'white' });

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: { color: 'black' },
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

class Toolbar extends React.Component {
  render() {
    return ( 
      <div>
        <p> Consumer can be arbitrary levels deep </p>
        <Consumer> 
          {value => <p> The toolbar will be in color {value.color} </p>}
        </Consumer>
      </div>
    );
  }
}

1 https://facebook.github.io/react/docs/context.html

Lyubomir
źródło
6
W przeciwieństwie do przyjętej odpowiedzi, będzie to działać poprawnie, nawet jeśli w ramach elementu nadrzędnego znajdują się inne elementy. To zdecydowanie najlepsza odpowiedź.
Zaptree,
6
Rekwizyty! = Kontekst
Petr Peller,
nie można polegać na zmianach propagowanych przez kontekst. Używaj rekwizytów, jeśli to możliwe, zmieniają się.
ThaJay,
1
Może nie rozumiem, ale czy nie jest błędem stwierdzenie „kontekst udostępnia rekwizyty”? Kiedy ostatnio użyłem kontekstu, była to osobna rzecz (tj. this.context) - nie magicznie połączyła kontekstu z rekwizytami. Trzeba było celowo ustawić i użyć kontekstu, co jest zupełnie inną rzeczą.
Josh
Rozumiesz doskonale, to było niepoprawne. Zredagowałem swoją odpowiedź.
Lyubomir
48

Przekazywanie rekwizytów zagnieżdżonym dzieciom

Wraz z aktualizacją do React 16.6 można teraz używać React.createContext i contextType .

import * as React from 'react';

// React.createContext accepts a defaultValue as the first param
const MyContext = React.createContext(); 

class Parent extends React.Component {
  doSomething = (value) => {
    // Do something here with value
  };

  render() {
    return (
       <MyContext.Provider value={{ doSomething: this.doSomething }}>
         {this.props.children}
       </MyContext.Provider>
    );
  }
}

class Child extends React.Component {
  static contextType = MyContext;

  onClick = () => {
    this.context.doSomething(this.props.value);
  };      

  render() {
    return (
      <div onClick={this.onClick}>{this.props.value}</div>
    );
  }
}


// Example of using Parent and Child

import * as React from 'react';

class SomeComponent extends React.Component {

  render() {
    return (
      <Parent>
        <Child value={1} />
        <Child value={2} />
      </Parent>
    );
  }
}

React.createContext świeci tam, gdzie przypadek React.cloneElement nie mógł obsłużyć zagnieżdżonych komponentów

class SomeComponent extends React.Component {

  render() {
    return (
      <Parent>
        <Child value={1} />
        <SomeOtherComp><Child value={2} /></SomeOtherComp>
      </Parent>
    );
  }
}
Kenneth Truong
źródło
3
Czy możesz wyjaśnić, dlaczego funkcje => są złą praktyką? Funkcja => pomaga powiązać procedury obsługi zdarzeń w celu uzyskania thiskontekstu
Kenneth Truong
@KennethTruong, ponieważ za każdym razem, gdy jest renderowany, tworzy funkcję.
itdoesntwork
9
@itdoesntwork to nieprawda. Tworzy nową funkcję tylko podczas tworzenia klasy. Nie jest tworzony podczas funkcji renderowania.
Kenneth Truong,
@KennethTruong reagjs.org/docs/faq-functions.html#arrow-function-in-render Myślałem, że mówisz o funkcji strzałki w renderowaniu.
itdoesntwork
24

Możesz użyć React.cloneElement, lepiej wiedzieć, jak to działa, zanim zaczniesz używać go w swojej aplikacji. Został wprowadzony React v0.13, czytaj dalej, aby uzyskać więcej informacji, więc coś z tą pracą dla Ciebie:

<div>{React.cloneElement(this.props.children, {...this.props})}</div>

Przynieś wiersze z dokumentacji React, aby zrozumieć, jak to wszystko działa i jak możesz z nich skorzystać:

W React v0.13 RC2 wprowadzimy nowy API, podobny do React.addons.cloneWithProps, z tym podpisem:

React.cloneElement(element, props, ...children);

W przeciwieństwie do cloneWithProps, ta nowa funkcja nie ma żadnego wbudowanego magicznego zachowania do łączenia stylu i nazwy klasy z tego samego powodu, dla którego nie mamy tej funkcji z transferPropsTo. Nikt nie jest pewien, czym dokładnie jest pełna lista magicznych rzeczy, co utrudnia rozumowanie kodu i ponowne użycie, gdy styl ma inną sygnaturę (np. W nadchodzącym React Native).

React.cloneElement jest prawie równoważny z:

<element.type {...element.props} {...props}>{children}</element.type>

Jednak w przeciwieństwie do JSX i cloneWithProps, zachowuje również ref. Oznacza to, że jeśli dostaniesz dziecko z referencją, nie przypadkowo okradniesz go od swojego przodka. Otrzymasz ten sam numer referencyjny dołączony do nowego elementu.

Jednym z powszechnych schematów jest mapowanie dzieci i dodawanie nowego rekwizytu. Zgłoszono wiele problemów związanych z utratą referencji przez cloneWithProps, co utrudnia rozumowanie kodu. Teraz stosowanie tego samego wzorca z cloneElement będzie działać zgodnie z oczekiwaniami. Na przykład:

var newChildren = React.Children.map(this.props.children, function(child) {
  return React.cloneElement(child, { foo: true })
});

Uwaga: React.cloneElement (child, {ref: 'newRef'}) zastępuje odwołanie, więc dwoje rodziców nadal nie może mieć odwołania do tego samego dziecka, chyba że użyjesz wywołań zwrotnych.

To była kluczowa cecha, aby dostać się do React 0.13, ponieważ rekwizyty są teraz niezmienne. Ścieżką aktualizacji jest często klonowanie elementu, ale może to spowodować utratę referencji. Dlatego potrzebowaliśmy tutaj ładniejszej ścieżki aktualizacji. Gdy aktualizowaliśmy strony na Facebooku, zdaliśmy sobie sprawę, że potrzebujemy tej metody. Otrzymaliśmy takie same opinie od społeczności. Dlatego postanowiliśmy zrobić kolejny RC przed ostatecznym wydaniem, aby upewnić się, że dostaniemy to.

Planujemy ostatecznie wycofać React.addons.cloneWithProps. Jeszcze tego nie robimy, ale jest to dobra okazja, aby zacząć myśleć o własnych zastosowaniach i rozważyć użycie React.cloneElement. Przed faktycznym usunięciem z pewnością wyślemy informację o wycofaniu, więc nie jest konieczne natychmiastowe działanie.

więcej tutaj ...

Alireza
źródło
17

Najlepszym sposobem, który pozwala na przeniesienie własności jest childrenjak funkcja

Przykład:

export const GrantParent = () => {
  return (
    <Parent>
      {props => (
        <ChildComponent {...props}>
          Bla-bla-bla
        </ChildComponent>
      )}
    </Parent>
  )
}

export const Parent = ({ children }) => {
    const somePropsHere = { //...any }
    <>
        {children(somePropsHere)}
    </>
}
Nick Ovchinnikov
źródło
1
Wydaje mi się to o wiele prostsze (i lepsze dla wydajności?) Niż zaakceptowana odpowiedź.
Shikyo
2
Wymaga to, aby dzieci były funkcjami i nie działają w przypadku słabo zagnieżdżonych komponentów
iluzja cyfrowa
@digitalillusion, nie rozumiem co to znaczy nested components. React nie ma zagnieżdżonych wzorów, tylko kompozycje. Tak, dzieci muszą być funkcją, nie ma żadnych konfliktów, ponieważ jest to poprawne dziecko JSX. Czy możesz podać przykład nesting components?
Nick Ovchinnikov
1
Masz rację, że w przypadku głęboko zagnieżdżonych dzieci można również <Parent>{props => <Nest><ChildComponent /></Nest>}</Parent>zająć się (nie pracować), <Parent><Nest>{props => <ChildComponent />}</Nest></Parent>więc zgadzam się, że to najlepsza odpowiedź
iluzja cyfrowa
Podczas próby otrzymuję następujące informacje:TypeError: children is not a function
Ryan Prentiss
6

Musiałem naprawić zaakceptowaną odpowiedź powyżej, aby działała przy użyciu tej zamiast tego wskaźnika. W zakresie funkcji mapy nie zdefiniowano funkcji doSomething .

var Parent = React.createClass({
doSomething: function() {
    console.log('doSomething!');
},

render: function() {
    var that = this;
    var childrenWithProps = React.Children.map(this.props.children, function(child) {
        return React.cloneElement(child, { doSomething: that.doSomething });
    });

    return <div>{childrenWithProps}</div>
}})

Aktualizacja: ta poprawka dotyczy ECMAScript 5, w ES6 nie ma potrzeby w var that = this

olenak
źródło
13
lub po prostu użyjbind()
plus-
1
lub użyj funkcji strzałki, która wiąże się z zasięgiem leksykalnym, zaktualizowałem swoją odpowiedź
Dominic
co by było, gdyby doSomethingwziął przedmiot, jak doSomething: function(obj) { console.log(obj) }w Dzieciątku, do którego byś this.props.doSomething(obj)się wylogował"obj"
conor909 10.04.16
4
@ plus - wiem, że to jest stare, ale użycie bind tutaj jest strasznym pomysłem, bind tworzy nową funkcję, która wiąże kontekst z nową. w zasadzie funkcja wywołująca applymetodę. użycie bind()funkcji renderowania spowoduje utworzenie nowej funkcji za każdym razem, gdy wywoływana jest metoda renderowania.
Bamieh,
6

Czystszy sposób, biorąc pod uwagę jedno lub więcej dzieci

<div>
   { React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>
i reszta
źródło
Ten nie działa dla mnie, daje błąd: dzieci nie zdefiniowane.
Deelux,
@Deelux this.props.children zamiast dzieci
Martin Dawson
przekazuje to dziecko jako swoje własne dzieci this.props. Zasadniczo zalecałbym klonowanie tylko za pomocą określonych rekwizytów, a nie całego shebang.
Andy
Przekazywanie {...this.props}nie działało dla mnie, czy sposób jest {...child.props}poprawny?
Felipe Augusto,
Dla komponentów funkcjonalnych:React.Children.map(children, child => React.cloneElement(child, props))
vsync
5

Żadna z odpowiedzi nie rozwiązuje problemu posiadania dzieci, które NIE są komponentami React, takimi jak ciągi tekstowe. Obejściem może być coś takiego:

// Render method of Parent component
render(){
    let props = {
        setAlert : () => {alert("It works")}
    };
    let childrenWithProps = React.Children.map( this.props.children, function(child) {
        if (React.isValidElement(child)){
            return React.cloneElement(child, props);
        }
          return child;
      });
    return <div>{childrenWithProps}</div>

}
poynting
źródło
5

Nie potrzebujesz już {this.props.children}. Teraz można owinąć dziecko przy użyciu komponentu renderw Routei przekazać swoje rekwizyty jak zwykle:

<BrowserRouter>
  <div>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/posts">Posts</Link></li>
      <li><Link to="/about">About</Link></li>
    </ul>

    <hr/>

    <Route path="/" exact component={Home} />
    <Route path="/posts" render={() => (
      <Posts
        value1={1}
        value2={2}
        data={this.state.data}
      />
    )} />
    <Route path="/about" component={About} />
  </div>
</BrowserRouter>
tak
źródło
2
Renderowane rekwizyty są teraz standardem w React ( reagjs.org/docs/render-props.html ) i warto je rozważyć jako nową akceptowaną odpowiedź na to pytanie.
Ian Danforth,
19
Jak to jest odpowiedź na pytanie?
Maximo Dominguez
4

Parent.jsx:

import React from 'react';

const doSomething = value => {};

const Parent = props => (
  <div>
    {
      !props || !props.children 
        ? <div>Loading... (required at least one child)</div>
        : !props.children.length 
            ? <props.children.type {...props.children.props} doSomething={doSomething} {...props}>{props.children}</props.children.type>
            : props.children.map((child, key) => 
              React.cloneElement(child, {...props, key, doSomething}))
    }
  </div>
);

Child.jsx:

import React from 'react';

/* but better import doSomething right here,
   or use some flux store (for example redux library) */
export default ({ doSomething, value }) => (
  <div onClick={() => doSomething(value)}/>
);

i main.jsx:

import React from 'react';
import { render } from 'react-dom';
import Parent from './Parent';
import Child from './Child';

render(
  <Parent>
    <Child/>
    <Child value='1'/>
    <Child value='2'/>
  </Parent>,
  document.getElementById('...')
);

patrz przykład tutaj: https://plnkr.co/edit/jJHQECrKRrtKlKYRpIWl?p=preview

Maksim Kostromin
źródło
4

Jeśli masz wiele dzieci, którym chcesz przekazać rekwizyty , możesz to zrobić w ten sposób, używając React.Children.map:

render() {
    let updatedChildren = React.Children.map(this.props.children,
        (child) => {
            return React.cloneElement(child, { newProp: newProp });
        });

    return (
        <div>
            { updatedChildren }
        </div>
    );
}

Jeśli twój komponent ma tylko jedno dziecko, nie ma potrzeby mapowania, możesz po prostu sklonować element:

render() {
    return (
        <div>
            {
                React.cloneElement(this.props.children, {
                    newProp: newProp
                })
            }
        </div>
    );
}
Nesha Zoric
źródło
3

Zgodnie z dokumentacją cloneElement()

React.cloneElement(
  element,
  [props],
  [...children]
)

Sklonuj i zwróć nowy element React, używając elementu jako punktu początkowego. W powstałym elemencie rekwizyty oryginalnego elementu zostaną połączone płytko z nowymi rekwizytami. Nowe dzieci zastąpią istniejące dzieci. klucz i odnośnik z oryginalnego elementu zostaną zachowane.

React.cloneElement() jest prawie równoważne z:

<element.type {...element.props} {...props}>{children}</element.type>

Jednak zachowuje również referencje. Oznacza to, że jeśli dostaniesz dziecko z referencją, nie przypadkowo okradniesz go od swojego przodka. Otrzymasz ten sam numer referencyjny dołączony do nowego elementu.

Więc cloneElement jest tym, czego użyłbyś do zapewnienia niestandardowych rekwizytów dzieciom. Jednak komponent może zawierać wiele elementów podrzędnych i należy go zapętlić. Co sugerują inne odpowiedzi, możesz zmapować je za pomocą React.Children.map. Jednak w React.Children.mapprzeciwieństwie do React.cloneElementzmian, klucze dołączanego elementu i dodatkowe .$jako przedrostek. Sprawdź to pytanie, aby uzyskać więcej informacji: React.cloneElement wewnątrz React.Children.map powoduje zmianę kluczy elementów

Jeśli chcesz tego uniknąć, powinieneś zamiast tego wybrać forEachfunkcję podobną do

render() {
    const newElements = [];
    React.Children.forEach(this.props.children, 
              child => newElements.push(
                 React.cloneElement(
                   child, 
                   {...this.props, ...customProps}
                )
              )
    )
    return (
        <div>{newElements}</div>
    )

}
Shubham Khatri
źródło
2

Oprócz odpowiedzi @ i_rest, w ten sposób sklonowałem dzieci i dodałem klasę.

<div className="parent">
    {React.Children.map(this.props.children, child => React.cloneElement(child, {className:'child'}))}
</div>
Sidonaldson
źródło
2

Myślę, że rekwizyt renderowania jest odpowiednim sposobem na poradzenie sobie z tym scenariuszem

Zezwalasz rodzicowi na dostarczenie niezbędnych rekwizytów używanych w komponencie potomnym, refaktoryzując kod rodzica, aby wyglądał mniej więcej tak:

const Parent = ({children}) => {
  const doSomething(value) => {}

  return children({ doSomething })
}

Następnie w komponencie potomnym można uzyskać dostęp do funkcji dostarczonej przez rodzica w ten sposób:

class Child extends React {

  onClick() => { this.props.doSomething }

  render() { 
    return (<div onClick={this.onClick}></div>);
  }

}

Teraz struktura fianl będzie wyglądać następująco:

<Parent>
  {(doSomething) =>
   (<Fragment>
     <Child value="1" doSomething={doSomething}>
     <Child value="2" doSomething={doSomething}>
    <Fragment />
   )}
</Parent>
ZEE
źródło
2

Metoda 1 - klonowanie dzieci

const Parent = (props) => {
   const attributeToAddOrReplace= "Some Value"
   const childrenWithAdjustedProps = React.Children.map(props.children, child =>
      React.cloneElement(child, { attributeToAddOrReplace})
   );

   return <div>{childrenWithAdjustedProps }</div>
}

Metoda 2 - użyj kontekstu kompozycyjnego

Kontekst umożliwia przekazanie rekwizytu do głębokiego komponentu potomnego bez jawnego przekazywania go jako rekwizytu przez komponenty pomiędzy nimi.

Kontekst ma wady:

  1. Dane nie przepływają normalnie - przez rekwizyty.
  2. Korzystanie z kontekstu tworzy umowę między konsumentem a dostawcą. Trudniej jest zrozumieć i powtórzyć wymagania potrzebne do ponownego użycia komponentu.

Korzystanie z kontekstu kompozycyjnego

export const Context = createContext<any>(null);

export const ComposableContext = ({ children, ...otherProps }:{children:ReactNode, [x:string]:any}) => {
    const context = useContext(Context)
    return(
      <Context.Provider {...context} value={{...context, ...otherProps}}>{children}</Context.Provider>
    );
}

function App() {
  return (
      <Provider1>
            <Provider2> 
                <Displayer />
            </Provider2>
      </Provider1>
  );
}

const Provider1 =({children}:{children:ReactNode}) => (
    <ComposableContext greeting="Hello">{children}</ComposableContext>
)

const Provider2 =({children}:{children:ReactNode}) => (
    <ComposableContext name="world">{children}</ComposableContext>
)

const Displayer = () => {
  const context = useContext(Context);
  return <div>{context.greeting}, {context.name}</div>;
};
Ben Carp
źródło
Trochę późno, ale czy mógłbyś wyjaśnić notację {children}:{children:ReactNode}?
Camille
@camille, To jest rzecz z maszynopisu. Patrząc na to teraz, po prostu odpowiadałbym przy pomocy Javascript, a nawet gdybym pisał maszynopis, zrobiłbym to inaczej. Może go edytować w przyszłości.
Ben Carp
1
@camille, w zasadzie oznacza to, że wartość, która ma klucz, "children"jest typuReactNode
Ben Carp
1

Najszybszy sposób to zrobić:

    {React.cloneElement(this.props.children, this.props)}
nitte93user3232918
źródło
5
Czy to nie kopiuje this.props.children do this.props.children dziecka? i w efekcie kopiowanie dziecka do siebie?
Arshabh Agarwal
1

Każdy, kto ma pojedynczy element potomny, powinien to zrobić.

{React.isValidElement(this.props.children)
                  ? React.cloneElement(this.props.children, {
                      ...prop_you_want_to_pass
                    })
                  : null}
użytkownik10884362
źródło
0

Czy tego potrzebujesz?

var Parent = React.createClass({
  doSomething: function(value) {
  }
  render: function() {
    return  <div>
              <Child doSome={this.doSomething} />
            </div>
  }
})

var Child = React.createClass({
  onClick:function() {
    this.props.doSome(value); // doSomething is undefined
  },  
  render: function() {
    return  <div onClick={this.onClick}></div>
  }
})
amit_183
źródło
4
Nie, nie chcę ograniczać zawartości mojego opakowania do niektórych konkretnych treści.
plus-
0

Z jakiegoś powodu React.children nie pracował dla mnie. To działało dla mnie.

Chciałem po prostu dodać klasę do dziecka. podobnie jak zmiana rekwizytu

 var newChildren = this.props.children.map((child) => {
 const className = "MenuTooltip-item " + child.props.className;
    return React.cloneElement(child, { className });
 });

 return <div>{newChildren}</div>;

Sztuką jest React.cloneElement . Możesz przekazać dowolny rekwizyt w podobny sposób

aWebDeveloper
źródło
0

Renderowanie rekwizytów jest najdokładniejszym podejściem do tego problemu. Zamiast przekazywać komponent podrzędny do komponentu nadrzędnego jako rekwizyty potomne, pozwól rodzicowi renderować komponent podrzędny ręcznie. Renderowanie to wbudowane rekwizyty w reakcji, która przyjmuje parametr funkcji. W tej funkcji możesz pozwolić komponentowi nadrzędnemu na renderowanie dowolnych parametrów za pomocą parametrów niestandardowych. Zasadniczo robi to samo, co rekwizyty dziecięce, ale można go lepiej dostosowywać.

class Child extends React.Component {
  render() {
    return <div className="Child">
      Child
      <p onClick={this.props.doSomething}>Click me</p>
           {this.props.a}
    </div>;
  }
}

class Parent extends React.Component {
  doSomething(){
   alert("Parent talks"); 
  }

  render() {
    return <div className="Parent">
      Parent
      {this.props.render({
        anythingToPassChildren:1, 
        doSomething: this.doSomething})}
    </div>;
  }
}

class Application extends React.Component {
  render() {
    return <div>
      <Parent render={
          props => <Child {...props} />
        }/>
    </div>;
  }
}

Przykład w codepen

omeralper
źródło
0

Podczas korzystania z komponentów funkcjonalnych często TypeError: Cannot add property myNewProp, object is not extensiblepojawia się błąd przy próbie ustawienia nowych właściwości props.children. Można to obejść poprzez klonowanie rekwizytów, a następnie klonowanie samego dziecka za pomocą nowych rekwizytów.

const MyParentComponent = (props) => {
  return (
    <div className='whatever'>
      {props.children.map((child) => {
        const newProps = { ...child.props }
        // set new props here on newProps
        newProps.myNewProp = 'something'
        const preparedChild = { ...child, props: newProps }
        return preparedChild
      })}
    </div>
  )
}
mistrz
źródło