Free React Course
For Beginners

L16 - SOLID & React

React For Beginners by Itera

Key Points

  • Single Responsibility Principle
  • Open Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency inversion Principle

SOLID можна запам'ятати за 1 один день.
А зрозуміти через 5 п'ять років.

Навіщо взагалі цей SOLID?

Принцип єдиної відповідальності

Кожен об'єкт має виконувати лише один обов'язок або мати єдину причину для зміни

export function User() {
  const [user, setUser] = useState<User | null>();

  useEffect(() => {
    getUser().then(setUser);
  }, []);

  if (!user) {
    return <div>Loading...</div>;
  }

  return <p>Hello, {user.fullName}</p>;
}

function getUser() {
  return fetch('https://jsonplaceholder.com/users/1')
    .then((response) => response.json())
    .then(({ firstName, secondName }) => ({
      fullName: `${firstName} ${secondName}`,
    }));
}
    

Головна ідея - зменшити крихкість коду та складність його окремих елементів

Головна складність SRP - баланс між наочністю коду і трудовитратами з однієї сторони та крихкістю/складгістю не розбитого коду з іншої сторони

Принцип відкритої закритості

Програмні сутності повинні бути відкритими для розширення, але закритими для змін.
Тобто, має бути спосіб змінювати поведінку коду без потреби зміни коду

Найпростіший приклад

fetch('https://test.org', { method: 'GET' });
// and /  /
fetch('https://test.org', { method: 'POST' });
    

Приклад з React

type MoneyProps = {
  value: number;
  currency: "₴" | "$" | "€";
};

export function Money({ value, currency }: MoneyProps) {
  const amount = value.toFixed(2);
  if (currency === "$") {
    return (
      <span className="currency">
        {currency} {amount}
      </span>
    );
  }

  if (currency === "₴") {
    return (
      <span className="currency">
        {amount} {currency}
      </span>
    );
  }
}
    
Головна ідея - розширення функціоналу з мінімальною зміною вже існуючого коду, що зменшує вірогідність "зламати" існуючий функціонал

Головна складність - така гнучкість зменшує наочність коду, що погіршує його читабельність та може збільшити складність

Принцип підстановки Лісков

Об'єкти в програмі можуть бути заміненими їх нащадками без зміни коду програми.
Простими словами - всі нащадки мають бути написані так, щоб їх можна було використовувати там, де використовується їх батьки

Простий приклад

class SomeComponent extends PureComponent {
  render() {} /*😈*/
}
    

Трохи хитріше

class SomeComponent extends PureComponent {
  setState = null; /*😈*/
  render() {
    return <>He-he-he</>;
  }
}
    
Проблема з LSP в тому, що він не завжди очевидний. Але, оскільки React підтримує композицію замість наслідування, це не становить великої проблеми

Принцип розділення інтерфейсу

Багато спеціалізованих інтерфейсів краще за один універсальний
interface IUser {
  name: string;
  login: () => Promise<void>;
}

function UserGreeting({ user }: { user: IUser }) {
  return <>Hello, {user.name}</>;
}

function App() {
  return <UserGreeting user={{ name: "Vitalii", login: () => Promise.resolve() }} />;
}
    
Головна ідея - спрощення використання коду, оскільки споживач "бачить" лише то, що йому необхідно. Це також призводить до спрощення тестування, оскільки нам потрібно підміняти лише частину реалізації.
Для JS/React цей принцип перетворюється на "не вимагай того, що не використовуєш"
// ✅ Use this 
function UserGreeting({ userName }: { userName: string }) {
  return <>Hello, {userName}</>;
}

// ⛔ Not this
function UserGreeting({ user }: { user: User }) {
  return <>Hello, {user.userName}</>;
}

Принцип інверсії залежностей

Залежності всередині системи будуються на основі абстракцій, що не повинні залежати від деталей; навпаки, деталі мають залежати від абстракцій.
Тобто, код не має покаладатися на конкретну реалізацію, а лише на інтерфейс або тип.
class UserApi {
  getUser(id) {
    return fetch(`https://.../user/${id}`);
  }
}
export function User() {
  const [user, setUser] = useState<User | null>();

  useEffect(() => {
    fetch('https://jsonplaceholder.com/users/1')
      .then((response) => response.json())
      .then(setUser);
  }, []);

  if (!user) {
    return <div>Loading...</div>;
  }

  return <p>Hello, {user.fullName}</p>;
}
    
Якщо ви використовуєте JS, достатньо не хардкодити конкретну реалізацію, а отримувати її ззовні.
Головна складність SOLID загалом - не завчити визнчення принципів, а зрозуміти для чого вони існують і яку проблему вирішують
А головна скоадність цієї лекції в тому, що ми намагаємося натягнути принципи OOP на фуннкціональну парадигму, яка панує в React

Takeaway

  • Keep your code simple
  • Don't hardcode
  • Don't change to the code unpredictable
  • Ask only needed
  • Don't hardcode (yea, again)

Join me

Twitter
GitHub