46
loading...
This website collects cookies to deliver better user experience
React+Typescript
is considered as one of the most popular bundles for creating client-side applications. This combination has a huge potential and allows to develop solutions of any complexity. In this article, we’ll see why Typescript
is so hyped, discuss some basic examples of working with components, storages, and API queries, and see the benefits of Typescript
.React
. Otherwise, it is pointless to proceed reading the article. To enthusiastic non-reacters please go to read the doc and then come back here.JS
. TS
helps us to fix most bugs and provides us with powerful tools to improve code quality. You can read more about the pros and cons of TS
in other articles, but we’ll go over them a little bit. TS
works within the React
, and what are the pros and cons of TS
and when we should use it.React+TS
project, I recommend using the create-react-app
utility with the template parameter:$ npx create-react-app --template typescript
React
project ready to run, in which TS
support is already configured, and you can immediately start writing code.Button
component, which contains the following logic:TS
, will be written like this:
import React, { useState } from "react";
const Button = ({ onClick, text }) => {
const [clickCount, setCount] = useState(0);
const handleClick = (event) => {
setCount(clickCount + 1);
onClick(event);
};
return <button onClick={handleClick}>{text}(count: {clickCount})</button>;
};
export default Button;
onClick
, or pass a different type of data there, the component will break at runtime (in the handleClick
function), which you might not notice during development.text
we can pass any value, which could lead to unexpected output.
Most of the problems are due to the fact that we don’t know what types of data come to us in props. This problem is partly solved by the propTypes
library, but it has its own limitations — it can only type the component’s props. TS
allows you to cover everything with types: handleClick
function, onClick
function, additional helpers and utilities, stores, and etc. An example of the same component, but using TS
:
import React, { useState } from "react";
interface IProps {
onClick: (event: React.SyntheticEvent) => void;
text: string;
}
const Button: React.FC<IProps> = ({ onClick, text }) => {
const [clickCount, setCount] = useState(0);
const handleClick = (event: React.SyntheticEvent) => {
setCount(clickCount + 1);
onClick(event);
};
return (
<button onClick={handleClick}>
{text}(count: {clickCount})
</button>
);
};
export default Button;
onClick: (event: React.SyntheticEvent) => void
and the string field text: string
.React.FC
, which takes our props interface as an argument React.FC
.handleClick
function by describing that it accepts an event with the type React.SyntheticEvent
(documentation).onClick
, text
) and this will prevent us from compiling the project.Redux
as a centralized data store. In this section, we will take a look at an example of a small Redux
store on TS
.increment
, decrement
):// action types
enum CounterActionTypes {
increment = 'increment',
decrement = 'decrement'
}
// interfaces & types
interface CounterState {
value: number
}
type CounterPayload = number
interface BaseAction<ActionTypes, Payload> {
type: ActionTypes
payload: Payload
}
type CounterAction = BaseAction<CounterActionTypes, CounterPayload>
// actions
const increment = (payload: number): CounterAction => ({
type: CounterActionTypes.increment,
payload
})
const decrement = (payload: number): CounterAction => ({
type: CounterActionTypes.increment,
payload
})
// reducer
const initialState: CounterState = {
value: 0
}
const counterReducer = (
state: CounterState = initialState,
action: CounterAction
): CounterState => {
switch(action.type) {
case CounterActionTypes.increment:
return { ...state, value: state.value + 1 }
case CounterActionTypes.decrement:
return { ...state, value: state.value - 1 }
default:
return state
}
}
export default counterReducer
I intentionally did not divide the code into several different > files, although usually in a real project you keep the code
divided by entities: reducer.ts
, interfaces.ts
,
actions.ts
,sagas.ts
(or epics.ts
).
actionTypes
. The enum
from TS
is ideal for this. Enum type — is a data type consisting of a set of named values called elements, members, enumeral, or enumerators of the type. In our case, we use an enum to declare the availability actionTypes
for this reducer. The declaration of actionTypes
is usually found in the file actions.ts
enum CounterActionTypes {
increment = 'increment',
decrement = 'decrement'
}
BaseAction
interface, which is not usually located directly in each store, but is a common interface used for all actions and is usually separate(for example, in the file store/interfaces.ts
). Then comes the declaration of the interface, which describes the state of the reducer. In our case the reducer stores only one field: value: number
. Also, we declare the CounterPayload = number
type for payloads of all actions that work with this reducer. Finally, the last type is CounterAction
, which uses a generic BaseAction
interface with the necessary parameters. All information about types is usually in the file interfaces.ts
, but it can also be stored next to entities (CounterState
in reducer.ts, CounterPayload
and CounterAction
in actions.ts
)interface CounterState { value: number }
type CounterPayload = number
interface BaseAction<ActionTypes, Payload> {
type: ActionTypes
payload: Payload
}
type CounterAction = BaseAction<CounterActionTypes, CounterPayload>
CounterAction
) we keep all action creators looking the same.const increment = (payload: number): CounterAction => ({
type: CounterActionTypes.increment,
payload
})
const decrement = (payload: number): CounterAction => ({
type: CounterActionTypes.increment,
payload
})
const initialState: CounterState = {
value: 0
}
const counterReducer = (
state: CounterState = initialState,
action: CounterAction
): CounterState => {
switch(action.type) {
case CounterActionTypes.increment:
return { ...state, value: state.value + 1 }
case CounterActionTypes.decrement:
return { ...state, value: state.value - 1 }
default:
return state
}}
CounterState
interface to create initialState
, and we use state: CounterState = initialState
and action: CounterAction
as parameters for the reducer. That way, we can’t use something that we didn’t declare in our interfaces. For example, you can’t add a new field to a state without updating the CounterState
interface; after adding it, you’ll have to refactor the cases where the new field isn’t returned and TS
will tell you where and what might be broken. Or, for example, you can’t add a new case to the reducer until you add actionType
to enum CounterActionTypes
. This allows us to make our code robust and bug-proof and protect developers from primitive bugs when working with code.API
. Usually, it’s very convenient to describe the response’s
interface. And if your server is written in NodeJS
using TS
, then you can once describe interfaces for reponse’s
and use them both on the server and on the client. It’s very convenient. Small example of working with API
using TS
:const api = {
posts: "https://jsonplaceholder.typicode.com/posts"
};
export async function request<T>(url: string): Promise<T> {
const response = await fetch(url);
const body = await response.json();
return body;
}
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
type PostResponse = Array<Post>
export async function getPosts(): Promise<PostResponse> {
const posts = await request<PostResponse>(api.posts);
return posts;
}
TS
for React
and highlight the main thesis — why and when to use TS
together with React
.TS
also has its disadvantages:TS
is definitely not the right choice for you:MVP
), then TS
is also not the best choice for you. You can write the basic version using JS
, and once the product finds the market fit, rewrite everything using TS
. JS
and you’re planning to rewrite it using TS
, most likely it’s not worth it. You’d be better off improving your current project code and covering it with tests. This is much more important.any
— do it. any
contradicts the main principle of TS — reliability, and exists only to transfer large complex projects to TS
gradually. And even in that case, it’s better to try not to use any
. If possible, try to never use any ever.TS
is a great tool, which is becoming more and more popular every year. And with React
it allows you to add the very reliability and transparency, which are usually missing in Frontend applications. In large corporations, TS
has long been a must-have, but gradually it becomes a must in smaller companies, and hence the number of developers who can use TS
is increasing. Just try learning and using Typescript
on a project and you’ll realize how cool this tool is.