Выкарыстанне TypeScript

TypeScript — папулярны спосаб дадаць тыпізацыю ў ваш JavaScript код. TypeScript падтрымлівае JSX з каробкі, вы можаце дадаць паўнавартасную падтрымку React для вэб, дадаўшы да праекта @types/react і @types/react-dom.

Усталёўка

Усе React фрэймворкі, гатовыя для выкарыстання ў працоўным асяроддзі прапануюць падтрымку TypeScript. Для кожнага фрэймворка трымайцеся ягоных уласных інструкцый:

Дадаванне TypeScript у існуючы праект React

Для ўсталявання апошняй версіі тыпаў React выкарыстоўвайце:

Terminal
npm install @types/react @types/react-dom

Ваш tsconfig.json мае змяшчаць наступныя налады:

  1. dom мае быць уключанае ў lib (Заўвага: калі значэнне для lib не зададзенае, dom прадвызначана уключанае).
  2. jsx мае мець любое валіднае значэнне. preserve мае падыходзіць для большасці выпадкаў. Калі вы публікуеце бібліятэку, звярніцеся да дакументацыі па полю jsx каб падабраць правільнае значэнне.

TypeScript з кампанентамі React

Note

Усе файлы, што змяшчаюць JSX, маюць мець пашырэнне .tsx. Гэта спецыфічнае для TypeScript пашырэнне, якое кажа TypeScript, што файл змяшчае JSX.

Напісанне TypeScript з React вельмі падобнае да напісання JavaScript з React. Асноўнае адрозненне ў тым, што падчас працы з кампанентамі вы зможаце ўказаць тыпы пропсаў. Гэтыя тыпы могуць быць скарыстаныя для праверкі правільнасці і прадастаўлення ўнутранай дакументацыі для рэдактараў кода.

Узяўшы кампанент MyButton са старонкі «Хуткі старт», мы можам дадаць апісанне тыпу для пропса title кнопкі:

function MyButton({ title }: { title: string }) {
  return (
    <button>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Вітаю ў маёй праграме</h1>
      <MyButton title="Я — кнопка" />
    </div>
  );
}

Note

Дадзеныя пясочніцы ўмеюць працаваць з TypeScript кодам, але яны не робяць праверку тыпаў. Гэта значыць, вы можаце ўносіць змены ў TypeScript пясочніцы дзеля вывучэння, але вы не будзеце бачыць памылкі тыпізацыі ці папярэджанні. Для праверкі тыпізацыі, можаце выкарыстоўваць тэставую пляцоўку TypeScript ці выкарыстоўваць анлайн-пясочніцы, што маюць больш магчымасцяў.

Дадзены аднарадковы сінтаксіс — найпрасцейшы спосаб тыпізацыі кампанента, але з павелічэннем колькасці пропсаў, дадзеная канструкцыя пачне станавіцца грузнай. Замест гэтага можна выкарыстоўваць interface ці type для апісання пропсаў кампанента:

interface MyButtonProps {
  /** Тэкст, які будзе паказвацца ў кнопцы */
  title: string;
  /** Задае, ці можна ўзаемадзейнічаць з кнопкай */
  disabled: boolean;
}

function MyButton({ title, disabled }: MyButtonProps) {
  return (
    <button disabled={disabled}>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Вітаю ў маёй праграме</h1>
      <MyButton title="Я — адключаная кнопка" disabled={true}/>
    </div>
  );
}

Тып, якім вы апісваеце пропсы кампанента, можа быць на столькі простым ці складаным, на колькі вам тое патрэбна, але гэта заўсёды мае быць апісанне тыпу аб’екта праз type ці interface. Вы можаце даведацца больш аб тым, як апісваць аб’екты ў TypeScript на старонцы «Тыпы аб’ектаў». Таксама вас могуць зацікавіць аб’яднаныя тыпы для апісання пропсаў, якія могуць адпавядаць аднаму з некалькіх тыпаў, ці інструкцыі як ствараць тыпы на аснове іншых тыпаў для больш складаных выпадкаў.

Прыклады хукаў

