头图

Writing TypeScript for 3 months, what have I learned?

趁你还年轻
中文

Original link: TypeScript

When I read the vue source code before, I found that there was TypeScript, and I was confused, so I needed to get in.

Recently, I also need to use TypeScript in my daily work in the new environment. I encountered some doubts during the learning process and recorded it.

Personally, I think it is more suitable for TypeScript entry students to read, because of the doubts I encountered, you may also encounter them.

  • What does the ?, <> in the ts type mean?
  • What is duck typing?
  • What is the variable definition before constructor?
  • What is declare?
  • What is the difference between unknown, void, null and undefined, never in ts?
  • What are generic constraints in ts?
  • Two ways to define an array type
  • Type assertion in ts
  • Generic functions and generic interfaces
  • How to understand as const?
  • What does declare global mean?
  • How to add a global variable to the TypeScript environment?
  • Can interface be inherited?
  • What does & mean in typescript?
  • What is the difference between interface and type?
  • What does enum mean as a type?
  • What does the declare module '*.scss' of xxx.d.ts in the project mean? declare module What else can cfdefb7 do?
  • How does typescript constrain the type of Promise?
  • How to use keyof in typescript?
  • How to use typeof in typescript?
  • What is non-null operator in typescript?

What does the ? in the ts type mean?

// https://github.com/vuejs/vue/blob/dev/src/core/observer/watcher.js
before: ?Function;
options?: ?Object,

This is a concept in the interface of ts. The interface of ts is "duck typing" or "structural subtyping", and type checking mainly focuses on the shape that values have. So let's get acquainted with the interface first, and then elicit the explanation of ?.

TypeScript defines functions in the normal way:
function print(obj: {label: string}) {
    console.log(obj.label);
}
let foo = {size: 10, label: "这是foo, 10斤"};
print(foo);
TypeScript interface way to define functions:
interface labelInterface {
    label: string;
}
function print(obj: labelInterface) {
    console.log(obj.label);
}
let foo = {size: 10, label: "这是foo, 10斤"};
print(foo);

Entering the topic, what does in TypeScript mean? Optional Properties.

Optional Properties
  • Not all properties in the interface are required, some exist under certain conditions, and some do not exist at all.
  • Optional Properties applies to the "option bags" design pattern, which means: we pass an object to a function that has only a few properties and no other properties.
  • The advantage of Optional Property is that it can clearly see which properties are there and prevent the passing of properties that do not belong to the interface.

    interface SquareConfig {
      color?: string;
      width?: number;
    }
    function createSquare(config: SquareConfig): {color: string; area: number} {
      let newSquare = {color: "white", area: 100};
      if (config.clor) {
          // Error: Property 'clor' does not exist on type 'SquareConfig'
          newSquare.color = config.color;
      }
      if (config.width) {
          newSquare.area = config.width * config.width;
      }
      return newSquare;
    }
    let mySquare = createSquare({color: "black"});
    Interfaces with optional properties are written similar to other interfaces, with each optional property denoted by a ? at the end of the property name in the declaration.

What is ? and Optional Properties? At the end of some non-required property names of the interface, add ? This is an optional property, which is actually a literal meaning, a conditional property.

Optional Property is just the property name, that is, the question mark after options in options?: ?Object, , what does the question mark before the property value type mean, that is, ?Object , what does it mean?
The question mark here represents whether the property value type can be null, but the value type can be null only when strictNullChecks is on.

  /**
   * @type {?number}
   * strictNullChecks: true -- number | null
   * strictNullChecks: off -- number
   * */
  var nullable;

In our case, options?:?Object means that the value type of options can be Object, null (only allowed when strictNullChecks is true).

What does <> in ts type mean?

deps: Array<Dep>a
newDeps: Array<Dep>

The array type in ts is similar to the definition in java:

let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

What is duck typing?

