Я трачу много времени на разработку новых многоразовых компонентов пользовательского интерфейса.

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

Эти компоненты используются повторно на протяжении всего проекта. Часто имеют разные стили или макеты при интеграции в функции.

Попутно я выучил несколько правил, которые помогли мне сэкономить время и боль.

1. Держите макет гибким

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

Сохраняя компонент 100% width, вы передаете ответственность за макет родительскому компоненту.

Например, намного проще использовать CardOne вместо CardTwo. Поскольку CardOne будет реагировать на изменения родительского столбца при изменении размера экрана.

const CardOne = ({ title }) => (
  <div style={{ padding: "20px 30px", background: "white" }}>{title}</div>
);

const CardTwo = ({ title }) => (
  <div style={{ width: 600, padding: "10px", background: "white" }}>
    {title}
  </div>
);

const App = () => (
  <div className="row">
    <div className="col-md-6">
      <CardOne title="I'm Fluid" />
    </div>
    <div className="col-md-6">
      <CardTwo title="I will break this layout" />
    </div>
  </div>
);

2. Разрешить проносить лишний реквизит.

Оказывается, вашему компоненту Button нужен определенный атрибут данных для работы с библиотекой, которую вы используете для одной из конкретных функций. Жаль, что компонент Button разрешает только className, children и onClick.

Как мы можем это исправить?

Современный ES6 JavaScript позволяет распространять параметры функций и объекты. Его можно использовать для подачи опор к компонентам.

// Before -> <button className="button">Click me</button>

const Button = ({ children, className, onClick }) => (
  <button onClick={onClick} className={`${styles.button} ${className || ""}`}>
    {children}
  </button>
);

// After -> <button className="button" data-theme="dark">Click me</button>

const Button = ({ className, ...props }) => (
  <button className={`${styles.button} ${className || ""}`} {...props} />
);

// Example

const App = ({ onActivate }) => (
  <SpecificFeature>
    <Button data-theme="dark" onClick={onActivate}>
      Click me
    </Button>
  </SpecificFeature>
);

3. Поднимите логику вверх / оставьте ее глупой.

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

Представьте, что у вас есть Dropdown компонент. То, что вы использовали в своей навигации при нажатии на ссылку.

const DropdownItem = ({ className, ...props }) => (
  <li className={`dropdown-item ${className || ""}`} {...props} />
);
class Dropdown extends React.Component {
  state = { toggled: false };
  toggle = () => this.setState(state => ({ toggled: !state.toggled }));
  render() {
    return (
      <div className="navbar-link">
        <span onClick={this.toggle}> {this.props.title} </span>
        {this.state.toggled && (
          <ul className="dropdown"> {this.props.children} </ul>
        )}
      </div>
    );
  }
}
const Navbar = () => (
  <div className="navbar">
    <div className="navbar-brand">Example</div>
    <ul className="navbar-menu">
      <Dropdown title="My Account">
        <DropdownItem>One</DropdownItem>
        <DropdownItem>Two</DropdownItem>
      </Dropdown>
    </ul>
  </div>
);

Проблема выше:

  1. Dropdown обрабатывает логику открытия и закрытия раскрывающегося списка. Значительно затрудняет повторное использование Dropdown для более сложных сценариев (например: открывать раскрывающийся список только при успешном запросе).
  2. Dropdown имеет разметку, относящуюся к компоненту Navbar.

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

const DropdownItem = ({ className, ...props }) => (
  <li className={`dropdown-item ${className || ""}`} {...props} />
);
const Dropdown = ({ className, ...props }) => (
  <ul className={`dropdown ${className || ""}`} {...props} />
);
class Navbar extends React.Component {
  state = { showUserOptions: false };
  toggleOptions = () =>
    this.setState(state => ({
      showUserOptions: !state.showUserOptions
    }));
  render() {
    return (
      <div className="navbar">
        <div className="navbar-brand">Example</div>
        <ul className="navbar-menu">
          <div className="navbar-link">
            <span onClick={this.toggleOptions}>My Account</span>
            {this.state.showUserOptions && (
              <Dropdown>
                <DropdownItem>One</DropdownItem>
                <DropdownItem>Two</DropdownItem>
              </Dropdown>
            )}
          </div>
        </ul>
      </div>
    );
  }
}

Резюме

  • Сохраняйте гибкость компоновки компонентов.
  • Позволяет передавать в компонент дополнительные свойства.
  • По возможности держите их подальше от логики.