Разберем компонентный подход на примере двух наиболее известных библиотек в Реакте
Помните, в прошлых уроках мы говорили про то, что Реакт силён компонентным подходом? Мы ещё долго будем это разбирать, но начнём сейчас.
Знаете, раньше был хайп по CSS-in-JS, люди изобретали всякие JSS и прочие Афродиты, но все они были неудобными: да, прикольно иметь мощь Джс вместо настройки постцсс-плагинов, но за это приходилось расплачиваться удобством — посмотрите на этот синтаксис.
import React from "react";
import injectSheet from "react-jss";
const styles = {
button: {
background: props => props.color
},
label: {
fontWeight: "bold"
}
};
const Button = ({ classes, children }) => (
<button className={classes.button}>
<span className={classes.label}>{children}</span>
</button>
);
export default injectSheet(styles)(Button);
Честно говоря, даже я долгое время не был впечатлён этим программированием объектами и обходился ЦСС-модулями, но однажды летом 2017 случилось две вещи: я увидел твитшторм Джеймса Кайла и стайлед-компонентс.
Какая основная проблема ЦСС? В том, что во времена приложений мы зачем-то занимаемся AOP (Aspect Oriented Programming): описываем селекторы чтобы прицепить к компоненту (у нас же везде компонентный подход).
Эндрю Стальтц нарисовал диаграммы с этими тремя разными подходами:
Видите победителя? Да, стайлед-компонентс оказался самым живучим и удобным. Ведь зачем делать .header--nav-____---_---button
когда можно сразу написать компонент и его стили?
Сравните:
// подобие БЭМ-а
import "./headerStyles.css";
// стайлед-компонентс
import styled from "styled-components";
const LogOutButton = styled.button`
color: #fff;
border: 1px solid #e3e3e3;
// etc
`;
function Header() {
return (
<header>
<button className="header--nav--button">Log In</button>
<LogOutButton>Log Out</LogOutButton>
</header>
);
}
Ого-го, это что за магия? Это стайлед-компонентс: наш первый босс, демонстрирующий компонентный подход.
Как с этим работать? Да просто: используете функцию styled(component)
чтобы застилизовать компонент. Как это работает? Автор, Макс Стойбер, рассказал в своём блоге: The magic behind 💅 styled-components. За это отвечает template literals — фича из ES2015 (сам Экмаскрипт мы ещё в будущем разберём).
Почему я написал styled(component)
, а в примере выше styled.button
? Потому что тут тот же принцип, что и в Реакте:
// Джсх
<h1>Заголовок</h1>;
// createElement
React.createElement("h1", null, "Заголовок");
Чтобы не грузить людей всякими import { h1 } from 'React'
или import { button } from 'styled-components'
, у Реакта и Стайлед-компонентс есть так называемые шорткаты (или альясы): эти библиотеки сами разбираются с дефолтными тегами ХТМЛ.
Окей, едем дальше. Одна из важных фич СК это работа с пропами: вы передаёте проп в СК, а в самом СК — читаете его.
import styled, { css } from styled-components;
const Button = styled.button`
display: inline-block;
border-radius: 3px;
padding: 0.5rem 0;
margin: 0.5rem 1rem;
width: 11rem;
background: transparent;
color: white;
border: 2px solid white;
${props => props.kind === 'primary' && css`
background: white;
color: palevioletred;
`}
`;
<Button>Обычная кнопка</Button>
<Button kind="primary">Розовая кнопка</Button>
На этом моменте я вас должен отпустить читать официальную документацию: ни один курс не объяснит лучше оригинала, а вам нужно учиться добывать информацию самому, если вы ещё не, а пока я отвечу на пару не заданных вопросов.
Можно, почему нет? У нас же компонентный подход, нам нет нужды разделять стили, логику и отображение, поэтому писать в одном файле и СК и их рендер — нормально.
Но если вам кажется что слишком много компонентов, то выделите их в файл styled.js
и положите в ту же директорию, что и компонент: например,
/src
- /Header
-- index.js
-- Search.js
-- styled.js
В будущем мы будем проходить стейты и классы, но отвечу на вопрос уже сейчас: стилизовать существующие компоненты можно, СК передаёт в них проп className
со стилями, поэтому у компонента он должен быть.
// допустим, из другой библиотеки
function Header(props) {
return (
<header className={props.className}>
<nav>...</nav>
<button>...</button>
</header>
);
}
const StHeader = styled(Header)`
background-color: #000;
`;
// если нет чилдрена, то можно тег сразу закрыть
<StHeader />;
Вот и всё знакомство с СК! Оцените красоту: больше не нужно писать стили в отдельном файле и работать с селекторами, если можно сразу писать компоненты и использовать их.
Кстати, Стайлед-компонентс, как и Реакт-роутер, ставятся через ярн: yarn add styled-components
.
Реакт-роутер иногда не любят: ещё бы, к четвёртой версии (вы помните про Семвер?) ребята сломали АПИ раза три, причём последний был настолько жёстким, что впору было называть отдельным продуктом: не было даже Upgrade Guide, который принят при смене версий. Реакт-роутер сильно поменялся и стал Реакт-роутером. И дело вот в чём.
Что такое роутер? Что такое роутинг? Базовая идея (если мы говорим про фронтэнд) проста: отобразить такой-то компонент (страницу, сборник компонентов) по такому адресу.
Например, если мы находимся на странице /courses/react
, то нужно отобразить страницу курса по Реакту, если на /courses
— список курсов, а на /
— главную. Давайте попробуем написать псевдокод для этого? Например, заведём компонент <Route />
, который будет отображать компонент в зависимости от пропов.
Для этого мы будем работать с window.location — глобальным объектом, который даёт браузер. В нём есть много разных свойств, нас интересует pathname
— текущий путь без домена.
В пропе path
мы указываем этот путь, а в component
— какой компонент нужно отрендерить.
function Route(props) {
// есть ли в текущем `pathname` путь, указанный в `props.path`
// .includes это метод у String, строки
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
if (window.location.pathname.includes(props.path)) {
// мы не можем сделать <component />,
// потому что Реакт подумает что это хтмл-тег,
// поэтому мы воспользуемся React.createElement()
React.createElement(props.component, null, null);
}
}
function App() {
return (
<section>
{/* хедер будет рендериться на всех страницах */}
<Header />
<Route path="/courses/react" component={Main} />
<Route path="/courses" component={Main} />
<Route path="/" component={Main} />
</section>
);
}
Поздравляю! Мы написали Реакт-роутер. Я не шучу, его философия динамического роутинга именно в этом и заключается: отрендерь компонент (component
) здесь, если путь (path
) сходится. Правда, у него есть ещё проп exact={true}
, чтобы рендерить только если жёсткое соответствие: у нас в примере на странице /courses/react
отрендерятся все три компонента.
Зацените, какая простая и крутая идея: использовать компонент, чтобы отрендерить другой, если путь сошёлся.
Реакт-роутер, на самом деле, чертовски мощный и вам нужно о нём знать три компонента: <BrowserRouter />
, <Route />
и <Link />
. Ещё у него есть <Redirect />
и немного других — прочтите документацию, а пока: корректно работающая демка. Кстати, эту демку я бесстыже скопировал из той же документации, раздел Examples.
iframe: https://codesandbox.io/embed/01650wwoxv
(синяя кнопка — редактор кода, бежевая — превью)
Компонентный подход простой, но богатый, и сегодня мы начали его разбирать. Нам ещё предстоит много уроков чтобы научиться с ним хорошо работать, но фундамент заложен сегодня.
Кстати, думаю у вас есть вопрос — а оправдывает ли себя такой подход на больших проектах? Ведь он такой простой, не сложно ли потом поддерживать, когда нужно больше фич? Нет, потому что легко сделать сложно и сложно сделать легко — командам Реакта, СК и Реакт-роутера приходится сильно стараться, чтобы выдавать такие простые продукты.