TypeScript in React

preface

  • Scope of application

    This article is not a good introduction to TS because it will not introduce the basic TS syntax. Instead, it will explain how TO use TS and React in a practical way.

  • motivation

    Although TS was used in the project, I personally experienced the advantages of static type checking in the maintainability of the project, but I also encountered various constraints brought by it in the actual project development, and I always loved and hated TS. Secondly, I have been in touch with TS for more than half a year, which is also a good time for me to summarize and review.

  • Question:

    • The time spent writing TS type comments and eliminating red wave errors exceeded the time spent coding formal functions. (An ideal time split is about 20% for type annotations and 80% for functional logic)
    • After development, review the code, see the any type everywhere, and have no idea where to optimize.
    • I learned TS systematically and was quite handy in pure JS projects, but I didn’t adapt to react.
  • why

    • Official documentation, lacking some guidance on best practices. Online learning materials and discussions are not as rich as other front-end stacks.
    • React does not provide detailed instructions for using React with TS
  • purpose

    Try to solve the above problems, enhance readers TS actual combat ability

TS writes the React component

Start by declaring a component

  • Functional component declaration
    1. Method 1 (recommended) : Declare the component with the react. FC< P >, the official function component generic interface.

    advantageAll static properties of the React component, defaultProps, propsTypes, displayName, etc., can get IDE friendly prompts.

      interface Props {
        text:string
        theme:Theme
      }
      const Button:React.FC<Props> = (props) => {
          const { text } = props;
          return <div><button>{text}</button></div>;
      };
    Copy the code
    1. Method 2: Handle the Children declaration yourself
    interface Props { text:string theme:Theme children? : ReactNode } const Button = (props:Props) => { const { text, children } = props; return <div><button>{text}</button>{children}</div>; };Copy the code
    1. Method 3: Handle children with PropsWithChildren< P >. The effect is the same as method two.
     interface Props {
        text:string
        theme:Theme
      }
      const Button = (props: React.PropsWithChildren<Props>) => {
          const { text, children } = props;
          return <div><button>{text}</button>{children}</div>;
      };
    Copy the code
    React.FC (recommended) Handle the children property yourself PropsWithChildren
    Automatic Children declaration is no is
    React static method declaration is no no
  • Class component declarationThis is usually defined by the generic classes provided by Component
    interface Props{ id: string } interface State{ count: number } class Button extends Component<Props, State> { state = { count: 0, } add = () => { this.setState({ count: this.state.count + 1, }); } render() { return <div>{this.state.count}<button onClick={this.add}>add</button></div>; }}Copy the code

Props verification and default values

  • Props check

TS type constraints vs propsType and other third-party libraries. // Use TS interface constraint (recommended) interface props {text:string theme: theme} const Button:React.FC = (props) => {const { text } = props; return

{text}

// Use a special JS type library (not recommended) import PropTypes from 'prop-types' const Button = (props)=>{const {text} = props; return <div><button>{text}</button></div>; } Button.propTypes = { text: PropTypes.string, }; ` ` ` | | | TS type constraint prop - types such as third-party libraries | | - | - | - | | | run time calibration timing | | compile time | validation rules abundance general | | | good summary * * * * : TS type constraints are preferred because language-level type constraint schemes (with interfaces, generics, tuples, and so on) are far more powerful than API level JS type libraries **. Also, TS type checking can detect code problems ** earlier at compile time (unless runtime validation is specifically required).Copy the code
  • Set the default value for the property

Button:React.FC = (Props) => {const {text} = props; return

; }; Button.defaultProps = { text: ”, }; // Const Button:React.FC = (Props) => {const {text = “} = Props; return

