Сборная солянка про фронтенд. JavaScript, React, TypeScript, HTML, CSS — здесь обсуждаем всё, что связано с веб-разработкой! Связь: @pmowq
В прошлом посте мы разобрали Promise.race
, который возвращает результат самого быстрого промиса. Сегодня решим задачу с собеседований по ограничению времени выполнения промиса с его помощью.Задача
Ограничить время выполнения промиса, чтобы он завершался с ошибкой, если не уложился в 5 секунд.
Решение
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('Время ожидания истекло!')), ms);
});
}
async function getDataWithTimeout() {
try {
const dataPromise = fetch('https://api.example.com/data');
const result = await Promise.race([dataPromise, timeout(5000)]);
console.log('Данные:', await result.json());
} catch (error) {
console.error('Ошибка:', error.message);
}
}
getDataWithTimeout();
timeout(ms)
создаёт промис, который отклоняется с ошибкой через заданное время.Promise.race
запускает гонку между dataPromise
и timeout
.timeout
, и мы ловим ошибку.Привет! Сегодня разберёмся в разнице между extends
и implements
.
Что такое extends?
Ключевое слово extends
используется, когда один класс наследует другой. Это значит, что он получает все свойства и методы родительского класса и может их переопределить или дополнить.
class Animal {
move() {
console.log('Moving');
}
}
class Dog extends Animal {
bark() {
console.log('Woof!');
}
}
const dog = new Dog();
dog.move(); // Moving
dog.bark(); // Woof!
Dog
получил доступ ко всем методам Animal
.extends
, когда: implements
используется, когда класс реализует интерфейс. Интерфейс — это просто контракт, набор правил, которые класс должен соблюдать. Он не содержит никакой логики.
interface Flyable {
fly(): void;
}
class Bird implements Flyable {
fly() {
console.log('Flying');
}
}
Bird
не будет метода fly
, TS сразу покажет ошибку — потому что мы обязались его реализовать.implements
, когда: Продолжаем серию постов про SOLID.
Сегодня разберём вторую букву — O, которая расшифровывается как Open/Closed Principle или принцип открытости/закрытости.
О чем этот принцип?
Этот принцип о том, что сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации. Это значит, что вы можете добавлять новую функциональность, не изменяя существующий код.
Пример
class Shape {
constructor(type) {
this.type = type;
}
getArea() {
if (this.type === 'circle') {
// Логика для круга
return Math.PI * 5 * 5;
} else if (this.type === 'rectangle') {
// Логика для прямоугольника
return 10 * 5;
}
}
getArea
, добавляя новую ветку в if
. Это нарушает принцип закрытости, так как мы модифицируем существующий код.
class Shape {
getArea() {
throw new Error('Method getArea() must be implemented');
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
getArea() {
return Math.PI * this.radius * this.radius;
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
// Теперь можно легко добавить новую фигуру
class Triangle extends Shape {
constructor(base, height) {
super();
this.base = base;
this.height = height;
}
getArea() {
return 0.5 * this.base * this.height;
}
}
// Использование
const shapes = [new Circle(5), new Rectangle(10, 5), new Triangle(6, 8)];
shapes.forEach(shape => console.log(shape.getArea()));
Всем привет!
Ухожу в мини-отпуск 🏝, но постараюсь не исчезать и выкладывать посты)
Всем хороших выходных 😘
В одном из предыдущих постов мы кратко познакомились с SOLID. Сегодня начнем серию постов и разберём первую букву — S. Эта буква расшифровывается как Single Responsibility Principle или принцип единственной ответственности.
Этот принцип говорит, что каждый модуль или класс должен отвечать только за одну задачу.
Пример плохого подхода:
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
save() {
// сохраняет пользователя в базу
}
sendEmail(message) {
// отправляет письмо пользователю
}
}
User
хранит данные, отвечает за сохранение и за отправку почты. Такой код сложнее расширять, тестировать и в нём выше риск случайно что-то сломать при изменениях.
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
class UserRepository {
save(user) {
// логика сохранения в базу
}
}
class EmailService {
sendEmail(user, message) {
// логика отправки письма
}
}
На фронтенде производительность — ключевой фактор для хорошего пользовательского опыта. Чем быстрее загружается и работает приложение, тем лучше.
Одним из инструментов оптимизации является динамический импорт. Он позволяет загружать модули только тогда, когда они действительно нужны.
Как это?
Вместо статического импорта:
import { heavyFunc } from './heavyModule.js';
button.addEventListener('click', () => {
heavyFunc();
});
button.addEventListener('click', async () => {
const module = await import('./heavyModule.js');
module.heavyFunc();
});
heavyModule.js
загрузится только после клика по кнопке, а не сразу при загрузке страницы. React.lazy
.
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<LazyComponent />
</Suspense>
);
}
MyComponent
загрузится только при первом рендере LazyComponent
.Закончим неделю на CSS 🖼️
Иногда нужно стилизовать элемент только если он единственный в родителе. В CSS это можно сделать с помощью псевдокласса :only-child
.
Как он работает?
Это псевдокласс выбирает элемент, если он единственный потомок родителя.
Пример:
.parent > div:only-child {
background-color: blue;
}
.parent
будет один div
.:only-child
позволяет находить потомков, но иногда нам нужно стилизовать их родителя. Для этого можно комбинировать с псевдоклассом :has()
.
.parent:has(:only-child) {
background-color: red;
}
:only-child
: Can I Use:has
: Can I UseПривет! Отдохнули? Начнем неделю с 🖼️
Каждый из нас работает с TypeScript, но не все знают про оператор satisfies
. Он помогает гарантировать, что объект соответствует нужному типу, сохраняя при этом точный тип значения.
Пример
type ButtonTypes = 'primary' | 'secondary';
type ButtonSizes = 'small' | 'big' | number;
interface ButtonConfig {
type: ButtonTypes;
size: ButtonSizes;
}
const button = {
type: 'primary', // тип "primary"
size: 'big' // тип "big"
} satisfies ButtonConfig;
const button2 = {
type: 'secondary', // тип "secondary"
size: 100 // тип "number"
} satisfies ButtonConfig;
console.log(button.size);
console.log(button2.size.toFixed(0));
type
и size
соответствуют интерфейсу ButtonConfig
type ButtonTypes = 'primary' | 'secondary';
type ButtonSizes = 'small' | 'big' | number;
interface ButtonConfig {
type: ButtonTypes;
size: ButtonSizes;
}
const button: ButtonConfig = {
type: 'primary', // тип "ButtonTypes"
size: 'big' // Тип "ButtonSizes"
};
if (typeof button.size === 'number') {
console.log(button.size.toFixed(0));
}
Привет! Начнем эту 3-дневную рабочую неделю с небольшой алгоритмической задачи.
Задача
Напишите функцию, которая возвращает массив уникальных значений, отсортированных:
1. по количеству повторений (по убыванию),
2. если количество совпадает — по алфавиту.
Пример входных данных:
const words = [
'banana', 'grapefruit', 'banana', 'grapefruit',
'banana', 'orange', 'banana', 'grapefruit',
'grapefruit', 'grapefruit'
];
['grapefruit', 'banana', 'orange']
function getSortedUnique(words) {
const freq = {};
for (let word of words) {
freq[word] = (freq[word] || 0) + 1;
}
return Object.keys(freq).sort((a, b) => {
if (freq[b] !== freq[a]) {
return freq[b] - freq[a]; // по убыванию частоты
}
return a.localeCompare(b); // по алфавиту
});
}
freq
.Использовали атрибут inert?
Этот атрибут позволяет отключить часть интерфейса от взаимодействия — элементы становятся недоступными для клика, фокуса и скринридеров.
Что делает inert?
Атрибут удаляет элемент из порядка навигации — кликнуть, сфокусироваться или ввести данные внутри него нельзя.
Поведение похоже на disabled
, но есть важные отличия:
— inert
можно применить к любым элементам
— В отличие от disabled
, текст внутри inert
нельзя выделить, и он не реагирует на кастомные эффекты наведения или фокуса
💻 Пример использования:
<main inert>
<!-- Здесь всё будет неактивно -->
</main>
inert
не влияет на стили по умолчанию — нужно самостоятельно делать элементы визуально неактивными, например:
[inert] {
opacity 0.7;
}
const element = document.body;
// Добавляем inert
element.inert = true;
// Удаляем inert
element.inert = false;
Работа с API, кэширование, обновление данных и обработка ошибок — всё это неизбежно возникает в любом приложении. На выбор у нас есть много подходов и инструментов. Один из таких — React Query.
Что такое React Query?
Это библиотека для управления данными в React-приложениях. Она помогает работать с запросами и решает типичные задачи, связанные с их загрузкой, кэшированием, обновлением и обработкой ошибок.
Какие проблемы решает?
1. Вместо написания шаблонного кода для запросов, состояний и обработки ошибок — можно использовать готовые хуки.
2. Данные автоматически кэшируются, и повторные запросы выполняются только при необходимости.
3. Готовые состояния и возможность легко повторить запрос.
4. Минимизирует количество запросов к API за счет кэширования и умной стратегии обновления.
5. Реализация сложных сценариев становится проще.
Как использовать React Query?
1. Установите библиотеку в проект:
npm install @tanstack/react-query
QueryClientProvider
:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<Component />
</QueryClientProvider>
);
}
useQuery
для загрузки данных:
const { data, isLoading, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
});
useMutation
для изменения данных:
const { mutate } = useMutation({
mutationFn: updateTodo,
});
mutate({ id: 1, text: 'Updated Todo' });
Всем привет!
Начнем эту неделю с мемов и настроимся на продуктивную рабочую неделю 🖥
А я начну неделю с дейоффа 😀
Когда мы пишем приложения, важно обеспечить их стабильность, расширяемость и простоту поддержки. В этом посте узнаем про принципы SOLID, которые помогают с этим)
Используя SOLID-принципы, вы пишете код, который легче тестировать, поддерживать и развивать.
SOLID — это аббревиатура, которая объединяет 5 принципов:
S - Single Responsibility Principle (Принцип единой ответственности)
Каждый класс или компонент должен отвечать только за одну задачу. Это упрощает тестирование и отладку, потому что если что-то идет не так, вы точно знаете, какой класс или компонент за это отвечает. Такой подход также облегчает расширение приложения, так как изменение в одном месте не затронет другие части.
O - Open/Closed Principle (Принцип открытости/закрытости)
Классы должны быть открыты для расширения, но закрыты для модификации. Это означает, что вы можете добавлять новый функционал, не изменяя существующий код. Это достигается через использование абстракций, таких как интерфейсы и наследование.
L - Liskov Substitution Principle (Принцип подстановки Лисков)
Объекты базового класса должны быть заменяемыми объектами производного класса без нарушения корректности программы. Другими словами, наследуемые классы должны вести себя как их базовые классы, чтобы не ломать логику приложения.
I - Interface Segregation Principle (Принцип разделения интерфейсов)
Не стоит создавать универсальные интерфейсы, которые будут иметь множество методов, не используемых в разных классах. Лучше создавать несколько более специализированных интерфейсов. Это уменьшает зависимость классов и облегчает поддержку кода.
D - Dependency Inversion Principle (Принцип инверсии зависимостей)
Высокоуровневые модули не должны зависеть от низкоуровневых, а обе эти категории должны зависеть от абстракций. Это позволяет сделать код гибким и менее зависимым от конкретных реализаций.
Применение этих принципов поможет писать более чистый и поддерживаемый код, который будет легко расширяться и адаптироваться под изменения.
#BestPractices
Привет! Хотя регулярность постов и изменилась, неделя по-прежнему начинается с разбора задач с собеседований)
У нас есть пример типизированого массива:
interface Student {
name: string;
age: number;
hasScar: boolean;
}
const students: Student[] = [
{ name: "Harry", age: 17, hasScar: true },
{ name: "Ron", age: 17, hasScar: false },
{ name: "Hermione", age: 16, hasScar: false },
];
function getBy(arr, prop, value) {
return arr.find(item => item[prop] === value) || null;
}
const result = getBy(students, "name", "Hermione");
console.log(result);
getBy
.
function getBy<T, K extends keyof T>(arr: T[], prop: K, value: T[K]) {
return arr.find(item => item[prop] === value) || null;
}
<T, K extends keyof T>
— дженерик:T
— это универсальный тип, который представляет тип объекта внутри массива arr
.K extends keyof T
— означает, что K
— это ключ объекта T
. Тип keyof T
— это объединение всех ключей объекта T
. arr: T[]
— массив объектов типа T
.prop: K
— ключ, по которому будем искать совпадение.value: T[K]
— значение, с которым будем сравнивать. Тип значения автоматически подтягивается из типа свойства prop
. Сегодня немного затронем работу с ошибками в 🖼️
Создание ошибок
Чтобы создать ошибку, нужно создать новый экземпляр класса Error
и передать сообщение в конструктор:
try {
throw new Error("Что-то пошло не так!");
} catch (error) {
console.error(error.message); // Что-то пошло не так!
}
throw
. Ошибка перехватывается в блоке catch
, где мы можем вывести её сообщение.message
— сообщение об ошибке.name
— тип ошибки, по умолчанию это Error
.stack
— стек вызовов, который позволяет увидеть, откуда была вызвана ошибка.throw
и try...catch
помогает контролировать ошибки в коде и избежать краша нашего приложения. Например, можно перехватить её и выполнить другие действия:
try {
const result = someFunction(); // Эта функция выбросит ошибку
} catch (error) {
console.error("Произошла ошибка:", error.message);
// Тут можно выполнить альтернативные действия
}
try...catch
можно контролировать, как ошибки влияют на дальнейшие действия.Продолжаем разбираться с промисами. Сегодня разберем Promise.race
.
Что такое Promise.race?Promise.race
- это метод, который принимает массив промисов и возвращает новый промис, который завершается или отклоняется так же, как самый быстрый промис в массиве. То есть, как только один из промисов завершается (успешно или с ошибкой), Promise.race
сразу возвращает его результат, игнорируя остальные.
Пример использования
Представим, что у нас есть несколько API, и нам нужен результат от того, который ответит быстрее:
async function getFastestData() {
try {
const api1 = fetch('https://api1.example.com/data');
const api2 = fetch('https://api2.example.com/data');
const api3 = fetch('https://api3.example.com/data');
const winner = await Promise.race([api1, api2, api3]);
console.log('Самый быстрый API:', await winner.json());
} catch (error) {
console.error('Ошибка:', error);
}
}
Promise.race
дождётся первого ответа от любого API. Если один из запросов завершится с ошибкой раньше остальных, Promise.race сразу перейдёт в состояние ошибки.Promise.race
не отменяет остальные промисы. Даже если один промис завершился, остальные продолжают выполняться в фоне.Привет! Сегодня обсудим TypeScript и поймем нужно ли его учить. TypeScript — это не просто хайп, а инструмент, который делает разработку проще.
Что такое TypeScript?
TypeScript — это надстройка над JavaScript, которая добавляет статическую типизацию. Код на TS компилируется в обычный JS, но с кучей бонусов для разработчика.
Зачем использовать TypeScript?
1. Типы помогают ловить ошибки ещё на этапе написания кода. Например, если вы случайно передадите строку вместо числа, TS сразу укажет на проблему.
function add(a: number, b: number): number {
return a + b;
}
add("2", 3); // TS не даст передать строку вместо числа
В прошлый раз мы познакомились с методом Promise.allSettled
, а сегодня разберём задачу с собеседования с реализацией кастомного allSettled
.
Задача:
Напишите функцию allSettled
, которая работает аналогично встроенному Promise.allSettled
.
Пример использования:
const p1 = Promise.resolve(1);
const p2 = Promise.reject('Ошибка');
const p3 = new Promise(res => setTimeout(() => res(42), 100));
allSettled([p1, p2, p3]).then(results => {
console.log(results);
/*
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 'Ошибка' },
{ status: 'fulfilled', value: 42 }
]
*/
});
function allSettled(promises) {
return Promise.all(
promises.map(p =>
Promise.resolve(p)
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
)
);
}
Promise.all
ждёт, пока все обёрнутые промисы завершатся, и возвращает массив результатов.В прошлом посте мы разобрали Promise.all
, а сегодня разберём Promise.allSettled
. В конце прошлого поста затронули проблему, что если один из промисов падает, то Promise.all
сразу выдаёт ошибку и игнорирует остальные.Promise.allSettled
работает иначе. Он ждёт, пока завершатся все промисы, и возвращает массив результатов по каждому из них. Неважно, завершился он успешно или с ошибкой.
Что это за метод?
Метод Promise.allSettled
возвращает промис, который ожидает завершения всех переданных промисов, вне зависимости от того, успешно они выполнились или нет.
После этого он возвращает массив объектов, каждый из которых содержит статус выполнения соответствующего промиса и его результат или причину ошибки.
Пример:
async function getData() {
try {
const userPromise = fetch('/user');
const postsPromise = fetch('/posts');
const commentsPromise = fetch('/comments');
const results = await Promise.allSettled([
userPromise,
postsPromise,
commentsPromise
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Запрос ${index + 1} выполнен`, result.value);
} else {
console.warn(`Запрос ${index + 1} упал`, result.reason);
}
});
} catch (error) {
console.error('Ошибка:', error);
}
}
{
status: "fulfilled",
value: ... // значение, которое вернул промис
}
{
status: "rejected",
reason: ... // ошибка
}
Многие из нас сталкиваются с проблемой, когда нужно сделать сразу несколько запросов. Часто такие запросы выполняются по цепочке, хотя на самом деле это не всегда нужно. Если запросы не зависят от ответа предыдущего, ждать их друг за другом бессмысленно. В таких случаях запросы лучше выполнять параллельно, чтобы ускорить работу и улучшить пользовательский опыт.
Пример работающего, но проблемного кода:
async function getData() {
try {
const user = await fetch('/user');
const posts = await fetch('/posts');
const comments = await fetch('/comments');
console.log('Все данные получены');
} catch (error) {
console.error('Ошибка:', error);
}
}
Promise.all
. Это функция, которая принимает массив промисов и позволяет запустить их одновременно. Она возвращает новый промис, который завершится успешно, когда все переданные промисы завершатся, или упадёт, если хотя бы один промис вернёт ошибку.
async function getData() {
try {
const userPromise = fetch('/user');
const postsPromise = fetch('/posts');
const commentsPromise = fetch('/comments');
const [user, posts, comments] = await Promise.all([
userPromise,
postsPromise,
commentsPromise
]);
console.log('Все данные получены');
} catch (error) {
console.error('Ошибка:', error);
}
}
Promise.allSettled
, который позволяет получить результаты всех промисов, даже если некоторые из них упали. Но эту тему мы затронем в одном из следующих постов.Привет! Начнем неделю с задачи которой со мной поделился коллега.
Задача:
Написать порядок вывода в консоль и объяснить.
Решение:
Всего у нас будет 2 рендера, так как в коде есть useEffect
, который меняет состояние после первого рендера.
1. App
— компонент рендерится, выводится лог.
2. useLayoutEffect
— этот эффект срабатывает синхронно после обновления DOM.
3. useEffect
— эффект после отрисовки интерфейса.
4. App
— повторный рендер компонента из-за обновления состояния.
5. useEffect cleanup
— очистка эффекта useEffect с предыдущего рендера.
6. useLayoutEffect cleanup
— очистка эффекта useLayoutEffect
с предыдущего рендера.
7. useLayoutEffect
— повторное выполнение синхронного эффекта с обновлённым состоянием.
8. useEffect
— повторное выполнение асинхронного эффекта с обновлённым состоянием.
Такой порядок из-за работы React:
- useLayoutEffect
вызывается после обновления DOM, но до того, как браузер нарисует изменения на экране.
- useEffect
выполняется после отрисовки.
- Функции очистки вызываются перед повторным выполнением эффекта или при размонтировании компонента.
#react #interview
В CSS появилось новое экспериментальное свойство: anchor-name
. Оно позволяет задавать якорь, к которому можно привязывать другие элементы через CSS.
Что и для чего?
Свойство anchor-name
позволяет задать якорь для элемента. Этот якорь может быть использован другими элементами для позиционирования.
Мы указываеем точку привязки, а другие элементы могут позиционироваться относительно неё.
Пример работы
1. Якорь объявляется на элементе:
.anchor { anchor-name: --info; }
.tooltip {
position: absolute;
position-anchor: --info;
}
anchor
.tooltip {
left: anchor(right);
top: anchor(top);
}
Когда-нибудь использовали CSS-функцию attr
? С помощью этой функции мы можем подставлять значения HTML-атрибутов прямо в стили.
Что это такое?attr()
— CSS-функция, которая извлекает значение HTML-атрибута и подставляет его в стиль или содержимое.
Раньше она работала только в content
, теперь экспериментально доступна и в других свойствах.
Пример:
<button class="tooltip-btn" data-tooltip="Удалить файл">Удалить</button>
.tooltip-btn::after {
content: attr(data-tooltip);
}
attr
в таких свойствах, как width
, height
, margin
и др. Пример:
<div data-width="150" data-height="150"></div>
div {
width: attr(data-width px, 100px);
height: attr(data-height px, 100px);
}
Недавно мы разбирали(тык) работу с ошибками в JS.
Сегодня рассмотрим создание кастомных ошибок. Они позволяют точнее описывать, что именно пошло не так, и обрабатывать ошибки более точечно.
Вместо обычного Error
можно создать свой класс, унаследованный от него:
class ValidationError extends Error { // 1
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function validateUser(user) { // 2
if (!user.name) {
throw new ValidationError("Поле 'name' обязательно");
}
}
try {
validateUser({}); // 3
} catch (error) {
if (error instanceof ValidationError) { // 4
console.error("Ошибка валидации:", error.message);
} else {
throw error;
}
}
Error
, чтобы определить новый тип ошибки — ValidationError
.validateUser
проверяет, есть ли у объекта поле name
. Если его нет — выбрасывает кастомную ошибку ValidationError
с понятным сообщением.try
вызываем validateUser
, передавая объект без name
.catch
ловим ошибку и проверяем, является ли она экземпляром ValidationError
с помощью instanceof
.instanceof
.Вы наверняка видели OTP-поля для ввода кодов. А знаете, как они делаются?
Сегодня разберёмся, как они делаются с помощью атрибута size
.
Что делает атрибут size?
Атрибут size
указывает примерное количество видимых символов в поле ввода. Это влияет только на ширину, но не ограничивает количество вводимых символов.
<input type="text" size="6">
<input type="text" size="1" maxlength="1">
<input type="text" size="1" maxlength="1">
<input type="text" size="1" maxlength="1">
<input type="text" size="1" maxlength="1">
size="1"
делает поле под один символ. maxlength="1"
ограничивает ввод до одного символа.text
, search
, tel
, url
, email
, password
maxlength
width
перекроет size
, если задать обаСегодня у нас очередная задача с собеседования, связанная с рекурсией)
Задача:
Нужно распарсить вложенный объект, чтобы получить значение самого глубокого уровня.
Пример вызова функции:
const obj = { red: { fast: { fancy: { car: "lamba" } } } };
console.log(objParse(obj)); // "lamba"
function objParse(obj) {
const [value] = Object.values(obj); // 1, 2
if (typeof value !== "object") { // 3
return value;
}
return objParse(value); // 4
}
Object.values(obj)
возвращает массив всех значений объекта. value
.objParse
рекурсивно.Надеюсь, посты этой недели вам понравились)
Хороших выходных, фронтендеры 😘
В TypeScript 🖼️ есть два основных способа описания структуры данных — интерфейсы и типы. Когда и что выбирать?
Интерфейсы в TypeScript обычно используются для описания структуры объектов, особенно если они предполагают возможность расширения.
Их можно расширять с помощью extends
, который позволяет создавать новые структуры на основе существующих.
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const dog: Dog = {
name: 'Кнопка',
breed: 'Лабрадор'
};
type Result = 'success' | 'error';
const result: Result = 'success'; // Ок
const error: Result = 'failure'; // Будет ошибка
type Person = { name: string };
type Worker = { job: string };
type Employee = Person & Worker;
const employee: Employee = {
name: 'Иван',
job: 'Frontender'
};
type Coordinates = [number, number];
const point: Coordinates = [10, 20];
Хотел бы обсудить сохранение постов.
Многие из нас сохраняют интересные посты. Но возвращаемся ли мы к этим сохранёнкам? Чаще всего — нет.
Почему так?
Потому что сохранение — это не запоминание. Это как положить книгу на полку и подумать, что ты уже её прочитал.
И главное мы почти никогда не возвращаемся к сохраненкам) Просто накапливаем.
А как лучше?
1. Если просто интересно — лучше прочитай сейчас, чем потом. Потом не наступит ✅
2. Если хочешь попробовать — сразу пробуй 🍺
3. Сохраняй меньше, но осознаннее 🧠
4. Не сохраняй вообще, а читай и забывай — это тоже нормально ✍️
Сохрани этот пост чтобы не потерять 🚨
Ставь палец вверх если сохранил 👍
#BestPractices #Obsidian
Я хорошо отдохнул на майских — и, честно говоря, мне это понравилось)
Посты могут выходить чуть реже, но качество контента не меняется. Хуже точно не станет 🙃