[Перевод] 8 Условия рендеринга RACT

внешний интерфейс JavaScript функциональное программирование React.js

предисловие

Эта статья - первый случай, когда переводчик сделал полный полный перевод. Основная цель - изучить стиль написания таких статей, поэтому я выбрал статью, которая является относительно вводной, от мелкой до глубокой, и вся статья переведена дословно, даже если автору иногда кажется, что она довольно многословна, но исходное содержание все же переведено.


исходный адрес:8 React conditional rendering methods

По сравнению с Javascript JSX — отличное расширение, которое позволяет нам определять компоненты пользовательского интерфейса. Однако он не обеспечивает встроенной поддержки условных циклических выражений (добавление условных выражений впроблемаобсуждалось в).

Примечание переводчика. Условные выражения и циклические выражения, как правило, являются самым основным синтаксисом, предоставляемым механизмом шаблонов по умолчанию.

Предположим, вам нужно просмотреть список, отобразить несколько компонентов или реализовать некоторую логику условного суждения, вы должны использовать JS. Но в большинстве случаев вариантов очень мало,Array.prototype.mapможет удовлетворить потребности.

Но как насчет условных выражений?

Это другая история.

у вас есть много вариантов

Есть несколько способов реализовать условные выражения в React. Также для разных сценариев подходят разные методы, в зависимости от того, с какой именно проблемой вам нужно разобраться.

В этой статье рассматриваются несколько наиболее распространенных методов условного рендеринга:

  • If/Else
  • Возврат null предотвращает рендеринг
  • Переменная
  • Тернарный оператор
  • Оператор короткого замыкания (&&)
  • Самоисполняющаяся функция (IIFE)
  • Подсборка
  • Компоненты высшего порядка (HOC)

Чтобы проиллюстрировать, как используются эти методы, в этой статье реализован компонент, который переключается между состояниями редактирования и отображения:

Вы можете запускать и играть со всем примером кода в JSFiddle.

Примечание переводчика: JSFiddle слишком медленно открывается в стене, поэтому в этой статье не публикуется полный адрес примера.При необходимости вы можете просмотреть исходную ссылку самостоятельно. Если есть подходящий альтернативный продукт, сообщите нам об этом.

If/Else

Сначала мы создаем базовый компонент:

class App extends React.Component {
  state = {
    text: '', 
    inputText: '', 
    mode: 'view',
  }
}

textСвойство хранит сохраненную копию,inputTextСвойство хранит введенную копию,modeАтрибут для хранения текущего состояния редактирования или состояния отображения.

Затем мы добавляем несколько методов для обработки ввода и переключения состояний:

class App extends React.Component {
  state = {
    text: '', 
    inputText: '', 
    mode: 'view',
  }
  
  handleChange = (e) => {
    this.setState({ inputText: e.target.value });
  }
  
  handleSave = () => {
    this.setState({text: this.state.inputText, mode: 'view'});
  }

  handleEdit = () => {
    this.setState({mode: 'edit'});
  }
}

это здесьrenderметод, нам нужно определить состояние вmodeсвойство, чтобы решить, отображать ли кнопку редактирования или текстовый ввод + кнопку сохранения:


class App extends React.Component {
  // …
  render () {
    if(this.state.mode === 'view') {
      return (
        <div>
          <p>Text: {this.state.text}</p>
          <button onClick={this.handleEdit}>
            Edit
          </button>
        </div>
      );
    } else {
      // 译者注:如果if代码块里有return时,一般不需要写else代码块,不过为了贴合标题还是保留了
      return (
        <div>
          <p>Text: {this.state.text}</p>
            <input
              onChange={this.handleChange}
              value={this.state.inputText}
            />
          <button onClick={this.handleSave}>
            Save
          </button>
        </div>
      );
    }
}

If/Else — это самый простой способ реализовать условный рендеринг, но я уверен, что вы не думаете, что это хороший способ.

Его преимущество в том, что его легко использовать в простых сценариях, и каждый программист понимает, как его использовать; его недостаток в том, что будет некоторое дублирование кода, и метод рендеринга станет раздутым.

Тогда давайте упростим это.Мы поместили всю логику условного суждения в два метода рендеринга, один для рендеринга поля ввода, а другой для рендеринга кнопки:

class App extends React.Component {
  // …
  
  renderInputField() {
    if (this.state.mode === 'view') {
      return <div />;
    } else {
      return (
          <p>
            <input
              onChange={this.handleChange}
              value={this.state.inputText}
            />
          </p>
      );
    }
  }
  
