Write High Quality maintainable code: Awesome TypeScript this is the 84th original post on Awesome TypeScript

preface

High quality maintainable code should be readable, structured, low coupling, easy to extend, and so on. Native JavaScript, due to its weak typing and lack of modularity, is not conducive to the development and maintenance of large applications, so TypeScript was born.

TypeScript is a superset of JavaScript. It is not designed to replace JavaScript, but rather builds on it with a number of enhancements, including static typing, interfaces, classes, generics, method overloading, and more. So getting started with TypeScript is pretty easy if you have some JavaScript background. Also, you can happily use JavaScript syntax in TypeScript.

Next, this article will share some of TypeScript’s key features and techniques for real-world use to help you write high-quality, maintainable code more efficiently.

Typescript VS Javascript

JavaScript

  • JavaScript is a dynamically typed language and does no type checking on variables during code compilation, bringing potential type errors to code execution. And in the case of variable assignment of different types, the type conversion will be carried out automatically, which brings uncertainty and bugs easily.
  • JavaScript does not have namespaces natively, so you need to create namespaces manually for modularity. Also, JavaScript allows for duplicate definitions of functions with the same name, with the latter definition overwriting the previous one. This also makes it difficult to develop and maintain large applications.

TypeScript

  • TypeScript is a statically typed language that provides compile-time static type checking through type annotations.
    • Type checking of variables is performed during code compilation to expose potential type errors in advance. And assignments between variables of different types are not allowed during code execution.
    • Clear type annotations not only make code more readable, but also enhance the IDE’s capabilities, including code completion, interface hints, jump to definitions, and so on.
  • TypeScript adds module types and built-in namespaces to facilitate modular development of large applications.
  • TypeScript is designed to be a fully object-oriented programming language with modules, interfaces, classes, type annotations, and so on to make our code structure clearer.

Through the above comparison, we can see that TypeScript makes up for some of the design flaws of JavaScript, bringing us great convenience, and improving the robustness and extensibility of code.

Important features

The data type

  • Basic data types include: Boolean, Number, String, Array, Enum, Any, Unknown, Tuple, Void, Null, Undefined, and Never. Here are a few typescript-specific types to explore:

    • Enum Enum: Avoid hard coding. If a constant can be listed one by one, it is recommended to define it with Enum types to make the code more maintainable.
    // Includes numeric enumerations, string enumerations, and heterogeneous enumerations (a mixture of numbers and strings).
    // Enumeration without setting a default value, the first value defaults to 0, and the rest grow successively
    enum STATUS {
      PENDING,
      PROCESS,
      COMPLETED,
    }
    let status: STATUS = STATUS.PENDING;  / / 0
    Copy the code
    • Any: this parameter is not recommended. The Any type is a top-level type, and all types can be treated as Any types. Using Any disables TypeScript’s type validation mechanism.
    • Unknown type: The Unknown type is also a top-level type and can accept Any type, but it differs from Any in that it determines the data type after the first assignment and does not allow the variable’s data type to be changed twice. Therefore, in scenarios where receiving all types is required, Unknown is preferred over Any.
      • Tuple: Supports storing elements of different data types in arrays, giving us more flexibility in organizing data.
    let tupleType: [string.boolean];
    tupleType = ["momo".true];
    Copy the code
    • Void: When a function does not return a value, the return type of the function is usually set to Void.

Type annotations

  • TypeScript provides compile-time static type checking through type annotations to catch potential bugs at compile time and make hints smarter during coding. The way to use it is very simple:The colon is followed by the type of variable.
const str: string = 'abc';
Copy the code

interface

  • In object-oriented programming languages, the interface is the key to achieve program decoupling. It only defines which properties and methods are included, and does not involve any implementation details. Interfaces are based on classes, and further abstraction of entities or behaviors makes programs more extensible.
  • Application scenario: For example, when we implement order-related functions, we need to abstract the order, define an interface of the order, including the basic information of the order and related operations on the order, and then do further implementation based on this interface. Later, if the operation function of the order changes, you only need to redefine a class to implement this interface.
interface Animal {
name: string;
getName(): string;
}
class Monkey implements Padder {
constructor(private name: string) {
  getName() {
    return 'Monkey: '+ name; }}}Copy the code

class

  • In addition to basic properties and methods, getters and setters, inheritance, and other features, TypeScript classes add private fields. Private fields cannot be accessed outside of the contained class or even detected. Javascript classes do not have private fields, and if you want to simulate private fields, you must use closures to do so. Here are some examples to illustrate the use of the following classes:
  • Properties and methods
class Person {
// Static attributes
static name: string = "momo";
// Member attributes
gender: string;
// constructor
constructor(str: string) {
  this.gender = str;
}
// Static method
static getName() {
  return this.name;
}
// Member methods
getGender() {
  return 'Gender: ' + this.gender; }}let person = new Person("female");