Апісанні тыпаў у @types/react уключае таксама і тыпізацыю ўбудаваных хукаў, таму вы можаце выкарыстоўваць іх у сваіх кампанентах без дадатковай налады. Яны пабудаваныя такім чынам, каб улічваць код, ужо напісаны ў кампаненце, такім чынам вы атрымаеце аўтаматычна вызначаныя тыпы і ў большасці выпадкаў вам не давядзецца разбірацца з дробнай тыпізацыяй самастойна.

Але далей мы разгледзім некалькі прыкладаў, як тыпізаваць хукі.

useState

Хук useState будзе выкарыстоўваць значэнне, зададзенае пры ініцыалізацыі, для аўтаматычнага вызначэння тыпу. Напрыклад:

// Тут будзе вызначаны тып boolean
const [enabled, setEnabled] = useState(false);

Для enabled будзе вызначаны тып boolean, а setEnabled будзе прымаць або boolean, або функцыю, якая вяртае boolean. Калі вы хочаце відавочна перадаць тып для вашага стану, вы можаце зрабіць гэта, паказаўшы тып аргумента пры выкліку useState:

// Тып відавочна зададзены як boolean
const [enabled, setEnabled] = useState<boolean>(false);

Гэта не надта карысна ў дадзеным выпадку, але вам можа спатрэбіцца падобнае, калі вы маеце аб’яднаны тып. Напрыклад, тут статус можа прымаць толькі пэўныя радковыя значэнні:

type Status = "idle" | "loading" | "success" | "error";

const [status, setStatus] = useState<Status>("idle");

Ці, як рэкамендавана ў прынцыпах структуравання коду, вы можаце групаваць звязаныя станы ў аб’екты і апісваць іх як розныя магчымыя тыпы аб’екта:

type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success', data: any }
| { status: 'error', error: Error };

const [requestState, setRequestState] = useState<RequestState>({ status: 'idle' });

useReducer

Хук useReducer — больш скаладаны хук, які прымае функцыю рэд’юсара і першапачатковы стан. Аўтаматычна тып будзе вызначаны па тыпу значэння стану. Таксама вы можаце ўказаць тып стану пры выкліку useReducer, але звычайна лепей пакінуць першапачатковы стан:

import {useReducer} from 'react';

interface State {
   count: number 
};

type CounterAction =
  | { type: "reset" }
  | { type: "setCount"; value: State["count"] }

const initialState: State = { count: 0 };

function stateReducer(state: State, action: CounterAction): State {
  switch (action.type) {
    case "reset":
      return initialState;
    case "setCount":
      return { ...state, count: action.value };
    default:
      throw new Error("Unknown action");
  }
}

export default function App() {
  const [state, dispatch] = useReducer(stateReducer, initialState);

  const addFive = () => dispatch({ type: "setCount", value: state.count + 5 });
  const reset = () => dispatch({ type: "reset" });

  return (
    <div>
      <h1>Вітаю на старонцы свайго лічыльніка</h1>

      <p>Колькасць: {state.count}</p>
      <button onClick={addFive}>Дадаць 5</button>
      <button onClick={reset}>Скінуць</button>
    </div>
  );
}

Мы выкарыстоўваем TypeScript у некаторых ключавых момантах:

  • interface State апісвае стан рэд’юсара.
  • type CounterAction апісвае розныя дзеянні, якія могуць быць адпраўленыя рэд’юсару.
  • const initialState: State задае тып першапачатковага стану, а таксама тып, які будзе выкарыстоўвацца ў useReducer.
  • stateReducer(state: State, action: CounterAction): State задае тып для функцыі рэд’юсара і значэння, якое будзе з яе вяртацца.

Больш відавочным спосабам задаць тып стану initialState будзе перадаць яго непасрэдна useReducer:

import { stateReducer, State } from './your-reducer-implementation';

const initialState = { count: 0 };

export default function App() {
const [state, dispatch] = useReducer<State>(stateReducer, initialState);
}

useContext

Хук useContext — тэхніка для перадачы даных кампанентам уніз па дрэве без неабходнасці перадаваць пропсы цераз кампаненты. Выкарыстоўваецца шляхам стварэння кампанента-правайдара і хука для атрымання даных у даччыных кампанентах.