  renderButton() {
    if (this.state.mode === 'view') {
      return (
          <button onClick={this.handleEdit}>
            Edit
          </button>
      );
    } else {
      return (
          <button onClick={this.handleSave}>
            Save
          </button>
      );
    }
  }

  render() {
    return (
      <div>
        <p>Text: {this.state.text}</p>
        {this.renderInputField()}
        {this.renderButton()}
      </div>
    );
  }
}

Обратите внимание, что в примереrenderInputFieldКогда функция находится в режиме просмотра, она возвращает пустой div. Вообще говоря, это не рекомендуется.

Возврат null предотвращает рендеринг

Если вы хотите скрыть компонент, вы можете вернуть его, позволив функции рендеринга компонента вернутьnull, нет необходимости использовать пустой div или другой элемент в качестве заполнителя.

Следует отметить, что даже если возвращается значение null, компонент является «невидимым», но его жизненный цикл все равно будет выполняться.

Например, в следующем примере счетчик реализуется с двумя компонентами:

class Number extends React.Component {
  constructor(props) {
    super(props);
  }
  
  componentDidUpdate() {
    console.log('componentDidUpdate');
  }
  
  render() {
    if (this.props.number % 2 == 0) {
        return (
            <div>
                <h1>{this.props.number}</h1>
            </div>
        );
    } else {
      return null;
    }
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 }
  }
  
  onClick(e) {
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  }