duck test. If "walks like a duck and quacks like a duck, then this is a duck".
In computer programming, used to 'determine whether an object can be used for its intended purpose'.
In usual typing, applicability depends on the type of the object. Unlike duck typing, the applicability of an object depends on the presence or of a specified method or , not on the type of the object itself.

Front-end engineers are basically duck typing, because JavaScript has no type. is what I said
Python3 example
class Duck:
    def fly(self):
        print("Duck flying")

class Airplane:
    def fly(self):
        print("Airplane flying")

class Whale:
    def swim(self):
        print("Whale swimming")

def lift_off(entity):
    entity.fly()

duck = Duck()
airplane = Airplane()
whale = Whale()

lift_off(duck) # prints `Duck flying`
lift_off(airplane) # prints `Airplane flying`
lift_off(whale) # Throws the error `'Whale' object has no attribute 'fly'`
Javascript example
class Duck {
    fly() {
        console.log("Duck flying")
    }
}
class Airplane {
    fly() {
        console.log("Airplane flying")
    }
}
class Whale {
    swim() {
        console.log("Whale swimming")
    }
}

function liftOff(entity) {
    entity.fly()
}

const duck = new Duck();
const airplane = new Airplane();
const whale = new Whale();

liftOff(duck); // Duck flying
liftOff(airplane); // Airplane flying
liftOff(whale); // Uncaught TypeError: entity.fly is not a function

What is the variable definition before constructor?

For example, the definition of vnode:

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  fnContext: Component | void; // real context vm for functional nodes
  fnOptions: ?ComponentOptions; // for SSR caching
  fnScopeId: ?string; // functional scope id support

  constructor ()
...
}

http://www.typescriptlang.org/docs/handbook/classes.html
There is one more class in typeScript than in es6: property. This is the same as in java or c#.

property
constructor
method

In fact, es6 provides a private variable that can only be accessed inside the class.

class Rectangle {
  #height = 0;
  #width;
  constructor(height, width) {    
    this.#height = height;
    this.#width = width;
  }
}

What does :VNode after the colon mean?

export function cloneVNode (vnode: VNode): VNode {
...
}

Functions in TypeScript return value types.

What is declare?

states that this is a definition.

  • declare is the keyword
  • declare can define global variables, global functions, global namespaces, classes, and more.
  • declare can be used as follows:

    declare var foo:number;
    declare function greet(greeting: string): void;
    declare namespace myLib {
      function makeGreeting(s: string): string;
      let numberOfGreeting: number;
    }
    declare function getWidget(n: number): Widget;
    declare function getWidget(s: string): Widget[];
    declare class Greeter {
      constructor(greeting: string);
    
      greeting: string;
      showGreeting(): void;
    }

What is the difference between any, unknown, void, null and undefined, never in ts?

null, undefined is the meaning in js.

any: any type, use it with caution, avoid making typescript anyscript
unknown: similar to any, but more secure than any
void: a function usually used to return a value
never: never occur type that never occurs, such as never having a result, throwing an exception or an infinite loop.

What are generic constraints in ts?

Based on string (boolean, Function) type

function loggingIdentity<T extends string>(arg: T): T {
    console.log(arg.length);
    return arg;
}

loggingIdentity("hello"); // 5
loggingIdentity(2); // Argument of type 'number' is not assignable to parameter of type 'string'.

Based on custom interface

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

loggingIdentity(3);  // Error, number doesn't have a .length property
loggingIdentity({length: 10, value: 3}); // 10

ts2.8 release notes

// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html
type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";

type T0 = TypeName<string>; // "string"
type T1 = TypeName<"a">; // "string"
type T2 = TypeName<true>; // "boolean"
type T3 = TypeName<() => void>; // "function"
type T4 = TypeName<string[]>; // "object"

Supports both type and interface generic constraints

interface reduxModel<T> {
    reducers: T extends string ? {[x in T]: () => void}: T,
}

type TType = "foo" | "bar" | 'baz'
interface TInterface {
    "foo": () => void,
    "bar": () => void,
    'baz': () => void
}

const ireducers = {
    "foo": () => void
}

