More React Typescript notes
This commit is contained in:
parent
d6bd380bc2
commit
523b6a4a7d
5 changed files with 241 additions and 42 deletions
|
@ -0,0 +1,99 @@
|
|||
---
|
||||
tags:
|
||||
- Programming_Languages
|
||||
- typescript
|
||||
- react
|
||||
- react-hooks
|
||||
---
|
||||
|
||||
# Typing built-in hooks
|
||||
|
||||
## `useState`
|
||||
|
||||
```tsx
|
||||
const [amount, setAmount] = useState<number | string>(3);
|
||||
```
|
||||
|
||||
### With a ustom type
|
||||
|
||||
```tsx
|
||||
interface IState {
|
||||
people: IPerson[];
|
||||
}
|
||||
|
||||
interface IPerson {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
const [people, setPeople] = useState<IState>({});
|
||||
|
||||
// Alternative declaration
|
||||
|
||||
interface IState {
|
||||
people: {
|
||||
name: string;
|
||||
age: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
const [people, setPeople] = useState<IState['people']>({});
|
||||
```
|
||||
|
||||
## `useReducer`
|
||||
|
||||
Mostly the same. They key thing to type is the reducer function itself. You don't need to type the intitial state unless it is a pre-existing type, you can just use type assertion:
|
||||
|
||||
```jsx
|
||||
interface IAction {
|
||||
| { type: "increment"; payload: number }
|
||||
| { type: "decrement"; payload: string };
|
||||
}
|
||||
|
||||
const initialState = { count: 0 };
|
||||
|
||||
function reducer(state: typeof initialState, action: IAction) {
|
||||
switch (action.type) {
|
||||
case "increment":
|
||||
return { count: state.count + action.payload };
|
||||
case "decrement":
|
||||
return { count: state.count - Number(action.payload) };
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then the syntax is as normal, i.e:
|
||||
|
||||
```js
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
```
|
||||
|
||||
## `useEffect`
|
||||
|
||||
> Limited potential for typing here as this hook does not return a value. See if there is useful inferred typing from use and update.
|
||||
|
||||
## `useContext`
|
||||
|
||||
We can use generics but mostly this is untyped:
|
||||
|
||||
```jsx
|
||||
type Theme = 'light' | 'dark';
|
||||
const ThemeContext = createContext < Theme > 'dark';
|
||||
|
||||
const App = () => (
|
||||
<ThemeContext.Provider value="dark">
|
||||
<MyComponent />
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
|
||||
const MyComponent = () => {
|
||||
const theme = useContext(ThemeContext);
|
||||
return <div>The theme is {theme}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## `useCallback` / `useMemo`
|
||||
|
||||
You must only type the parameters that are passed to the callback, the return value will be inferred.
|
27
Programming_Languages/React/React_Typescript/Components.md
Normal file
27
Programming_Languages/React/React_Typescript/Components.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
tags:
|
||||
- Programming_Languages
|
||||
- typescript
|
||||
- react
|
||||
---
|
||||
|
||||
# Components
|
||||
|
||||
We write functional components in the manner of a normal [function](/Programming_Languages/React/React_Typescript/Functions.md) that take a props argument and return a JSX element.
|
||||
|
||||
```jsx
|
||||
interface IProps = {
|
||||
message: string;
|
||||
};
|
||||
const App = ({message}: IProps): JSX.Element => <div>{message}</div>;
|
||||
```
|
||||
|
||||
## `React.FunctionComponent` / `React.FC`
|
||||
|
||||
It used to be the case that you would type a function component like so:
|
||||
|
||||
```jsx
|
||||
const App: React.FunctionComponent<{message: string}> = ({message}) => <div>{message}</div>;
|
||||
```
|
||||
|
||||
This is now discouraged as it means the component can always accept children, even when you do not want this to be the case. There are several other minor drawerbacks but it's generally best to leave out the declaration.
|
|
@ -33,7 +33,7 @@ interface IProps {
|
|||
setPeople: React.Dispatch<React.SetStateAction<Props["people"]>>
|
||||
}
|
||||
|
||||
const AddToList: React.FC<IProps> = () => {
|
||||
const AddToList = () => {
|
||||
const [people, setPeople] = useState<IState["people"]>({})
|
||||
const [formVals, setFormVals] = useState({});
|
||||
|
||||
|
@ -62,7 +62,7 @@ return (
|
|||
<input type="text" name="age" value={input.age} onChange={handleChange} />
|
||||
</form>
|
||||
<button onClick={handleClick}>Add to list</button>
|
||||
);
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -71,3 +71,24 @@ This follows standard practise for [controlled-components](/Programming_Language
|
|||
- We define the change event as being of the type `React.ChangeEvent` and state that it corresponds to a generic - `HTMLInputElement`. So we are saying that whenever this function is called we must be passing it an input element so that we can extract the event associated with its `target` property.
|
||||
|
||||
- We are passing around variations on the `IState` interface in order to type the values that we are adding to the people array.
|
||||
|
||||
## Further standard types for event handling
|
||||
|
||||
### onClick
|
||||
|
||||
```tsx
|
||||
handleClick(event: MouseEvent<HTMLButtonElement>) {
|
||||
event.preventDefault();
|
||||
alert(event.currentTarget.tagName);
|
||||
}
|
||||
```
|
||||
|
||||
### onSubmit
|
||||
|
||||
```tsx
|
||||
handleSubmit(e: React.SyntheticEvent) {
|
||||
event.preventDefault();
|
||||
}
|
||||
```
|
||||
|
||||
> Most event types and their associated generics will be revealed by VS Code Intellisense so you don't need to memorize them all
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
---
|
||||
tags:
|
||||
- Programming_Languages
|
||||
- typescript
|
||||
- react
|
||||
---
|
||||
|
||||
# Managing state
|
||||
|
||||
## Basic: `useState`
|
||||
|
||||
```tsx
|
||||
const [amount, setAmount] = useState<number | string>(3);
|
||||
```
|
||||
|
||||
### Custom type
|
||||
|
||||
```tsx
|
||||
interface IState {
|
||||
people: IPerson[];
|
||||
}
|
||||
|
||||
interface IPerson {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
const [people, setPeople] = useState<IState>({});
|
||||
|
||||
// Alternative declaration
|
||||
|
||||
interface IState {
|
||||
people: {
|
||||
name: string;
|
||||
age: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
const [people, setPeople] = useState<IState['people']>({});
|
||||
```
|
|
@ -7,6 +7,46 @@ tags:
|
|||
|
||||
# Props
|
||||
|
||||
## Types or interfaces?
|
||||
|
||||
They are mostly interchangeable but there are some specific differences
|
||||
|
||||
### Interfaces
|
||||
|
||||
- Classes and other interfaces can extend an interface but not a type
|
||||
|
||||
```ts
|
||||
interface Animal {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Bear extends Animal {
|
||||
honey: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
- You can add properties to an interface without generating a double declaration error:
|
||||
|
||||
```ts
|
||||
interface Window {
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
ts: TypeScriptAPI;
|
||||
}
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
You can create unions with types but not interfaces.
|
||||
|
||||
> The consensus seems to be that interfaces should be used over types unless there are ocassions when you do not want a type to be extendable / redeclarable. Or when you want to use unions.
|
||||
|
||||
## How we type props
|
||||
|
||||
The custom is to type props as an interface. This way we can easily type function prop types.
|
||||
|
||||
```tsx
|
||||
interface IProps {
|
||||
people: {
|
||||
|
@ -35,4 +75,56 @@ const [people, setPeople] = useState<IState['people']>({});
|
|||
|
||||
```
|
||||
|
||||
## Common types for props
|
||||
|
||||
### Object with any number of properties
|
||||
|
||||
```tsx
|
||||
interface IProps {
|
||||
obj: {};
|
||||
}
|
||||
```
|
||||
|
||||
### Array of objects
|
||||
|
||||
```tsx
|
||||
interface IProps {
|
||||
val1: string;
|
||||
val2: boolean;
|
||||
}
|
||||
[];
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
```tsx
|
||||
|
||||
// Function that doesn't receive or return anything
|
||||
interface IProps {
|
||||
onClick: () => void;
|
||||
}[]
|
||||
|
||||
// Function with named parameter
|
||||
interface IProps {
|
||||
onClick: (id: number) => void;
|
||||
}[]
|
||||
|
||||
// Function that takes an event
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
```
|
||||
|
||||
### Other React components
|
||||
|
||||
```js
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
children: JSX.Element;
|
||||
}
|
||||
```
|
||||
|
||||
`React.ReactNode` covers everything that React can render. This should be definitely used when the prop is a child component, otherwise `JSX.element` is ok.
|
||||
|
||||
<p style="color: red;">Should I use type or interface? What is consensus?</p>
|
||||
|
|
Loading…
Add table
Reference in a new issue