Аўтаматычна тып будзе вызначаны згодна з тыпам даных, што былі перададзеныя пры выкліку createContext:

import { createContext, useContext, useState } from 'react';

type Theme = "light" | "dark" | "system";
const ThemeContext = createContext<Theme>("system");

const useGetTheme = () => useContext(ThemeContext);

export default function MyApp() {
  const [theme, setTheme] = useState<Theme>('light');

  return (
    <ThemeContext.Provider value={theme}>
      <MyComponent />
    </ThemeContext.Provider>
  )
}

function MyComponent() {
  const theme = useGetTheme();

  return (
    <div>
      <p>Цяперашняя тэма: {theme}</p>
    </div>
  )
}

Дадзеная тэхніка працуе калі маецца прадвызначанае значэнне, якое мае сэнс, але бываюць сітуацыі, калі такога няма. У такім выпадку разумна будзе скарыстацца null у якасці прадвызначанага значэння. Але каб сістэме тыпізацыі было зразумела, трэба відавочна ўказаць тып ContextShape | null для createContext.

Разам з тым з’яўляецца патрэба выключаць | null пры атрыманні значэння. Мы раім дадаць у хук праверку, якая будзе падчас выканання правяраць наяўнасць значэння і выкідваць памылку пры яго адсутнасці:

import { createContext, useContext, useState, useMemo } from 'react';

// Гэта просты прыклад, але вы можаце прыдумаць больш складаны аб’ект
type ComplexObject = {
kind: string
};

// Кантэкст створаны з ужываннем `| null`, каб дакладна адлюстроўваць прадвызначанае значэнне
const Context = createContext<ComplexObject | null>(null);

// `| null` будзе выключаны пасля таго, як спрацуе хук.
const useGetComplexObject = () => {
const object = useContext(Context);
if (!object) { throw new Error("useGetComplexObject must be used within a Provider") }
return object;
}

export default function MyApp() {
const object = useMemo(() => ({ kind: "complex" }), []);

return (
<Context.Provider value={object}>
<MyComponent />
</Context.Provider>
)
}

function MyComponent() {
const object = useGetComplexObject();

return (
<div>
<p>Бягучы аб’ект: {object.kind}</p>
</div>
)
}

useMemo

Хук useMemo дапамагае ствараць і паўторна атрымліваць доступ да запомненых вынікаў запуску функцыі. Функцыя будзе выконвацца наноў толькі ў выпадках, калі зменіцца залежнае значэнне, перададзенае другім параметрам. Вынік выкліку функцыі будзе аўтаматычна тыпізаваны адпаведна функцыі, што была перададзеная першым параметрам. Вы можаце тыпізаваць хук відавочна, перадаўшы яму тып.

// Тып visibleTodos вызначаны аўтаматычна з тыпу значэння, што вяртае функцыя filterTodos
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);

useCallback

Хук useCallback забяспечвае стабільную спасылку на функцыю пакуль залежнасці, перададзеныя другім параметрам, не змяняюцца. Як і useMemo, тып функцыі вызначаецца па тыпе функцыі, што перададзеная першым параметрам, але болей відавочна ўказаць тып можна перадаўшы яго ў хук.

const handleClick = useCallback(() => {
// ...
}, [todos]);

Пры працы з TypeScript у строгім рэжыме, useCallback патрабуе дадатковай тыпізацыі параметраў функцыі. Гэта таму што тып функцыі вызначаны па тыпе вяртаемага значэння і не можа быць цалкам зразумелым.

У залежнасці ад вашых пераваг у стылі кода, вы можаце выкарыстоўваць функцыі *EventHandler з тыпаў React для забеспячэння тыпізацыі апрацоўшчыкаў падзей прама падчас аб’яўлення:

import { useState, useCallback } from 'react';

export default function Form() {
const [value, setValue] = useState("Change me");

const handleChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((event) => {
setValue(event.currentTarget.value);
}, [setValue])

return (
<>
<input value={value} onChange={handleChange} />
<p>Значэнне: {value}</p>
</>
);
}

Карысныя тыпы

