Компоненты и проект: конвертер
Компоненты во Vue
Как и в React, интерфейс во Vue собирают из компонентов. Компонент — объект с setup и template,
а использовать его в другом компоненте можно, зарегистрировав в components:
const Hello = {
template: `<p>Привет!</p>`,
};
const App = {
components: { Hello },
template: `
<div>
<Hello />
<Hello />
</div>
`,
};
В файлах .vue то же самое пишут через import Hello from "./Hello.vue".
Props
Данные от родителя компонент объявляет в props. В шаблоне они доступны по имени, а в setup —
через первый аргумент:
const UserCard = {
props: ["name", "age"],
setup(props) {
const isAdult = computed(() => props.age >= 18);
return { isAdult };
},
template: `
<div class="card">
<h3>{{ name }}</h3>
<p v-if="isAdult">18+</p>
</div>
`,
};
Родитель передаёт строки как обычные атрибуты, а числа, массивы и данные из setup — через ::
<UserCard name="Аня" :age="20" />
<UserCard :name="user.name" :age="user.age" />
Без двоеточия age="20" придёт строкой "20", а не числом.
Props можно описать подробнее — с типом и значением по умолчанию:
props: {
name: { type: String, required: true },
age: { type: Number, default: 0 },
},
Как и в React, props только для чтения: компонент не должен их менять. props реактивен —
когда родитель передаёт новое значение, компонент обновится сам.
Событие наверх: emit
Ребёнок не меняет данные родителя, а сообщает ему о событии. Родитель решает, что делать:
const Counter = {
props: ["value"],
emits: ["change"],
setup(props, { emit }) {
const inc = () => emit("change", props.value + 1);
return { inc };
},
template: `<button @click="inc">{{ value }}</button>`,
};
<!-- родитель -->
<Counter :value="count" @change="count = $event" />
emit("имя", данные) вызывает обработчик @имя у родителя; переданные данные в шаблоне родителя
доступны как $event. Список событий объявляют в emits — это документация для читателя кода.
Это та же схема, что в React: данные вниз через props, события вверх через emit.
Слоты
Содержимое между тегами компонента попадает в <slot> — аналог children в React:
const Card = {
props: ["title"],
template: `
<section class="card">
<h2>{{ title }}</h2>
<slot></slot>
</section>
`,
};
<Card title="Новости">
<p>Сегодня вышел Vue 3.5!</p>
</Card>
Проект: конвертер валют
Конвертер собирает весь курс:
- ref для суммы, валют и истории;
- v-model для поля (с
.number, чтобы получить число) и списковselect; - v-for для вариантов валют и истории;
- computed для результата и строки «100 USD = 92 EUR»;
- события для обмена валют местами и сохранения в историю.
Курсы задают «сколько единиц валюты за 1 доллар». Тогда любую пару считают через доллар: сначала
переводим сумму в доллары (amount / rates[from]), потом в нужную валюту (× rates[to]):
const result = computed(() =>
Math.round((amount.value / rates[from.value]) * rates[to.value] * 100) / 100
);
Обмен местами — деструктуризация: [from.value, to.value] = [to.value, from.value].
История с ограничением длины — новый массив: [новое, ...старое].slice(0, 3).
В реальном приложении курсы загружали бы с сервера в onMounted или watch с immediate, а историю
сохраняли бы в localStorage через watch — всё это ты уже умеешь.
Итог
- Компоненты регистрируют в
componentsи используют как теги. props— данные от родителя (только чтение);:propдля не-строк.emit("событие", данные)— сообщить родителю; родитель слушает@событие, данные в$event.<slot>— место для содержимого между тегами компонента.