In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-26 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article mainly introduces "how to write efficient TS code". In daily operation, I believe many people have doubts about how to write efficient TS code. The editor consulted all kinds of materials and sorted out simple and easy-to-use operation methods. I hope it will be helpful to answer the doubts about "how to write efficient TS code". Next, please follow the editor to study!
First, minimize duplicated code
For partners who are new to TypeScript, similar duplicate code may occur accidentally when defining an interface. For example:
Interface Person {firstName: string; lastName: string;} interface PersonWithBirthDate {firstName: string; lastName: string; birth: Date;}
Obviously, compared to the Person interface, the PersonWithBirthDate interface only has an extra birth attribute, and the other properties are the same as the Person interface. So how do you avoid duplicated code in the example? To solve this problem, you can use the extends keyword:
Interface Person {firstName: string; lastName: string;} interface PersonWithBirthDate extends Person {birth: Date;}
Of course, in addition to using the extends keyword, you can also use the crossover operator (&):
Type PersonWithBirthDate = Person & {birth: Date}
In addition, sometimes you may find that you want to define a type to match the "shape" of an initial configuration object, such as:
Const INIT_OPTIONS = {width: 640, height: 480, color: "# 00FF00", label: "VGA",}; interface Options {width: number; height: number; color: string; label: string;}
In fact, for the Options interface, you can also use the typeof operator to quickly get the "shape" of the configuration object:
Type Options = typeof INIT_OPTIONS
Duplicate code may also occur in the process of using recognizable unions (algebraic data types or label union types). For example:
Interface SaveAction {type: 'save'; / /...} interface LoadAction {type:' load'; / /...} type Action = SaveAction | LoadAction; type ActionType = 'save' |' load'; / / Repeated types!
To avoid repeatedly defining 'save' and' load', we can use member access syntax to extract the types of attributes in the object:
Type ActionType = Action ['type']; / / Type is "save" | "load"
However, in the actual development process, duplicate types are not always easy to find. Sometimes they are obscured by grammar. For example, multiple functions have the same type signature:
Function get (url: string, opts: Options): Promise {/ *... * /} function post (url: string, opts: Options): Promise {/ *... * /}
For the get and post methods above, to avoid duplicate code, you can extract a uniform type signature:
Type HTTPFunction = (url: string, opts: Options) = > Promise; const get: HTTPFunction = (url, opts) = > {/ *... * /}; const post: HTTPFunction = (url, opts) = > {/ *... * /}
Second, use more precise types instead of string types
Suppose you are building a music collection and want to define a type for the album. At this point you can use the interface keyword to define an Album type:
Interface Album {artist: string; / artist title: string; / / album title releaseDate: string; / / release date: YYYY-MM-DD recordingType: string; / / recording type: "live" or "studio"}
For the Album type, you want the format of the releaseDate attribute value to be YYYY-MM-DD, while the range of the recordingType attribute value is live or studio. However, because the types of the releaseDate and recordingType attributes in the interface are strings, the following problems may occur when using the Album interface:
Const dangerous: Album = {artist: "Michael Jackson", title: "Dangerous", releaseDate: "November 31, 1991", / / does not match expected format recordingType: "Studio", / / does not match expected format}
Although the values of releaseDate and recordingType do not match the expected format, the TypeScript compiler does not find the problem at this time. To solve this problem, you should define more precise types for the releaseDate and recordingType properties, such as this:
Interface Album {artist: string; / artist title: string; / / album title releaseDate: Date; / / release date: YYYY-MM-DD recordingType: "studio" | "live"; / / recording type: "live" or "studio"}
After redefining the Album interface, the TypeScript compiler prompts the following exception information for the previous assignment statement:
Const dangerous: Album = {artist: "Michael Jackson", title: "Dangerous", / / you cannot assign type "string" to type "Date". Ts (2322) releaseDate: "November 31, 1991", / / Error / / cannot assign type "Studio" to type "studio" | "live". Ts (2322) recordingType: "Studio", / / Error}
To solve the above problem, you need to set the correct type for the releaseDate and recordingType properties, such as this:
Const dangerous: Album = {artist: "Michael Jackson", title: "Dangerous", releaseDate: new Date ("1991-11-31"), recordingType: "studio",}
Another scenario in which string types are mistakenly used is to set the parameter type of a function. Suppose you need to write a function that extracts the value of an attribute from an array of objects and saves it to the array. In the Underscore library, this operation is called "pluck". To achieve this, you may be the first to think of the following code:
Function pluck (record: any [], key: string): any [] {return record.map ((r) = > r [key]);}
This is not good for the above pluck function, because it uses the any type, especially as the type of return value. So how do you optimize the pluck function? First, you can improve the type signature by introducing a generic parameter:
Function pluck (record: t [], key: string): any [] {/ / Element implicitly has an' any' type because expression of type 'string' can't be used to / / index type' unknown'. / / No index signature with a parameter of type 'string' was found on type' unknown'. (7053) return record.map ((r) = > r [key]); / / Error}
From the above exception information, it is known that a key of string type cannot be used as an index type of unknown type. To get the value of a property from an object, you need to make sure that the parameter key is a property in the object.
Speaking of which, I'm sure some of your buddies have thought of the keyof operator, which was introduced in TypeScript 2.1 and can be used to get all keys of a certain type, whose return type is a union type. Then use the keyof operator to update the pluck function:
Function pluck (record: t [], key: keyof T) {return record.map ((r) = > r [key]);}
For the updated pluck function, your IDE will automatically infer the return type of the function for you:
Function pluck (record: t [], key: keyof T): t [keyof T] []
For the updated pluck function, you can test it using the Album type defined earlier:
Const albums: Album [] = [{artist: "Michael Jackson", title: "Dangerous", releaseDate: new Date ("1991-11-31"), recordingType: "studio",}]; / / let releaseDateArr: (string | Date) [] let releaseDateArr = pluck (albums, 'releaseDate')
The releaseDateArr variable in the example, whose type is inferred to be (string | Date) [], is obviously not what you expect, and its correct type should be Date []. So how to solve the problem? At this point you need to introduce the second generic parameter K, and then use extends to constrain:
Function pluck (record: t [], key: K): t [K] [] {return record.map ((r) = > r [key]);} / / let releaseDateArr: Date [] let releaseDateArr = pluck (albums, 'releaseDate')
The defined type always represents a valid state
Suppose you are building a Web application that allows the user to specify a page number, then load and display the corresponding content on that page. First, you might define the State object first:
Interface State {pageContent: string; isLoading: boolean; errorMsg?: string;}
Then you will define a renderPage function to render the page:
Function renderPage (state: State) {if (state.errorMsg) {return `woo, abnormal loading page occurred. ${state.errorMsg} `;} else if (state.isLoading) {return` page loading ~ ~ `;} return` ${state.pageContent} `;} / / output result: page loading ~ ~ console.log (renderPage ({isLoading: true, pageContent: ""})) / / output result: Hello, everyone. I'm renderPage ({isLoading: false, pageContent: "Hello, I'm Brother Po"}).
After you have created the renderPage function, you can continue to define a changePage function to obtain the corresponding page data according to the page number:
Async function changePage (state: State, newPage: string) {state.isLoading = true; try {const response = await fetch (getUrlForPage (newPage)); if (! response.ok) {throw new Error (`Unable to load ${newPage}: ${response.statusText} `);} const text = await response.text (); state.isLoading = false; state.pageContent = text;} catch (e) {state.errorMsg = "" + e;}}
For the above changePage function, it has the following problems:
In the catch statement, the state of state.isLoading is not set to false
The value of state.errorMsg is not cleaned up in time, so if the previous request fails, you will continue to see error messages instead of loading messages.
The reason for the above problem is that the previously defined State type allows both isLoading and errorMsg values to be set, although this is an invalid state. To solve this problem, you can consider introducing recognizable federation types to define different page request states:
Interface RequestPending {state: "pending";} interface RequestError {state: "error"; errorMsg: string;} interface RequestSuccess {state: "ok"; pageContent: string;} type RequestState = RequestPending | RequestError | RequestSuccess; interface State {currentPage: string; requests: {[page: string]: RequestState};}
In the above code, three different request states are defined by using identifiable federation types, so that the different request states can be easily distinguished, thus making the business logic processing clearer. Next, you need to update the previously created renderPage and changePage functions based on the updated State type:
Updated renderPage function
Function renderPage (state: State) {const {currentPage} = state; const requestState = state.requests [currentPage]; switch (requestState.state) {case "pending": return `page loading ~ ~`; case "error": return `woo, there is an exception on page ${currentPage}.; case "ok": `content of page ${currentPage}: ${requestState.pageContent}`;}}
Updated changePage function
Async function changePage (state: State, newPage: string) {state.requests [newPage] = {state: "pending"}; state.currentPage = newPage; try {const response = await fetch (getUrlForPage (newPage)); if (! response.ok) {throw new Error (`unable to load page ${newPage}: ${response.statusText} `);} const pageContent = await response.text () State.requests [newPage] = {state: "ok", pageContent};} catch (e) {state.requests [newPage] = {state: "error", errorMsg: "" + e};}}
In the changePage function, different request states are set according to different situations, and different request states contain different information. In this way, the renderPage function can handle accordingly according to the uniform value of the state attribute. Therefore, by using recognizable federation types, each state of the request is a valid state without the problem of invalid states.
Select the condition type instead of overloading the declaration
Suppose you want to use TS to implement a double function that supports string or number types. At this point, you may have thought of using federated types and function overloads:
Function double (x: number | string): number | string; function double (x: any) {return x + x;}
Although the declaration of this double function is correct, it is a bit imprecise:
/ / const num: string | number const num = double (10); / / const str: string | number const str = double ('ts')
For the double function, you expect the parameter type passed in to be of type number, and the type of the return value to be type number. When the parameter type you pass in is string, the returned type is also string. The above double function returns a string | number type. To achieve the above requirements, you may have thought of introducing generic variables and generic constraints:
Function double (x: t): T; function double (x: any) {return x + x;}
In the double function above, the generic variable T is introduced and its type range is constrained using extends.
/ / const num: 10 const num = double (10); / / const str: "ts" const str = double ('ts'); console.log (str)
Unfortunately, our pursuit of accuracy has exceeded expectations. The current type is a little too precise. When passing a string type, the double declaration returns a string type, which is correct. But when you pass a string literal type, the returned type is the same string literal type. This is wrong because ts returns tsts, not ts, after being processed by the double function.
Another option is to provide multiple type declarations. Although TypeScript only allows you to write a specific implementation, it allows you to write any number of type declarations. You can use function overloading to improve the type of double:
Function double (x: number): number; function double (x: string): string; function double (x: any) {return x + x;} / const num: number const num = double (10); / / const str: string const str = double ("ts")
It is obvious that both the num and str variables are of the correct type at this time, but unfortunately, there is a small problem with the double function. Because the declaration of the double function only supports values of type string or number, but not string | number union type, such as:
Function doubleFn (x: number | string) {/ / Argument of type 'string | number' is not assignable to / / parameter of type' number'. / / Argument of type 'string | number' is not assignable to / / parameter of type' string'. Return double (x); / / Error}
Why do you prompt for the above errors? Because when the TypeScript compiler handles function overloading, it looks for the overloaded list until it finds a matching signature. For the number | string union type, it is obvious that the match failed.
However, although the above problem can be solved by adding the overloaded signature of string | number, the best solution is to use the condition type. In type space, conditional types are like if statements:
Function double (x: t): T extends string? String: number; function double (x: any) {return x + x;}
This is similar to the previous introduction of a generic version of the double function declaration, except that it introduces more complex return types. The condition type is easy to use, the same rule as the ternary operator (?:) in JavaScript. T extends string? String: number means that if the T type is a subset of the string type, the return type of the double function is of type string, otherwise it is of type number.
With the introduction of conditional types, all the previous examples work:
/ / const num: number const num = double (10); / / const str: string const str = double ("ts"); / / function f (x: string | number): string | number function f (x: number | string) {return double (x);}
Fifth, create objects at one time
You can easily create an object that represents 2D coordinate points in JavaScript:
Const pt = {}; pt.x = 3; pt.y = 4
However, for the same code, the following error message is prompted in TypeScript:
Const pt = {}; / / Property 'x' does not exist on type'{} 'pt.x = 3; / Error / / Property 'y'does not exist on type' {} 'pt.y = 4; / / Error
This is because the type of the pt variable in the first line is inferred from its value {}, and you can only assign values to known properties. To solve this problem, you might think of a solution, which is to declare a new Point type and then use it as the type of the pt variable:
Interface Point {x: number; y: number;} / / Type'{}'is missing the following properties from type 'Point': x, y (2739) const pt: Point = {}; / / Error pt.x = 3; pt.y = 4
So how to solve the above problems? One of the simplest solutions is to create objects at once:
Const pt = {x: 3, y: 4,}; / / OK
If you want to create objects step by step, you can use type assertions (as) to eliminate type checking:
Const pt = {} as Point; pt.x = 3; pt.y = 4; / / OK
But a better approach is to create an object at once and explicitly declare the type of the variable:
Const pt: Point = {x: 3, y: 4,}
When you need to build a larger object from a smaller object, you might do this, such as:
Const pt = {x: 3, y: 4}; const id = {name: "Pythagoras"}; const namedPoint = {}; Object.assign (namedPoint, pt, id); / / Property 'id' does not exist on type' {}'. (2339) namedPoint.name; / / Error
To solve the above problem, you can use the object expansion operator. To build large objects at once:
Const namedPoint = {... pt,... id}; namedPoint.name; / / OK, type is string
In addition, you can use the object expansion operator to build objects field by field in a type-safe manner. The key is to use a new variable with each update, so that each variable gets a new type:
Const pt0 = {}; const pt1 = {... pt0, x: 3}; const pt: Point = {... pt1, y: 4}; / / OK
While this is a circuitous way to build such a simple object, it can be a useful technique for adding properties to the object and allowing TypeScript to infer new types. To add attributes conditionally in a type-safe manner, you can use the object expansion operator with null or {}, which does not add any attributes:
Declare var hasMiddle: boolean; const firstLast = {first: 'Harry', last:' Truman'}; const president = {... firstLast,... (hasMiddle? {middle:'S'}: {})}
If you mouse over president in the editor, you will see the type inferred by TypeScript:
Const president: {middle?: string; first: string; last: string;}
Finally, by setting the value of the hasMiddle variable, you can control the value of the middle property in the president object:
Declare var hasMiddle: boolean; var hasMiddle = true; const firstLast = {first: 'Harry', last:' Truman'}; const president = {... firstLast,... (hasMiddle? {middle:'S'}: {})}; let mid = president.middle console.log (mid); / / so far, the study of "how to write efficient TS code" is over, hoping to solve everyone's doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.