🌴 Принцип контекстов

Объединяй и властвуй

Очень часто можно наблюдать, как разработчики пишут код в таком духе:

export const findUser = (id: number) => {};
export const checkUserExists = (id: number) => {};
export const userValidate = (user: User) => {};
export const getUserName = (id: number) => {};

Проблема в том, что здесь явно прослеживается связь функций - это сущность User. Это означает, что можно эти функции объединить под один контекст.

export const user = {
    find(id: number) {},
    checkExists(id: number) {},
    validate(user: User) {},
    getName(id: number) {},
};

as user from ..., вот пример файла user/user.ts:

export const find = (id: number) => {};
export const checkExists = (id: number) => {};
export const validate = (user: User) => {};
export const getName = (id: number) => {};

Файл user/index.ts:

export * as user from './user';

Теперь это можно использовать следующим образом:

const name = user.getName(id);

// ...

if (user.validate(admin)) {
}

Кошелечек в сумочке, сумочка в мешочке

Контексты могут быть вложенными! Вложенные контексты очень удобны. Представим такую ситуацию, что у нас несколько разных сущностей - User, Post, Comment

Но над ними могут быть наборы разных функций, которые под собой будут иметь единый контекст. Например функции-хелперы, селекторы, события, хранилища. Здесь может быть два вида вложения.

helpers.user.someFunction / helpers.post.someFunction ...
store.users / store.posts / store.comments
selectors.users / selectors.posts / selectors.comments

Второй вариант:

users.selectors. / users.herlpers. / users.store /
posts.selectors. / posts.herlpers. / posts.store /
comments.selectors. / comments.herlpers. / comments.store /

Два варианта никак не противоречат друг другу. Более того, иногда можно встретить обе варианта в одном проекте и это не будет считаться ошибкой. Например в какой то момент store может быть неудобно хранить в сущности users / posts / comments и потребуется инвертирование, более того, могут накладываться ограничения используемых технологий.

Эй вы, трое, а ну сюда оба подошли, да, я тебе говорю!

Часто возникают проблемы с тем, а как правильно именовать сущности - во множественном или единственном числе. Например как user в контексте. Если речь идет про какое-то хранилище, ответ довольно прост. Задается вопрос - здесь будет храниться один элемент или несколько?, если несколько, то используется множественное, иначе единственное. Здесь существует несколько решений.

Проще простого

Первое - именование происходит от одиночной сущности и там где требуется множественное, используется префикс или постфикс:

// user.ts

export const find = (id: number) => {};
export const validate = (user: User) => {};
export const getName = (id: number) => {};

// many
export const findMany = (ids: number[]) => {};
export const getNames = (ids: number[]) => {};
export const everyValid = (users: User[]) => {};

Шиворот на выворот

Второй - обратное первому, всегда используется множественное, но тогда единственное число будет префикс или постфикс у функций, которые оперируют единичными сущностями:

// users.ts

export const find = (ids: number[]) => {};
export const validate = (user: User[]) => {};
export const getName = (ids: number[]) => {};

// single
export const findOne = (id: number) => {};
export const getNameOne = (id: number) => {};
export const validOne = (users: User) => {};

Как можно заметить, данный метод менее лаконичный. Более того, он более сложный при чтении. Программисты уже привыкли оперировать сущностями в единственном числе. К тому же, как можно заметить, часто можно применять синонимы для функций, которые дают понять, что здесь выработаете с множеством. В обратную сторону это практически не работает.

И волки сыты и овцы целы

Третий вариант - разбить контекст на два. На самом деле у вас может сложиться впечатление, что это лишнее, но это не так. Лично мне он нравится гораздо больше двух предыдущих. Когда я работаю с этим вариантом, я начинаю разделять сущности и коллекции сущностей. Например пользователь это одна сущность, а пользователи - уже другая. Пример на типах:

type User = {
    id: number;
    firstName: string;
    lastName: string;
};

type Users = Record<number, User>;
type UserList = User[];

Соответственно по данному примеру у меня будет примерно такая структура:

selectors.user.find(id);
selectors.users.find(ids);
selectors.userList.find(ids);

helpers.userList.convert.toUsers(userList); // -> Record<number, User>

Мне кажется это самым правильным и чистым решением.