Functional programming has been hot on the front end since Redux; Last July, Typescript 2.0 was released, OOP data flow frameworks became popular, and the community graved toward type-friendly Mobx and DOBS that were less verbose than Redux.

Static types, however, do not bind OOP. With the Redux community’s embrace of TS and the development of TS itself, TS’s ability to express FP is bound to become stronger and stronger. The Redux community also needs to brainstorm and contribute to the great combination of TS and FP.

This article focuses on some of Typescript’s interesting advanced features; These features are used to optimize the Redux type. For example, derive the global Redux State type and obtain different payload types under each Reducer case. Redux to formalize the incorporation of Typescript; Finally, we introduce some Typescript techniques commonly used in React.

Theoretical basis

Mapped Types

In Javascript, literal objects and arrays are very powerful and flexible. Typescript’s flexible interface is a great way to keep literal objects and arrays from being dead due to type constraints once types are introduced.

The Mapped Types described below make interfaces even more powerful. You’ve all used maps in JS. In TS, interfaces can also do maps.

// Make each attribute optional. type Optional<T> = { [key in keyof T]? : T[key]; }Copy the code

Derive the interface type from the literal object value and perform the map operation:

type NumberMap<T> = { } function toNumber<T>(obj: T): NumberMap<T> { return Object.keys(obj).reduce((result, key) => { return { ... result, [key]: Number(result[key]), }; }, {}) as any; } const obj2 = toNumber({ a: '32', b: '64', });Copy the code

With the support of interface map arithmetic, Obj2 can derive precise types.

Gets the return value type of the function

In TS, some types are a set of types, such as interface, function. TS can get subtypes of a set of types in several ways. Such as:

interface Person { name: string; } // Get subtype const personName: Person['name'];Copy the code

However, TS does not have direct support for function subtypes. However, there is a method of type inference to get the return value type.

Although this approach is somewhat convoluted and unelegant, the derivation of the return value type of a function can better support functional programming, and the benefits far outweigh the costs.

type Reverse<T> = (arg: any) => T; function returnResultType<T>(arg: Reverse<T>): T { return {} as any as T; } // result type is number const result = returnResultType((arg: any) => 3); type ResultType = typeof result;Copy the code

For example, when we write React-Redux connect, the return structure will most likely be different from the state structure. The exact return type can be obtained by deriving the return type of the function:

type MapProps<NewState> = (state? : GlobalState, ownProps? : any) => NewState; function returnType<NewState>(mapStateToProps: MapProps<NewState>) { return {} as any as NewState; }Copy the code

Usage:

function mapStateToProps(state? : GlobalState, ownProp? : any) { return { ... state.dataSrc, a: '', }; }; const mockNewState = returnType(mapStateToProps); type NewState = typeof mockNewState;Copy the code

Discriminated Unions

The official document provides a detailed explanation of Discriminated Unions, which will not be repeated in this article. Here’s the link:

Viewing English Documents

Viewing Chinese documents

For a quick introduction to what a recognizable union is, I’ll just quote a snippet from the official documentation:

interface Square { kind: "square"; size: number; } interface Rectangle { kind: "rectangle"; width: number; height: number; } type Shape = Square | Rectangle; Function area(s: Shape) {switch (s.type) {// in this case, the variable s type is Square case "Square ": return s.size * s.size; Rectangle case "Rectangle ": return s.eight * s.width; }}Copy the code

The variable S can have different types in different cases. I think the Reducer function is immediately relevant to the reader. Notice the type of the kind attribute defined in interface, which is a string literal type.

Redux type optimization

CombineReducer optimization

The original definition in redux:

type Reducer<S> = (state: S, action: any) => S;

function combineReducers<S>(reducers: ReducersMapObject): Reducer<S>;
Copy the code

At first glance, this definition seems fine. But readers familiar with Redux will know that this definition ignores the logical relationship between ReducersMapObject and S, the structure of WHICH is determined by the structure of ReducersMapObject.

As shown below, get the structure of the ReducersMapObject using Mapped Types, then get the type of the child State using the method that gets the return value type of the function, and finally spell out a big State type.

type Reducer<S> = (state: S, action: any) => S;

type ReducersMap<FullState> = {

}

function combineReducers<FullState>(reducersMap: ReducersMap<FullState>): Reducer<FullState>;
Copy the code

After overwriting the original type definition with the new combineReducers type, we can finally deduce the type of Redux global State through RootReducer through the layer of recursion of combineReducers. In Redux Thunk and Connect, you can enjoy the global State type without having to worry about writing the wrong local State path!

Get the global State type:

function returnType<FullState>(reducersMap: ReducersMap<FullState>): FullState {
  return ({} as any) as FullState;
}

const mockGlobalState = returnType(RootReducer);

type GlobalState = typeof mockGlobalState;
type GetState = () => GlobalState;
Copy the code

Deformalization & type derivation

The Redux community has always had a lot of tools for de-formalizing. But now tuyere is different, to formalize more a major task, do a good job in type support!

Regarding type and de-formalization, since the type of Redux ActionCreator depends on the actual project using the Redux asynchronous middleware. Therefore, this paper puts aside the author’s own business scenarios and only talks about methodology and the simplest ActionCreator solution. Readers can use these methodologies to create type systems that are appropriate for their projects.

Reminded by the team members, the author created a REPO for readers to experience in order to have a better sense of type body:

Github.com/jasonHzq/re…

You can clone and play with vscode.

Redux Type

The simplest way to declare Redux types is to use enums.

enum BasicTypes {
  changeInputValue,
  toggleDialogVisible,
}

const Types = createTypes(prefix, BasicTypes);
Copy the code

The createTypes function is then used to correct the type and value of the enum.

CreateTypes is defined as follows, on the one hand using Proxy to modify the attribute values. On the other hand, the Types are modified using Mapped Types.

type ReturnTypes<EnumTypes> = { [key in keyof EnumTypes]: key; } function createTypes<EnumTypes>(prefix, enumTypes: EnumTypes): ReturnTypes<EnumTypes> { return new Proxy(enumTypes as any, { get(target, property: any) { return prefix + '/' + property; }})}Copy the code

Please note that in ReturnTypes, the Redux Type is corrected to a string literal Type (key)! To prepare for creating an identifiable union.

Redux Action type optimization

There are a number of Redux de-formalization tools available, so this article will not describe the de-formalization of Redux actions, but only the type optimization of Redux actions.

The author summarizes three points as follows:

  • 1. There should be an overall ActionCreators Interface type.

For example, you could define a literal object to store actionCreators.

Const actions = {/** + */ add:... /** / multiply:... }Copy the code

On the one hand, it is easy for other modules to reference, on the other hand, it can do batch type derivation for literals. And the comments, only in this literal, can be parsed in vscode to improve the identification and development experience when referenced by other modules.

  • 2. Each actionCreator needs to define the payload type.

As shown in the code below, regardless of how actionCreator is created, its payload type must be explicitly specified. To enjoy the payload type in the Reducer.

Const actions = {/** add */ add() {return {type: types.add, payload: 3}; }, /** multiply: createAction<{num: number}>(types.multiply)}Copy the code
  • 3. Derive distinguishable union types.

Finally, you need to be able to derive identifiable union types from actions. In this way, different payload types can be enjoyed under different Reducer cases.

The ActionType structure to be derived is as follows:

type ActionType = { type: 'add', payload: number }
  | { type: 'multiply', payload: { num: number } };
Copy the code

The derivation process is as follows:

type ActionCreatorMap<ActionMap> = { [key in keyof ActionMap]: (payload? , arg2? , arg3? , arg4?) => ActionMap[key] }; type ValueOf<ActionMap> = ActionMap[keyof ActionMap]; function returnType<ActionMap>(actions: ActionCreatorMap<ActionMap>) { type Action = ValueOf<ActionMap>; return {} as any as Action; } const mockAction = returnType(actions); type ActionType = typeof mockAction; function reducer(state: State, action: ActionType): State { switch (action.type) { case Types.add: { return ... } case Types.muliple: { return ... }}}Copy the code

Front-end type optimization

Common React type

  • Event

The Event parameter is common in React, so React provides a rich set of Event types. For example, the most common React.ChangeEvent:

// HTMLInputElement is the element type that triggers the Event. React.ChangeEvent<HTMLInputElement>) { // e.target.value // e.stopPropagation }Copy the code

The author prefers to convert events into corresponding values

function pipeEvent<Element = HTMLInputElement>(func: any) {
  return (event: React.ChangeEvent<HTMLInputElement>) => {
    return func(event.target.value, event);
  };
}

<input onChange={pipeEvent(actions.changeValue)}>
Copy the code
  • RouteComponentProps

ReactRoute provides RouteComponentProps and provides type definitions for location and params

type Props = OriginProps & RouteComponentProps<Params, {}>
Copy the code

Automatically generates the interface type

Generally speaking, an API convention platform or interface convention document is used to decouple the front and back ends, such as RAP and Swagger. I worked on a team that converts interface conventions into Typescript type definition code. Through the practice of the author’s team, this tool has greatly improved the development efficiency and maintainability.

Interface type definitions help with development:



On maintainability. For example, when interface conventions change, the API’s type-definition code is regenerated, Typescript can detect field mismatches, and the front end can quickly fix the code. Most importantly, because of the binding relationship between the front-end code and the interface contract, the interface contract document has 100% reliability. We were able to build a reliable test system through interface conventions for automated tuning and testing.

Common default type

  • Partial

Make all interface properties optional:

interface Obj { a: number; b: string; } type OptionalObj = Partial<Obj> // interface OptionalObj { // a? : number; // b? : string; / /}Copy the code
  • Readonly

Change all attributes of interface to readonly:

interface Obj {
  a: number;
  b: string;
}

type ReadonlyObj = Readonly<Obj>

// interface ReadonlyObj {
//   readonly a: number;
//   readonly b: string;
// }
Copy the code
  • Pick
interface T {
  a: string;
  b: number;
  c: boolean;
}

type OnlyAB = Pick<T, 'a' | 'b'>;

// interface OnlyAB {
//   a: string;
//   b: number;
// }
Copy the code

conclusion

In FP, functions are like pipes, and the types of data blocks at the joints of pipes are always different. The next layer of pipe usage types often need to be redefined.

But if there is a definite way to derive the return value type of the function, then all you need to know is the type of the data block at the beginning of the pipe, and all the pipe joins can be inferred.

Directly fetching function return value types is not supported in current VERSIONS of TS, although the indirect method described in this article can also solve the problem, but it is best to hope that TS directly supports: issue soon.

FP in JS is like a runaway horse, please tie it with type.

At last! Welcome to send resume [email protected], post is not limited.