Copy the code
  • Getter and setter
    • Getter and setter methods are used to encapsulate and verify the validity of data to prevent abnormal data.
class Person {
private _name: string;
get name() :string {
  return this._name;
}
set name(newName: string) {
  this._name = newName; }}let person = new Person('momo');
console.log(person.name); // momo
person.name = 'new_momo';
console.log(person.name); // new_momo
Copy the code
  • inheritance
class Animal {
name: string;
constructor(nameStr=:string) {
  this.name = nameStr;
}  
move(distanceInMeters: number = 0) {
  console.log(`The ${this.name} moved ${distanceInMeters}m.`); }}class Snake extends Animal {
constructor(name: string) {
  super(name);
} 
move(distanceInMeters = 5) {
  super.move(distanceInMeters); }}let snake = new Snake('snake');
snake.move(); // Output: 'Snake Moved 5m'
Copy the code
  • Private field
    • Private field to#Character start. Private fields cannot be accessed outside of the contained class or even detected.
class Person {
#name: string;
constructor(name: string) {
  this.#name = name;
}
greet() {
 	console.log(`Hello, The ${this.#name}! `); }}let person = new Person('momo');
person.#name;   // Access will report an error
Copy the code

The generic

  • Application scenario: Generics are used when we need to consider the reusability of code. Enable components to support not only current data types, but also future data types. Generics allow the same function to take different types of arguments, and components created using generics are more reusable and extendable than components created using Any because they preserve parameter types. Generics can be applied to interfaces, classes, and variables. Here are some examples to illustrate the use of generics:

  • A generic interface

    interface identityFn<T> {
      (arg: T): T;
    }
    Copy the code
  • A generic class

    class GenericNumber<T> {
      zeroValue: T;
      add: (x: T, y: T) = > T;
    }
    let myGenericNumber = new GenericNumber<number> (); myGenericNumber.zeroValue =0;
    myGenericNumber.add = function (x, y) {
      return x + y;
    };
    Copy the code
  • Generic variables

    Type variables defined using uppercase a-z are generic. Common generic variables are as follows:

    • T (Type) : represents a TypeScript Type
    • K (Key) : indicates the Key type of the object
    • V (Value) : indicates the Value type of the object
    • E (Element) : indicates the Element type

Cross type

  • To cross type is to combine multiple types into one type. through&Operator definition. In the following example, combining the Person and Company types produces a new type Staff that has all the members of both types.
interface Person {
name: string;
gender: string;
}
interface Company {
companyName: string;
}
type Staff = Person & Company;
const staff: Staff = {
name: 'momo'.gender: 'female'.companyName: 'ZCY'
};
Copy the code

The joint type

  • A union type is a combination of types that have or relationships, as long as one of the types is satisfied. through|Operator definition. In the following example, the input parameter of the function is string or number.
function fn(param: string | number) :void {
  console.log("This is the union type");
}
Copy the code

Type of protection

  • Type protection is the ability to safely call properties and methods of a data type that we have identified as a data type. Common type protection includes in type protection, Typeof type protection, Instanceof type protection, and custom type protection. See the following examples for details:

    • inType of protection
    interface Person {
      name: string;
      gender: string;
    }
    interface Employee {
      name: string;
      company: string;
    }
    type UnknownStaff = Person | Employee;
    function getInfo(staff: UnknownStaff) {
      if ("gender" in staff) {
        console.log("Person info");
      }
      if ("company" in staff) {
        console.log("Employee info"); }}Copy the code
    • typeofType of protection
    function processData(param: string | number) :unknown {
    	if (typeof param === 'string') {
      	return param.toUpperCase()
      }
      return param;
    }
    Copy the code
    • instanceofType protection: andtypeofA type is used to determine whether it is an object of a class or an inherited object.
    function processData(param: Date | RegExp) :unknown {
    	if (param instanceof Date) {
      	return param.getTime();
      }
      return param;
    }
    Copy the code
    • The customType protection: by type predicatesparameterName is TypeTo implement custom type protection. The following example implements type protection for the request parameters of the interface.
    interface ReqParams {
    	url: string; onSuccess? :() = > void; onError? :() = > void;
    }
    // Return the URL only if the request object contains the required parameters
    function validReqParams(request: unknown) :request is ReqParams {
    	return request && request.url
    }
    Copy the code

Development tips

  • Need to continuously determine whether there is a deep property in an object, can be used? .
if(result && result.data && result.data.list) // JS
if(result? .data? .list)// TS
Copy the code
  • Union check whether the value is null can be used??
lettemp = (val ! = =null&& val ! = =void 0 ? val : '1'); // JS
let temp = val ?? '1'; // TS
Copy the code
  • Don’t rely entirely on type checking, and write defensive code when necessary.

    • Since the type error does not affect code generation and execution, it is still possible to call fn(‘ STR ‘) in principle, so default is needed for defensive code.
