IlmHamroh
JavaScript Full-stack/22-qism. Frontend testlash11/16-dars22 daqiqa
Mundarija (39)

Storybook I: komponentlarni izolyatsiyada ishlab chiqish — story, args, controls va dekoratorlar

Qisqacha: Storybook — komponentlar ustaxonasi: har komponentni ilovadan alohida, har bir holati bilan (oddiy, bo'sh, xato, uzun matn) ko'rsatadi. Holat — story: Komponent.stories.tsx faylidagi oddiy obyekt, uning args i — komponentga beriladigan props. Default eksport (meta) komponentni va umumiy args'ni aytadi, har nomli eksport — bitta story. Komponentga router, tema yoki provider kerak bo'lsa — preview.tsx dagi dekorator uni o'rab beradi. vazifalar da Storybook 10.6.1: 4 fayl, 9 story, node_modules ga +53 MB, ilova bundle'i o'zgarmaydi.

Bu darsda

  • Storybook qaysi muammoni hal qilishini va qachon kerak emasligini tushuntira olasiz.
  • Storybook 10 ni React + Vite loyihasiga qo'lda o'rnatasiz: .storybook/main.ts va preview.tsx.
  • CSF3 formatida story yozasiz: meta, args, story'ning o'z args i, fn() bilan actions.
  • Controls (argTypes), dekoratorlar, autodocs va MSW addon nima qilishini bilasiz.
  • vazifalar ga Storybook qo'shib, ikki haqiqiy tuzoqni (sw.js va router) tuzatasiz.

Oldin bilishingiz kerak: RTL II: foydalanuvchi harakatlari, Dizayn tizimi: tokenlar va variantlar, Komponent API'sini loyihalash, Playwright II.

1. Nega bu kerak?

Sardor OchirishDialog ning matnini o'zgartirdi. Natijani ko'rish uchun u nima qiladi? Ilovani ochadi, kiradi, vazifa qo'shadi, qator menyusidan «O'chirish» ni bosadi. Besh qadam — bitta dialogni ko'rish uchun. Ro'yxatning bo'sh holatini ko'rish uchun esa hamma vazifani o'chirish kerak. Uzun nomli vazifa qanday chiqishini bilish uchun uzun nom yozish kerak. Har o'zgarishdan keyin yana boshidan.

Yana bir muammo — holatlarni hech kim eslab qolmaydi. Dizayner "xavfli tugma qanday ko'rinadi?" deb so'rasa, Sardor uni ilovaning qaysi burchagidan topishini bilmaydi.

Storybook — komponentlarni ilovadan alohida, izolyatsiyada (isolation) chizadigan ustaxona. Har komponentning har bir holati — alohida sahifa: chap tomonda ro'yxat, o'rtada komponent, pastda uning props'ini o'zgartirish paneli. Ilova kerak emas, server kerak emas, kirish kerak emas.

Hayotiy o'xshatish: oshxonada oshpaz yangi sousni zalga chiqarmasdan, alohida likopchada tatib ko'radi — sho'r, achchiq, quyuq variantlarini yonma-yon. Storybook — komponentlar uchun shunday tatib ko'rish stoli.

flowchart LR
  subgraph I["Ilovada"]
    A1["kirish"] --> A2["vazifa qo'shish"] --> A3["menyu"] --> A4["dialog"]
  end
  subgraph S["Storybook'da"]
    B1["OchirishDialog / OchiqDialog"]
  end

Rasmda: ilovada dialogga to'rt qadam bilan yetasiz, Storybook'da — ro'yxatdagi bitta bosish bilan.

Storybook bilan nima keladi:

Imkoniyat Nima beradi
Story ro'yxati har komponentning hamma holati bir joyda
Controls props'ni panelda o'zgartirib ko'rish
Actions onClick kabi hodisalar chaqirilganini ko'rish
Docs komponent hujjati (props jadvali, misollar)
Testlar story'lar testga aylanadi (Storybook II)

Tekshirib ko'ring: Sardorning jamoasida bitta sahifali kichik landing bor, unda 5 ta komponent va bitta dasturchi. Storybook kerakmi?

Javob

Ehtimol yo'q. Storybook ko'p komponentli, ko'p holatli loyihada o'zini oqlaydi: dizayn tizimi, bir nechta dasturchi, dizayner bilan ishlash. Narxi bor — bu darsda o'lchaymiz (+53 MB, alohida sozlash, alohida build). 5 ta komponent uchun RTL testlari (RTL I) yetarli.

2. Story nima: CSF3

2.1 Fayl tuzilmasi

Story'lar CSF (Component Story Format — komponent story formati) da yoziladi. CSF — oddiy ES modul: maxsus sintaksis yo'q. Joriy versiyasi — CSF3. vazifalar dagi Tugma story fayli to'liq:

tsx
import type { Meta, StoryObj } from "@storybook/react-vite";
import { fn } from "storybook/test";
import { Tugma } from "../src/ui/Tugma.tsx";

const meta = {
  component: Tugma,
  args: { children: "Saqlash", onClick: fn() },
} satisfies Meta<typeof Tugma>;
export default meta;
type Story = StoryObj<typeof meta>;

export const Oddiy: Story = {};
export const Asosiy: Story = { args: { variant: "asosiy" } };
export const Xavfli: Story = {
  args: { variant: "xavfli", children: "O'chirish" },
};

Qatorma-qator:

Qism Ma'nosi
export default meta fayl haqida: qaysi komponent, umumiy args
component: Tugma shu komponent chiziladi
args (meta'da) hamma story uchun umumiy props
export const Oddiy bitta story; nomi ro'yxatda «Oddiy» bo'lib chiqadi
args (story'da) faqat shu story uchun props — umumiysining ustiga
satisfies Meta<…> TypeScript props nomlarini tekshiradi (satisfies)

Args (argumentlar) — story komponentga beradigan props. Xavfli story'sida args birlashadi: children — story'dan («O'chirish»), onClick — meta'dan. Story'niki ustun.

title yozilmagan — Storybook nomni fayl yo'lidan oladi (stories/Tugma.stories.tsx → «Tugma»). Kerak bo'lsa title: "UI/Tugma" bilan guruhlash mumkin.

2.2 Story — oddiy obyekt: o'zimiz chizib ko'ramiz

Natija oynasida Storybook yo'q — u alohida dastur. Lekin CSF oddiy JavaScript bo'lgani uchun g'oyani RTL bilan sinab ko'rish mumkin: meta va story'larni olamiz, args'ni birlashtiramiz va har story'ni chizamiz. Bu — o'quv modeli; Storybook ichida ham mohiyatan shu ish bo'ladi.

jsx
import { render, screen } from "@testing-library/react";
import { expect, test, vi } from "vitest";

function DishCard({ name, price, soldOut = false, onAdd }) {
  return (
    <article aria-label={name}>
      <h3>{name}</h3>
      <p>{price} so'm</p>
      <button disabled={soldOut} onClick={onAdd}>
        {soldOut ? "Tugadi" : "Qo'shish"}
      </button>
    </article>
  );
}

// DishCard.stories.jsx — xuddi shu shaklda yoziladi
const meta = {
  component: DishCard,
  args: { name: "Osh", price: "35 000", onAdd: vi.fn() },
};
const Default = {};
const SoldOut = { args: { name: "Manti", soldOut: true } };
const LongName = {
  args: { name: "Qovurma lag'mon, katta porsiya" },
};

const stories = { Default, SoldOut, LongName };

for (const [title, story] of Object.entries(stories)) {
  test(title, () => {
    const args = { ...meta.args, ...story.args };
    const Component = meta.component;
    render(<Component {...args} />);
    const card = screen.getByRole("article");
    console.log(card.textContent);
    expect(screen.getByRole("button")).toBeInTheDocument();
  });
}

Konsolda:

text
Osh35 000 so'mQo'shish
  ✓ Default
Manti35 000 so'mTugadi
  ✓ SoldOut
Qovurma lag'mon, katta porsiya35 000 so'mQo'shish
  ✓ LongName
Testlar: 3 o'tdi (jami 3)

textContent — maqoladagi hamma matn yopishgan holda: nom (<h3>), narx (<p>), tugma. Qatorlarni solishtiring. SoldOut faqat ikki arg yozdi (name, soldOut), narx esa meta'dan keldi — «35 000 so'm» uchala story'da ham bor. Tugma matni soldOut ga qarab almashdi. Uchta story — uchta holat, va ularni yozish uchun komponentga bitta qator ham qo'shilmadi.

Storybook bundan ko'proq qiladi: har story'ni brauzerda alohida sahifa qilib ko'rsatadi, onAdd bosilganini panelda yozadi. Lekin asosi — shu ikki qator: { ...meta.args, ...story.args } va <Component {...args} />.

2.3 Args birlashishi qoidasi

Storybook args'ni uch qavatdan yig'adi, keyingisi oldingisining ustiga yoziladi:

flowchart LR
  P["preview.tsx: args"] --> M["meta.args (fayl)"] --> S["story.args"] --> C["komponent props"]

Rasmda: eng umumiysi — butun loyiha (preview.tsx), keyin fayl, keyin story. Shuning uchun umumiy narsani (onClick: fn(), standart matn) meta'ga qo'ying, story'ga faqat farqni yozing. Story fayli shunda "holatlar ro'yxati" bo'lib o'qiladi: Xavfli — "oddiyning o'zi, faqat variant boshqa".

Story'ning args dan boshqa maydonlari ham bor:

Maydon Nima uchun
render: (args) => <…/> komponentni o'zingiz chizish (o'ram, bir nechta nusxa)
decorators story atrofiga o'ram (keyingi bo'limlar)
parameters addon'lar sozlamasi (a11y, layout, msw)
tags belgilar: autodocs, !test
play story chizilgach harakat va tekshiruv (Storybook II)

Storybook 10 da yangi yozuv ham bor — CSF Factories (preview.meta(…), meta.story(…)): turlar avtomatik chiqadi. vazifalar CSF3 bilan qoldi — hujjatdagi asosiy misollar hali CSF3 da.

Tekshirib ko'ring: meta.args da variant: "asosiy", story'da args: { variant: undefined }. Komponentga qaysi qiymat boradi?

Javob

undefined — story'niki ustun, undefined ham qiymat ({ ...a, ...b } dagi kabi: kalit bor bo'lsa, eskisini bosadi). Komponent o'z standartini oladi (Tugma da "oddiy"). Umumiy args'ni "o'chirish" uchun shu yo'l ishlaydi.

3. O'rnatish: npm create storybook yoki qo'lda

3.1 Rasmiy buyruq

Hujjatdagi yo'l — bitta buyruq:

bash
npm create storybook@latest

U loyihani tanib (React + Vite), paketlarni o'rnatadi, .storybook/ papkasini, namuna story'larni va skriptlarni yozadi. Qulay — lekin savollar beradi (interaktiv), qo'shimcha addon'lar va namuna fayllar qo'shadi. vazifalar da qo'lda o'rnatdik: har fayl nima uchun borligini bilish uchun.

Storybook 10 talablari: faqat ESM (main.ts ham ES modul), Node 20.19+ yoki 22.12+. Bizda Node 24.21.0.

3.2 Qo'lda: ikki paket

bash
git switch -c chore/storybook
npm i -D -E storybook@10.6.1 @storybook/react-vite@10.6.1
  • storybook — yadro: CLI (storybook dev, storybook build), interfeys, controls, actions, viewport.
  • @storybook/react-vite — framework paketi: React'ni Vite bilan chizadi va loyihaning vite.config.ts ini oladi.

10.6.1 ni oldik — 10.6.2 o'rnatish kunida 7 kundan yangi edi (zanjir hujumi xavfi uchun yangi relizni bir hafta kutish qoidasi, paket xavfsizligi). Storybook 9 dan beri controls, actions va viewport yadroning ichida — alohida "essentials" paketi yo'q.

3.3 .storybook/main.ts

ts
import type { StorybookConfig } from "@storybook/react-vite";

const ILOVA_PLAGINLARI = /^(vazifalar-|spa-zaxira$|blocking-render$)/;

function ilovaPlaginimi(plagin: unknown): boolean {
  return (
    typeof plagin === "object" &&
    plagin !== null &&
    "name" in plagin &&
    typeof plagin.name === "string" &&
    ILOVA_PLAGINLARI.test(plagin.name)
  );
}

const config: StorybookConfig = {
  stories: ["../stories/**/*.stories.tsx"],
  addons: [],
  framework: "@storybook/react-vite",
  core: { disableTelemetry: true },
  viteFinal: (sozlama) => ({
    ...sozlama,
    plugins: (sozlama.plugins ?? [])
      .flat()
      .filter((p) => !ilovaPlaginimi(p)),
  }),
};
export default config;
Kalit Nima qiladi
stories story fayllari qayerda (glob)
addons qo'shimcha imkoniyatlar — hozircha bo'sh, Storybook II da ikkita
framework React + Vite
core.disableTelemetry Storybook'ga foydalanish statistikasini yubormaslik
viteFinal Vite sozlamasini Storybook'ga berishdan oldin o'zgartirish

viteFinal nega kerak — bu birinchi haqiqiy tuzoq.

3.4 Tuzoq: Storybook ilovaning plaginlarini ham oladi

Birinchi npm run build-storybook (viteFinal'siz) muvaffaqiyatli tugadi, lekin storybook-static/ ichida ikki g'alati fayl bor edi: sw.js va 404.html. Build logida: sw.js: QOBIQ — 52 ta. Bu — ilovamizning Service Worker plagini (21-qism): u Storybook build'ini ham "ilova" deb, uning 52 ta faylini keshga yozadigan sw.js yaratdi. Storybook'ni bir marta ochgan dasturchi brauzerida keyin ilova emas, eski Storybook keshi ochilishi mumkin edi.

Sabab: framework loyihaning vite.config.ts ini to'liq oladi — React plagini va Tailwind kerak, lekin SW, 404 nusxasi, dev serverlari kerak emas. viteFinal ularni nomi bo'yicha olib tashlaydi. Natija — o'lchab ko'rdik:

storybook build vaqti (9 story)
  • ilova plaginlari bilan7.8 MB, ichida sw.js22,8 s
  • viteFinal bilan7.0 MB10 s

Manba: O'lchandi: vazifalar kanoni (holat 11), i5-12500H, Windows 11, Node 24.21.0, Storybook 10.6.1, 2026-10-20

Ikki baravar tezroq va keraksiz sw.js yo'q. public/sw.js shabloni baribir nusxalanadi (Vite public/ papkasini o'zi ko'chiradi), lekin hech kim uni ro'yxatdan o'tkazmaydi — xavfsiz.

3.5 Skriptlar va .gitignore

json
{
  "storybook": "storybook dev -p 6006",
  "build-storybook": "storybook build"
}

npm run storybook — dev server (http://localhost:6006), fayl o'zgarsa sahifa o'zi yangilanadi. npm run build-storybook — statik sayt storybook-static/ ga: uni istalgan hostingga qo'yib, jamoaga havola berish mumkin. Bu papka .gitignore, ESLint ignores va .prettierignore ga qo'shildi — u build natijasi, manba emas.

O'lchov: storybook dev tayyor bo'lishi 9.5 s (sovuq) va 4.9 s (issiq).

4. Dekoratorlar: komponentga kerakli muhit

4.1 Ikkinchi tuzoq: router yo'q

OddiyQator story'si ochilganda Storybook qizil quti ko'rsatdi:

text
TypeError: Cannot destructure property 'basename' of 'React.useContext(...)' as it is null.

Tarjima: "useContext(...) natijasidan basename xususiyatini ajratib bo'lmaydi — u null". Qatorda Link bor (React Router), Link esa router kontekstini qidiradi. Ilovada router App atrofida turadi. Story esa komponentni yolg'iz chizadi — atrofda hech narsa yo'q. Natija oynasida ham xuddi shunday:

tsx
import { render, screen } from "@testing-library/react";
import type { ComponentType, ReactElement } from "react";
import { Link, MemoryRouter } from "react-router";
import { expect, test } from "vitest";

function TaskRow({ text }: { text: string }) {
  return (
    <li>
      <Link to="/vazifa/1">{text}</Link>
    </li>
  );
}

// Dekorator: story'ni oladi, o'ralgan holda qaytaradi
type Decorator = (Story: ComponentType) => ReactElement;

const withRouter: Decorator = (Story) => (
  <MemoryRouter>
    <Story />
  </MemoryRouter>
);

test("router'siz — xato", () => {
  try {
    render(<TaskRow text="Non olish" />);
  } catch (e) {
    console.log(String(e));
  }
});

test("dekorator bilan", () => {
  const Story = () => <TaskRow text="Non olish" />;
  render(withRouter(Story));
  const link = screen.getByRole("link", { name: "Non olish" });
  expect(link).toHaveAttribute("href", "/vazifa/1");
});

Konsolda:

text
TypeError: Cannot destructure property 'basename' of 'React$111.useContext(...)' as it is null.
  ✓ router'siz — xato
  ✓ dekorator bilan
Testlar: 2 o'tdi (jami 2)

React$111 — natija oynasidagi to'plam ichidagi nom; Storybook'da va loyihangizda boshqacha chiqadi, ma'nosi bir xil.

Dekorator (decorator) — story'ni qabul qilib, uni biror narsaga o'rab qaytaradigan funksiya. 15-qismdagi dekoratorlar bilan nomi bir, g'oyasi o'xshash: asl narsani o'zgartirmay, atrofiga qo'shimcha qo'shadi. O'xshatish — sovg'a qog'ozi: ichidagi narsa o'sha, faqat endi u yetkazib berishga tayyor.

4.2 preview.tsx: hamma story uchun

tsx
import type { Preview } from "@storybook/react-vite";
import "../src/vazifalar.css";
import "../src/i18n/index.ts";
import { MemoryRouter } from "react-router";
import { TemaProvider } from "../src/kontekst/TemaKonteksti.tsx";

const preview: Preview = {
  decorators: [
    (Story) => (
      <MemoryRouter>
        <TemaProvider>
          <Story />
        </TemaProvider>
      </MemoryRouter>
    ),
  ],
};
export default preview;

preview.tsx — har story'dan oldin ishlaydigan fayl. Unda uch narsa bor:

  • import "../src/vazifalar.css" — ilovaning CSS'i (Tailwind ham). Busiz komponentlar "yalang'och" chiqadi.
  • import "../src/i18n/index.ts" — tarjimalar (i18n): komponentlar t("…") bilan matn oladi.
  • dekorator — MemoryRouter (xotiradagi router, manzil satri kerak emas) va TemaProvider.

Dekorator uch darajada yoziladi — args kabi: preview.tsx (hamma story), meta.decorators (fayl), story.decorators (bitta story).

4.3 Fayl darajasidagi dekorator

OddiyQator ga router'dan tashqari amallar konteksti ham kerak (QatorAmallariKonteksti — qator tugmalari shu orqali onOchir, onTahrirla ni oladi) va <ul>:

tsx
const amallar = {
  onAlmashtir: fn(),
  onOchir: fn(),
  onTahrirla: fn(),
  onSaqla: fn(() => null),
  onBekor: fn(),
  tugmaRef: () => () => undefined,
};

const meta = {
  component: OddiyQator,
  args: {
    vazifa: { id: 1, matn: "Non olish #bozor", bajarildi: false },
    sorov: "",
  },
  decorators: [
    (Story) => (
      <QatorAmallariKonteksti value={amallar}>
        <ul>
          <Story />
        </ul>
      </QatorAmallariKonteksti>
    ),
  ],
} satisfies Meta<typeof OddiyQator>;

Story'lar: Faol, Bajarilgan (bajarildi: true), QidiruvBilan (sorov: "non" — qidiruv so'zi ajratib ko'rsatiladi). <ul> nega kerakligini Storybook II da a11y tekshiruvi aytadi — u yerda ishlatilmagan holatda bu xato edi.

Tekshirib ko'ring: Hamma story'ga qorong'i tema kerak emas — faqat bittasiga. Dekoratorni qayerga yozasiz?

Javob

O'sha story'ning o'ziga: export const Qorongi: Story = { decorators: [(Story) => <div data-tema="qorongi"><Story /></div>] }. preview.tsx dagisi hammaga, meta dagisi butun faylga ta'sir qiladi.

5. Controls va actions

5.1 Controls: props'ni panelda o'zgartirish

Storybook'da story ostida Controls paneli bor: har arg uchun maydon. children — matn maydoni, disabled — katakcha. Turini Storybook TypeScript turlaridan o'zi chiqaradi. variant kabi cheklangan qiymatlar uchun ro'yxatni aniq berish mumkin — argTypes:

tsx
const meta = {
  component: Tugma,
  args: { children: "Saqlash", onClick: fn() },
  argTypes: {
    variant: {
      control: "radio",
      options: ["oddiy", "asosiy", "xavfli"],
    },
    onClick: { table: { disable: true } },
  },
} satisfies Meta<typeof Tugma>;

Bu qism vazifalar da yo'q — Storybook turlardan o'zi to'g'ri tanladi. Boshqaruv turlari: text, boolean, number, range, color, date, select, radio, check, object, file.

Controls'da o'zgartirish faqat sizning ekraningizda — fayl o'zgarmaydi. Qiziq holat topsangiz (masalan, juda uzun matn tugmani buzdi), uni yangi story qilib yozing: shunda u hamma uchun va testlar uchun ham saqlanadi.

5.2 Actions: fn() — josus funksiya

onClick: fn() — storybook/test dan. Tugma bosilganda Actions panelida yozuv chiqadi: onClick va uning argumentlari. fn() — aslida Vitest'ning vi.fn() i (Vitest II), Storybook uni o'z paneliga ulaydi. Shuning uchun keyingi darsda expect(args.onClick).toHaveBeenCalled() deb tekshira olamiz.

Natija oynasida o'sha g'oya vi.fn() bilan:

jsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { expect, test, vi } from "vitest";

function DeleteButton({ onDelete }) {
  return (
    <button onClick={() => onDelete({ id: 7 })}>O'chirish</button>
  );
}

test("action — kim, nima bilan chaqirildi", async () => {
  const args = { onDelete: vi.fn() };
  const user = userEvent.setup();
  render(<DeleteButton {...args} />);
  await user.click(screen.getByRole("button", { name: "O'chirish" }));
  console.log(JSON.stringify(args.onDelete.mock.calls));
  expect(args.onDelete).toHaveBeenCalledWith({ id: 7 });
});

Konsolda:

text
[[{"id":7}]]
  ✓ action — kim, nima bilan chaqirildi
Testlar: 1 o'tdi (jami 1)

mock.calls — har chaqiruv argumentlari massivi: bitta chaqiruv, unda bitta argument ({ id: 7 }). Storybook'ning Actions paneli shu ro'yxatni chiroyli ko'rsatadi.

6. Hujjat va soxta server: autodocs, MSW addon

6.1 Autodocs

Meta'ga tags: ["autodocs"] qo'shsangiz, Storybook komponent uchun hujjat sahifasi yasaydi: tavsif (JSDoc izohidan), props jadvali (turlardan), hamma story'lar bitta sahifada. Dizayn tizimi uchun bu "jonli hujjat": kod o'zgarsa, hujjat ham o'zgaradi. Storybook 10 da buning uchun alohida paket kerak — @storybook/addon-docs (npx storybook add @storybook/addon-docs). vazifalar da u o'rnatilmagan: bizda dizayn tizimi kutubxonasi emas, bitta ilova — story ro'yxati yetarli.

6.2 MSW addon

Ba'zi komponent ma'lumotni o'zi oladi (fetch, TanStack Query). Story'da server yo'q — nima qilish kerak? MSW darsidagi handlerlarni story'ga ulash mumkin: msw-storybook-addon (3.0.3, 2026-10 holatiga; MSW 2+ va Storybook 10 bilan mos deb e'lon qilingan). Hujjat bo'yicha: preview.tsx da initialize() va loaders: [mswLoader], story'da parameters: { msw: { handlers: [...] } }.

vazifalar da bu addon yo'q va sinalmagan. Sabab — tuzilma: story'li komponentlar (Tugma, OddiyQator, OchirishDialog, VazifaRoyxati) ma'lumotni props orqali oladi, fetch qilmaydi. Bu ataylab: "ko'rinish" komponentlari ma'lumot qayerdan kelishini bilmaydi (komponent API). Butun sahifani (ma'lumot bilan) ko'rish — e2e va visual testlar ishi (Visual regression).

7. TypeScript va npm tuzoqlari

7.1 Kutubxona turlari va TypeScript 7

npm run tip (tsc -b) story fayllarini tekshirganda node_modules ichida 4 ta xato chiqdi:

text
TS2578: Unused '@ts-expect-error' directive.
TS2344: Type '…' does not satisfy the constraint '…'.

Tarjima: "ishlatilmagan @ts-expect-error ko'rsatmasi" va "tur cheklovni qanoatlantirmaydi". Sabab: @storybook/react 10.6.1 ning tur fayli bir joyda "bu yerda xato bo'ladi, e'tibor berma" (@ts-expect-error) deb yozilgan. TypeScript 6 u yerda xato beradi, TypeScript 7 (tsgo) esa bermaydi — "kutilgan xato yo'q" degan yangi xato chiqadi. Bizning kodda xato yo'q, kutubxona turlari TS 7 ga hali moslashmagan.

Yechim — alohida tsconfig.storybook.json: ilova sozlamasidan meros, faqat skipLibCheck: true (kutubxona .d.ts lari tekshirilmaydi). Ilova va testlar sozlamalarida skipLibCheck yo'q — o'z kodimiz to'liq strict. Bu jest-dom turlari dagidan farqli qaror: u yerda xatoni o'zimiz tuzatdik, bu yerda tuzatadigan narsa kutubxona ichida.

7.2 npm 11 va allowScripts

O'rnatishda npm ogohlantirdi:

text
install-scripts ... esbuild@0.28.2 (postinstall) not yet covered by allowScripts

npm 11 o'rnatish skriptlarini sukut bo'yicha ishga tushirmaydi (paket xavfsizligi). esbuild'ning postinstall i o'tkazib yuborildi — Storybook baribir ishladi: esbuild binary'si @esbuild/win32-x64 paketi orqali keldi. Ruxsat berish shart bo'lmadi.

8. Vazifalar qadami: Storybook 10 (holat 11)

8.1 O'lchab qaror

Storybook kanonga kiradimi — avval narxini o'lchadik:

O'lchov Qiymat
O'rnatish 12 s
node_modules +53.3 MB (keyingi dars addon'lari bilan jami +58 MB: 375 → 433 MB)
storybook dev 9.5 s sovuq / 4.9 s issiq
storybook build 10.0 s, 7.0 MB
Ilova JS (gzip) 206.7 KB — o'zgarmadi

Narx sezilarli. Kirdi, chunki keyingi darsda story'lar Vitest testiga aylanadi (alohida test vositasi kerak emas) va a11y tekshiruvi o'sha yerda ishlaydi. Ilova foydalanuvchisi hech narsa to'lamaydi: Storybook — faqat devDependencies.

Paket o'rnatilgach birinchi npm test 70.8 s oldi (odatda 9–13 s): Vite keshi qaytadan quriladi, vaqtning 67 % i muhitni tayyorlashga ketdi. Bu bir martalik sovuq start — keyingi ishga tushirish odatdagidek.

8.2 Fayllar

text
.storybook/
  main.ts       stories, framework, viteFinal (ilova plaginlarisiz)
  preview.tsx   CSS, i18n, MemoryRouter + TemaProvider
stories/
  Tugma.stories.tsx           Oddiy, Asosiy, Xavfli
  OddiyQator.stories.tsx      Faol, Bajarilgan, QidiruvBilan
  OchirishDialog.stories.tsx  OchiqDialog
  VazifaRoyxati.stories.tsx   BoshHolat, UchVazifa
tsconfig.storybook.json       skipLibCheck faqat shu yerda

4 fayl, 9 story. Story'lar src/ da emas, alohida stories/ da — ilova kodi va ustaxona aralashmasin. Ko'p loyihalarda story komponent yonida turadi (Tugma.tsx + Tugma.stories.tsx) — ikkalasi ham to'g'ri, jamoada bitta qoida bo'lsin.

8.3 Dialog story'si

OchirishDialog ref orqali showModal() bilan ochiladi — o'zi ochiq turmaydi. Story uni ochib qo'yadigan kichik o'ram komponent yozadi:

tsx
function Ochiq(props: { matn: string; onJavob: (t: boolean) => void }) {
  const ref = useRef<HTMLDialogElement>(null);
  useEffect(() => ref.current?.showModal(), []);
  return <OchirishDialog ref={ref} {...props} />;
}

const meta = {
  component: Ochiq,
  args: { matn: "Non olish #bozor", onJavob: fn() },
} satisfies Meta<typeof Ochiq>;

Storybook haqiqiy brauzerda chizadi — showModal ishlaydi (jsdom'da yo'q, RTL I).

8.4 Natija va commit

Testlar o'zgarmadi: Vitest 348, brauzer 6, e2e 10. Lint, format:check, tip, knip — toza. JS 206.7 KB.

bash
git add -A
git commit

Commit (kanon): chore: Storybook 10 (react-vite) — Tugma, vazifa qatori, dialog, ro'yxat story'lari.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Cannot destructure property 'basename'… Link router'siz preview.tsx dekoratori: MemoryRouter
komponent uslubsiz CSS import qilinmagan preview.tsx da ilova CSS'i
storybook-static da sw.js ilova Vite plaginlari viteFinal da olib tashlash
useX must be used within Provider kontekst yo'q fayl yoki loyiha dekoratori
TS2578 Unused '@ts-expect-error' kutubxona turlari, TS 7 skipLibCheck faqat story tsconfig'da
story ro'yxatda yo'q stories glob mos emas main.ts dagi yo'l, fayl nomi *.stories.tsx
har story'da bir xil props hammasi story'ga yozilgan umumiysini meta.args ga

10. Mashqlar

Mashqlar kurs/mashqlar/22/11-storybook-story/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): bo'sh joylarni to'ldiring

  • Fayl haqidagi obyekt — export meta
  • Story komponentga beradigan props —
  • Hamma story'ni o'raydigan funksiyalar turadigan fayl — .storybook/.tsx
  • Panelda cheklangan qiymatlar ro'yxati —
  • Actions panelida ko'rinadigan josus — ()
Yechim
  • default — export default meta: komponent va umumiy args.
  • args — story'niki meta'nikidan ustun.
  • preview — preview.tsx dagi decorators hamma story'ga ta'sir qiladi.
  • argTypes — control: "radio", options: [...].
  • fn — storybook/test dan; aslida vi.fn().

2-mashq (o'rta): narx yorlig'i story'lari

PriceTag komponenti: price (son), discount (foiz, ixtiyoriy). Chegirma bo'lsa — eski narx <s> ichida va yangisi <strong> da; bo'lmasa — faqat <strong>. Meta (price: 35000) va uch story yozing: Default, WithDiscount (discount: 10), Free (price: 0 — matn «Bepul»). Keyin «Story — oddiy obyekt» bo'limidagidek har story'ni chizib, <strong> matnini konsolga chiqaring. Narxni String(n) bilan chiqaring (toLocaleString Chrome va Node'da farq qiladi).

Ishora: args birlashishi — { ...meta.args, ...story.args }; <strong> ni container.querySelector("strong") bilan emas, matn bilan tekshiring.

Yechim
jsx
import { render, screen } from "@testing-library/react";
import { expect, test } from "vitest";

function PriceTag({ price, discount }) {
  if (price === 0) return <strong>Bepul</strong>;
  if (!discount) return <strong>{String(price)} so'm</strong>;
  const final = price - (price * discount) / 100;
  return (
    <p>
      <s>{String(price)} so'm</s>{" "}
      <strong>{String(final)} so'm</strong>
    </p>
  );
}

const meta = { component: PriceTag, args: { price: 35000 } };
const stories = {
  Default: {},
  WithDiscount: { args: { discount: 10 } },
  Free: { args: { price: 0 } },
};

for (const [title, story] of Object.entries(stories)) {
  test(title, () => {
    const Component = meta.component;
    render(<Component {...meta.args} {...story.args} />);
    const strong = screen.getByText(/so'm|Bepul/, {
      selector: "strong",
    });
    console.log(strong.textContent);
    expect(strong).toBeVisible();
  });
}

Konsolda:

text
35000 so'm
  ✓ Default
31500 so'm
  ✓ WithDiscount
Bepul
  ✓ Free
Testlar: 3 o'tdi (jami 3)

{ ...meta.args, ...story.args } o'rniga ikki marta yoyish ({...meta.args} {...story.args}) ham bir xil: keyingisi ustun. selector: "strong" — getByText ni bitta tegga toraytiradi: <s> dagi eski narx ham «so'm» bilan tugaydi.

3-mashq (qiyin): dekoratorlar zanjiri

Storybook bir nechta dekoratorni zanjir qilib qo'llaydi: decorators: [a, b] — b tashqarida, a ichkarida (ro'yxatdagi birinchisi story'ga eng yaqin). applyDecorators(Story, decorators) funksiyasini yozing: u har dekoratorni navbat bilan qo'llab, yakuniy komponent qaytarsin. withTheme (<div data-theme="dark">) va withFrame (<section aria-label="Ramka">) bilan sinang: tema section ichida bo'lishi kerak. Greeting komponenti useContext orqali ismni oladi — uni withUser dekoratori UserContext.Provider bilan bersin.

Ishora: reduce (massiv metodlari); har qadamda const Inner = acc; return () => decorator(Inner);.

Yechim
jsx
import { render, screen, within } from "@testing-library/react";
import { createContext, useContext } from "react";
import { expect, test } from "vitest";

const UserContext = createContext(null);

function Greeting() {
  const user = useContext(UserContext);
  if (!user) throw new Error("UserContext topilmadi");
  return <p>Salom, {user}!</p>;
}

function applyDecorators(Story, decorators) {
  return decorators.reduce((Acc, decorator) => {
    const Inner = Acc;
    return () => decorator(Inner);
  }, Story);
}

const withUser = (Story) => (
  <UserContext.Provider value="Malika">
    <Story />
  </UserContext.Provider>
);
const withTheme = (Story) => (
  <div data-theme="dark">
    <Story />
  </div>
);
const withFrame = (Story) => (
  <section aria-label="Ramka">
    <Story />
  </section>
);

test("zanjir: birinchi dekorator — eng ichkarida", () => {
  const Final = applyDecorators(Greeting, [
    withUser,
    withTheme,
    withFrame,
  ]);
  render(<Final />);
  const frame = screen.getByRole("region", { name: "Ramka" });
  const theme = frame.firstElementChild;
  console.log(theme.getAttribute("data-theme"));
  expect(within(frame).getByText("Salom, Malika!")).toBeVisible();
});

Konsolda:

text
dark
  ✓ zanjir: birinchi dekorator — eng ichkarida
Testlar: 1 o'tdi (jami 1)

reduce birinchi qadamda withUser(Greeting) ni, keyin uning ustiga withTheme, oxirida withFrame ni o'raydi — shuning uchun section eng tashqarida. withUser ni ro'yxatdan olib tashlasangiz, Greeting "UserContext topilmadi" xatosini tashlaydi — router misolidagi kabi.

4-mashq: Vazifalar qadami

chore/storybook branch'ida «Vazifalar qadami» dagi o'zgarishlarni qiling: ikki paket, main.ts, preview.tsx, 4 story fayli, skriptlar, tsconfig.storybook.json.

Yechim

Tekshiruv ro'yxati:

  • Avval viteFinal siz npm run build-storybook — storybook-static/sw.js bormi? Keyin viteFinal bilan — yo'qligiga ishonch hosil qiling.
  • preview.tsx dagi MemoryRouter ni vaqtincha olib tashlang — OddiyQator story'si basename xatosini beradi. Qaytaring.
  • npm run storybook — chap ro'yxatda 4 komponent, 9 story; Tugma/Xavfli da Controls'dan children ni o'zgartirib ko'ring.
  • npm run tip — toza (skipLibCheck faqat tsconfig.storybook.json da).
  • npm test — 348 test, ilova npm run build — JS 206.7 KB.

11. Real ishda

  • Dizayn tizimlari va UI kutubxonalari Storybook'ni hujjat sifatida chiqaradi — komponent "do'koni": dizayner, menejer va dasturchi bir joyga qaraydi.
  • Kod ko'rib chiqishda (pull request) Storybook build'i havola bilan qo'shiladi: ko'rib chiquvchi o'zgargan komponentni ilovani ishga tushirmasdan ko'radi.
  • Yangi komponent ko'pincha avval story'da quriladi (ilovaga ulanmasdan) — "komponentdan boshlab ishlab chiqish" (component-driven development).
  • Muqobillar: Ladle (Vite'ga asoslangan, yengil, CSF bilan mos), Histoire (Vue/Svelte). React ekotizimida eng keng tarqalgani — Storybook.
  • Intervyu savollari: "story nima?", "args va argTypes farqi?", "dekorator qachon kerak?", "Storybook'ning narxi nima?".

Xulosa

  • Storybook — komponentlarni ilovasiz, har holati bilan ko'rsatadigan ustaxona; holat — story.
  • CSF3: export default meta (komponent, umumiy args) + nomli eksportlar (story'lar); args birlashadi — story'niki ustun.
  • Dekorator story'ni kerakli muhitga o'raydi: router, tema, provider; preview.tsx — hamma story uchun.
  • Controls (argTypes) — panelda props, actions — fn() (Vitest josusi); autodocs — @storybook/addon-docs, MSW addon — ma'lumot oladigan komponentlar uchun.
  • vazifalar: 4 fayl, 9 story, +53 MB; viteFinal ilova plaginlarini olib tashlaydi, bundle o'zgarmaydi.

Keyingi dars: Storybook II: interaction, a11y va Vitest integratsiyasi — story'larga play funksiyasi, axe bilan a11y tekshiruvi va har story'ning Vitest testiga aylanishi.

Manbalar

  • Storybook: Install, Writing stories (CSF3), Args, ArgTypes, Decorators, Controls, Actions (storybook.js.org/docs)
  • Storybook 10 migration guide — ESM, Node 20.19+ / 22.12+; MIGRATION.md — essentials yadroga ko'chgani, addon-docs alohida
  • msw-storybook-addon — npm (3.0.3, peer: msw ≥ 2, storybook ^10)
  • Haqiqiy o'lchovlar: Storybook 10.6.1, Node 24.21.0, npm 11.19.0, Windows 11, i5-12500H (2026-10-20); natija oynasi — Chrome 155
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Storybook I: komponentlarni izolyatsiyada ishlab chiqish — story, args, controls va dekoratorlar — IlmHamroh