- Type vs. Interface
- Enums
- Object, object & {}
- Type guards
- Type assertions
- Implicit vs. explicit typing
- Generics vs.
any
vs.unknown
- File Names
- Casing and naming conventions
- Comments
- Redux
What's the difference?
Source: https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types/37233777
When to use interface
?
- Whenever you can 😉
Why?
- Declaration merging,
- Extending is safer than intersections. You won't accidentally override a property.
When to use type
?
- When you want to alias something in the scope of the particular file.
- When you are not exporting the particular type.
- You need a union type.
- When you need mapped types.
Don't use enums.
Why?
- Come with a huge variation surface (numeric, string-based, manually counted, automatically counted, inline, dynamic, one-directional, bi-directional).
- Create bloated objects in runtime.
- Produce unexpected results because of merging (two of the same name can live in one scope)
- Consider the following example:
enum Status {
a = 1,
b = 2,
c = 3,
}
const foo = (x: Status) => console.log(x);
foo(100); // ok, no error
What to use instead?
- String literal types:
type State = "loading" | "error" | "initial"
- Numeric literal types:
type State = 1 | 2 | 3
You can read more about how enums work here.
Don't use {}
Why?
{}
is the empty type and is assignable from any non-null/non-undefined type.- It allows extra properties:
const obj: {} = { size: 10 } // OK
. - It allows primitives:
const obj: {} = "hello" // OK
.
Don't use Object
Why?
Don’t ever use the types Number, String, Boolean, Symbol, or Object These types refer to non-primitive boxed objects that are almost never used appropriately in JavaScript code. https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#number-string-boolean-symbol-and-object
Use object
or <T extends object>
Bad
const foo = (x: Object) => null;
const foo = (x: {}) => null;
Good
const foo = (x: object) => null;
const foo = <T extends object>(x: T, key: keyof T) => null;
- Use type guards wherever for every predicate that is checking the type. (Prefer type guard instead of usual predicates where applicable.)
- Use
unknown
for the parameter type. - It allows TypeScript to narrow the type correctly.
Bad
const isUser = (x: unknown) => x && x.name && x.age;
// x is of type unknown
if (isUser(x)) {
// x is still of type unknown
}
Good
const isUser = (x: unknown): x is User => x && x.name && x.age;
// x is of type unknown
if (isUser(x)) {
// x is of type User
}
Use as
for type assertions. Why?
- Consistency,
<type>value
is often associated with type casts in other languages.
Bad
const env = <string>process.env.NODE_ENV;
Good
const env = process.env.NODE_ENV as string;
Let the TypeScript compiler infer as much as possible and avoid defining types when it is unnecessary.
Bad
const trim = (x: string): string => x.trim();
const x: number = 10;
Good
const trim = (x: string) => x.trim();
const x = 10;
Type parameters should be used to enforce constraints between types.
- capture the relationship between function arguments and its return type
- capture the relationship between the class type and its properties and methods
Type parameters should always describe a relationship.
Bad
// type parameter is only used once
declare const doSomething = <T>(x: T): void;
// type parameter is not used to define any parameter
declare const parse = <T>(): T;
const x = parse<string>();
// any is unsafe and "turns off" type checking
declare const parse = (): any;
const x = parse();
Good
declare const doSomething = (x: unknown): void;
declare const parse = (): unknown;
const x = parse() as number;
Use one-letter type parameters names
Bad
type Transform<InputProps, OutputProps> = (props: InputProps) => OutputProps
Good
type Transform<A, B> = (props: A) => B
Why?
- It makes the type reusable,
- Expressed the generic nature of the function.
If you have too many type parameters that one-letter names are hard to read, then you have too many type parameters.
Use PascalCase
for React components (if the file's main export is React component), and camelCase
otherwise.
Use camelCase
for variable and function names.
Bad
const FooBar = '';
Good
const fooBar = '';
Use PascalCase
for classes, type aliases, and interfaces and camelCase
for members.
Bad
type foo = string;
interface bar {
Name: string
}
class component { }
Good
type Foo = string;
interface Bar {
name: string
}
class Component { }
Don't prefix interfaces with I
.
Bad
interface IRequest {}
Good
interface Request {}
Make sure your comments are meaningful.
Bad
// we are setting counter to 0
const counter = 0;
Good
const counter = 0;
If you are documenting a function behavior consider using JSDoc format.
Example:
/**
* This is a function.
*
*
* @example
*
* foo('hello')
*/
function foo(n: string) { return n }
It's okay to leave todo
comments.
interface State {
items: any; // todo — dependent on X/requires Y to type correctly
}
How to type actions and actions creators?
// service/types.ts
export const INSERT_REQUEST = 'INSERT_REQUEST' as const;
export const UPDATE_REQUEST = 'UPDATE_REQUEST' as const;
const insert = (payload: string) => ({
type: INSERT_REQUEST,
payload,
})
const update = (payload: { id: number }) => ({
type: UPDATE_REQUEST,
payload,
})
export type ActionTypes = ReturnType<typeof insert | typeof update>;
How to type connect
HOC?
import { connect, ConnectedProps } from 'react-redux'
const mapState = (state: RootState) => ({
tables: state.tables
})
const mapDispatch = {
setTableName: (name: string) => ({ type: 'SET_TABLE_NAME', name })
}
const connector = connect(mapState, mapDispatch)
interface Props extends ConnectedProps<typeof connector> {
tableName: string
}
const MyComponent: React.FC<Props> = props => (
<div>
{props.tableName}
<input onChange={e => props.setTableName(e.target.value)} type="text" />
{props.tables.map(t => (<p>t.name</p>))}
</div>
)
export default connector(MyComponent)
How to type reducers?
Follow this documentation
How to use TypeScript with Redux Thunk?
Follow this documentation.
Great reference doc! ❤️