; }; Conclusion: We recommend using es6 deconstruction because the properties declaration and default Settings are written separately in the code for defaultProps, which is not convenient and easy to miss

  • Smart use of mapping types to improve interface writing efficiency

    • Homomorphism: applies only to existing properties of the target, no new properties are created

      interface Props { a: string, b: ReadonlyObj = Readonly<Props> // PartiaObj = Partial<Props> // Make all attributes mandatory Type RequiredProps = Required < Props > / type/extract subset PickObj = Pick < Props, 'a' | 'b' >Copy the code
    • Non-homomorphism: Used to create new properties

      / / create the different attributes of the same type type RecordObj = Record < 'x' | 'y', string >Copy the code

3. Generic function components

React function components are also functions. How do you use generics? Example: Example of a table component

/ table. TSX Interface Props<RecordType>{columns:Array<{dataIndex:string, Key :string title:string}> dataSource:Array<RecordType>} //* MyTable<RecordType extends object>(props :Props<RecordType>) { const { dataSource = [], columns = [] } = props; return <table> { dataSource.map((row) => <tr> { columns.map(({ dataIndex }) => <td>{row[dataIndex as keyof typeof row]}</td>) } </tr>) } </table>; } export default MyTable;Copy the code
./index.tsx import React, { useState } from 'react'; import MyTable from './Table'; Interface RecordType {name: string age: number address:string} const columns = [{title: 'name ', dataIndex: 'name', key: 'name',}, {title: 'age', dataIndex: 'age', key: 'age',}, {title: 'address', dataIndex: 'address', key: 'address', }, ]; export default function App() { const [data] = useState<RecordType[]>([]); // * < component name < generic input parameter >>... </MyTable <RecordType> dataSource={data} columns={columns}></MyTable> </div>; }Copy the code

DOM event processing

React’s DOM events are synthetic events (handled by the React framework for cross-browser compatibility) and do not correspond to native event objects. React’s official event type must be modified to obtain advanced functions such as code hints for event object property methods. Such as:

  import React, { MouseEvent } from 'react';

  export default function App() {
      const handleTap = (e:MouseEvent) => {
          e.stopPropagation();
      };

      return <button onClick={handleTap}>click</button>;
  }
Copy the code
Common Event Event object types: DragEvent<T = Element> DrageEvent <T = Element> Change KeyboardEvent<T TouchEvent<T = Element> TouchEvent object WheelEvent<T = Element> WheelEvent object AnimationEvent<T = Element> AnimationEvent object TransitionEvent<T = Element> TransitionEvent objectCopy the code

Harmony with third party library (component library or JS library)

Skill: skillfully borrow the third party itself provided by the TS type, to extend their own functions. Example Requirement: Extend ANTD’s button component to support a feature where detailed text prompts appear above the mouse cursor button.

import React, { MouseEvent } from 'react'; import { Tooltip, Button, ButtonProps } from 'antd'; // Use Antd ButtonProps to support {... The props} extension property implements mouse hints without losing the configurability of Button. interface Props extends ButtonProps{ tipText? :string } const TipButton:React.FC<Props> = (props) => { return <Tooltip title={props.tipText}> <Button {... props}>click</Button>; </Tooltip>; }; export default TipButton;Copy the code

Try to solve some common TS problems

Could not find module ‘XXX’ or its corresponding type declaration.

TSX import _ from 'lodash'; //shell NPM install lodash //index.tsx import _ from 'lodash'; ** Cause ** : the main package is missing the module declaration file **. Json => If the types field is "", the import declaration is automatically degraded to any type, causing that friendly code prompts and type verification cannot be obtained. ** Solution ** : First of all, you can try to install the corresponding declaration file directly. Mainstream third-party libraries such as jquery, Lodash, Redux, etc have official release of the specific type declaration package. (can also be [point this query] (https://www.typescriptlang.org/dt/search?search=lodash) have corresponding type declaration package) ` ` ` / / shell NPM install @ types/lodash - D If not, analyze the source code and the corresponding API directly and try to build one yourself. This is a good time to contribute to the open source community.Copy the code

Type ‘Window & Typeof globalThis’ does not have property’ XXX ‘.

** Common scenario ** : when you want to declare a global variable, ' 'window.globalConfig = {}; //ts error cause: There is no attribute declaration on global objects such as window or globalThis. // global.d.ts can be placed in any folder of the current project (global is easier to find in the root path), and the prefix of global can be named arbitrarily. declare interface Window { globalConfig:{ color? :string } } // index.tsx window.globalConfig = { color: 'red', }; $SRC ='https://ukg-jquery.min.js'> < script SRC ='https://ukg-jquery.min.js'> < script SRC ='https://ukg-jquery.min.js'> < script SRC ='https://ukg-jquery.min.js'> ${function toast(text:string):void} // jq.d. s declare namespace ${function toast(text:string):void} // index.tsx $. ` ` `Copy the code

Attribute “B” does not exist on type “XXX”.

Common scenarios: add properties to objects or read and call object properties or methods' 'obj. //[ts] type 'XXX' does not have attribute 'b'. Solution: Method 1: Complete the attribute type (ideally) interface Obj{a? Method 2: Type assertion + cross type (&). Type Obj = {a:string} & typeof Obj (Obj as Obj). A = 'XXX '; (obj as any). A = 'XXX '; // Any ' 'is not recommendedCopy the code

Index signature not found on type ‘XXX’ with parameter of type ‘string

** Common scenarios ** : Traversal of object scenes ** Reason ** : Traversal of the key attribute is restricted to string, too broad, should be restricted to the object's existing attribute string, access to unknown attributes will report an error (as mentioned in 3 above). ``` interface Item{ a:string } const item:Item = { a: 1, }; Object. Keys (item). The map ((key) = > item [key]) ` ` ` * * solution: Interface Row{a:string} const obj:Row = {a:string} const obj:Row = {a: 'xxx', } Object.keys(obj).map((k:keyof Row) => obj[k]); ` ` `Copy the code

5, TSX file type assertion writing method note

The type assertion <> syntax is TSX incompatible with the way elements or components are written. So you should always assert as.Copy the code

The usefulness of as const

** Example ** : String array to string union type. ** Difficulty ** : The ts type check happens at compile time, and we want to initialize the compile-time type for values that can be changed at any time (even if const is defined, it just keeps the memory address unchanged), normally unless we go back in time, but as const can. Asserted as a permanent read-only constant, TS can safely convert a value to a type. ``` const arr = ['name', 'age', 'location', 'email']; / / this is obtained by type after operation type type arrType = 'name' | 'age' | 'location' | 'email' ` ` ` ` ` ` const arr = [' name ', 'age, 'location', 'email'] as const; type arrType = typeof arr[number] const str:arrType = 'age'; console.log(str); ` ` `Copy the code

Q&A

How to choose between interface and type alias?

Similarities: Both can be used to describe the shape of an object or function, and can even extend fields. Interface extends, and type can be mixed with & (crossed type). Differences: 1. There is specialization. Interfaces have the advantage of being good at describing the structure of object types. You can implement seamless integration with classes, such as interface extends Class. Type is mainly used when defining simple types, union types, cross types, and tuples. 2. Declare the merge. Interface is supported, type is not supported. 3.

type Alias = { num: number } 
interface Interface { num: number; } 
declare function aliased(arg: Alias): Alias;
declare function interfaced(arg: Interface): Interface; 
Copy the code

Hovering over interfaced shows that it returns the Interface name, but hovering over Aliased shows the object literal type. Summary: If you cannot describe a type through an interface and need to use a federated or tuple type, you will usually use a type alias, so interfaces are preferred in principle.

2. How to distinguish extends from &?

Similarities: Both can be used to extend fields. Difference: Purpose: Extends applies to a typical object-oriented scenario with a clear parent-child inheritance & it’s good for mixins and has a flat hierarchy. Handles the behavior of shared properties: extends reports an error, and & converts to never.

What is the difference between unkown and any?

Similarities: both are Top Types in TS, to which any value can be assigned. Differences: Semantics: Unkown stands for temporary unknown type, any stands for accepting arbitrary assignments and operations. Security: If a value is of type any, we can perform any operation on it, including arbitrary operations or method calls. Unkown does not support assignment of any type, but does not support any operation. The operation scope must be narrowed. Relatively speaking, Unkown is safer. Summary: Unkown represents a type that is temporarily unknown, and can be made up for later once the type is determined. Any is essentially giving up treatment, making ts type checks completely useless.

4. Be rational about the Any type

Viewpoint: Existence may not be reasonable, but it must have its existence significance. So limiting the use of any might make more sense than banning it altogether. What we see now: There are still plenty of javascript projects out there that don’t have type checking, and any is a necessary type to degrade compatibility when your project works with projects that aren’t written by TS. Recommended scenarios for using any:

  1. When you introduce a non-TS library, you can temporarily set the core API object (such as $in jquery) to any to avoid TS type checking.
  2. When you need to write a long, time-consuming type just to convince TypeScript that my code works fine, PS (TS is just a type checking tool for your project. Remember, sometimes you don’t have to fight the tool to the end, just use it where you think it pays off).
  3. When you change a TS project that you are not familiar with, but the hours are tight and you think any must be used. Instead of directly setting it to any, try to set it to unkown for the time being and optimize it later.

Conclusion: The any type is a double-edged sword, and it exists to a great extent to reflect the flexibility of TS, but it is important to remember not to overuse it.

— end —-