Jak używać instrukcji switch wewnątrz komponentu React?

120

Mam komponent React, a wewnątrz rendermetody komponentu mam coś takiego:

render() {
    return (
        <div>
            <div>
                // removed for brevity
            </div>

           { switch(...) {} }

            <div>
                // removed for brevity
            </div>
        </div>
    );
}

Chodzi o to, że mam dwa divelementy, jeden na górze i jeden na dole, które są naprawione. W środku chcę mieć instrukcję przełącznika i zgodnie z wartością w moim stanie chcę renderować inny komponent. Zasadniczo chcę, aby te dwa divelementy były zawsze naprawione i tylko w środku, aby za każdym razem renderować inny komponent. Używam tego do implementacji wieloetapowej procedury płatności). Chociaż, podobnie jak kod, obecnie nie działa, ponieważ wyświetla mi błąd, mówiąc, że switchjest to nieoczekiwane. Jakieś pomysły, jak osiągnąć to, czego chcę?

literówki
źródło
1
Cóż, nie musisz mieć całej tej logiki w returnoświadczeniu ani nawet rendermetody w tej sprawie. Czy mógłbyś zdefiniować każdy <div>jako stałą, a następnie użyć switch przed swoim, returnaby określić, który <div>powinien być renderowany?
Jared Goguen
@JaredGoguen Ale potem musiałbym powtórzyć divna górze i na dole wiele razy dla każdego przypadku switch. Albo po prostu źle zrozumiałem, ty ...
literówki
nie, możesz utworzyć kod, let middleDiv = ...a następnie dołączyć go {middleDiv}do swojego powrotu JSX między dwoma <div>kodami, które tam zakodowałeś.
Jared Goguen

Odpowiedzi:

218

Spróbuj tego, co też jest o wiele czystsze: usuń ten przełącznik z renderowania w funkcji i po prostu wywołaj go, przekazując żądane parametry. Na przykład:

renderSwitch(param) {
  switch(param) {
    case 'foo':
      return 'bar';
    default:
      return 'foo';
  }
}

render() {
  return (
    <div>
      <div>
          // removed for brevity
      </div>
      {this.renderSwitch(param)}
      <div>
          // removed for brevity
      </div>
    </div>
  );
}

Kelvin De Moya
źródło
103

W przeciwieństwie do innych odpowiedzi, wolałbym wstawić „przełącznik” w funkcji renderowania. Wyjaśnia, jakie komponenty mogą być renderowane w tym miejscu. Możesz zaimplementować wyrażenie podobne do przełącznika, używając zwykłego starego obiektu javascript:

render () {
  return (
    <div>
      <div>
        {/* removed for brevity */}
      </div>
      {
        {
          'foo': <Foo />,
          'bar': <Bar />
        }[param]
      }
      <div>
        {/* removed for brevity */}
      </div>
    </div>
  )
}
lenkan
źródło
1
To jest całkiem niezłe. Zmodyfikowałem go nieznacznie, aby użyć tablicy zamiast POJO i po prostu używam indeksu do nawiasu w tablicy.
Nicholas Gentile
1
Jest to standardowy sposób obsługi przypadków przełączników w Pythonie. W tym przypadku bardziej mi się podoba ze względu na lepszą czytelność.
Zjem mój kapelusz
16
Takie podejście ma swoje ograniczenia i koszty ogólne. Każdy widok zostanie przetworzony i będzie zależał od aktualnego stanu / właściwości, które mogą nie istnieć. Przykład: powiedzmy, że chcesz wyrenderować: <SearchResults />lub <NoResults />. Jeśli stan widoku powinien być renderowany <NoResults />, <SearchResults />może się nie kompilować, ponieważ zależy to od właściwości, które jeszcze nie istnieją.
ABCD.ca
6
A co z przypadkiem domyślnym?
kungfooman
7
@ lama12345 Jako domyślny przypadek użyj ||w następujący sposób:{ 'foo': <Foo />, 'bar': <Bar /> }[param] || <Baz />
Aaron Adrian
56

Dzieje się tak, ponieważ switchinstrukcja jest a statement, ale tutaj javascript oczekuje wyrażenia.

Chociaż nie jest zalecane używanie instrukcji switch w rendermetodzie, możesz użyć funkcji samo-wywoływania, aby to osiągnąć:

