Презентационные и контейнерные компоненты

Проблема

Данные и логика вместе.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {time: this.props.time};
    this._update = this._updateTime.bind(this);
  }

  render() {
    var time = this._formatTime(this.state.time);
    return (
      <h1>{ time.hours } : { time.minutes } : { time.seconds }</h1>
    );
  }

  componentDidMount() {
    this._interval = setInterval(this._update, 1000);
  }

  componentWillUnmount() {
    clearInterval(this._interval);
  }

  _formatTime(time) {
    var [ hours, minutes, seconds ] = [
      time.getHours(),
      time.getMinutes(),
      time.getSeconds()
    ].map(num => num < 10 ? '0' + num : num);

    return {hours, minutes, seconds};
  }

  _updateTime() {
    this.setState({time: new Date(this.state.time.getTime() + 1000)});
  }
}

ReactDOM.render(<Clock time={ new Date() }/>, ...);

Решение

Разделим компонент на две части - контейнер и презентациные компоненты.

Контейнерный компонент

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

// Clock/index.js
import Clock from './Clock.jsx'; // <-- that's the presentational component

export default class ClockContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {time: props.time};
    this._update = this._updateTime.bind(this);
  }

  render() {
    return <Clock { ...this._extract(this.state.time) }/>;
  }

  componentDidMount() {
    this._interval = setInterval(this._update, 1000);
  }

  componentWillUnmount() {
    clearInterval(this._interval);
  }

  _extract(time) {
    return {
      hours: time.getHours(),
      minutes: time.getMinutes(),
      seconds: time.getSeconds()
    };
  }

  _updateTime() {
    this.setState({time: new Date(this.state.time.getTime() + 1000)});
  }
};

Презентационный компонент

Презентационные компоненты связаны с тем, как выглядят данные. У них есть дополнительная разметка, необходимая для создания страницы. Такие компоненты не привязаны ни к чему и не имеют зависимостей. Очень часто реализуемые функциональные компоненты не имеют внутреннего состояния.

// Clock/Clock.jsx
export default function Clock(props) {
  var [ hours, minutes, seconds ] = [
    props.hours,
    props.minutes,
    props.seconds
  ].map(num => num < 10 ? '0' + num : num);

  return <h1>{ hours } : { minutes } : { seconds }</h1>;
};

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

import Clock from './Clock.jsx';
export default class ClockContainer extends React.Component {
  render() {
    return <Clock />;
  }
}

Мы можем экспортировать функцию, которая принимает презентационный компонент:

export default function (Component) {
  return class Container extends React.Component {
    render() {
      return <Component />;
    }
  }
}

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

Ссылки по теме:

https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.mbglcakmp

https://github.com/krasimir/react-in-patterns/tree/master/patterns/presentational-and-container

https://medium.com/@learnreact/container-components-c0e67432e005

results matching ""

    No results matching ""