const model : reduxModel<TType> = {
    reducers: ireducers
    // 正常运行
}

const model : reduxModel<TInterface> = {
    reducers: ireducers
    // Type '{ foo: () => undefined; }' is missing the following properties from type 'TInterface': "bar", 'baz'
}

Two ways to define an array type

Array<type>

Array is followed by a <>, and the element type is declared in <>.

type Foo= Array<string>;
interface Bar {
     baz: Array<{
          name: string,
          age: number,
     }>
}

Types of[]

Add a [] after the element type.

type Foo = string[]
interface Bar {
    baz : {
          name: string,
          age: number,
     }[]
}

Type assertion in ts

TypeScript allows us to override the inferred and parsed view types in any way we want. This mechanism is called Type Assertion. Type Assertion will tell the compiler that you know which type is more specific than it is. The compiler does not need to Inferred again.
Type assertions often occur during compiler compilation to inform the compiler how to analyze our code.

  • grammar
  • Migrate js code
  • The problem with type assertions
  • Specify event type
  • Use as any and as unknown with caution
  • type and type assertion

grammar

interface Foo {
    name: string,
}
type Any = any;

let a:Foo = {} as Foo;
let a:Foo = {} as Any;

any is a subtype of any type, so any type can be used as any. It is recommended to use it with caution to avoid becoming anyscript.

Migrate js code

var foo = {};
foo.bar = 123; // Error: property 'bar' does not exist on `{}`
foo.bas = 'hello'; // Error: property 'bas' does not exist on `{}`
interface Foo {
    bar: number;
    bas: string;
}
var foo = {} as Foo;
foo.bar = 123;
foo.bas = 'hello';  // 注释掉这一行也不会报错

The problem with type assertions

foo.bas = 'hello'; // commenting out this line will not give an error
If it is the following way, it will report an error, and it will prompt that the definition of bas is missing

interface Foo {
    bar: number;
    bas: string;
}
var foo : Foo= {
    bar: 123
};

Therefore, the type assertion is not rigorous enough, and it is recommended to use var foo : Foo .

Specify event type

function handler (event: Event) {
    let mouseEvent = event as MouseEvent;
}
function handler(event: Event) {
    let element = event as HTMLElement; // HTMLElement不是一个完全的event子类型,因此不能充分重叠,需要加一个unknown或者any
}

The second assertion compilation prompt is canceled:

function handler(event: Event) {
    let element = event as unknown as HTMLElement; // Okay!
}

Use as any and as unknown with caution

Usually, if the type asserts S and T, S is a subtype of T, or T is a subtype of S, which is relatively safe.
If it is used as any or as unknown, it is very unsafe. Use with caution! Use with caution!

// 谨慎使用
as any
as known

type and type assertion

What does type keys = 'foo' | 'bar' | 'baz' , obj[key as keys] mean?
Similar to variable:type, this is another type constraint.

If you don't understand the flower, you will understand after reading the demo below.

type keys = 'foo' | 'bar' | 'baz'
const obj = {
    foo: 'a',
    bar: 'b',
    baz: 'c'
}
const test = (key:any) => {
    return obj[key] ; // 提示错误 type 'any' can't be used to index type '{ foo: string; bar: string; baz: string; }'.
}

How to solve this error?
The first way: type constraints

const test = (key:keys) => {
    return obj[key] ;
}

The second method: type assertion (this method is often used in the callback of third-party libraries, and the return value type is not constrained)

const test = (key:any) => {
    return obj[key as keys] ;
}

It should be noted that the type of keys in obj[key as keys] can be less than the type of obj, and the properties of obj cannot be less than the type of keys.

Generic functions and generic interfaces

generic function

Consider a scenario where we want the input of a function to be of the same type as the output.
You might do this, but it doesn't guarantee that the input and output types are the same.

function log(value: any):any {
    return value;
}

It can be accurately implemented by generic functions: add a <T> after the function name. The T here can be understood as the name of the generic type. The specified input type is T and the return value is T.

