Презентационные и контейнерные компоненты
Проблема
Данные и логика вместе.
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