This article was posted in November of ’19, but couldn’t be found in Denver. I have to send it again.

I. 为何要用TypeScript

Our company also has a team in Germany. We’re picking up one of their repositories this time. One of the apis that requires us to pass in parameters is defined like this:

/*
 * 
 * @param {Object} input The first object
 * @param {Object} options The second object
 * /
function init(input, options){
  ...
}  
Copy the code

I was devastated to see such code. This input is of type Object, which is clear, but Object is a variable in the JavaScript world, so what kind of value should I pass in?

This is a prime example of why we need TypeScript.

1).typescript (TS for short) helps us detect types and make it easy to use/read other modules. 2).typescript is mandatory checking and non-corruptible. In JavaScript you just add a comment that says “Option is {isA: Boolean, id: nubmer}” and that’s not good either. Since you changed the structure of option later, the general idea is that your comments are not changed. If the option structure is changed by TS, an error will be reported at the point where it is called, saying that it no longer corresponds. In fact, TS has some benefits, such as powerful types (and therefore harder to learn), support for some new features of modern languages (such as generics)… We won’t go into that in this article. Instead, I’d like to explain some of the pitfalls of developing React/ReactNative(R/RN) in TypeScript to help you make a smoother transition to the TypeScript world.

II. React

In the JS world we use PropTypes to define types, but it’s not very precise. For example, PropTypes. Object can’t be very precise about what members this object requires, so if you accidentally pass too few values, you’ll get NPE errors.

In TS you can restrict props, state – this applies to class and function components.

1. The class components

interface IProps { name: string; } interface IState { offset: number; } class SomeScreen extends React.Component<IProps, IState> { state = { offset: 0 }; constructor(props: IProps) { super(props); console.log(props.name); }}Copy the code

This defines the exact types of Props and state. You can’t pass props wrong or short

2. The function component

interface IProps {
  name: string;
}

const SomeScreen = (props: IProps) => {
  const [offset, setOffset] = useState<number>(0);
  console.log(props.name);
};
Copy the code

3. Advanced: Child View is flexible

So we could have one child View, or we could have multiple child Views, depending on the data. For example, if you give me an array and I have a few items, I’ll display a few views.

These flexible child views can then be defined as type jsX. Element.

render() { const children : JSX.Element[] = this.props.data.map((item, index) => { return <Image source={{ uri: item.url }} style={styles.item} key={item${index}}/>; });

return (
  <View style={[this.props.style, styles.container]}>
    {children}
  </View>
);
Copy the code

} Of course, these flexible child views must have a key, otherwise you will have a Yelloe box to warn you.

4. Default attribute values

This is going to be the distinction. Class components are also written differently from function components.

// function components interface IProps {id: number; text? : string; } const MyView = (props: IProps) => { return ( <>.... < / a >); }; MyView.defaultProps = { text: "default" }; = = = = = = = = = = // class MyScreen extends Component<IProps> {static defaultProps = {text: "default"};Copy the code

There’s actually a little hole here. If you want to defaultProps, you can write properties arbitrarily, you can not use IProps at all. This TS is indeterminate. There are articles on the Internet that address these issues, but in my opinion they are too complex and not as good. Fortunately, IProps can hold up most of the checks, so we’re using IProps instead of using defaultProps directly.

5. Reference (ref)

5.1 the React

There are several ways to use a ref in React:

// React (Approach 1)
const MyView = () => {
  let viewRef : HTMLDivElement | null;

  return (
    <div ref={v => viewRef = v} />
  );
};
Copy the code

As well as

// React (Approach 2)
const MyView = () => {
  const viewRef = createRef<HTMLDivElement | null>();
  return (
    <div ref={viewRef}/>
  );
};
Copy the code

5.2 the React Native

In the ref section, React Native differs from React in that it is no longer an HTML****Element.

const MyView = ()=>{ let ref: View|null = null ; let imageRef = createRef<Image>(); return ( <View ref={ref}> <Image ref={imageRef} source={require(".. /a.png")} /> </View> ) }Copy the code

Note, of course, that ref is used with care when it comes to function components. See the React website for details.

6. HoC

HoC says it’s a higher-order component, but it’s really just a function. It’s just that the input participation returns only components. HoC is also a way to combine multiple components, with much less repetitive code and a clear division of logic.

HoC Hell, for example:

(photo: miro.medium.com/max/2586/1 *…).

However, we will stick to TS in this article. When using TS to do HoC, the problem is primarily one of type. What is the type of the component you pass in versus the new component you return.

A HoC that adds a Loading effect to an incoming parameter component can be written like this:

interface IProps { loading: boolean; } const withLoader = <P extends object>(InputComponent: React.ComponentType<P>): React.FC<P & IProps> => { props.loading ? (...). : (...). . ;Copy the code

Note the use of React.Com ponentType, the definition of this type is the type the pop3access.componenttype < P = {} > = ComponentClass < P > | FunctionComponent < P >; That is, a function component or a class component.

Also, notice the Props declaration. Our input parameter can be any component, so Props should not be dead, i.e. use generics. As for our HoC, if it has its own needs, it can be combined with P & IProps.

P.S. the A & B, A | B is the powerful TypeScript. Its type component is easy. If this were Java, you would have to define A new type called C, and then assign all the attributes of A and B to C — that would have duplicate code.

7. Properties commonly used in daily development

At first glance, this doesn’t sound like a big deal. But in TS, you can’t get anywhere without defining type. So we need to know some common libraries and what types of properties are commonly used in React. For example, what are the types of navigation in react-Navigation and Redux?

Here is a successful example I wrote:

interface IViewProps {
  // ... your own props
}

type IProps = IViewProps &
  ViewProps & 
  NavigationScreenProps & 
  ReturnType<typeof mapStateToProps> & 
  ReturnType<typeof mapDispatchToProps>

class MyScreen extends React.Component<IProps, IState> {
  // ....
}
Copy the code

Among them:

ViewProps contains properties like style, children, onLayout, and testID. NavigationScreenProps is a react-native class. It comes from the React-Navigation library and has navigation, screenProps, navigationOptions, etc. The other two returnTypes are props generated by redux. We’ll talk about this in a later chapter

III. Redux

Redux, the well-known state container, needs no further details. There are some caveats to TypeScript versions of Redux, however.

1. action

There is an AnyAction type in Redux, which means AnyAction will do — subject to the basic law, the standard Action definition in flux

In a module, we are talking about a module that only handles certain actions. For example, the audioPlayer module only handles audio Play-related actions. At this point we can go like this:

export interface IAddAction{
  type: "Add"
}

export interface IRemoveAction{
  type: "Remove",
  paylaod: {
    id: number
  }
}

export type MyAction = IAddAction | IRemoveAction
Copy the code

We can combine different actions to make one total action. This allows us to use this total Action in the reducer() later — otherwise, anyactions with a wider scope will be mispositioned and error prone

2. state

Redux is a Single Source, so it usually stores a large amount of state. Especially after we have many reducer and combine combinations one by one, the global state of the whole application is very large and hierarchical. If you don’t have a clear description of the type, six months or a year or two later, the whole state is in a mess. Those of you who have written large projects know this.

export interface IProduct {
  id: string;
  name: string;
  category: IProductCategory;
  sku: Sku;
}

export interface MyState {
  readonly products: IProduct | null;
}
Copy the code

3. reducer

With the state and Action definitions above, our Reducer is now clearer than ever. If state. A field is used in reducer, there will be a prompt on whether it is correct, which reduces the possibility of typo errors.

export const MyReducer : Reducer<MyState, MyAction> = (
  state = new MyState(),
  action: MyAction
) => {
  switch(action.type){
    ...
  }
  return state;
}
Copy the code

4. store

Here store is a little bit more complicated, but it’s a little bit clearer. The main problem is that the reducer reducer in the entire application can be combined at different levels — this will also affect the layout of our state.

CombineReducer () = combineReducer()

export interface IAppState { products: MyState, books: AnotherState } const rootReducer = combineReducer<IAppState>({ products: MyReducer, books: ANotherReducer }) export const store = createStore(rootReducer, undefined, applyMiddleware(...) );Copy the code

If you say your Reducer hierarchy is complex, say something like this:

const RootReducer = combineReducer({
  oneReducer,
  combineReducer(
    twoReducer, 
    combineReducer(fourReducer, fiveReducer)),

})
Copy the code

And then you have to write the state level, which is pretty tiring. Export type IAppState = ReturnType

5. async action

I used Redux-Saga to do asynchronous in my project. But it’s easy to use Thunx if you want to, and that’s it:

export const fetches = async (): Promise<IProduct[]> => {
  await wait(1000);
  return products;
}
Copy the code

6. AnyAction

As mentioned earlier, we have a built-in AnyAction type. The source code for AnyAction is:

export interface AnyAction extends Action {
  // Allows any extra properties to be defined in an action.
  [extraProps: string]: any
}
Copy the code

Note: in TS, [extraProps: string]: any is any key name (as long as it is of type string) and value is of type any.

Use AnyAction sparingly, just like use any sparingly.

7. Redux-Persist

If you use the Redux-Persist library in your project, the IAppState definition is problematic. Because redux-persist adds its own definition to our appState, TS detects a type mismatch and reports an error.

For example, we now want to store a state like this: {book: {id: 22, name: “Harry”}} But when redux-persist is used, the state becomes: {book: {id: 22, name: “Harray”, _persist: {…. }}}

So we need to change it like this:

interface IAppState {

  // book: IBookState  // ERROR!!!

  book: IBookState & PersistPartial;

}
Copy the code

8. React-Redux

This is actually described above, is to use ReturnType to do flexible configuration.

type IProps = ReturnType<typeof mapStateToProps> 
        & ReturnType<typeof mapDispatchToProps>
        & ViewProps
Copy the code

9. Middlewares

I see a lot of books and websites defining middleware like this :const middleware = store => next => action => {… }. But now store really doesn’t refer to the store in Redux. The type is a newly defined type: MiddlewareApi.

Type MiddlewareAPI = {dispatch: dispatch, getState: ()=> State}

So how do we define middleware in TypeScript? The trouble is that you don’t know the types of input arguments to some functions. The following snippet is a successful example:

const myMiddleware = (store: MiddlewareAPI) => (next: Dispatch<AnyAction>) => (action: AnyAction) => { ... . }Copy the code

Note: We all use generics at Dispatch, otherwise the compile will not pass. This is actually one place where you might use AnyAction. Because you really don’t know what action is coming.

IV. The test

Conclusion first, writing tests in TypeScript can be cumbersome. Because TS detects various types, such Mock methods will be too hacky and will be reported by TS as type mismatch.

Here is an example where we use jest. Mock () to inject mock methods into the Worker class. But TS does not know that Workder also has the mockReturnThis() method and reports an error.

import { work } from ".. /Worker" jest.mock(".. /Worker") test("some..." , ()=>{ work.mockReturnThis(); // ERROR!!! , as TypeScript does not know this method exist ... })Copy the code

In order for it to work, you have to add @ts-ignore:

  // @ts-ignore
  work.mockReturnThis()
Copy the code

But with @ts-ignore, it’s always uncomfortable. So I personally recommend using JS files for testing.

V. other

1. lazy init

While TypeScript is powerful, it’s not perfect. For example, the LateInit var that works well in Kotlin doesn’t work in TS. TS, like KotLin, first declares a const object and gives it a value.

But we can actually go a little further.

interface People {
  id: number,
  name: string
}

...
// const p = {}  // ERROR! `{}` and `People` are not compatilbe
const p = {} as People

// when time is ripe
p.id = 100
Copy the code

As People, which specifies the type, does not have to assign values to all attributes, which is convenient. TS static Check is where we want to use it. Where the above techniques are used, we’d better have code review.

Note: If you use ‘any’, it’s even worse. I’ll probably talk more about any in the future, how to avoid any

2. The generic

Generics are a powerful tool, as those of you who have used Java or Swift know. For JS students may be relatively new, but also suggested to learn.

Also, be careful about using generics in TS. For example:

If you look at the generic notation on TS, it’s not bad at all. How can you still report an error?

Haha, this is a pit. Note that the code that failed above is in a.tsx file.

When the.tsx file sees the <>, the first thing it says is, “This is an element of React “, and it wants to load the component.

So said.

In the.ts file, the above code does not report an error. In the.tsx file, the above code will report an error. To fix this, you need to tell the TS compiler, “This is a generic, not a component.”

// ***.tsx const example = (url: T) : number => { return 20; };

VI. Conclusion

That concludes some of TypeScript’s more advanced technologies. It’s mainly the unfamiliar types of tripartite libraries, and the unfamiliar usage of TS (compared to Java/Swift, etc.). If I have more techniques later, I will introduce them to you. Thank you for your support