function log<T>(value: T):T {
    return value;
}

This is a generic function instance, how to define a generic function type?

type Log = <T>(value: T) => T

Constrain the function with a generic function type:

let log : Log = function <T>(value: T):T {
    return value;
}

generic interface

All attributes of the interface are flexible, and the input and output are consistent.

interface Log {
     <T>(value: T): T
}
let myLog: Log = log
myLog("s")// "s"
myLog(1)// 1

All properties of an interface must be of the same type.

interface Log<T> {
     (value: T): T
}
let myLog: Log<string> = log
myLog("s")// "s"
myLog(1)// Error

<> in ts

In ts, if <> is encountered, the middle of the angle brackets is mostly a type.

  • Array<string>
  • <string>[]
  • function <T>(value: T): T { ... }
  • type MyType = <T>(value : T) => T
  • interface MyInterface<T> { (value: T): T }

How to understand as const?

  • In order to solve the let assignment problem, change a mutable variable to readonly.
  • Avoid type inference as a union type.

In order to solve the let assignment problem, change a mutable variable to readonly.

let x = "hello";
x = "world"; // 报错
The first way is const
const x = "hello"
Second way "hello" type
let x: "hello" = "hello";
x = "world"; // 
The third way discriminated unions
type Shape =
    | { kind: "circle", radius: number }
    | { kind: "square", sideLength: number }
function getShapes(): readonly Shape[] {
    // to avoid widening in the first place.
    let result: readonly Shape[] = [
        { kind: "circle", radius: 100, },
        { kind: "square", sideLength: 50, },
    ];
    return result;
}
Fourth way as const

.tsx type file

// Type '10'
let x = 10 as const;

// Type 'readonly [10, 20]'
let y = [10, 20] as const;

// Type '{ readonly text: "hello" }'
let z = { text: "hello" } as const;

Non-.tsx type files

// Type '10'
let x = <const>10;

// Type 'readonly [10, 20]'
let y = <const>[10, 20];

// Type '{ readonly text: "hello" }'
let z = <const>{ text: "hello" };

Optimize discriminated unions

function getShapes() {
    let result = [
        { kind: "circle", radius: 100, },
        { kind: "square", sideLength: 50, },
    ] as const;
    
    return result;
}

for (const shape of getShapes()) {
    // Narrows perfectly!
    if (shape.kind === "circle") {
        console.log("Circle radius", shape.radius);
    }
    else {
        console.log("Square side length", shape.sideLength);
    }
}

Avoid type inference as a union type.

Avoid inferring type as (boolean | typeof load)[], instead infer as [boolean, typeof load].

export function useLoading() {
  const [isLoading, setState] = React.useState(false);
  const load = (aPromise: Promise<any>) => {
    setState(true);
    return aPromise.finally(() => setState(false));
  };
  return [isLoading, load] as const; // infers [boolean, typeof load] instead of (boolean | typeof load)[]
}

What does declare global mean?

It is for making declarations in the global namespace, such as adding an undefined property to an object.

Add the definition of csrf to Window

declare global {
  interface Window {
    csrf: string;
  }
}

Add the definition of fancyFormat to String

declare global {
  /*~ Here, declare things that go in the global namespace, or augment
   *~ existing declarations in the global namespace
   */
  interface String {
    fancyFormat(opts: StringFormatOptions): string;
  }
}

Note that the global scope can only be used for exported modules or external module declarations

Augmentations for the global scope can only be directly nested in external modules or ambient module declarations.

How to add a global variable to the TypeScript environment?

For example, we want to achieve the following effect, but will report the error Property '__INITIAL_DATA__' does not exist

<script>
  window.__INITIAL_DATA__ = {
    "userID": "536891193569405430"
  };
</script>
const initialData = window.__INITIAL_DATA__; // 报错 

Use type assertions

const initialData = (window as any).__INITIAL_DATA__;
type InitialData = {
  userID: string;
};

