Author: a tuque warehouse: Github, Gitee Tuque community main station (first) : Tuque community blog: Digijin, Zhihu, MOC public number: Tuque community contact me: after paying attention to the public number can add Tuque sauce wechat oh original is not easy, ❤️ like + comment + collection ❤️ three, encourage the author to write a better tutorial.
Welcome to TypeScript’s Getting Started to Practice series on Type is Justice:
- Type is Justice: TypeScript from Starter to Practice (Prologue)
- Type is Justice: TypeScript from Inception to Practice (PART 1)
- Type is Justice: TypeScript from Getting Started to Practice, PART II
To understand the basic types of TS interface, we began to learn how to give more complex structure annotation type, this is we want to draw out this section function, and then we explain how to operation type: cross type and joint type, finally, we explained the atom types: literal type, and how to implement type and combined type collocation guard effect.
The source code for this article is available on Github or Gitee. If you think it is well written, please give ❤️ a thumbs up and Github or Gitee a star ❤️
This tutorial is part of the React front End engineer’s learning path. Welcome to Star and encourage us to continue to create better tutorials
function
We annotated the onChange function in TodoInputProps earlier, but we didn’t cover it in detail, so in this section we’ll look at the TS function in more detail.
Annotation function
For example, we have the following function:
function add(x, y) {
return x + y;
}
Copy the code
So how do we annotate this function? In fact, the main part of a function is the input and output, so when we annotate a function, we only need to annotate the parameter and return value of the function, because the above function is to perform x+y operation, x and y should be number, and the return value should be number. So we annotate the above function as follows:
function add(x: number, y: number) :number {
return x + y;
}
Copy the code
You can see that we annotate x and y with the number type as colon annotations, and for the return value, we annotate it directly as add(): number. Sometimes the return value is not written. TS can calculate the return value type based on the parameter type and function body, which is commonly known as the automatic type inference mechanism.
Function types
In addition to annotating functions, we sometimes involve assigning functions to a variable, as in the following example:
const add = function (x, y) {
return x + y;
}
Copy the code
A function type is args1: type1, args2: type2, args2: type2… Args2: typen) => the type of returnType, so we can comment the above example as follows:
const add: (x: number, y: number) :number = function(x, y) {
return x + y;
}
Copy the code
If you have any questions, we annotated the function type of the add variable, but we didn’t annotate the function behind it. In fact, TS will automatically deduce the type. By comparing the structure of the function type with that of the following function, it will automatically infer that the x, y and return value of the latter function are all number.
Optional parameters
Just as there are optional attributes in the Interface before, there are also optional parameters in our function, because one of the biggest benefits of using TS is to make the type definition of function and Interface as clear as possible, so that other team members can clearly understand the Interface of the code and greatly improve the efficiency of team collaboration. So if a function might have some parameters, but we don’t need to pass them every time, they fall into the optional parameters category.
For example, if we want to write a function that constructs a person’s name, including firstName and lastName, but sometimes we don’t know lastName, how to write such a function? :
function buildName(firstName: string, lastName? :string) {
// ...
}
Copy the code
As you can see above, we have to pass firstName to build a person’s name function, but since lastName may not be available at times, we set it as an optional parameter, so the following function calls are available:
buildName('Tom'.'Huang');
buildName('mRcfps');
Copy the code
overloading
Overloading (Overloads) is a special characteristic in TS, in JS: no, it is mainly for the function return type service, more specifically a function may be performed inside a conditional statement that returns the value of different according to different conditions, these values may be of different types, so this time we how to give the return value annotation type?
The answer is to use overloading to annotate a multi-type return value function by defining a series of functions with the same function name, different argument lists, and return values. Let’s look at a multi-type return function:
let suits = ["hearts"."spades"."clubs"."diamonds"];
function pickCard(x) :any {
// If x is of type 'object', then we return pickCard and fetch pickCard D1 data from myDeck
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// If x is of type 'number', return a pickCard2 that can fetch data directly
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13}; }}let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4}];let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
Copy the code
In this example, the pickCard function has a different return type depending on the type of x. In this example, the pickCard function has a different return type depending on the type of x. Yes, in the previous example, the parameter type had only one option, so the return value type could be automatically inferred, but here the situation is: “The parameter type can have multiple options, corresponding to the parameter type of different options, there will be different return value types, and we do not know the parameter type”. In this case, we decouple the correspondence directly, which is best expressed using overloading:
let suits = ["hearts"."spades"."clubs"."diamonds"];
function pickCard(x: { suit: string; card: number} []) :number;
function pickCard(x: number) :{ suit: string; card: number };
function pickCard(x) :any {
// If x is of type 'object', then we return pickCard and fetch pickCard D1 data from myDeck
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// If x is of type 'number', return a pickCard2 that can fetch data directly
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13}; }}let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4}];let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
Copy the code
Function pickCard(x: type1): type2 function pickCard(x: type1): type2 function pickCard(x: type1): type2
- The first overload, we give arguments
x
Assigns an array type whose item is an object containing two propertiessuit
和card
And their types are respectivelystring
和number
; It then returns a value of typenumber
Type, this corresponds tox
The type ofobject
, the return type isnumber
This is the case. - The second overload, we give arguments
x
I assign onenumber
Type and then return value Type is an object that has two propertiessuit
和card
, the corresponding type isstring
和number
; The correspondingx
The type ofnumber
The return value type isobject
Type in this case.
Hands-on practice
SRC /utils/data. TS SRC /utils/data. TS
export interface Todo {
id: string;
user: string;
date: string;
content: string;
isCompleted: boolean;
}
export interface User {
id: string;
name: string;
avatar: string;
}
export function getUserById(userId: string) {
return userList.filter(user= > user.id === userId)[0];
}
export const todoListData: Todo[] = [
{
id: "1",
content: "Tuq Community: A Great Free Tutorial.",
user: "23410977",
date: "19:34 on 2 March 2020",
isCompleted: false
},
{
id: "2",
content: "Tuq Community: A Great Free Tutorial.",
user: "23410976",
date: "19:34 on 2 March 2020",
isCompleted: false
},
{
id: "3",
content: "Tuq Community: A Great Free Tutorial.",
user: "58352313",
date: "19:34 on 2 March 2020",
isCompleted: false
},
{
id: "4",
content: "Tuq Community: A Great Free Tutorial.",
user: "25455350",
date: "19:34 on 2 March 2020",
isCompleted: false
},
{
id: "5",
content: "Tuq Community: A Great Free Tutorial.",
user: "12345678",
date: "19:34 on 2 March 2020",
isCompleted: true}];export const userList: User[] = [
// ...
{
id: "23410976",
name: "pftom",
avatar: "https://avatars1.githubusercontent.com/u/26423749?s=88&v=4"
},
// ...
{
id: "12345678",
name: "pony",
avatar: "https://avatars3.githubusercontent.com/u/25010151?s=96&v=4"}];Copy the code
As you can see, we mainly made the following changes above:
- will
todoListData
Of each element ofuser
Field changed to corresponduserList
Elements of theid
, convenient based onuser
的id
Search for user information. - And then we give
todoListData
Each element is addedid
Convenient sign, then puttime
Property is replaced bydate
Properties. - And then we defined one
getUserById
Function for eachtodo
According to theuser
< span style = “max-width: 100%; clear: both; min-height: 100%; This is also a case where TS automatic type inference is used. The TS compiler calculates the return value type based on the parameter type, so we don’t need to specify the return value explicitly. - And then finally we derived it
Todo
和User
Interface.
TSX: SRC /TodoList. TSX: SRC /App. TSX: SRC /TodoList. TSX: SRC /App.
import React from "react"; import { List, Avatar, Menu, Dropdown } from "antd"; import { DownOutlined } from "@ant-design/icons"; import { Todo, getUserById } from "./utils/data"; Const menu = (< menu > < menu. Item> Complete </ menu. Item> < menu. Item> delete </ menu. Item> </ menu >); interface TodoListProps { todoList: Todo[]; } function TodoList({ todoList }: TodoListProps) { return ( <List className="demo-loadmore-list" itemLayout="horizontal" dataSource={todoList} renderItem={item => { const user = getUserById(item.user); Return (< list. Item key={item.id} actions={[<Dropdown overlay={menu}> <a key=" list-loader-more "> action <DownOutlined /> </a> </Dropdown> ]} > <List.Item.Meta avatar={<Avatar src={user.avatar} />} title={<a href="https://ant.design">{user.name}</a>} description={item.date} /> <div style={{ textDecoration: item.isCompleted ? "line-through" : "none" }} > {item.content} </div> </List.Item> ); }} / >); } export default TodoList;Copy the code
As you can see, the main changes we made above are as follows:
- We imported it first
Todo
Interface added to TodoList componentTodoListProps
Interface for this componentprops
Make type annotations. - And then we import the sum
getUserById
forrenderItem
According to the insideitem.user
Get the details of the user, then display the avatar and name. - And then we’re going to
item.time
Updated toitem.date
- Finally, we set up based on whether the to-do list has been completed
line-through
的textDecoration
Property to mark completed items.
SRC/app.tsx SRC/app.tsx
import React, { useRef, useState } from "react"; import { List, Avatar, // ... Dropdown, Tabs } from "antd"; import TodoInput from "./TodoInput"; import TodoList from "./TodoList"; import { todoListData } from "./utils/data"; import "./App.css"; import logo from "./logo.svg"; const { Title } = Typography; const { TabPane } = Tabs; function App() { const [todoList, setTodoList] = useState(todoListData); const callback = () => {}; const onFinish = (values: any) => { const newTodo = { ... values.todo, isCompleted: false }; setTodoList(todoList.concat(newTodo)); }; const ref = useRef(null); const activeTodoList = todoList.filter(todo => ! todo.isCompleted); const completedTodoList = todoList.filter(todo => todo.isCompleted); return ( <div className="App" ref={ref}> <div className="container header"> // ... <div className="container"> <Tabs onChange={callback} type="card"> <TabPane TAB =" all "keys ="1"> <TodoList TodoList ={todoList} /> </TabPane> <TabPane TAB =" in progress "key="2"> < todoList todoList={activeTodoList} /> </TabPane> <TabPane TAB =" completed "key="3"> <TodoList TodoList ={completedTodoList} /> </TabPane> </Tabs> </div> //...Copy the code
As you can see, the above content has been modified as follows:
- First we delete it
TodoList
Part of the code was imported insteadTodoList
component - And then we use
useState
Hooks to receivetodoListData
As the default data, and then passisCompleted
Filter, generate
summary
Let’s summarize and review what we learned in this section:
- First of all, we looked at functions in TS, and we looked at how to annotate functions
- Then it introduces how to annotate the function type of a variable when a function is assigned to a variable, and thus explains the ability of TS to automatically infer the type
- Next, we showed that functions also have optional parameters for standard interfaces
- Finally, we explained the overloading of TS in particular, it is mainly used to solve the function arguments exist a variety of types, and then the corresponding parameters of different types have different return value type, then we will give this function for type annotations, can through the way of overloading, decoupling parameter value type and returns a value type, can present all possible situations by overloading.
Because this article is a cup of tea series in The Tuq community, we still have some content about the function knowledge, but the specific content is by analogy, such as annotation function rest parameters, this, etc., interested students can refer to the official document: TS- function.
Cross type, union type
In the first three big chapters, we looked at the basic TS types, and then we used those basic types to combine enumerations and interfaces, and to annotate function arguments and return values. This is the practice of annotating TS types on JS elements. So just as there are element operations in JS such as addition, subtraction, multiplication, division, and even set operations “interchange and complement,” there is also an operation of type in TS, and that’s the crossover and union types we’ll be looking at in this section.
Cross type
Cross type is more than one type, through type & operator, merged into one type of this type contains multiple types of all types of members, let’s look at an example of response body, if we have a query is the artist’s request, we will according to the results of the query – response body, print the corresponding information, general response body is a combination of two types of information:
- Request success returns the status indicating success of the request, along with target data
- Failed request returns a status indicating that the request failed, along with an error message
For this one scenario, we can use cross types. Now that we know about such a scenario, let’s look at a specific example for this scenario:
interface ErrorHandling {
success: boolean; error? : { message:string };
}
interface ArtistsData {
artists: { name: string} []; }const handleArtistsResponse = (response: ArtistsData & ErrorHandling) = > {
if (response.error) {
console.error(response.error.message);
return;
}
console.log(response.artists);
};
Copy the code
We can see in this example that our artist information Interface is ArtistsData, which is one of the concrete data that is returned after a successful request. In addition to this, our response body usually has a status that indicates whether the response was successful or not, and a printed message that indicates if the response was wrong. So we also define ErrorHandling, and the two make up our artist response body by performing an intertype operation: ArtistsData & ErrorHandling, then we mark response as the result of this cross type in the function argument and print the corresponding information in the function body based on the reponse.error judgment of whether the request was successful or not.
The joint type
So what is the union type? Joint type is actually through the operator | to multiple types of joint, forming a complex type, when using the composite type annotations when a variable, the variable can take any one type of the compound type, this is similar to enumeration, is a variable may have multiple types, but in the end can only take one type.
One of the details of the difference between union types and enumerated types is for the reader to see for himself.
For example, we have a padLeft function that takes two parameters, value and padding. The main goal is to add the padding to the left of the value string. The padding can be a space, but the padding can be either a string or a number. When the padding is a string, a simple example is as follows:
const value: string = 'Hello Tuture';
const padding: string = ' ';
padLeft(value, padding) // => ' Hello Tuture';
Copy the code
Ok, now that we know the scenario, let’s go ahead and explain an updated version of the above example:
function padLeft(value: string, padding: any) {
if (typeof padding === "number") {
return Array(padding + 1).join("") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'. `);
}
padLeft("Hello world".4);
Copy the code
You can see in this example, the padding is given by any for the moment, and then the body of the function determines the string and number types and performs the corresponding “left space fill” operation. This logic works for early development, but when it comes to collaborative development, Other members can’t figure out what type of value to pass to the padding function just by looking at the function’s variable definition.
padLeft('Hello world'.true)
Copy the code
Bam! The program crashes! So you see, the program is still fragile.
To more explicitly constrain the padding type, it is necessary to introduce a union type:
function padLeft(value: string, padding: string | number) {
/ /... In the middle as
}
Copy the code
At this point, we find that even if there are many more teammates, they will know how to call the interface, because the compiler will force teammates to write the correct type if they continue:
padLeft('Hello world'.true)
Copy the code
The compiler will prompt you with the following error:
summary
In this section, we studied the cross type and joint type, they are the product of the type of operation of the TS type system, cross type is more than one type of a type, type of the end result is the sum of multiple types, and the joint type is a combination of multiple types, the final result type is more than one type of a certain type, The crossover type is used to construct response bodies, the union type is used to handle scenarios where a single variable is annotated as one of multiple types, and it also interacts chemically with literal types, implementing enumerations, and handling type guards, which we’ll cover in a moment.
Literal types and type guards
Finally, let’s talk about type guards, which are used in conjunction with union types in many scenarios. We also need to talk about literal types before we talk about type guards. Ugh! In fact, the three are complementary.
Literal type
In fact, literal types have been more or less mentioned in section 2. Remember that error?
const tutureSlogan: string = 5201314 Type '5201314' is not assignable to Type 'string'
Copy the code
The TS compiler suggests that “Type ‘5201314’ is not assignable to Type ‘string ‘.
Literals are the smallest type in the TS system. Like the number 1 in JS, they cannot be broken into smaller parts. There are two types of literals:
- Numeric literal
- String literals
Numeric literal
A number like 520, used as a type, is an array literal type, and is used to annotate a variable like this:
let tuture: 520
Copy the code
When we initialize the tuture variable, we can only assign the number 520:
tuture = 520; / / right
tuture = 521; // Error Type '521' is not assignable to Type '520'
Copy the code
String literals
The corresponding string literal is similar, we now annotate tuture with the string literal type ‘520’ :
let tuture: '520';
tuture = '520';
tuture = '521'; // Type '"521"' is not assignable to type '"520"'
Copy the code
As you can see, a further feature of the literal type is that the annotated variables that correspond to the literal type can only be assigned to the annotated literal.
Now that we’ve looked at literal types and talked about their characteristics, what’s so special about such a simple type? Here are two examples of the unexpected power of combining literal types with union types:
- To realize the enumeration
- Implement type guarding
Collocation examples – implement enumeration effect
When we match the joint type and literal type, we can achieve a certain effect of enumeration, let’s look at an example, we are generally three types of buying a computer system, we can choose the three types of computers to obtain corresponding to a user’s situation, we now give only a general frame, function concrete realization in type guard in detail:
function getUserInfo(osType: 'Linux' | 'Mac' | 'Windows') { // ... Subsequent implementation}Copy the code
As we can see in the example above, osType can take a value from one of three operating systems. This is similar to enumeration, and we can create a similar enumeration:
enum EnumOSType {
Linux,
Mac,
Windows
}
function getUserInfo(osType: EnumOSType) {}
Copy the code
The above two examples have similar effects. We have achieved a simple enumeration effect by combining the type with the literal type.
Type the guards
Type of guard is our joint type + literal type of another application scenario, it is mainly used for the “joint” between multiple types, having the same field, also exist in different fields, and then need to distinguish between specific when it is used which type, so may be confused, let’s look at an example, The parameter added to our getUserInfo function receives the OS and prints the user information carried by the OS according to os.type:
interface Linux {
type: 'Linux';
linuxUserInfo: 'geek';
}
interface Mac {
type: 'Mac';
macUserInfo: 'geek + 1';
}
interface Windows {
type: 'Windows';
windowsUserInfo: 'geek + 2';
}
function getUserInfo(os: Linux | Mac | Windows) {
console.log(os.linuxUserInfo);
}
Copy the code
OS type = xxxUserInfo; OS type = xxxUserInfo; OS type = xxxUserInfo;
Some students have a question, we are not a joint type, then OS should have Linux this type ah, so why would be wrong to print? The point to make is that the end result of a federated type is one of multiple federated types, that is, the OS could be Mac or Windows, so there is a problem with printing OS.linuxUserinfo, so we need a type guard at this point, It is based on the same field in multiple types, and the field is a literal type, and then performs different logic to ensure that the type is executed correctly. Let’s extend the above example:
function getUserInfo(os: Linux | Mac | Windows) {
switch (os.type) {
case 'Linux': {
console.log(os.linuxUserInfo);
break;
}
case 'Mac': {
console.log(os.macUserInfo);
break;
}
case 'Windows': {
console.log(os.windowsUserInfo);
break; }}}Copy the code
Type = OS. Type = OS. Type = OS. Type = OS. Type = OS. Linux will be prompted:
Hands-on practice
Once we know about literal types and type guards, we immediately apply them to our to-do list application.
SRC/todolist.tsx: todolist.tsx: todolist.tsx: todolist.tsx
import React from "react"; import { List, Avatar, Menu, Dropdown, Modal } from "antd"; import { DownOutlined, ExclamationCircleOutlined } from "@ant-design/icons"; import { ClickParam } from "antd/lib/menu"; import { Todo, getUserById } from "./utils/data"; const { confirm } = Modal; interface ActionProps { onClick: (key: "complete" | "delete") => void; isCompleted: boolean; } function Action({ onClick, isCompleted }: ActionProps) { const handleActionClick = ({ key }: ClickParam) => { if (key === "complete") { onClick("complete"); } else if (key === "delete") { onClick("delete"); }}; return ( <Menu onClick={handleActionClick}> <Menu.Item key="complete">{isCompleted ? "Redo" : "complete"} < / Menu Item > <. Menu Item key = "delete" > delete < / Menu Item > < Menu >); } interface TodoListProps { todoList: Todo[]; onClick: (todoId: string, key: "complete" | "delete") => void; } function TodoList({ todoList, onClick }: TodoListProps) { return ( <List className="demo-loadmore-list" // ... <List.Item key={item.id} actions={[ <Dropdown overlay={() => ( <Action isCompleted={item.isCompleted} onClick={(key: "Complete" | "delete") = > onClick (item id, key)} / >)} > < a key = "list - loadmore - more" > action < DownOutlined / > < / a > / /...Copy the code
As you can see, the above changes mainly include the following parts:
- We extended the menu component of a single Todo click drop-down menu, defining one
Action
Component, which takes two arguments,isCompleted
和onClick
, the former is used to indicate whether the Todo operation is being redone or completed, and the latter is used to handle the click event, according totodo.id
And the type of operationkey
To deal with. - We are in
Action
The component’sonClick
PropertyonClick
Functions are passed down from the parent component, so we need additional functions inTodoListProps
Combined with theonClick
The type definition of a function, and in accordance with what we learned about annotation functions, here we need the annotation argument list and the return value becauseonClick
The function performs click logic internally and does not return a value, so we annotate itvoid
Type, for parameter lists,todoId
It’s pretty simple, it’s usually a string, so the annotation isstring
Type, andkey
The type of the annotation operation, which is a literal union type, is allowedcomplete
和delete
Two kinds of - Next, let’s look at the Action component. We explained in the previous step that it takes two parameters, so let’s add one
ActionProps
To annotate the parameter list of the Action component, see theonClick
As we explained in the last step,isCompleted
Annotated asboolean
。 - Then in the Action component we define Menu
onClick
Handler function ofhandleActionClick
Is aClickParam
Type, which is fromantd/lib/menu
Imported, provided by the component library, and then we deconstruct it from the parameterskey
, in turn, type guards through literal types, handling the values ofonClick
logic - The last improvement we made was to use the Menu
isCompleted
Show “redo” or “finish”.
TSX: SRC /App. TSX: TodoList /App. TSX: TodoList /App. TSX: TodoList /App.
import React, { useRef, useState } from "react"; import { List, Avatar, // ... function App() { const [todoList, setTodoList] = useState(todoListData); const callback = () => {}; / /... const activeTodoList = todoList.filter(todo => ! todo.isCompleted); const completedTodoList = todoList.filter(todo => todo.isCompleted); const onClick = (todoId: string, key: "complete" | "delete") => { if (key === "complete") { const newTodoList = todoList.map(todo => { if (todo.id === todoId) { return { ... todo, isCompleted: ! todo.isCompleted }; } return todo; }); setTodoList(newTodoList); } else if (key === "delete") { const newTodoList = todoList.filter(todo => todo.id ! == todoId); setTodoList(newTodoList); }}; return ( <div className="App" ref={ref}> <div className="container header"> // ... <div className="container"> <Tabs onChange={callback} type="card"> <TabPane TAB =" all "keys ="1"> <TodoList TodoList ={todoList} onClick={onClick} /> </TabPane> <TabPane TAB =" in progress "key="2"> < todoList todoList={activeTodoList} OnClick ={onClick} /> </TabPane> <TabPane TAB =" completed "key="3"> <TodoList TodoList ={completedTodoList} onClick={onClick} /> </TabPane> </Tabs> </div> </div> ); } export default App;Copy the code
As you can see, there are two main changes:
- to
TodoList
increaseonClick
attribute - implementation
onClick
Function, according to literal typekey
Perform type guarding to handle the corresponding data change logic
summary
In this summary, we studied the literal type and type of guard, literal type and combined type collocation can achieve the effect of enumeration, can also handle type guard, literal type is the most types of atoms in the TS, it can not be disassembled, and type of guard is mainly on the joint type, TS compiler can’t handle, The TS compiler needs to be manually assisted by the developer to handle the type.
Want to learn more exciting practical skills tutorial? Come and visit the Tooquine community.
The source code for this article is available on Github or Gitee. If you think it is well written, please give Github or Gitee a thumbs up at ❤️ or ❤️