render() {
    // Don't forget to return a value in a switch statement
    return (
        <div>
            {(() => {
                switch(...) {}
            })()}
        </div>
    );
}
1ven
źródło
Dzięki, użyłem tego jak: render () {return (<div> {(() => {switch (this.state.type) {case commons.HARD_SOFT: return <HardSoft params = {this.state.param} onSubmitHead = {this.onSubmit} />;}}) ()} </div>);
JhonQO
Dlaczego nie jest to zalecane?
bluenote10
Prawdopodobnie przeczytał gdzieś coś, co mówi, żeby tego nie robić, ponieważ jest to dla kogoś brzydkie. Wiele osób nie lubi instrukcji przełączania z różnych powodów. Chociaż nie ma o tym wzmianki w dokumentacji Reacta, renderowanie warunkowe jest oczywiście obsługiwane przez Reacta, a instrukcje switch nie powodują żadnych problemów z Reactem.
William
22

Zrobiłem to wewnątrz metody render ():

  render() {
    const project = () => {
      switch(this.projectName) {

        case "one":   return <ComponentA />;
        case "two":   return <ComponentB />;
        case "three": return <ComponentC />;
        case "four":  return <ComponentD />;

        default:      return <h1>No project match</h1>
      }
    }

    return (
      <div>{ project() }</div>
    )
  }

Próbowałem zachować czystość powrotu render (), więc umieściłem moją logikę w funkcji „const” tuż powyżej. W ten sposób mogę również starannie wciskać obudowy przełączników.

williamsi
źródło
@a_m_dev Zamiast funkcji „const project” wewnątrz metody render, możemy umieścić ją jako metodę komponentu, a następnie wywołać ją wewnątrz funkcji renderowania return, np. „<div> {this.project ()} </div>”. O ile w ogóle nie mówisz o nieużywaniu przełącznika, mogę pomyśleć o użyciu if / else lub pokazaniu / ukryciu komponentów za pomocą className, aktualizując stan.
williamsi
to mogłoby być dobre nawet bardziej, ponieważ na przykład używam head()metody komponentów mojej trasy, aby wstrzyknąć dane react-helmetdo nagłówka mojego dokumentu
a_m_dev
16

Sposób reprezentowania rodzaju przełącznika w bloku renderowania przy użyciu operatorów warunkowych:

{(someVar === 1 &&
    <SomeContent/>)
|| (someVar === 2 &&
    <SomeOtherContent />)
|| (this.props.someProp === "something" &&
    <YetSomeOtherContent />)
|| (this.props.someProp === "foo" && this.props.someOtherProp === "bar" &&
    <OtherContentAgain />)
||
    <SomeDefaultContent />
}

Należy upewnić się, że warunki ściśle zwracają wartość logiczną.

arvymetal
źródło
2
Ładny i elegancki, można go używać bezpośrednio w bloku renderowania. Najlepsza odpowiedź IMHO
GavinBelson
2
Zauważyłem, że jest to naruszenie zasad EsLints eslint.org/docs/rules/no-mixed-operators mixer && i ||
FishFingers
1
@FishFingers Zauważyłem to również, gdy próbowałem go użyć dokładnie tak, jak powyżej. Można tego łatwo uniknąć, umieszczając każdy „przypadek” w nawiasach.
Ch0sen
15

Odpowiedź lenkana to świetne rozwiązanie.

<div>
  {{ beep: <div>Beep</div>,
     boop: <div>Boop</div>
  }[greeting]}
</div>

Jeśli potrzebujesz wartości domyślnej, możesz to zrobić

<div>
  {{ beep: <div>Beep</div>,
     boop: <div>Boop</div>
  }[greeting] || <div>Hello world</div>}
</div>

Ewentualnie, jeśli to ci się nie podoba, możesz zrobić coś takiego

<div>
  { 
    rswitch(greeting, {
      beep: <div>Beep</div>,
      boop: <div>Boop</div>,
      default: <div>Hello world</div>
    }) 
  }
</div>

z

function rswitch (param, cases) {
  if (cases[param]) {
    return cases[param]
  } else {
    return cases.default
  }
}
Luke Burns
źródło
2
{{klucz1: <Component1 />, ...} [klucz] nie jest dobrym rozwiązaniem. Widzisz, zanim wybór dzieje, cały początkowy przedmiot jest zbudowany - czyli każdy oddział przełącznika jest renderowany - komponent1, komponent2, etc ...
Gleb Varenov
Tak, odpowiedź lenkana powinna być poprawna, ponieważ przełącznik nie powinien być używany w komponencie funkcjonalnym. Dziękujemy za dodanie OR dla przypadku domyślnego. I nie przejmuj się rswitch (), rozwiązanie mapy jest na miejscu! kciuki w górę
Eugenijus S.
14

Nie jestem wielkim fanem żadnej z aktualnych odpowiedzi, ponieważ są one albo zbyt szczegółowe, albo wymagają przeskakiwania po kodzie, aby zrozumieć, co się dzieje.

Wolę robić to w sposób bardziej skoncentrowany na komponencie reagowania, tworząc plik <Switch/>. Zadaniem tego komponentu jest pobranie właściwości i renderowanie tylko dzieci, których właściwość potomna pasuje do tego. W poniższym przykładzie utworzyłem testrekwizyt na przełączniku i porównałem go z valuerekwizytem na dzieciach, renderując tylko te, które pasują.

Przykład:

const Switch = props => {
  const { test, children } = props
  // filter out only children with a matching prop
  return children.find(child => {
    return child.props.value === test
  })      
}

const Sample = props => {
  const someTest = true
  return (
    <Switch test={someTest}>
      <div value={false}>Will display if someTest is false</div>
      <div value={true}>Will display if someTest is true</div>
    </Switch>
  )
}

ReactDOM.render(
  <Sample/>,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Możesz uczynić przełącznik tak prostym lub złożonym, jak tylko chcesz. Nie zapomnij przeprowadzić dokładniejszego sprawdzenia dzieci i ich wartości.

Matt Way
źródło
3
Dokładnie to, czego szukałem! Oto mała poprawa: w javascript const Switch = props => { const { test, children } = props; return children.find(child => { return child.props.value === test; }); }; const Case = ({ children, value }) => { return children; // I don't want do add container around my cases ! }; ten sposób możesz napisać:javascript <Switch test={true}> <Case value={true}> <ItAlwaysFeelsRightToBeTrue /> </Case> <Case value={false}> <FalseAlarm /> </Case> </Switch>
lsmod
Chociaż jest to dobre rozwiązanie, dodanie wyjaśnienia do kodu sprawiłoby, że byłby jeszcze lepszy
papigee
@papigee Zaktualizowano o nieco więcej szczegółów.
Matt Way
@MattWay Chociaż jest to dobra opcja dla czystego JS, generuje błąd TS, gdy cel jest ustawiony na es5, ponieważ funkcja znalezienia właściwości nie istnieje w ReactNode
Zakriya Bilal
@ZakriyaBilal Kod jest bardziej dowodem słuszności koncepcji i pokazuje, jak komponowanie pomysłów, takich jak przełączniki, sprawi, że Twój kod będzie bardziej reagował. Kluczem jest po prostu znalezienie sposobu na porównanie rekwizytów dzieci.
Matt Way
3

Co powiesz na:

mySwitchFunction = (param) => {
   switch (param) {
      case 'A':
         return ([
            <div />,
         ]);
      // etc...
   }
}
render() {
    return (
       <div>
          <div>
               // removed for brevity
          </div>

          { this.mySwitchFunction(param) }

          <div>
              // removed for brevity
          </div>
      </div>
   );
}
James
źródło
2

Możesz zrobić coś takiego.

 <div>
          { object.map((item, index) => this.getComponent(item, index)) }
 </div>

getComponent(item, index) {
    switch (item.type) {
      case '1':
        return <Comp1/>
      case '2':
        return <Comp2/>
      case '3':
        return <Comp3 />
    }
  }
monkeyjs
źródło
2

Nie możesz mieć przełącznika w renderowaniu. Podejście pseudoprzełącznika polegające na umieszczeniu literału obiektowego, który uzyskuje dostęp do jednego elementu, nie jest idealne, ponieważ powoduje przetwarzanie wszystkich widoków, co może powodować błędy zależności właściwości, które nie istnieją w tym stanie.

Oto ładny, czysty sposób na zrobienie tego, który nie wymaga wcześniejszego renderowania każdego widoku:

render () {
  const viewState = this.getViewState();

  return (
    <div>
      {viewState === ViewState.NO_RESULTS && this.renderNoResults()}
      {viewState === ViewState.LIST_RESULTS && this.renderResults()}
      {viewState === ViewState.SUCCESS_DONE && this.renderCompleted()}
    </div>
  )

Jeśli twoje warunki, dla których stan widoku są oparte na więcej niż prostej właściwości - takiej jak wiele warunków w wierszu, to wyliczenie i getViewStatefunkcja hermetyzująca warunki są dobrym sposobem na oddzielenie tej logiki warunkowej i oczyszczenie renderowania.

ABCD.ca
źródło
Prosty i czysty sposób.
Abhilekh Singh
2

Oto pełny przykład roboczy z użyciem przycisku do przełączania między komponentami

możesz ustawić konstruktora w następujący sposób

constructor(props)
{
    super(props);
    this.state={
        currentView: ''
    }
}

następnie możesz renderować komponenty w następujący sposób

  render() 
{
    const switchView = () => {

    switch(this.state.currentView) 
    {

      case "settings":   return <h2>settings</h2>;
      case "dashboard":   return <h2>dashboard</h2>;

      default:      return <h2>dashboard</h2>
    }
  }

    return (

       <div>

            <button onClick={(e) => this.setState({currentView: "settings"})}>settings</button>
            <button onClick={(e) => this.setState({currentView: "dashboard"})}>dashboard</button>

            <div className="container">
                { switchView() }
            </div>


        </div>
    );
}

}

Jak widać, używam przycisku do przełączania się między stanami.

jerryurenaa
źródło
1

Bardzo podobała mi się sugestia w https://stackoverflow.com/a/60313570/770134 , więc dostosowałem ją do Typescript w ten sposób

import React, { FunctionComponent } from 'react'
import { Optional } from "typescript-optional";
const { ofNullable } = Optional

interface SwitchProps {
  test: string
  defaultComponent: JSX.Element
}

export const Switch: FunctionComponent<SwitchProps> = (props) => {
  return ofNullable(props.children)
    .map((children) => {
      return ofNullable((children as JSX.Element[]).find((child) => child.props['value'] === props.test))
        .orElse(props.defaultComponent)
    })
    .orElseThrow(() => new Error('Children are required for a switch component'))
}

const Foo = ({ value = "foo" }) => <div>foo</div>;
const Bar = ({ value = "bar" }) => <div>bar</div>;
const value = "foo";
const SwitchExample = <Switch test={value} defaultComponent={<div />}>
  <Foo />
  <Bar />
</Switch>;
fieldju
źródło
0

Nie byłem w pełni zadowolony z żadnej odpowiedzi. Ale mam kilka pomysłów z @Matt Way.

Oto moje rozwiązanie:

Definicje:

const Switch = props => {
    const { test, children = null } = props;
    return children && children.find(child => child && child.props && child.props.casevalue === test) || null;
}

const Case = ({ casevalue = false, children = null }) => <div casevalue={`${casevalue}`}>{children}</div>;

Case.propTypes = {
    casevalue: PropTypes.string.isRequired,
    children: PropTypes.node.isRequired,
}

const Default = ({ children }) => children || <h1>NO_RESULT</h1>;

const SwitchCase = ({ test, cases = [], defaultValue = null }) => {

    const defaultVal = defaultValue
        && React.cloneElement(defaultValue, { key: 'default-key', casevalue: `${test}` })
        || <Default key='default-key' casevalue={`${test}`} />;

    return (
        <Switch test={`${test}`} >
            {
                cases.map((cas, i) => {
                    const { props = {} } = cas || {};
                    const { casevalue = false, ...rest } = props || {};

                    return <Case key={`case-key-${i}`} casevalue={`${casevalue}`}>{ React.cloneElement(cas, rest)}</Case>
                })
                .concat(defaultVal)
            }
        </Switch>
    );
}

Stosowanie:

<SwitchCase
  cases={[
    <div casevalue={`${false}`}>#1</div>,
    <div casevalue={`${true}`}>#2</div>,
    <div casevalue={`${false}`}>#3</div>,
  ]}
  defaultValue={<h1>...nothing to see here</h1>} // You can leave it blank.
  test={`${true}`}
/>
Piotr
źródło
0

function Notification({ text, status }) {
  return (
    <div>
      {(() => {
        switch (status) {
          case 'info':
            return <Info text={text} />;
          case 'warning':
            return <Warning text={text} />;
          case 'error':
            return <Error text={text} />;
          default:
            return null;
        }
      })()}
    </div>
  );
}
A.com
źródło
-1

To jest inne podejście.

render() {
   return {this[`renderStep${this.state.step}`]()}

renderStep0() { return 'step 0' }
renderStep1() { return 'step 1' }
Rodrigo Hernan Ramos
źródło