const initialData = (window as any).__INITIAL_DATA__ as InitialData;
const userID = initialData.userID; // Type string

declare global variables

declare var __INITIAL_DATA__: InitialData;
const initialData = __INITIAL_DATA__;
const initialData = window.__INITIAL_DATA__;

In the es module, there are import and export, which need to be done:

export function someExportedFunction() {
  // ...
}

declare global {
   var __INITIAL_DATA__: InitialData;
}
const initialData = window.__INITIAL_DATA__;

If used in many files, you can use a globals.d.ts file.

Merge using interface

interface Window {
  __INITIAL_DATA__: InitialData;
}
const initialData = window.__INITIAL_DATA__;

In the js module it needs to be like the following:

export function someExportedFunction() {
  // ...
}

declare global {
  interface Window {
    __INITIAL_DATA__: InitialData;
  }
}

const initialData = window.__INITIAL_DATA__;

Can interface be inherited?

OK.

interface Base {
    foo: string;
}

interface Props extends Base {
    bar: string
    baz?: string
}

const test = (props: Props) => {
    console.log(props);
}

test({ foo: 'hello' }) // Property 'bar' is missing in type '{ foo: string; }' but required in type 'Props'
test({ foo: 'hello', bar: 'world' })

When Props inherits Base, it actually ends up as follows:

interface Props extends Base {
    foo: string;
    bar: string
    baz?: string
}

Can Props override Base? Yes, but only required can override optional, optional cannot override required.

// ✅
interface Base {
    foo?: string;
}

interface Props extends Base {
    foo: string;
    bar: string
    baz?: string
}
// ❌
interface Base {
    foo: string;
}

interface Props extends Base {
    foo?: string;
    bar: string
    baz?: string
}

What does & mean in typescript?

There is such a definition in the dts file of react.

type PropsWithChildren<P> = P & { children?: ReactNode };

The & in typescript refers to the intersection type.

interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtworksData {
  artworks: { title: string }[];
}

interface ArtistsData {
  artists: { name: string }[];
}

// These interfaces are composed to have
// consistent error handling, and their own data.

type ArtworksResponse = ArtworksData & ErrorHandling;
type ArtistsResponse = ArtistsData & ErrorHandling;

const handleArtistsResponse = (response: ArtistsResponse) => {
  if (response.error) {
    console.error(response.error.message);
    return;
  }

  console.log(response.artists);
};

After knowing that & is the intersection type in ts, we understand the meaning of PropsWithChildren, and we also understand why react functional components have more children properties than ordinary functional components.

It means that the type of PropsWithChildren is the intersection of P and the object {children?: ReactNode}, that is, after connecting the two objects through &, the final generated object has the optional property of children.

What is the difference between interface and type?

An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot.
An interface can have multiple merged declarations, but a type alias for an object type literal cannot.
  1. Interface can be inherited (such as with extends), type cannot
  2. An interface can implement multiple merge declarations, but a type cannot

What does enum mean as a type?

In reading the source code of pixi.js, I found that enum is used as a type.

An enum can also be used as a type to constrain.

// pixi/constants
export enum BLEND_MODES {
    NORMAL = 0,
    ADD = 1,
    MULTIPLY = 2,
    SCREEN = 3,
    OVERLAY = 4,
}

export enum ANOTHER_ENUM {
    FOO = 5,
    BAR = 6
}

import { BLEND_MODES } from '@pixi/constants';

export class Sprite extends Container
{
    public blendMode: BLEND_MODES;
    constructor(){
        this.blendMode = BLEND_MODES.NORMAL; // 最佳
        //  this.blendMode = 0 这样是可以的,次之
        //  this.blendMode = ANOTHER_ENUM.FOO 这样ts会报错
    }
}

What does the declare module '*.scss' of xxx.d.ts in the project mean? What else can declare module do?

What does the declare module '*.scss' of xxx.d.ts in the project mean?

