React - defaultProps vs ES6 default params при деструкции (проблемы с производительностью)

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

У этого компонента был defaultProps, который определил row: false, но мне это не понравилось, потому что defaultProps в конце файла, что на самом деле затрудняет просмотр, И, таким образом, мы не знаем свойства по умолчанию. Поэтому я переместил его в объявление функции напрямую и назначил его с использованием значения по умолчанию для параметров ES6.

const FormField = ({
  row = false,
  ...others,
}) => {
  // logic...
};

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

Я считаю, что в этом случае это тривиально. Поскольку он является логическим, а не объектом/массивом и поэтому не будет рассматриваться как другое значение во время согласования.


Но теперь посмотрим более продвинутый прецедент:

const FormField = ({
  input: { name, value, ...inputRest },
  label = capitalize(name),
  placeholder = label,
  row = false,
  meta: { touched, error, warning },
  ...others,
}) => {
  // logic...
};

Здесь я устанавливаю значение placeholder из label, которое само основано на input.name. Использование деструкции ES6 со значениями по умолчанию для параметров делает все это очень легко писать/понимать и работает как шарм.

Но это хорошая идея? А если нет, тогда как бы вы сделали это правильно?

Ответы

Ответ 1

Я разговаривал с несколькими людьми на канале Discord #reactiflux и фактически получил ответ, который искал.

В основном, есть три варианта использования с компонентами React, и в некоторых из них параметры деструктурирования влияют на производительность, поэтому важно понимать, что происходит на самом деле.

Функциональный компонент без сохранения состояния

const MyComponent = ({ name = 'John Doe', displayName = humanize(name), address = helper.getDefaultAddress() }) => {
  return (
    <div>{displayName}</div>
  );
};

Это функциональный компонент без сохранения состояния. Нет состояния, и оно функционально, потому что это не экземпляр Class, а простая функция.

В этом случае жизненного цикла нет, у вас не может быть componentWillMount или shouldComponentUpdate или constructor. И поскольку отсутствует управление жизненным циклом, оно никак не влияет на производительность. Этот код совершенно действителен. Некоторые могут предпочесть обработать значение по умолчанию displayName в теле функции, но, в конце концов, это не имеет значения, это не повлияет на производительность.

Нефункциональный компонент без состояния

(Не делай этого!)

class MyComponent extends React.Component {
    render() {
        const { name = 'John Doe', displayName = humanize(name), address = helper.getDefaultAddress() } = this.props;
        return (
            <div>{displayName}</div>
          );
    }
}

Это нефункциональный компонент без сохранения состояния. Нет состояния, но оно не "функционально", так как это class. И поскольку это класс, расширяющий React.Component, это означает, что у вас будет жизненный цикл. Вы можете иметь componentWillMount или shouldComponentUpdate или constructor там.

А поскольку он имеет жизненный цикл, способ написания этого компонента плох. Но почему?

Проще говоря, React предлагает атрибут defaultProps для работы со значениями реквизита по умолчанию. И на самом деле лучше использовать его при работе с нефункциональными компонентами, потому что он будет вызываться всеми методами, основанными на this.props.

В предыдущем фрагменте кода создаются новые локальные переменные с именами name и displayName, но значения по умолчанию применяются только для этого метода render!. Если вы хотите, чтобы значения по умолчанию применялись для каждого метода, например значений из жизненного цикла React (shouldComponentUpdate и т.д.), Тогда вы должны использовать вместо него defaultProps.

Таким образом, предыдущий код на самом деле является ошибкой, которая может привести к неправильному пониманию значения по умолчанию name.

Вот как это должно быть написано, чтобы получить то же поведение:

class MyComponent extends React.Component {
    render() {
        const { name, displayName = humanize(name), address } = this.props;
        return (
            <div>{displayName}</div>
          );
    }
}

MyComponent.defaultProps = {
    name: 'John Doe',
    address: helper.getDefaultAddress(),
};

Это лучше. Потому что имя всегда будет John Doe, если оно не было определено. address Значение по умолчанию также рассматривалось, но не displayName... Почему?

Ну, я еще не нашел способ обойти этот особый вариант использования. Потому что displayName должен основываться на свойстве name, к которому мы не можем получить доступ (AFAIK) при определении defaultProps. Единственный способ, которым я вижу, - иметь дело с этим напрямую в методе render. Может быть, есть лучший способ.

У нас нет этой проблемы со свойством address, поскольку оно не основано на свойствах MyComponent, а полагается на что-то совершенно независимое, которое не нуждается в подпорках.

Нефункциональный компонент с состоянием

Он работает точно так же, как "Нефункциональный компонент без состояния". Поскольку жизненный цикл еще существует, поведение будет таким же. Тот факт, что в компоненте есть дополнительный внутренний state, ничего не изменит.


Я надеюсь, что это помогает понять при использовании деструктуризации с компонентами. Мне очень нравится функциональный способ, он намного чище ИМХО (+1 для простоты).

Вы можете предпочесть всегда использовать defaultProps, независимо от того, работаете ли вы с функциональными или нефункциональными компонентами, это также допустимо. (+1 для согласованности)

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


Изменить 10-2019: defaultProps в конечном итоге будет удалено из React API в будущем, см. fooobar.com/questions/2444550/... и https://github.com/reactjs/rfcs/pull/107 для RFC.

Ответ 2

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

Если бы я пытался использовать <FormField /> в своем приложении, и мне нужно было посмотреть, какие зависимости у этого конкретного компонента, я бы немного смутился относительно того, почему вы создаете параметры, основанные на других параметры. Я бы переместил label и placeholder в тело функции, чтобы очистить они не являются зависимостями компонентов, а просто побочными эффектами.

Что касается производительности здесь, я не уверен, что в любом случае будет значительная разница. У компонентов без состояния нет действительно "экземпляра поддержки", который выполняет компонент состояния, что означает, что в объекте памяти не отслеживается компонент. Я считаю, что это просто чистая функция передачи параметров и возврата представления.

В этой же заметке.. добавление PropTypes поможет с проверкой типа.

const FormField = ({
  input: { name, value, ...inputRest },
  row = false,
  meta: { touched, error, warning },
  ...others,
}) => {
  const label = capitalize(name),
  const placeholder = label,

  return (
    // logic
  );
};

FormField.propTypes = {
  input: PropTypes.shape({
    name: PropTypes.string.isRequired,
    value: PropTypes.string,
  }).isRequired,
  meta: PropTypes.shape({
    touched: PropTypes.bool.isRequired,
    error: PropTypes.bool.isRequired,
    warning: PropTypes.bool.isRequired,
  }).isRequired,
  row: PropTypes.bool.isRequired,
};