  render() {
    return (
      <div>
        <Number number={this.state.count} />
        <button onClick={this.onClick.bind(this)}>Count</button>
      </div>
    )
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

NumberКомпоненты отображаются только в том случае, если их четное количество. Потому что, когда число нечетное, функция рендеринга возвращает null. Однако, когда вы смотрите на консоль,componentDidUpdateФункция будет выполняться каждый раз, независимо отrenderчто возвращает функция.

Возвращаясь к примеру в этой статье, мы имеемrenderInputFieldФункция немного изменена:

  renderInputField() {
    if (this.state.mode === 'view') {
      return null;
    } else {
      return (
          <p>
            <input
              onChange={this.handleChange}
              value={this.state.inputText}
            />
          </p>
      );
    }
  }

Кроме того, еще одно преимущество возврата null вместо пустого div заключается в том, что это может немного повысить производительность всего приложения React, поскольку React не нужно размонтировать пустой div при обновлении.

Например, если он возвращает пустой div, в консоли вы можете обнаружить, что под корневым узломdivЭлементы всегда обновляются:

И наоборот, если он возвращает null, когдаEditПри нажатии на кнопку этоdivЭлементы не будут обновляться:

ты сможешьздесьПродолжайте узнавать больше о том, как React обновляет элементы DOM и как работает алгоритм согласования.

В этом простом примере, возможно, этот разрыв в производительности тривиален, но если это большой компонент, разрыв в производительности нельзя игнорировать.

Ниже я продолжу обсуждение влияния условного рендеринга на производительность. Но пока давайте продолжим фокусироваться на этом примере.

Переменная

Иногда мне не нравится иметь несколько в одном методеreturn. Итак, я буду использовать переменную, чтобы указать на этот элемент JSX, и только если условиеtrueвремя для инициализации.

renderInputField() {
    let input;
    
    if (this.state.mode !== 'view') {
      input = 
        <p>
          <input
            onChange={this.handleChange}
            value={this.state.inputText} 
          />
        </p>;
    }
      
    return input;
  }
  
  renderButton() {
    let button;
    
    if (this.state.mode === 'view') {
      button =
          <button onClick={this.handleEdit}>
            Edit
          </button>;
    } else {
      button =
          <button onClick={this.handleSave}>
            Save
          </button>;
    }
    
    return button;
  }

Возвращаемые результаты этих методов совпадают с результатами, возвращаемыми двумя методами из предыдущего раздела.

Функция рендеринга теперь намного читабельнее, но в этом случае нет необходимости использовать блок if/else (или переключатель) или несколько методов рендеринга.

Мы можем написать это немного более кратко.

Тернарный оператор

Мы можем использовать тернарный оператор вместо блока if/else:

condition ? expr_if_true : expr_if_false

Весь оператор можно поместить в jsx{}, каждое выражение можно использовать с()обернуть JSX для улучшения читабельности.

Тернарный оператор можно использовать в разных местах компонента (?), давайте посмотрим его в действии на примере.

Примечание переводчика: лично я не понимаю помеченного предложения?

я удаляю первыйrenderInputFieldа такжеrenderButtonметод, а вrenderДобавьте переменную, чтобы указать, находится ли компонент вviewрежим ещеeditмодель:


render () {
  const view = this.state.mode === 'view';

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

Затем добавьте тернарный оператор - когда вviewрежим возвращает ноль; вeditрежиме, вернитесь в поле ввода:


  // ...

  return (
      <div>
        <p>Text: {this.state.text}</p>
        
        {
          view
          ? null
          : (
            <p>
              <input
                onChange={this.handleChange}
                value={this.state.inputText} />
            </p>
          )
        }

      </div>
  );

С помощью тернарного оператора вы можете отобразить кнопку сохранения/редактирования, изменив метку или функцию обратного вызова внутри компонента:

  // ...

  return (
      <div>
        <p>Text: {this.state.text}</p>
        
        {
          ...
        }

        <button
          onClick={
            view 
              ? this.handleEdit 
              : this.handleSave
          } >
              {view ? 'Edit' : 'Save'}
        </button>

      </div>
  );

оператор короткого замыкания

В некоторых случаях тернарный оператор можно упростить. Например, когда вы либо визуализируете компонент, либо нет, вы можете использовать&&оператор.

В отличие от&оператор если&&Если результат подтвержден выполнением выражения слева, выражение справа не будет выполнено.

Например, если левое выражение оценивается как false (false && ...), то следующее выражение не нужно выполнять, потому что результат всегда ложный.

В React вы можете использовать это так:

return (
    <div>
        { showHeader && <Header /> }
    </div>
);

еслиshowHeaderРезультатtrue,Так<Header />компонент будет возвращен; еслиshowHeaderрезультат неверный, то<Header />Компонент будет проигнорирован, а возврат будет пустым.div.

В приведенном выше коде:

{
  view
  ? null
  : (
    <p>
      <input
        onChange={this.handleChange}
        value={this.state.inputText} />
    </p>
  )
}

можно изменить на:

!view && (
  <p>
    <input
      onChange={this.handleChange}
      value={this.state.inputText} />
  </p>
)

Теперь полный пример выглядит следующим образом:

class App extends React.Component {
  state = {
    text: '',
    inputText: '',
    mode: 'view',
  }
  
  handleChange = (e) => {
    this.setState({ inputText: e.target.value });
  }
  
  handleSave = () => {
    this.setState({ text: this.state.inputText, mode: 'view' });
  }

  handleEdit = () => {
    this.setState({mode: 'edit'});
  }
  
  render () {
    const view = this.state.mode === 'view';
    
    return (
      <div>
        <p>Text: {this.state.text}</p>
        
        {
          !view && (
            <p>
              <input
                onChange={this.handleChange}
                value={this.state.inputText} />
            </p>
          )
        }
        
        <button
          onClick={
            view 
              ? this.handleEdit 
              : this.handleSave
          }
        >
          {view ? 'Edit' : 'Save'}
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Это выглядит намного лучше?

Однако иногда тернарный оператор может сбивать с толку, например следующий сложный код:


return (
  <div>
    { condition1
      ? <Component1 />
      : ( condition2
        ? <Component2 />
        : ( condition3
          ? <Component3 />
          : <Component 4 />
        )
      )
    }
  </div>
);

Вскоре этот код превратится в беспорядок, поэтому иногда вам понадобятся некоторые другие приемы, такие как самовыполняющиеся функции.

самовыполняющаяся функция

Как следует из названия, самовыполняющиеся функции выполняются сразу после их определения, нет необходимости вызывать их явно.

Обычно функции определяются и выполняются следующим образом:

function myFunction() {
// ...
}
myFunction();

Если вы хотите, чтобы функция выполнялась сразу после ее определения, вам нужно заключить все определение в круглые скобки (используя функцию как выражение), а затем передать параметры, которые будут использоваться.

Пример выглядит следующим образом:

( function myFunction(/* arguments */) {
    // ...
}(/* arguments */) );

или:


( function myFunction(/* arguments */) {
    // ...
} ) (/* arguments */);

Если функция больше нигде не будет вызываться, имя можно опустить:

( function (/* arguments */) {
    // ...
} ) (/* arguments */);

или используйте функции стрелок:

( (/* arguments */) => {
    // ...
} ) (/* arguments */);

В React вы можете обернуть всю самовыполняющуюся функцию в фигурную скобку, поместить внутрь всю логику (если/иначе, переключатель, тернарные операторы и т. д.) и вернуть то, что вам нужно для рендеринга.

Например, при использовании самовыполняющейся функции для отображения кнопки редактирования/сохранения код будет выглядеть следующим образом:


{
  (() => {
    const handler = view 
                ? this.handleEdit 
                : this.handleSave;
    const label = view ? 'Edit' : 'Save';
          
    return (
      <button onClick={handler}>
        {label}
      </button>
    );
  })()
} 

Подсборка

Иногда самовыполняющиеся функции выглядят как черная технология.

Лучшая практика с React — максимально разделить логику внутри компонентов и использовать функциональное программирование вместо императивного программирования.

Поэтому хорошим решением будет поместить логику условного рендеринга в дочерний компонент, а дочерний компонент будет рендерить другой контент через пропсы.

Но здесь я этого не делаю, ниже я покажу вам более декларативный, функциональный способ написания.

Сначала я создаюSaveComponent:

const SaveComponent = (props) => {
  return (
    <div>
      <p>
        <input
          onChange={props.handleChange}
          value={props.text}
        />
      </p>
      <button onClick={props.handleSave}>
        Save
      </button>
    </div>
  );
};

Через реквизиты он принимает достаточно данных для отображения. Аналогично напишу ещеEditComponent:

const EditComponent = (props) => {
  return (
    <button onClick={props.handleEdit}>
      Edit
    </button>
  );
};

renderТеперь метод будет выглядеть так:

render () {
    const view = this.state.mode === 'view';
    
    return (
      <div>
        <p>Text: {this.state.text}</p>
        
        {
          view
            ? <EditComponent handleEdit={this.handleEdit}  />
            : (
              <SaveComponent 
               handleChange={this.handleChange}
               handleSave={this.handleSave}
               text={this.state.inputText}
             />
            )
        } 
      </div>
    );
}

Если компонент

Некоторые библиотеки, такие какJSX Control Statements, они расширяют JSX для поддержки условных состояний:

<If condition={ true }>
  <span>Hi!</span>
</If>

Эти библиотеки предоставляют более продвинутые компоненты, однако, если нам нужен только какой-то простой if/else, мы можем написать компонент, как Майкл Дж. Райан в этомissueупоминается в ответе:

const If = (props) => {
  const condition = props.condition || false;
  const positive = props.then || null;
  const negative = props.else || null;
  
  return condition ? positive : negative;
};

// …

render () {
    const view = this.state.mode === 'view';
    const editComponent = <EditComponent handleEdit={this.handleEdit}  />;
    const saveComponent = <SaveComponent 
               handleChange={this.handleChange}
               handleSave={this.handleSave}
               text={this.state.inputText}
             />;
    
    return (
      <div>
        <p>Text: {this.state.text}</p>
        <If
          condition={ view }
          then={ editComponent }
          else={ saveComponent }
        />
      </div>
    );
}

компоненты более высокого порядка

Компоненты высшего порядка (HOC)Относится к функции, которая принимает существующий компонент и возвращает новый компонент с некоторыми новыми методами:

const EnhancedComponent = higherOrderComponent(component);

Применительно к условному рендерингу компонент более высокого порядка может возвращать разные компоненты через некоторые условия:

function higherOrderComponent(Component) {
  return function EnhancedComponent(props) {
    if (condition) {
      return <AnotherComponent { ...props } />;
    }

    return <Component { ...props } />;
  };
}

Эта статья Робина Вирухаотличная статья, он более подробно рассматривает использование компонентов более высокого порядка для выполнения условного рендеринга.

Благодаря этой статье я готов учиться уEitherComponentКонцепция чего-либо.

В функциональном программированииEtherЧасто используется в качестве оболочки для возврата двух разных значений.

Давайте сначала определим функцию, которая принимает два параметра типа function, первая функция будет возвращать логическое значение (результат выполнения условного выражения), другая — когда результатtrueКомпонент вернулся, когда .

function withEither(conditionalRenderingFn, EitherComponent) {

}

Имя этого компонента более высокого порядка обычноwithначало.

Эта функция вернет функцию, которая принимает исходный компонент в качестве параметра и возвращает новый компонент:

function withEither(conditionalRenderingFn, EitherComponent) {
    return function buildNewComponent(Component) {

    }
}

Компонент, возвращаемый внутренней функцией, будет тем, что вы используете в своем приложении, поэтому для работы ему необходимо принять некоторые свойства:

function withEither(conditionalRenderingFn, EitherComponent) {
    return function buildNewComponent(Component) {
        return function FinalComponent(props) {

        }
    }
}

Поскольку внутренняя функция может получить параметры внешней функции, поэтому на основеconditionalRenderingFnвозвращаемое значение, вы можете вернутьEitherComponentили оригиналComponent:

function withEither(conditionalRenderingFn, EitherComponent) {
    return function buildNewComponent(Component) {
        return function FinalComponent(props) {
            return conditionalRenderingFn(props)
                ? <EitherComponent { ...props } />
                 : <Component { ...props } />;
        }
    }
}

В качестве альтернативы используйте функции стрелок:

const withEither = (conditionalRenderingFn, EitherComponent) => (Component) => (props) =>
  conditionalRenderingFn(props)
    ? <EitherComponent { ...props } />
    : <Component { ...props } />;

Вы можете использовать ранее определенныеSaveComponentа такжеEditComponentсоздатьwithEditConditionalRenderingКомпоненты более высокого порядка, в конечном счете, создаютEditSaveWithConditionalRenderingКомпоненты:

const isViewConditionFn = (props) => props.mode === 'view';

const withEditContionalRendering = withEither(isViewConditionFn, EditComponent);
const EditSaveWithConditionalRendering = withEditContionalRendering(SaveComponent);

Примечание переводчика: уже поздно убивать курицу бычьим ножом

Наконец, вrender, вы передаете все свойства, которые вам нужно использовать:


render () {    
    return (
      <div>
        <p>Text: {this.state.text}</p>
        <EditSaveWithConditionalRendering 
               mode={this.state.mode}
               handleEdit={this.handleEdit}
               handleChange={this.handleChange}
               handleSave={this.handleSave}
               text={this.state.inputText}
             />
      </div>
    );
}

Вопросы производительности

Условный рендеринг иногда неуловим, и многие из упомянутых выше методов различаются по производительности.

Однако в большинстве сценариев эти различия незначительны. Но когда вам нужно это сделать, вам нужно хорошо понимать, как работает виртуальный DOM React, и иметь некоторыеСоветы по оптимизации:

Вот статья об оптимизации условного рендерингастатья, рекомендую к прочтению.

Суть в том, что если условно отображаемый компонент вызывает изменение положения, это вызовет перекомпоновку, что приведет к монтированию/размонтированию компонента в приложении.

Примечание переводчика. Перестановка здесь относится не к перестройке рендеринга в браузере, а к концепции виртуального DOM.

На основе примеров в тексте я сделал следующие два примера.

Первый использует if/else, чтобы показать/скрытьSubHeaderКомпоненты:

const Header = (props) => {
  return <h1>Header</h1>;
}

const Subheader = (props) => {
  return <h2>Subheader</h2>;
}

const Content = (props) => {
  return <p>Content</p>;
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
    
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }
  
  render() {
    if(this.state.isToggleOn) {
      return (
        <div>
          <Header />
          <Subheader /> 
          <Content />
          <button onClick={this.handleClick}>
            { this.state.isToggleOn ? 'ON' : 'OFF' }
          </button>
        </div>
      );
    } else {
      return (
        <div>
          <Header />
          <Content />
          <button onClick={this.handleClick}>
            { this.state.isToggleOn ? 'ON' : 'OFF' }
          </button>
        </div>
      );
    }
  }
}

ReactDOM.render(
    <App />,
  document.getElementById('root')
);

адрес скрипки

Другой использует оператор короткого замыкания (&&)выполнить:

const Header = (props) => {
  return <h1>Header</h1>;
}

const Subheader = (props) => {
  return <h2>Subheader</h2>;
}

const Content = (props) => {
  return <p>Content</p>;
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
    
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }
  
  render() {
    return (
      <div>
        <Header />
        { this.state.isToggleOn && <Subheader /> }
        <Content />
        <button onClick={this.handleClick}>
          { this.state.isToggleOn ? 'ON' : 'OFF' }
        </button>
      </div>
    );
  }
}

ReactDOM.render(
    <App />,
  document.getElementById('root')
);

адрес скрипки

Откройте консоль и нажмите кнопку несколько раз, вы найдетеContentПроизводительность компонента несовместима между двумя реализациями.

Примечание переводчика: в примере 1 контент будет перерисовываться каждый раз

В заключение

Как и все остальное в программировании, в React существует множество способов реализации условного рендеринга.

Вы можете выбрать любой способ, кроме первого (if/else и содержит много возвратов).

Вы можете найти лучшее решение для вашего текущего сценария по следующим причинам:

  • ваш стиль программирования
  • Сложность условной логики
  • Насколько вам комфортно с передовыми концепциями в Javascript, JSX и React (например, компонентами более высокого порядка)

Конечно, некоторые вещи всегда важны, и это делает их простыми и удобочитаемыми.