function fn(value:boolean){
	switch(value){
  	case true: 
    	console.log('true');
      break;
    case false: 
      console.log('false');
      break;
    default: 
      console.log('dead code'); }}Copy the code
  • For functions, strictly control the type of the return value.
//
function getLocalStorage<T> (key: string) :T | null {
  const str = window.localStorage.getItem(key);
  return str ? JSON.parse(str) : null;
}
const data = getLocalStorage<DataType>("USER_KEY");
Copy the code
  • Implement the factory pattern with new()

    • TypeScript syntax implements the factory pattern simply by defining a function, declaring a constructor type parameter, and then returning the c class object in the function body. In the following example, the factory function constructs an object of type T.
function create<T> (c: { new(): T }) :T {
	return new c();
}
class Test {
  constructor() {
  }
}
create(Test);
Copy the code
  • Prefer the Unknown type to Any

  • Use the readonly tag to ensure that the parameter is not modified within the function

function fn(arr:readonly number[] ){
  let sum=0, num = 0;
  while((num = arr.pop()) ! = =undefined){
    sum += num;
  }
  return sum;
}
Copy the code
  • Use Enum to maintain regular tables for safer type checking
// Maintain constants with const enum
const enum PROJ_STATUS {
  PENDING = 'PENDING',
  PROCESS = 'PROCESS',
  COMPLETED = 'COMPLETED'
}
function handleProject (status: PROJ_STATUS) :void {
}
handleProject(PROJ_STATUS.COMPLETED)
Copy the code
  • It is recommended that you enable the following compilation check options to detect potential bugs in the compilation environment
{
  "compilerOptions": {
    /* Strict type checking options */
    "strict": true.// Enable all strict type checking options
    "noImplicitAny": true.// There is an error with an implied any type on expressions and declarations
    "strictNullChecks": true.// Enable strict null checking
    "noImplicitThis": true.// An error is generated when this is of type any
    "alwaysStrict": true.// Check each module in strict mode and add 'use strict' to each file
    
    /* Additional checks */
    "noUnusedLocals": true.// An error is thrown when there are unused variables
    "noUnusedParameters": true.// An error is thrown if there are unused arguments
    "noImplicitReturns": true.// An error is thrown when not all code in a function returns a value
    "noFallthroughCasesInSwitch": true.// Switch statement fallthrough error reported. (that is, switch case statements are not allowed to run through)}}Copy the code

Recommended VSCode plug-ins

The TypeScript Extension Pack is a collection of TypeScript plugins that we use every day:

  • TSLint: Automatically detects and fixes TypeScript code that does not conform to the specification.
  • TypeScript Hero: Sorts and organizes import module orders, removing unused ones. Shortcuts on MacOSCtrl+Opt+o, Shortcuts on Win/LinuxCtrl+Alt+o.
  • Json2ts: Converts JSON from a clipboard to a TypeScript interface. Shortcuts on MacOSCtrl+Opt+V, Shortcuts on Win/LinuxCtrl+Alt+V.
  • Move TS: When you Move a TypeScript file or a folder containing a TypeScript file, the import paths of dependent modules are automatically updated.
  • Path Intellisense: Automatic reminder of Path and file name completion.
  • TypeScript Importer: import automatically searches for all export modules in the workspace and automatically completes them when importing modules.
  • Prettier-code formatter: formatting Code.

Recommended reading

How does React Fiber keep the update process under control

V8 engine garbage collection and memory allocation

, recruiting

ZooTeam, a young passionate and creative front-end team, belongs to the PRODUCT R&D department of ZooTeam, based in picturesque Hangzhou. The team now has more than 40 front-end partners, with an average age of 27, and nearly 30% of them are full-stack engineers, no problem in the youth storm group. The members consist of “old” soldiers from Alibaba and netease, as well as fresh graduates from Zhejiang University, University of Science and Technology of China, Hangzhou Electric And other universities. In addition to daily business docking, the team also carried out technical exploration and practice in material system, engineering platform, building platform, performance experience, cloud application, data analysis and visualization, promoted and implemented a series of internal technical products, and continued to explore the new boundary of front-end technology system.

If you want to change what’s been bothering you, you want to start bothering you. If you want to change, you’ve been told you need more ideas, but you don’t have a solution. If you want change, you have the power to make it happen, but you don’t need it. If you want to change what you want to accomplish, you need a team to support you, but you don’t have the position to lead people. If you want to change the pace, it will be “5 years and 3 years of experience”; If you want to change the original savvy is good, but there is always a layer of fuzzy window… If you believe in the power of believing, believing that ordinary people can achieve extraordinary things, believing that you can meet a better version of yourself. If you want to be a part of the process of growing a front end team with deep business understanding, sound technology systems, technology value creation, and impact spillover as your business takes off, I think we should talk. Any time, waiting for you to write something and send it to [email protected]