Atomic Design — методология создания интерфейсов через систематизацию компонентов по уровням: атомы, молекулы, организмы, шаблоны и страницы. В контексте Stencil, который является фреймворком для создания Web Components, этот подход идеально подходит для построения модульного, повторно используемого и легко поддерживаемого кода.
В Stencil каждый компонент — это отдельный веб-компонент, который инкапсулирует логику, разметку и стили. Создание атома начинается с генерации компонента через CLI:
npm init stencil
Структура атома в Stencil:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'my-button',
styleUrl: 'my-button.css',
shadow: true,
})
export class MyButton {
@Prop() label: string;
@Prop() disabled: boolean = false;
render() {
return (
<button disabled={this.disabled}>
{this.label}
</button>
);
}
}
Ключевые моменты:
@Component определяет тег, стили и использование Shadow
DOM.@Prop позволяет передавать данные внутрь
компонента.render() возвращает JSX-разметку.Молекула объединяет несколько атомов. В Stencil это делается простым импортом и использованием атомов внутри другого компонента:
import { Component, h } from '@stencil/core';
@Component({
tag: 'login-input',
styleUrl: 'login-input.css',
shadow: true,
})
export class LoginInput {
render() {
return (
<div class="login-field">
<label htmlFor="username">Username:</label>
<my-input id="username" placeholder="Введите имя" />
</div>
);
}
}
Особенности:
Организмы формируют значительные функциональные блоки интерфейса. В Stencil их создание подразумевает управление состоянием и обработку событий:
import { Component, State, h } from '@stencil/core';
@Component({
tag: 'login-form',
styleUrl: 'login-form.css',
shadow: true,
})
export class LoginForm {
@State() username: string = '';
@State() password: string = '';
handleLogin() {
console.log('Logging in:', this.username, this.password);
}
render() {
return (
<form onSub mit={(e) => { e.preventDefault(); this.handleLogin(); }}>
<login-input />
<login-input />
<my-button label="Войти" />
</form>
);
}
}
Здесь:
@State используется для локального состояния
компонента.Шаблоны определяют расположение организмов на макете, а страницы подставляют реальные данные:
import { Component, h } from '@stencil/core';
@Component({
tag: 'login-page',
styleUrl: 'login-page.css',
shadow: false,
})
export class LoginPage {
render() {
return (
<main>
<header>Авторизация</header>
<login-form />
<footer>© 2025</footer>
</main>
);
}
}
Отличия шаблонов и страниц:
Рекомендуемая структура проекта:
src/components/
├── atoms/
│ ├── my-button/
│ └── my-input/
├── molecules/
│ └── login-input/
├── organisms/
│ └── login-form/
├── templates/
│ └── login-template/
└── pages/
└── login-page/
Преимущества:
Stencil автоматически изолирует стили через Shadow DOM, что особенно полезно в Atomic Design, где одни и те же атомы используются в разных контекстах.
Пример объединения локальных и глобальных стилей:
/* my-button.css */
button {
padding: 0.5em 1em;
border-radius: 4px;
background-color: var(--primary-color, #007bff);
color: white;
border: none;
cursor: pointer;
}
Стили атомов наследуются молекулами и организмами без конфликта имен.
Stencil поддерживает EventEmitter для коммуникации между компонентами:
import { Component, Event, EventEmitter, h } from '@stencil/core';
@Component({
tag: 'my-button',
styleUrl: 'my-button.css',
shadow: true,
})
export class MyButton {
@Event() clicked: EventEmitter<void>;
handleClick() {
this.clicked.emit();
}
render() {
return <button onCl ick={() => this.handleClick()}>Нажми меня</button>;
}
}
На уровне организма можно подписаться на событие:
<my-button onClic ked={() => console.log('Button clicked')} />
@State и
события позволяет строить сложные взаимодействия между организмами и
страницами.Atomic Design и Stencil вместе создают мощный инструмент для построения модульных и масштабируемых интерфейсов, где каждый уровень компонентов имеет своё место и строго определённые функции.