// externals.d.ts
declare module '*.scss'
By default, import style from 'style.scss' will report an error in the IDE validator of ts, then use d.ts to assume that all files ending with scss are defined as modules. --President

Suppose the declare module '*.scss' is commented out, the ide will report an error, but it can be passed through lint.

What else can declare module do?

When we introduce a custom package that does not exist in Microsoft's official @types/*, the ide will report an error.

For example the following:
image

How to fix this red flag error? declare module

// typing.d.ts
declare module 'visual-array'

That way the redemption disappears.

How does typescript constrain the type of Promise?

Promise generic function


interface Promise<T> {
    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
    catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}
interface foo {
   bar: ()=>Promise<string>,
   baz: ()=>Promise<number[]>,
   car: (id)=>Promise<boolean[]>
}

How to use keyof in typescript?

the simplest

type Point = { x: number; y: number };
type P = keyof Point; // 'x' | 'y'
let foo: P = 'x';
let bar: P = 'y';
let baz: P = 'z'; // ❌

Commonly used

interface Person {
  name: string;
  age: number;
  location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string

type P1 = Person["name"]; // string
type P2 = Person["name" | "age"]; // string | number
type P3 = string["charAt"]; // (pos: number) => string
type P4 = string[]["push"]; // (...items: string[]) => number
type P5 = string[][0]; // string

keyof makes functions type-safe

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]; // Inferred type is T[K]
}
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
  obj[key] = value;
}
let x = { foo: 10, bar: "hello!" };
let foo = getProperty(x, "foo"); // number
let bar = getProperty(x, "bar"); // string
let oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar"
setProperty(x, "foo", "string"); // Error!, string expected number

Implementation principle of Partial,Required,Readonly,Pick generic tool types

type Partial<T> = {
    [P in keyof T]? : T[P];
}
type Required<T> = {
    [P in keyof T]?- : T[P]; 
}
type Readonly<T> = {
    readonly [P in keyof T] : T[P];
}
type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
}

How to use typeof in typescript?

typeof in js is mainly used in expression context, while typeof in ts is mainly used in type context.

let s = "hello";
let n: typeof s;
//  ^ = let n: string
type Predicate = (x: unknown) => boolean;
type K = ReturnType<Predicate>;
//   ^ = type K = boolean
function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;
//   ^ = type P = {
//       x: number;
//       y: number;
//   }

What is non-null assert operator in typescript?

Non-null assertion operator: When null, an assertion occurs and an exception is thrown.
Optional chain: when null/undefined, return undefined.

non-null assertion operator and optional chaining operator tests

// Non-Null Assertion Operator

const obj = null;

interface Entity {
  name?: string;
}

// 非空断言操作符
function nonNull(e?: Entity) {
  const s = e!.name; // 发生断言,抛出TypeError
}

try {
  nonNull(obj);
} catch (e) {
  console.error("nonNull catch", e); // TypeError: Cannot read property 'name' of null
}

// 可选链
function optionalChaining(e?: Entity) {
  const s = e?.name;
  console.log(s); // undefined
}

optionalChaining(obj);

Used for function return value null detection

function returnNullFunc() {
  return null;
}

try {
  returnNullFunc()!.age;
} catch (e) {
  console.error("returnNullFunc", e); // TypeError: Cannot read property 'age' of null
}

function returnNonNullFunc() {
  return {
    age: "18"
  };
}
returnNonNullFunc()!.age;

Online demo: https://codesandbox.io/s/non-null-operator-6bcf4

Looking forward to communicating with you and making progress together:

  • WeChat public account: Dada big front end / excellent_developers
  • Front-end Q&A mutual aid planet: t.zsxq.com/yBA2Biq
Strive to be an excellent front-end engineer!
阅读 3.8k

趁你还年轻,做个优秀的前端工程师
努力成为优秀的前端工程师! 文章在微信公众号:大大大前端 同步更新,期待你的关注~
4k 声望
4.1k 粉丝
0 条评论
4k 声望
4.1k 粉丝
文章目录
宣传栏