Пакет @types/react змяшчае даволі вялізны набор тыпаў, якія вартыя вывучэння, калі ўзаемадзеянне паміж React і TypeScript стане камфортным для вас. Вы можаце знайсці іх у папцы React праекта DefinitelyTyped. Тут мы разгледзім некаторыя тыпы, якія найчасцей бываюць патрэбнымі.

Падзеі DOM

Калі працуеце з падзеямі DOM у React, тып падзеі ў апрацоўшчыку падзеі можа быць вызначаны аўтаматычна. Але ж, калі вы хочаце вынесці функцыю-апрацоўшчык вонкі, вам давядзецца відавочна ўказаць тып падзеі.

import { useState } from 'react';

export default function Form() {
  const [value, setValue] = useState("Change me");

  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    setValue(event.currentTarget.value);
  }

  return (
    <>
      <input value={value} onChange={handleChange} />
      <p>Значэнне: {value}</p>
    </>
  );
}

React таксама прадастаўляе шмат тыпаў падзей. Поўны іх спіс можна знайсці тут, ён змяшчае самыя папулярныя падзеі DOM.

Калі вызначаецеся, які тып вы шукаеце, вам можа дапамагчы інфармацыя, што паказваецца пры навядзенні на апрацоўшчык падзеі, што вы выкарыстоўваеце, яна пакажа тып вашай падзеі.

Калі вам трэба скарыстаць падзею, якая не ўключаная ў спіс, вы можаце скарыстацца тыпам React.SyntheticEvent, які з’яўляецца базавым тыпам для ўсіх падзей.

Даччыныя элементы

Існуюць два распаўсюджаныя шляхі апісання даччыных элементаў. Першы — выкарыстоўваць тып React.ReactNode, які ўключае ў сябе ўсе магчымыя тыпы, якія можа перадаць у JSX у якасці даччынага элемента:

interface ModalRendererProps {
title: string;
children: React.ReactNode;
}

Гэта вельмі шырокае вызначэнне даччыных элементаў. Іншы шлях — выкарыстоўваць тып React.ReactElement, якому адпавядаюць толькі элементы JSX, выключаючы прымітывы JavaScript, такія як радкі ці лічбы:

interface ModalRendererProps {
title: string;
children: React.ReactElement;
}

Заўважце, што немагчыма выкарыстоўваць TypeScript для апісання нейкага пэўнага элемента JSX. То-бок вы не можаце выкарыстоўваць сістэму тыпізацыі каб апісаць кампанент, які прымае, напрыклад, толькі даччыныя элементы <li>.

Вы можаце пабачыць прыклады абодвух тыпаў React.ReactNode і React.ReactElement з праверкай тыпаў на гэтай тэставай пляцоўцы TypeScript.

Пропсы стыляў

Калі вы выкарыстоўваеце ўбудаваныя стылі ў React, вы можаце выкарыстоўваць React.CSSProperties для апісання аб’екта, перадаваемага ў пропс style. Дадзены тып уключае ўсе магчымыя CSS параметры і з’яўляецца добрым спосабам пераканацца, што вы перадаяце правільны пропс style, і атрымаць аўтадапаўненне ў рэдактары кода.

interface MyComponentProps {
style: React.CSSProperties;
}

Далейшае вывучэнне

Дадзеная старонка тлумачыць базавае выкарыстанне TypeScript з React, але ёсць яшчэ шмат, што вартае вывучэння. Асобныя старонкі дакументацыі API могуць змяшчаць больш дэтальную дакументацыю, як выкарыстоўваць іх з TypeScript.

Мы раім наступныя рэсурсы:

  • The TypeScript handbook — афіцыйная дакументацыя TypeScript, якая тлумачыць большасць асноўных функцый мовы.

  • The TypeScript release notes падрабязна тлумачыць кожную новую функцыю.

  • React TypeScript Cheatsheet — мадэруемая супольнасцю шпаргалка па выкарыстанні TypeScript з React, што тлумачыць шмат карысных выпадкаў і забяспечвае ахоп большы за гэтую старонку.

  • TypeScript супольнасць у Discord — выдатнае месца, каб задаць пытанні і атрымаць дапамогу па пытаннях TypeScript і React.