Пашчэтныя памылкі TypeScript у React і як іх выправіць
Практычныя нарады пра пяць часта з’яўляючыхся катэгорый адносоў у TypeScript у дапрацоўкіх на React — props, з’явы, стан, асінхронныя даны і дзецкі элементы — з чысткімі спосабамі ўсунення проблем для кожнай з іх.
Якщо вы прыходзіце з JavaScript, TypeScript спачатку можа здавацца стрэмным. Першыя калькі можна падумаць, што кампайляр актываўа працюе проты вас: па всім месцам червоныя і жоўтыя лініі, загадкавыя паведамлення, якія, здаецца, нікуды не вядуць... і ў певны момент вы можаце запытацца, чы не было бы кращэ застацца ў звычным JavaScript, думаючы, што варта было б проста вернуцца да яго.
Але справа ў тым, што включэнне TypeScript у кодбазу React — адна з найрозумнейшых дзеянняў, якія вы можетэ здазіць, якщо вам важліва падтрымка высокай якосці коду з часам.
А тые паказанні пра бяглы і застерэжэння, з якімі вы постаўляецеся? Яны абавесна вартыя таго, каб іх даканаліць, проста трэба навучыцца распазнаўаць патерны, якія за імі стоіць.
Большая частка проблем з TypeScript, з якіми стикаюцца пад стварэнні дапраўкі на React, зовсім не ўзбачальныя. Тые ж канейкі памылак з’яўляюцца знова і знова ў розных проектах, і як толькі вы калькі разоў з ними расправіцеся, вы пачнёте ўпісна іх адрозумеваць і точна ведаць, што яны значаць. У этай статыце рассматрываюцца пяць категорый памылак, якія з’яўляюцца практычна ў кожным дапраўку на React, які выкарыстоўвае TypeScript, а таксама пояснюецца, чым вызваны кожны з іх і як іх усунуць.
Адна парада перш чым прыступіце: завжды чытайце цэлую прыведзенную памылку. Памылкі TypeScript можу здавацца страшнымі на першы погляд, але на самай працэ ў яных є досыта прагнозаваная структура. Не дазволяйце великім об’ёмам тэксту заплутаць вас, а якщо вы не ведаеце, з чаго пачаць, то апошняя лінія прыведзення зазвычай ёсць найкорыстнейшай часткай, на якую трэба звернуць увагу.
Усё сказанае — і прыступаем.
1. Памылкі параметраў компонента
Прапсы ўскладнююць кожны компонент React, таму зрозумела, што падчас роботы з ними развіваюцца типавыя памылкі, якія зазвычай стаюць першымія, з якіми сталкаюцца разработчыкі.
Памылка: Атрыбут 'X' не існуе ў типе '{}'.
Эта памылка з’яўляецца, калі вы викорыстоўваете прапс у сваём компоненте, не задавшы для яго жаднага типу.
// This won't give any errors in JavaScript...
function UserCard({ name, email }) {
return (
<div>
<h3>{name}</h3>
<p>{email}</p>
</div>
);
};
// But in TypeScript...
// Error: Parameter 'name' implicitly has an 'any' type
// Error: Parameter 'email' implicitly has an 'any' type
Эта памылка з’яўляецца проста таму, што прапсам ніколі не былі заданы явныя типы.
Рашэння: адзначыце інтарфейс, які будзе описваць вашы прапсы.
interface UserCardProps {
name: string;
email: string;
};
function UserCard({ name, email}: UserCardProps) {
return (
<div>
<h3>{name}</h3>
<p>{email}</p>
</div>
);
};
Памылка: Тып 'string' не можа быть прызначаны тыпу 'number'.
Гэта досыць проста: значыць, вы передалі прапс з значэнням неправильнага типу.
Рашэння: пераканайцеся, што тип значэння, якое вы передаеце ў прапс, правільны.
interface ItemForSaleProps {
imgUrl: string;
itemName: string;
amount: number;
currency: string;
};
function ItemForSale({
itemName,
imgUrl,
amount,
currency
}: ItemForSaleProps) {
return (
<div class="item-for-sale">
<img src={imgUrl} alt="item image" />
<h5>{itemName}</h5>
<p>{`${currency} ${amount.toFixed(2)}`}</p>
</div>
);
};
// Error happens when passing a string where a number is expected.
<ItemForSale
imgUrl="api.example.com/image"
itemName="Sample"
amount="10.99"
currency="USD"
/>
// Should be like this
<ItemForSale
imgUrl="api.example.com/image"
itemName="Sample"
amount={10.99}
currency="USD"
/>
Заўважыце разліку между двумя версіямі? Компонент ItemForSale чакае, што amount будзе чыслаўым значэнням, таму перадача строкі вызывае адказку, тады як перадача справжнега числовага значэння є правільной. Гэта самэ тое, каму прызначаны TypeScript — выяўляць патэнцыйнае багі ўсё раней, чым ваш код запускаецца. У гэтым прыкладзе вызов .toFixed(2) для строкі '10.99' фактычна спрычыніў бы збой пад час выканання, але TypeScript пазначае проблему ў часа компіляцыі.
Адказка: У типе '{}' не існуе атрыбута 'X', які ў тыпе 'Props' є обав’язковым.
Гэта трапляецца, калі атрыбут пазначаецца як обав’язковы ў вашай інтэрфейсе, але вы забываеце яго перадаць пад час викорыстання компонента.
interface CustomButtonProps {
label: string;
onClick: () => void;
};
function CustomButton({ label, onClick }: CustomButtonProps) {
return <button onClick={onClick}>{label}</button>;
};
// Bad usage:
<CustomButton label="Submit" />
// Error: Property 'onClick' is missing in type '{ label: string; }'
// but required in type 'ButtonProps'
Рашэнне: двойная перапрацоўка як вялічын пропаў у вашым компаненте, так і тых пропаў, якія вы фактычна задаеце пад час ўтварэння екранавання.
// Correct usage:
<CustomButton label="Submit" onClick={handleSubmit} />
Інодзе у вас бываюць пропы, якія не завжды ўпэўнены. У такіх случаях вы можете пазначыць проп як неабавязковы за дапамогою ?. Толькі памятайце, што калі вы робите проп неабавязковым, вам таксама трэба задаць або значэнне за змовай, або якісь спосаб працы з null, каб безпечна расправіцца з яго відсутнасцю.
interface CustomButtonProps {
label: string;
onClick: () => void;
disabled?: boolean; // this prop is now optional
className?: string; // this one too
};
function CustomButton({ label, onClick, disabled = false, className }: CustomButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
className={className}
>
{label}
</button>
);
};
// This component now works with or without the optional props
<CustomButton label="Submit" onClick={handleSubmit} />
<CustomButton label="Submit" onClick={handleSubmit} disabled={true} />
Тепер, калі ўжо є неабавязковыя пропы, існуе ўжо адна большая памылка, якую варта згадаць.
Памылка: тип 'X | undefined' не можа быць прызначаны типу 'X'.
Пры зробленні пропа неабавязковым автаматычна дадаецца undefined да яго типу, што стварае проблемы, якщо вы прабуяце викорыстаць гэтае значэнне без паветранага пераканання, чы гэта значэнне існуе.
interface ProfileProps {
name: string;
bio?: string;
};
function Profile({ name, bio }: ProfileProps) {
return (
<p>{name}</p>
<p>{bio.toUpperCase()}</p>
// Error: Object is possibly 'undefined'
);
};
Загалом існуе тры спосабы распрацавання гэтага прыблэмы:
Варыянт 1: задаць значэнне за замовчанням пад разбіранне пропсаў.
// bio is always a string, will render a default text when there's
// no bio provided
function Profile({ name, bio = 'No bio available' }: ProfileProps) {
return (
<p>{name}</p>
<p>{bio.toUpperCase()}</p>
);
};
Варыянт 2: выкарыстоўваць неабавязковы ланцуг.
// returns undefined if bio is undefined
function Profile({ name, bio }: ProfileProps) {
return (
<p>{name}</p>
<p>{bio?.toUpperCase()}</p>
);
};
Варыянт 3: выкарыстоўваць умовнае атрыбутаванне.
// will only render if bio exists
function Profile({ name, bio }: ProfileProps) {
return (
<p>{name}</p>
<p>{bio && bio.toUpperCase()}</p>
);
};
2. Адказкі на адбыванні падзеяў
Кожны компонент React, які рэагуе на дзействыя корыстніка, патрэбуе адказакоў на падзеі, і TypeScript прыносіць вельмі точныя типы для кожнага роду падзеяў DOM. Неправильнае выкарыстоўванне гэтых типаў ёсць аднам з найчастэйшых выкарыстанняў плутанні для разработчыкаў, якія працуюць з типаваным кодам React.
Проблема: параметр 'e' неявна мае тип 'any'.
Гэта выклікаецца, калі вы пішаце адказак на падзею як самастоятельную функцыю за межамі JSX і забываеце атрыбутаваць параметр падзеі.
// Error: Parameter 'e' implicitly has an 'any' type
const handleClick = (e) => {
e.preventDefault();
};
Рашэнак: Даце параметру адпаведны тип адзіявы React.
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
};
Варта згадаць: калі вы пісвеце функцыю-обрабавальнік безпосередна ўнутрь JSX, TypeScript можа сама вывучыць тип, таму ў такім случае гэтая адзінечная памылка не з’являецца.
<button onClick={(e) => {
e.preventDefault() // e is automatically React.MouseEvent<HTMLButtonElement>
}}>
Click here
</button>
Проблема: Атрыбут 'value' не існуець у типе 'EventTarget'.
Гэта, можа, самая популярная памылка TypeScript сярод разрабоў React — яна таксама ўваходзіць у числе найскладнейшых для разумэння, калі вы першы раз на яе натрапляеце. Яна з’являецца, калі вы працуеце з e.target.value у функцыі-обрабавальніку змян.
// This will give an error because TS doesn't know target is an input element.
const handleChange = (e: React.ChangeEvent) => {
console.log(e.target.value);
// Error: Property 'value' does not exist on type 'EventTarget'
};
Асалінным прычынам ёст тое, што EventTarget — гэта універсальны інтэрфейс DOM, таму TypeScript не можа знайсці, што ў гэтым конкретным обрабоўчыку цэлью ёсць насправды элемент <input>, які володзіць атрыбутам value.
Рашэнне: Указаць конкретны тип элемента як гэнерычны параметр для типу здарэння.
// TypeScript now knows the target is an HTMLInputElement
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
Той жа прыем дапамагае для іншых элементаў — напрыклад, для элемента select трэба заменіць HTMLInputElement на HTMLSelectElement. Ёсць кароткая справака па некалькіх типаў здарэння, якія вы будете вжываць частаўш:
// Click events
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
onClick: (e: React.MouseEvent<HTMLDivElement>) => void
// Input change events
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
// Form submission
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
// Keyboard events
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void
onKeyUp: (e: React.KeyboardEvent<HTMLInputElement>) => void
// Focus events
onFocus: (e: React.FocusEvent<HTMLInputElement>) => void
onBlur: (e: React.FocusEvent<HTMLInputElement>) => void
Калі мова заходзіць працэўніківах з дыячамі падзеяў, можа з’явіцца пытанне: як правільна задаць іх тип, калі перадаваць іх як пропсы дзецявым компонентам. У такім случае іх трэба задаваць як функцыі, якія прыймаюць відпаведны об’ект падзеі.
interface SearchInputProps {
onSearch: (e: React.ChangeEvent<HTMLInputElement>) => void;
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
};
function SearchInput({ onSearch, onSubmit }: SearchInputProps) {
return (
<form onSubmit={onSubmit}>
<input type="text" onChange={onSearch} />
<button type="submit">Search</button>
</form>
);
};
А якщо падазначаны працэўнік зовсім не патрэбуе прыймать падзею, можна адпаведна спростіць його тип.
interface SearchInputProps {
onClick: () => void;
onHover: () => void;
};
3. useState і useRef
Hooks знаходзяцца ў цэнтры сучаснага розвіцця React, таму не дзівуе, што useState і useRef маюць своія особлівасці ў TypeScript.
Проблема: Аргумент типу 'X' не можа быць прызначаны параметру типу 'never'.
Это адзін з найбольш зацікавляючых кашлёў, з якімі можна стаць. Ён выходзіць, калі вы ініцыялізуеце useState пустым масавым, чаго як следстве TypeScript выважвае тип стану як never[]. Ось як гэта выглядае:
// TypeScript infers state type as never[]
const [items, setItems] = useState([]);
// So later when we try to set a value...
setItems([{ id: 1, name: 'Item 1' }]);
// Error: Argument of type '{ id: number; name: string; }[]' is not assignable
// to parameter of type 'never[]'
Прычына гэтага: калі пачатковая значенне вашага стану — пусты масавы, TypeScript не можа здогадацца, якія элементы востаннўе будуць у ём, таму ён выбірае тип never[] за замовчаннем.
Рашэнне: Заўжды указвайце явны аргумент типу, калі ваш пачатковы стан — пусты масавы або null.
interface Item {
id: number;
name: string;
}
// ✅ Explicitly typed
const [items, setItems] = useState<Item[]>([]);
const [selectedItem, setSelectedItem] = useState<Item | null>(null);
У гэтым фрагменте стан selectedItem явна прызначаецца так, каб ён мог або приймаць об’ект Item, або null. Калі вы спакульватэе, што якісь стан спачатку будзе порожнім і пазьней заполнены, вам неабходна ясна прызначэння гэтага для TypeScript — інакш вы страждзеце ад такой памылкі:
Type 'null' is not assignable to type 'X'.
interface User {
id: number;
name: string;
email: string;
}
// TypeScript infers state as User, not User | null
const [user, setUser] = useState<User>({} as User); // Dangerous cast
// Later...
if (user.name) { /* ... */ }
// This might not catch the case where user is empty
Рашэнне: Отарыце аб’еднаны тип, які включае null, каб TypeScript разумеў, што даныя ўсё яшчэ не завантажыліся.
// Correctly typed, can be type User or null
const [user, setUser] = useState<User | null>(null);
// Now TypeScript forces you to handle the null case
if (user) {
console.log(user.name); // TypeScript knows user is User here
}
// Or with optional chaining:
console.log(user?.name); // string | undefined
Тая ж ідея прымае значэнне, калі ваш стан храніць болей складны об’ект. Спакульваймо, што вы моделюеце стан для формы кантакта з калькамі поль — правильны падход — спачатку з’явіць інтерфейс, які будзе описваць такую структуру.
interface FormState {
name: string;
email: string;
message: string;
isSubmitting: boolean;
}
function SubmitMessageForm() {
const [form, setForm] = useState<FormState>({
name: '',
email: '',
message: '',
isSubmitting: false,
});
// Then do partial updates with spread operator
const handleChange = (field: keyof FormState, value: string) => {
setForm(prevState => ({ ...prevState, [field]: value }));
};
}
Проблема: Об’ект можа быць ‘null’.
Эта проблема падае ўсё час, калі рэферэнс, створаны за дапамою useRef, спачатку мае значэнне null і апавяроўваецца, што ён павінен вясці да элемента DOM. TypeScript пазначыць кожную спробу выкарыстоўваць такі рэферэнс прычым, што ён яшчо не мае рэальнае значэнне.
const inputRef = useRef<HTMLInputElement>(null);
// Typescript here knows that inputRef.current might be null
inputRef.current.focus();
// Error: Object is possibly 'null'
Рашэння: пераканацца, што current ўжо заданы, прычым яго выкарыстоўваць. Калі TypeScript бачыць такую перакананню, ён перастае падазроўваць, таму што больш не можа падтвердзіць, што значэнне можа быць null.
// Null check before use
const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
// Or if you're more into one-liners, you can use optional chaining
inputRef.current?.focus();
Перш чым перейść да наступнай катэгорыі, існуе важлівая змяна, яку варта адзначыць. React 19 змяніў спосаб дзеяння useRef так, што гэта створяе проблемы для багатох разработчыкаў — а саме у розбіжнасці межу RefObject<T|null> і MutableRefObject<T>. Тепер за адзначэнне спосабу дзеяння не ўжо відпаведае пачатковая значэнне, якое вы прынасіце, а генерычны параметр типу, які вы задаеце хуку.
До React 19 вызов useRef(null) завжды вяртаў MutableRefObject<T>, таму current завжды могаў быть перазначаны.
// This worked fine
const ref = useRef<HTMLInputElement | null>(null);
ref.current = someElement;
Пачынаючы з React 19, генерычны тип, які вы задаеце, вялічыць, чы current можа быць змінены.
const ref = useRef<HTMLInputElement>(null);
ref.current = someElement;
// The above will result in an error.
Хоць параметр гэнерыка ў даным случае являецца типам HTML-элемента, React 19 спрацьвоўвае ref як такі, який вядома да DOM, таму ён стае толькі для чытання і фактычна керуецца самим React.
Якщо ж вам патрэбен ref для зберагчыць змінную значэння, а не вузол DOM, рабіце так:
// Let's suppose we're building a timer
const ref = useRef<ReturnType<typeof setTimeout> | null>(null);
ref.current = setTimeout(() => {}, 1000);
Это працюе таму, што тип, які передаёцца гэнерыку, не ўзначае тип HTML-элемента, таму React спрацьвоўвае яго як звычайны змінны ref — current застаецца можлівым для прызначэння.
Правіла для React 19: калі тип гэнерыка ўзначае тип HTML-элемента, current стае толькі для чытання і керуецца React. Калі ж це будзе любы іншы тип, current застаецца можлівым для змены. Короткая відпаведь:
useRef<HTMLElement>(null)для вузлаў DOM, керованых React (толькі для чытання)
useRef<T|null>(null) для будзь-яга іншага зменяемага значэння, якое вы хочаце зберагчы самі4. Асінхронныя даны і памылкі API
Калі вы запускаеце захопленне даных з API, з’являецца новы набор памылак у TypeScript, таму што захопленыя даны спачатку маюць тип unknown і павінны пройсці калькольванняя перад тым, як стаць чымсь, што можна безпечна выкарыстоўваць.
Памылка: Тып 'unknown' не можа быць прызначаны тыпу 'X'.
Па замовчанню рэзультат вызову fetch мае тип unknown, адколі TypeScript не мае вбудованай інформацыі пра тое, які формат вяртае даныя пэўны канцэнтрацый.
// Fetch response is unknown
const response = await fetch('/api/users');
const data = await response.json(); // data: any (in older TS) or unknown
// So when trying to use the fetched data:
const userName = data.name;
// With the code above, you might get a warning, something like
// 'data' is of type 'unknown'
Рашэнне: явна задаце тип для адпаведзі да API.
interface User {
id: number;
name: string;
email: string;
};
Пасля чаго у вас зазвычай є два варыянты для застосавання гэтага типу:
// Option 1: Type assertion. Only use this when you trust the API shape.
const response = await fetch('/api/users/1');
const data = await response.json() as User;
console.log(data.name); // You'll see that you have a string here
//////////////////////////////////////////////////////////////////////////
// Option 2: Create a generic fetch wrapper, this is a safer approach
// and it's reusable.
async function fetchJSON<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json() as Promise<T>;
}
const user = await fetchJSON<User>('/api/users/1');
console.log(user.name); // TypeScript now knows this is a string
5. Дзеці і памылкі JSX
У React компаненты зазвычай прыймаюць і атрыбутуюць children, а TypeScript праставляе калькі перакрываючыхся типоў, якія описваюць, каму можа быць прызначаны элемент children. Розумеўшы, у чым ўсё разніця, вы зможаеце ухіліцца ад шматароў памылак типаў.
Тры типы часта вжываюцца так, нібы яны можна было заменіць адзін на другі, хоця гэта не так:
- ReactNode
- ReactElement
- JSX.Element
Сярод іх ReactNode і ReactElement ёсць абоўсумна разныя, тады як JSX.Element на самай працоўцы ёсць проста іншым назвам для ReactElement.
import { ReactNode, ReactElement } from 'react';
// ReactNode is the most permissive one, accepts basically
// everything React can render:
// string, number, boolean, null, undefined, ReactElement, arrays...
type ReactNode =
| ReactElement
| string
| number
| boolean
| null
| undefined
| ReactPortal
| Iterable<ReactNode>;
// ReactElement is literally a React element created by
// JSX or React.createElement
// It does not include string, number, null, undefined
type ReactElement = {
type: string | ComponentType;
props: any;
key: string | null;
};
Як правільнае кераванне: як правіло, вжывайце ReactNode для атрыбута children, якщо толькі у вас няма конкрэтнай прычыны для яго сузкага выбору.
// Most flexible: use ReactNode for children in most cases
interface CardProps {
children: ReactNode;
};
// And use ReactElement when the requirements are not very flexible
interface StrictWrapperProps {
children: ReactElement;
};
Варта ўточніць разлік межаў ReactNode і ReactElement.
ReactNode можа выглядаць як будзь-што з наступных:
const example1 = <div>Hello</div>; // ReactElement ✅
const example2 = "Hello"; // string ✅
const example3 = 42; // number ✅
const example4 = true; // boolean ✅ (renders nothing)
const example5 = null; // null ✅ (renders nothing)
const example6 = undefined; // undefined ✅ (renders nothing)
const example7 = [<div />, <span />]; // array of ReactElements ✅
// All of the above are ReactNode
З іншай жа баку, ReactElement мае больш строгіяя абмежэння:
const example1 = <div>Hello</div>;
const example2 = <Button onClick={someHandlerFn}>Submit</Button>;
const example3 = React.createElement('div', null, 'Hello');
// Under the hood, every ReactElements look like this:
{
type: 'div',
props: { children: 'Hello' },
key: null,
}
Памылка: Type 'X' is not assignable to type 'ReactNode'.
Гэта зазвычай выйшвае, калі вы працявалі з значэнням, якое TypeScript не вважае правямым контэнтам для адрасавання.
// Objects are not a valid React children
interface User {
name: string;
email: string;
}
// Then if you try to do this
function DisplayUser({ user }: { user: User }) {
return <div>{user}</div>;
}
// It'll probably give you an error that looks something like...
// Error: Type 'User' is not assignable to type 'ReactNode'
// Objects are not valid React Children
Рашэнне: адрасаваць окремыя поля заместо цэлага об’екта.
function DisplayUser({ user }: { user: User }) {
return (
<div>
<p>{user.name}</p>
<p>{user.email}</p>
</div>
);
}
Памылка: JSX element type 'X' does not have any construct or call signatures.
Спачатку гэта можа быць заплутаным. Гэта выклікаецца тады, калі вы перадаеце значэнне куды-небудзь, спакульваючыся, што яно будзе паводзіцца як компонент, але у TypeScript няма можлівасці параболічна пераканацца, чы рэальна яго сутнісць такая.
// TypeScript doesn't know 'icon' is a valid component
interface ButtonProps {
icon: object; // too vague
};
// So when you try this...
function Button({ icon: Icon }: ButtonProps) {
return <Icon />;
}
// The above code will give you the error
// Error: JSX element type 'Icon' does not have any construct
// or call signatures
Рашэнне: задаць тип дынамічных компонентоў за дапамой React.ComponentType:
import { ComponentType } from 'react';
interface ButtonProps {
icon: ComponentType;
};
function Button({ icon: Icon }: ButtonProps) {
return (
<button>
<Icon />
</button>
);
}
// TypeScript now knows Icon is a valid component
Якщо гэты компонент таксама прыймае props, вы можете яшчэ адначасова апісаць іх:
interface IconProps {
size?: number;
color?: string;
};
interface ButtonProps {
icon: ComponentType<IconProps>;
};
function Button({ icon: Icon }: ButtonProps) {
return (
<button>
<Icon size={12} color="white" />
</button>
);
}
// So now the props are typed too
Прыдатны скорыя, якія варта знать, — это вбудованы у React тип-інструмент PropsWithChildren. Ён аўтаматычна дадае children?: ReactNode да будзь-якага інтерфейсу props, які вы абгортаеце навакола, таму вам не трэба занова прызначаць гэтае поле ў кожны раз:
import { PropsWithChildren } from 'react';
// Now instead of doing this
interface CardProps {
title: string;
children?: ReactNode;
};
// You can do this
type CardProps = PropsWithChildren<{ title: string }>;
// So with this, instead of explicitly typing children manually,
// you can use the above code
function Card({ title, children }: CardProps) {
return (
<div>
<h1>{title}</h1>
<div>{children}</div>
</div>
);
}
Проблема: атрыбутаванне спісаэлементаў
TypeScript накладае строгія правілы ў зв’язку з клучамі ў адрасаваных спісах, але большасць памылак типавання, з якімі вы станэце сустрэчыцца, на самай працо выходзіць з таго, як самы элементы задаюцца як типы, а не з клучоў.
// Items might be undefined, or item.id might not be a valid key type
function ItemList({ items }: { items: Item[] | undefined }) {
return (
<ul>
{
items.map(item => (
<li key={item.id}>{item.name}</li>
))
}
</ul>
);
}
// Error: Object is possibly 'undefined'
Рашэнне: захавацеся ад значэнняў undefined і пераканацеся, што типаванне адпавядае правілам:
function ItemList({ items = [] }: { items?: Item[] }) {
return (
<ul>
{
items.map((item) => (
<li key={item.id}>{item.name}</li>
))
}
</ul>
);
}
Ёшча кілька слоў…
Памылкі TypeScript перестаюць быць пераканальнікамі, калі вы начынаеце спрыяваць іх як падказкі. Гэта змена адбываецца тады, калі вы перестаеце рэагаваць з фразай „Ух, не гэта зноў“ і уместа таго начынаеце запытацца: „Што на самай працо TypeScript намагаецца мне сказаць?“
Адыякты, прытамуленыя ў гэтым кансале, — тояякты, з якімі вам давядзе сабрацца працуючы з React і TypeScript. Тое, што адзін разработчык постаўляе труднасці з системай типаў, а другі працуе з яю без проблем, часта залежыць толькі ад умеласці распазнаваць патэрны, больш нічога. У гэты момент вы ведаеце этыя патэрны.