React, TypeScript and defaultProps Dilemma Github React, TypeScript and defaultProps Dilemma github

preface

Maintainability and readability are important for large front-end projects these days, so choosing to use “safe” JavaScript — namely Typescript — can be very helpful, making your job easier and avoiding potential bugs. Typescript paired with React makes it easy to build your application, configure tsconfig.json, and Typescript will guide you through writing robust code. Everything was going so well until you ran into a “big” problem — dealing with the defaultProps in the component

The authors have posted the Rex-Tils code base on Github, which contains the solution discussed in this article

This article is based on TypeScript version 2.9 with strict mode enabled. I’m going to show you this problem and how do you solve it in the component

A Button component

Let’s start by defining a Button component that follows the following API:

Button API:

  • onClick
  • Color (define button color)
  • We made the color and type optional, and used the defaultProps to define their default values. (Component users may not want to add these parameters.)
import {MouseEvent, Component} from 'react';
import * as React from 'react';

typeProps = { onClick(e: MouseEvent<HTMLElement>): void; color? :'blue' | 'green' | 'red';
	type? :'button' | 'submit';
}
class Button extends Component<Props> {
	static defaultProps = {
		color: 'blue'.type: 'button'
	};
	render() {
		const {onClick: handleClick, color, type, children} = this.props;
		return (
			<button
				type= {type} style={{color}} onClick={handleClick} > {children} </button> ); }}export default Button;
Copy the code

Button component implementation

Now, when we use buttons in other components, we can edit the code to get the Props (optional, required)

However, the defaultProps property of the Button component is not checked because the type checker cannot infer its type from a static property defined by the generic class extension.

Specific explanation:

  • Set any type to staticstatic defaultProps
  • Define the same thing (type and implementation) twice

You can extract the properties for color and type by separating Props, and then map the default value to an optional value by crossing the types. This latter step is quickly implemented using Partial types from the TS library.

It then explicitly specifies that the type of the defaultProps is defaultProps, so that the inspector can examine the defaultProps.

I prefer to use the following method to extract the defaultProps and initialState (if the component has a state) to separate the state types. This has the added benefit of getting a clear definition of the types from the implementation, reducing the number of templates that define props, and keeping only the core required functions.

Next, add a little more complex logic to the components. The requirement is that you don’t just want to use CSS inline styles (which is a reverse design pattern and poor performance), but can generate CSS classes that have some pre-defined attributes based on the color attribute.

Defines a resolveColorTheme function that takes a color argument and returns a custom className.

TS Error:
Type 'undefined' is not assignable to type '"blue" | "green" | "red"'
Copy the code

Why is that? Color is optional, the compilation is in strict mode, and the union type extension has undefined/void types, but the function does not accept undefined.

How to fix this high-value problem?

TypeScript 2.9 provides four methods to fix it:

  • Non-null Assertion operator
  • Component Type Casting
  • The high-level component defines defaultProps
  • Props getter function

1. Non-empty assertion statements

This method is obvious, and explicitly tells the type checker that this will not be null or undefined. Pass! Operator implementation:

This is fine for simple use cases (those with few props, only those that accept certain props in the render method), but as business grows, such methods will add to the clutter of your components and make them unreadable. You need to spend a lot of time checking which props are defined as defaultProps, which takes up a lot of developer time, which also leads to errors

2. Reset the component type

So how to solve the limitations of the previous problem? We do this by creating an anonymous class, asserting that its type is composite, and setting the single type of defaultProps (and the read-only type limit) as follows:

TypeScript version 2.8 introduces an advanced type called conditional types

T extends U ? X: Y // means that T is of type X if it inherits from U, and Y otherwiseCopy the code

3. Define defaultProps for the higher-order function

Defines a factory/higher order function for the props combination of defaultProps and conditional types.

WithDefaultProps is a higher-order function. You don’t need to explicitly use the React interface to define defaultProps. If you don’t need to check for defaultProps, you can remove type defaultProps from the code above. Note, however, that it cannot be used with generic components, like this:

4. Props getter function

Use the factory/closure pattern for conditional type mapping.

Just as with the withDefaultProps function, the type-mapping structure is utilized, but defaultProps is not mapped as optional because it is not optional when implementing the component.

Further explanation:

So far, the final solution covers all of the above issues:

  • There is no need to use non-empty assertion statements to avoid type checking
  • There is no need to force an indirect conversion of a component to another type
  • The component does not need to be created again, so the type is not lost in the process
  • Generic components can also be used
  • Easy to reason with TypeScript 3.0

If there are any mistakes or irregularities, please be sure to correct them. Thank you very much!

reference

  1. TS official website Advanced type
  2. TS the use and implementation of some tools generics