The following is the TS specification that I followed during the development process. It is only my personal opinion and is being updated
Naming conventions
- Use camelCase to name attributes or local variables.
- Use camelCase to name functions.
- Name the type using PascalCase.
- Don’t use
I
As the prefix of the interface name. - Name enumeration values using PascalCase.
- Do not add for private attribute names
_
Prefix, usingprivate
The rhetoric. - Use full word spellings whenever possible.
variable
Variables are named in camelCase.
// Bad
const Foo = 1
// Good
const foo = 1
Copy the code
// Bad
const UserList:string[] = []
// Good
const userList:string[] = []
Copy the code
function
Functions are named in camelCase.
// Bad
function Foo() {}
// Good
function foo() {}
Copy the code
class
Classes themselves are named PascalCase, and class members are named camelCase.
// Bad
class foo {}
// Good
class Foo {}
Copy the code
// Bad
class Foo {
Bar: number;
Baz(): number{}}// Good
class Foo {
bar: number;
baz(): number{}}Copy the code
interface
The interface name itself is PascalCase. Do not prefix the interface name with I. Interface members are named in camelCase mode.
// Bad
interface IFoo {
Bar: number;
Baz(): number;
}
// Good
interface Foo {
bar: number;
baz(): number;
}
Copy the code
Why not name the interface with an I prefix
In TS, classes can implement interfaces, interfaces can inherit interfaces, and interfaces can inherit classes. Both classes and interfaces are abstractions and encapsulation in a sense, and inheritance does not care whether it is an interface or a class. With the I prefix, when the type of a variable changes, such as from an interface to a class, the variable name must be changed simultaneously.
The enumeration
The enumeration object itself and the enumeration members are named PascalCase.
// Bad
enum status {}
// Good
enum Status {}
Copy the code
// Bad
enum Status {
success = 'success'
}
// Good
enum Status {
Success = 'success'
}
Copy the code
The file name
Common TS files and React components are distinguished by file names. The React component must use TSX as the suffix.
- use
camelCase
Name files that start with verbs, such as functionsgetName.ts
,fetchData.ts
- File names that start with a noun, such as class, can be named in one of two ways:
-
- Use PascalCase names, for example
UserList.ts
. - Use dashes to separate descriptive word names, such as
user-list.ts
.
- Use PascalCase names, for example
Both naming methods are allowed, but only one of them can be used in a project. \
Type declaration specification
The type declaration should rely on the automatic type inference function of TS as far as possible. If the correct type can be inferred, do not manually declare it as far as possible.
variable
Base type variables do not need to be manually typed.
let foo = 'foo'
let bar = 2
let baz = false
Copy the code
References to type variables should be guaranteed to be of the correct type. Incorrect references need to be declared manually.
// Automatic inference
let foo = [1.2] // number[]
Copy the code
// Display the declaration
// Bad
let bar = [] // any[]
// Good
let bar:number[] = []
Copy the code
function
We follow Typescript’s official best practices and see the repository for Chinese translations.
Callback function type
The return value type of the callback function
Do not set the return type any for a callback whose return value is ignored:
// Bad
function fn(x: () => any) {
x();
}
Copy the code
The return type void should be set for callback functions whose return value is ignored:
// Good
function fn(x: () => void) {
x();
}
Copy the code
Void is relatively safe because it prevents the accidental use of an unchecked return value of x:
function fn(x: () => void) {
var k = x(); // oops! meant to do something else
k.doSomething(); // error, but would be OK if the return type had been 'any'
}
Copy the code
Optional arguments in the callback function
Do not use optional arguments in callback functions unless this is what you want:
// Bad
interface Fetcher {
getObject(done: (data: any, elapsedTime? :number) = > void) :void;
}
Copy the code
Here’s what it means: The done callback can be called with one or two arguments.
The general idea of the code is that this callback does not care about the elapsedTime argument, but it does not need to be defined as an optional argument to do so, because it is always possible to provide a callback that takes fewer arguments.
The callback function should be defined with no optional arguments:
// Good
interface Fetcher {
getObject(done: (data: any, elapsedTime: number) = > void) :void;
}
Copy the code
Overloading and callback functions
Do not write different overloads depending on the number of arguments to the callback function.
// Bad
declare function beforeAll(action: () => void, timeout? :number) :void;
declare function beforeAll(
action: (done: DoneFn) => void, timeout? :number
) :void;
Copy the code
An overload should be written only for the maximum number of arguments:
// Good
declare function beforeAll(
action: (done: DoneFn) => void, timeout? :number
) :void;
Copy the code
The callback function always allows an argument to be ignored, so there is no need to write an overload for the absence of an optional argument.
Providing an overload for cases where optional arguments are missing may result in a typical-error callback function being passed in because it matches the first overload.
Function overloading
The order
Do not put vague overloads in front of concrete overloads:
// Bad
declare function fn(x: any) :any;
declare function fn(x: HTMLElement) :number;
declare function fn(x: HTMLDivElement) :string;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: any, wat?
Copy the code
Overloads should be sorted, with specific ones placed before vague ones:
// Good
declare function fn(x: HTMLDivElement) :string;
declare function fn(x: HTMLElement) :number;
declare function fn(x: any) :any;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: string, :)
Copy the code
When parsing a function call, TypeScript selects the first overload that matches it. When the front overload is more “obscure” than the back overload, the back overload is hidden and not selected.
Use optional parameters
Don’t write different overloads because only the trailing arguments are different:
// Bad
interface Example {
diff(one: string) :number;
diff(one: string.two: string) :number;
diff(one: string.two: string.three: boolean) :number;
}
Copy the code
Optional arguments should be used whenever possible:
// Good
interface Example {
diff(one: string, two? :string, three? :boolean) :number;
}
Copy the code
Note that this is only fine if the return value type is the same.
There are two important reasons:
-
TypeScript parses signature compatibility to see if a target signature can be called with the original arguments and allows additional arguments.
The following code exposes a bug only if the signature is defined correctly with optional arguments:
function fn(x: (a: string, b: number, c: number) = >void) {} var x: Example; // Function overloading will use the first overload without error, but this is not true for our case // There is an error when using optional arguments. The types of callback arguments do not match fn(x.diff); Copy the code
-
The second reason is when TypeScript’s “strict null checking” feature is used.
Because an unspecified parameter is represented as undefined in JavaScript, there is usually no problem passing undefined explicitly for an optional parameter. This code works in strict NULL mode:
var x: Example; // Function overloading is an error because (true? Undefined: "hour") is of type string | undefined, overloading and function can be a string, or not fill (and undefined there's a difference). // Optional parameters will not report errors x.diff("something".true ? undefined : "hour"); Copy the code
Using union types
Do not define overloading just because the parameter type is different at a particular location:
// Bad
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}
Copy the code
Union types should be used whenever possible:
// Good
interface Moment {
utcOffset(): number;
utcOffset(b: number | string): Moment;
}
Copy the code
Note that we did not make b optional because the return value type of the signature is different.
This is important for users who pass in values for this function:
function fn(x: string) :void;
function fn(x: number) :void;
function fn(x: number | string) {
// An error is reported when the function is overloaded
// Use the union type for normal parsing
return moment().utcOffset(x);
}
Copy the code
class
Class members should be declared with an explicit scope rhetoric except for public members.
// Bad
class Foo {
foo = 'foo'
bar = 'bar'
getFoo() {
return this.foo
}
}
const foo = new Foo()
foo.foo
foo.bar
// Good
class Foo {
private foo = 'foo'
bar = 'bar'
getFoo() {
return this.foo
}
}
const foo = new Foo()
foo.getFoo()
foo.bar
Copy the code
The React components
The React component recommends using function components as much as possible, embracing hooks, and using class components for a few complex scenarios.
- Function component declaration:
- If the component has props, write the corresponding declaration type above the component and export it
Component name + Props
. - Components use
React.FC
Definition,React.FC
The default children type is provided, and the type of props is passed as a generic parameter.
import React from 'react' // If the component has props, write the declared type above the component and export it. The type name is component name + props export interface ExampleProps { a:string } // The component is defined using React.FC, which provides the default children type and passes the type of props as a generic parameter const Example: React.FC<ExampleProps> = () = > {} export default Example Copy the code
- If the component has props, write the corresponding declaration type above the component and export it
- Class component declaration:
- If the component has props, write the corresponding declaration type above the component and export it
Component name + Props
. - Since state does not need to be exported, it can be named as
State
But you still need to declare it in advance. - Try not to run code inside constructor. Omit writing the constructor function and add the correct props type if the constructor function is used.
import { Component } from 'react' export interface ExampleProps { a: string } interface State { value: number } const initialState: State = { value: 1 } class Example extends Component<ExampleProps.State> { readonly state = initialState // Don't write without constructor; otherwise, add the correct props type constructor(props: ExampleProps) { super(props) this.setState((state) = > ({ ...state, value: state.value + 1}}}))export default Example Copy the code
- If the component has props, write the corresponding declaration type above the component and export it
The generic
Do not define a generic type that never uses its type parameters.
Business development specification
Class inheritance methods
When a subclass inherits a parent class and needs to override a parent class method, the override modifier is required.
class Animal {
eat() {
console.log('food')}}// Bad
class Dog extends Animal {
eat() {
console.log('bone')}}// Good
class Dog extends Animal {
override eat() {
console.log('bone')}}Copy the code
Do not use namespaces
Due to the rise of ES6 Module, the original namesapce notation in TS has been gradually abandoned, and module should be used instead in business.
Set a collection of constants using enumerations
An ordinary set of constants defined by an object is modified without error, unless manually as const.
// Bad
const Status = {
Success: 'success'
}
// Good
enum Status {
Success = 'success'
}
Copy the code
Definition file (.d.ts) writing specification
Global type/variable definitions
Global types/variables are written in the global.d.ts file.
- If external modules are introduced, use
declare global {}
Formal definition
import { StateType } from './state'
declare global {
export const globalState: StateType
export const foo: string
export type AsyncFunction = (. args:any[]) = > Promise<any>}Copy the code
- If no external modules are introduced, use them directly
declare
define
interface StateType {}
declare const globalState: StateType
declare const foo: string
declare type AsyncFunction = (. args:any[]) = > Promise<any>
Copy the code
Expand the definition file for third-party libraries
Third party definition files should be named after the [package].d.ts rule and stored in the project’s type directory.
Here’s an example:
// types/references/react-redux.d.ts
// It is better to add this sentence, otherwise the export may be overwritten, only DefaultRootState exists
export * from 'react-redux'
import { FooState } from './foo'
// Extend third-party libraries
declare module "react-redux" {
// DefaultRootState was defined as {}, we changed it to an index type
export interface DefaultRootState {
foo: FooState
[key: string] :any}}Copy the code
Specification for annotation writing
Use the tsDoc form when writing comments for an interface or SDK, and the IDE can recognize tsDoc when it is used.
/** ** the sum of two numbers */
export function add(a: number, b: number) {
return a + b
}
Copy the code
Tsdoc specification
Tsdoc’s syntax is stricter than JSDoc’s, and the focus is different. Jsdoc focuses more on providing type annotations to JS, while TS naturally supports types, so TSDoc focuses on document and API management.
Some jSDoc tags are useless in TS, such as:
- @function: Marks a variable as a function
- @enum: Marks an object as enum
- @access: Annotates the access levels of object members (private, public, protected). Typescript supports private, public, readOnly, etc
- @type: standard variable type
See here for some tsDoc tags
Lint Reference Configuration
npm install @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-import @typescript-eslint/parser eslint-import-resolver-typescript -D
Copy the code
.eslintrc.js
module.exports = {
env: {
browser: true.es2021: true.node: true,},root: true.extends: [
/ /... The rest of the configuration
'plugin:import/recommended',].// rules can be configured according to the conditions
rules: {
// Here we suggest these js rules
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts'.'.tsx'],},'import/extensions': ['.tsx'.'.ts'.'.js'.'.jsx'.'.json'].'import/resolver': {
typescript: {
// Make sure the file is in the root directory
project: ['./tsconfig.json'],}}},// the ts rule is overridden separately
overrides: [{files: ['*.ts'.'*.tsx'].// Use typescript-eslint only for TS
parser: '@typescript-eslint/parser'.// Enable static check
parserOptions: {
tsconfigRootDir: __dirname,
ecmaFeatures: {
jsx: true,},sourceType: 'module'.project: ['./tsconfig.json'],},plugins: ['@typescript-eslint'].extends: [
/ / ts support
'plugin:import/typescript'.'plugin:@typescript-eslint/recommended'.'plugin:@typescript-eslint/recommended-requiring-type-checking',].rules: {
// close js rules
'no-shadow': 'off'.// ts
'@typescript-eslint/no-var-requires': 'warn'.'@typescript-eslint/ban-ts-comment': 'off'.'@typescript-eslint/no-misused-promises': 'off'.'@typescript-eslint/no-floating-promises': 'off'.'@typescript-eslint/ban-types': 'off'.'@typescript-eslint/no-shadow': 'error'.'@typescript-eslint/explicit-module-boundary-types': 'off'.'@typescript-eslint/no-unsafe-member-access': 'off'.'@typescript-eslint/no-loss-of-precision': 'off'.'@typescript-eslint/no-unsafe-argument': 'off'.// no any
'@typescript-eslint/no-explicit-any': 'off'.'@typescript-eslint/no-unsafe-assignment': 'off'.'@typescript-eslint/no-unsafe-return': 'off'.'@typescript-eslint/no-unsafe-call': 'off'.// ! operator
'@typescript-eslint/no-non-null-assertion': 'off',},},],}Copy the code
Tsconfig See configuration
The React project
{
"compilerOptions": {
"target": "esnext"."module": "esnext"."useDefineForClassFields": true.Emit ECMAScript compliant class fields
"forceConsistentCasingInFileNames": true.// File import is case-sensitive
"allowJs": true."checkJs": true."skipLibCheck": true.// Skip checking all.d.ts files, mainly the node_modules directory
"moduleResolution": "node"."lib": ["ESNext"."DOM"."DOM.Iterable"]."importHelpers": true."jsx": "react"."allowSyntheticDefaultImports": true.// Allow CJS module default to be imported
"esModuleInterop": false./ / conversion CJS modules, add dedault import, because do not need to TSC compilation, so there is no need to open, only need allowSyntheticDefaultImports do check
"sourceMap": true."noImplicitOverride": true.// The inherited class override method must write the override keyword
"strict": true."isolatedModules": true.// Make sure every file has an import or export
"resolveJsonModule": true./ / parsing json
"noEmit": true,},"exclude": ["node_modules"]}Copy the code
Library project
{
"compilerOptions": {
"target": "es5"."module": "CommonJS"."useDefineForClassFields": true.Emit ECMAScript compliant class fields
"forceConsistentCasingInFileNames": true.// File import is case-sensitive
"allowJs": true."checkJs": true."skipLibCheck": true.// Skip checking all.d.ts files, mainly the node_modules directory
"moduleResolution": "node"."lib": ["ESNext"."DOM"."DOM.Iterable"]."importHelpers": true."allowSyntheticDefaultImports": true.// Allow CJS module default to be imported
"esModuleInterop": true.// Enable CJS module conversion
"sourceMap": true."noImplicitOverride": true.// The inherited class override method must write the override keyword
"strict": true."isolatedModules": true.// Make sure every file has an import or export
"resolveJsonModule": true./ / parsing json
"noEmit": false."declaration": true."declarationMap": true."outDir": "./dist"."baseUrl": "."
},
"exclude": ["node_modules"]}Copy the code
Some third-party libraries related to TS
@microsoft/api-extractor
Website: api-extractor.com
You can synthesize multiple. D. ts files into one, and have the ability of document generation and API export detection. It is recommended to use it in library development.
typedoc
Website: typedoc.org
Compared to @Microsoft/apI-Extractor, TypeDoc focuses on one thing – documentation. If you only want to convert.ts files to the corresponding API documentation, consider using it in your project.
See this issue for comparison with @Microsoft/api-Extractor.