Trong phần trước, bạn đã tìm hiểu những loại nào bạn có thể làm việc với React và TypeScript. Bạn cũng đã học về suy luận kiểu để bạn biết khi nào tùy thuộc vào bạn để chú thích mã của mình và khi nào TypeScript sẽ thực hiện công việc này cho bạn. Một thứ có thể giúp bạn rất nhiều là giao diện.
Nói một cách đơn giản, giao diện là một tập hợp các kiểu giống như đối tượng. Nó được sử dụng để mô tả hình dạng hoặc cấu trúc của một số dữ liệu. Dữ liệu này có thể là bất cứ thứ gì, tham số hàm (đối tượng và mảng), dữ liệu bên trong kiểu dữ liệu, lớp đạo cụ, đạo cụ trạng thái và biến. Các kiểu trong giao diện được cấu trúc dưới dạng các cặp khóa / giá trị.
Trong mỗi cặp, khóa là thuộc tính tồn tại hoặc có thể tồn tại trong dữ liệu bạn muốn mô tả. Giá trị là kiểu dữ liệu của thuộc tính đó, được chỉ định làm khóa. Cú pháp của giao diện có thể trông quen thuộc. Nó trông rất giống với cú pháp của đối tượng theo nghĩa đen. Có một số khác biệt. Đầu tiên, giao diện phải bắt đầu với từ khóa giao diện.
Từ khóa này đứng trước tên của giao diện. Thứ hai, không có dấu bằng giữa tên của giao diện và tập hợp các cặp khóa / giá trị. Thứ ba, các cặp khóa / giá trị bên trong giao diện có thể được phân tách bằng dấu phẩy (,) hoặc dấu chấm phẩy (;). Cả hai đều sẽ hoạt động. Vì vậy, nó phụ thuộc vào bạn mà bạn chọn để sử dụng.
Thứ tư, về quy ước đặt tên, luôn bắt đầu tên của giao diện bằng chữ in hoa, giống như một lớp. Thứ năm, một số quy ước đặt tên nữa, bạn nên kết thúc tên của giao diện bằng từ "Giao diện".
Một thực hành khác là bắt đầu tên của giao diện bằng chữ cái “I”. Điều này làm cho nó rõ ràng đâu là giao diện và đâu là không. Hãy xem một số ví dụ đơn giản về giao diện.
///
// Create UserInterface
// interface is the keyword
// UserInterface is the name of the interface
interface UserInterface {
name: string;
age: number;
isEmployed: boolean;
}
// Use UserInterface to annotate new 'user' object
const userOne: UserInterface = {
name: 'Tony Smith',
age: 23,
isEmployed: false
}
const userTwo: UserInterface = {
name: 'Bobby Stone',
age: 28,
isEmployed: true
}
///
// This will not work
// the 'age' property is required
const userThree: UserInterface = {
name: 'Bobby Stone',
// missing required age property here
isEmployed: true
}
// Error: Property 'age' is missing in type '{ name: string; isEmployed: true; }' but required in type 'UserInterface'.
///
// Using interface with function
// Create interface for assingment
interface AssignentInterface {
subject: string;
lesson: string;
chapter: number;
time: string;
}
// Create function that accepts object as 'assignent' parameter
// Use AssignentInterface interface to annotate 'assignent' parameter
function study(assignent: AssignentInterface) {
return `I will study ${assignent.subject}, lesson ${assignent.lesson}, chapter ${assignent.chapter} for ${assignent.time}.`
}
// Create some assignment data
const math = {
subject: 'Mathematics',
lesson: 'Trigonometry',
chapter: 5,
time: '45 minutes'
}
// Let's study
study(math)
// 'I will study Mathematics, chapter Trigonometry, exercise 5 for 45 minutes.'
Optional properties
Khi bạn không chắc chắn một số thuộc tính tồn tại trên dữ liệu bạn đang mô tả, bạn cũng có thể đánh dấu thuộc tính đó là tùy chọn. Bạn có thể làm điều này bằng cách thêm? ở cuối tên thuộc tính (thuộc tính ?: string). Điều này sẽ thông báo cho TypeScript mong đợi thuộc tính này, nhưng nó không yêu cầu.
Vì vậy, nếu thuộc tính tùy chọn đó không tồn tại trên dữ liệu mà bạn đã sử dụng giao diện, TypeScript sẽ không khiếu nại và biên dịch mã của bạn. Nếu không, nó sẽ hiển thị cảnh báo và không cho phép mã của bạn biên dịch. Vì vậy, hãy nhớ rằng, bất kỳ thuộc tính nào không phải là tùy chọn sẽ tự động được yêu cầu.
///
// Create CustomUserInterface interface
// with optional 'age' property
interface CustomUserInterface {
username: string;
age?: number; // this is optional (the '?' at the end of the property name)
}
// This will work because 'age' is optional, not required
const userOne: CustomUserInterface = {
username: 'tomtom'
// missing age property
}
// This will naturally work as well
const userTwo: CustomUserInterface = {
username: 'tomtom'
age: 23
}
Readonly properties
Trong một số trường hợp, bạn có thể muốn ngăn thay đổi một số thuộc tính sau khi chúng được đặt lần đầu tiên. Các giao diện cũng cho phép điều này. Tất cả những gì bạn phải làm là thêm từ chỉ đọc trước tên của tài sản. Sau đó, khi bạn cố gắng ghi đè thuộc tính này, sau khi bạn gán nó, TypeScript sẽ cảnh báo bạn rằng thuộc tính ở chế độ chỉ đọc.
///
// Create UserInterface with read-only property 'password'
interface UserInterface {
username: string;
readonly password: string; // This is read-only property ('readonly')
// it can be modified only when the object is first created.
age?: number; // This is optional property ('?')
}
// Create new user using UserInterface interface
let userOne: UserInterface = {
username: 'tomtom',
password: 'some very secret thing'
}
// Log userOne's username
console.log(userOne.username) 'tomtom'
// This will work:
// Try to change username property
userOne.username = 'buggy'
console.log(userOne.username) // 'buggy'
// ! This will not work
// Try to change read-only password property
userOne.password = 'another very secrert thing'
// Error: Cannot assign to 'password' because it is a read-only property.
Extending interfaces
Điều thú vị về giao diện là bạn cũng có thể mở rộng giao diện này với giao diện khác, hoặc nhiều giao diện khác (phân tách bằng dấu phẩy). Điều này tương tự với các lớp JavaScript. Vì vậy, khi một giao diện mở rộng giao diện khác, giao diện đó sẽ kế thừa hình dạng của nó. Nó sẽ chứa tất cả các cặp khóa / giá trị và sau đó bạn có thể thêm một số cặp khác nếu muốn.
Trong ES6 trở lên, có tùy chọn sử dụng câu lệnh xuất và nhập để xuất và nhập các đoạn mã của bạn. Hai câu lệnh này có thể khá tiện dụng khi bạn làm việc với các giao diện. Bạn có thể đặt tất cả các giao diện trong một tệp, xuất chúng và nhập chúng vào nơi bạn cần. Điều này có thể giúp bạn giữ cho mã của mình có tổ chức.
Nó cũng có thể giúp bạn giảm kích thước cơ sở mã của bạn. Bạn không phải khai báo đi khai lại một số giao diện chỉ vì một số đối tượng hoặc dữ liệu có cùng hình dạng. Thay vào đó, bạn có thể khai báo giao diện đó một lần, xuất và nhập bất kỳ lúc nào, bất cứ lúc nào bạn cần.
Khi bạn muốn xuất giao diện nào đó hãy đặt từ khóa xuất trước từ khóa giao diện trong quá trình khai báo. Khi bạn muốn nhập nó vào đâu đó và sử dụng nó, hãy sử dụng câu lệnh nhập và tên chính xác của giao diện.
///
// ./interfaces/interfaces.ts
// Create AdminInterface and export it
export interface AdminInterface {}
// Create UserInterface and export it
export interface UserInterface {}
///
// ./index.ts
// Import AdminInterface and UserInterface interfaces
import { AdminInterface, UserInterface } from './interfaces/interfaces'
// use AdminInterface interface
let newAdmin: AdminInterface
// use UserInterface interface
let newUser: UserInterface
Interfaces and compilation
Một điều quan trọng về giao diện. Các giao diện sẽ không hiển thị khi bạn biên dịch mã React và TypeScript, hoặc chỉ TypeScript, mã sang JavaScript. TypeScript chỉ sử dụng các giao diện để kiểm tra kiểu trong thời gian chạy và biên dịch. Tuy nhiên, TypeScript sẽ không biên dịch chúng. Vì vậy, bạn không phải lo lắng JavaScript của mình sẽ bị cồng kềnh. Nó sẽ không.
Bạn biết về những loại nào có sẵn trong TypeScript. Bạn cũng biết về các giao diện và cách sử dụng chúng để chú thích mã của bạn. Bây giờ, hãy xem cách sử dụng React và TypeScript cùng nhau. Hãy cùng xem cách chú thích đúng các thành phần và móc nối của lớp và chức năng.
Class components
Các thành phần lớp không còn được sử dụng thường xuyên như trước đây nữa. Tuy nhiên, chúng vẫn được sử dụng trong React. Vì vậy, vẫn tốt nếu bạn biết cách viết chúng trong các dự án được xây dựng bằng React và TypeScript. Với các lớp, có hai tùy chọn cho các loại. Bạn có thể cung cấp cho lớp các loại đạo cụ và trạng thái.
Khi bạn khai báo lớp mới, các loại cho props và cho trạng thái sẽ nằm giữa các dấu ngoặc đứng sau React.Component mở rộng và trước khi mở dấu ngoặc nhọn. Hãy nhớ rằng nó theo thứ tự chính xác này. Loại cho đạo cụ luôn là loại đầu tiên và loại cho trạng thái thứ hai. Bạn cũng có thể tùy ý chú thích trạng thái lớp.
Khi bạn muốn để trống một trong các kiểu, bạn có thể thêm đối tượng rỗng bên trong dấu ngoặc nhọn thay vì đối tượng giao diện. Nếu không muốn sử dụng giao diện, bạn cũng có thể cung cấp trực tiếp các loại cho prop và state thông qua các đối tượng bên trong dấu ngoặc.
///
// Create interface for class component props
interface PropsInterface {
heading: string;
}
// Create interface for class component state
interface StateInterface {
firstName: string;
lastName: string;
}
// Create new class
// use PropsInterface and StateInterface interfaces (<Props, State>)
class MyApp extends React.Component < PropsInterface, StateInterface > {
// This state annotation is optional
// it is for better type inference
state: StateInterface = {
firstName: 'Andrew',
lastName: 'Coboll'
}
render() {
return (
<div>
{this.props.heading} {this.state.firstName} {this.state.lastName}
</div>
)
}
}
///
// Or, class with constructor
class MyApp extends React.Component < PropsInterface, StateInterface > {
// Declare state property and annotate it with StateInterface
state: StateInterface
// Add constructor and annotate props with PropsInterface
constructor(props: PropsInterface) {
super(props)
this.state = {
firstName: 'Andrew',
lastName: 'Coboll'
}
}
render() {
return (
<div>
{this.props.heading} {this.state.firstName} {this.state.lastName}
</div>
)
}
}
///
// Class with types only for props
// Replace the interface for state with empty object
class MyApp extends React.Component < PropsInterface, {} > { ... }
///
// Class with types only for state
// Replace the interface for props with empty object
class MyApp extends React.Component < {}, StateInterface > { ... }
///
// Class with types, without interface - one prop, one state prop
class MyApp extends React.Component < { classProp: string }, { stateProp: boolean } > {}
// Class with types, without interface - multiple props, multiple state props
class MyApp extends React.Component < {
propOne: number; // Props types
propTwo: string; // Props types
}, {
statePropOne: boolean; // State types
statePropTwo: number; // State types
} > { ... }
Functional components
///
// Create interface for functional component
interface PropsInterface {
propOne: string;
propTwo: string;
}
// Create functional component
// and use PropsInterface interface
// to annotate its props
function MyComponent(props: PropsInterface) {
return (
<div>{props.propOne} {props.propTwo}</div>
)
}
// Arrow function version
const MyComponent = (props: PropsInterface) => {
return (
<div>{props.propOne} {props.propTwo}</div>
)
}
///
// Annotate props directly - one prop
function MyComponent(props: string) {
return (
<div>{props.propOne} {props.propTwo}</div>
)
}
// Arrow function version
const MyComponent = (props: string) => {
return (
<div>{props.propOne} {props.propTwo}</div>
)
}
///
// Annotate props directly - multiple props
function MyComponent(props: {
propOne: string;
propTwo: string;
}) {
return (
<div>{props.propOne} {props.propTwo}</div>
)
}
// Arrow function version
const MyComponent = (props: {
propOne: string;
propTwo: string;
}) => {
return (
<div>{props.propOne} {props.propTwo}</div>
)
}
Việc chú thích các hàm thậm chí còn dễ dàng hơn các lớp vì không có trạng thái và cũng như trong JS cũ hơn, không có hàm tạo. Bạn khai báo thành phần chức năng của mình như bình thường. Nếu nó chấp nhận một số đạo cụ, bạn sử dụng giao diện để chú thích các đạo cụ này. Hoặc, bạn cũng có thể chú thích trực tiếp các đạo cụ.
Hooks
Chú thích móc rất dễ dàng. Nếu bạn khởi tạo một hook với một số giá trị mặc định, TypeScript sẽ suy ra kiểu của nó cho bạn. Vì vậy, bạn không cần phải làm gì cả. Nếu bạn khởi tạo mà không có giá trị, bạn có thể thêm kiểu của nó bên trong dấu ngoặc ngay sau tên của hook và trước dấu ngoặc (tức là React.useState ()).
Hãy cùng xem các ví dụ về ba hook phổ biến nhất, useState, useRef và useReducer.
Example of useState hook:
// Initialize useState hook with default value
const MyComponent = () => {
const [name, setName] = React.useState('') // Will be inferred as string
const [name, setName] = React.useState('Tom') // Will be inferred as string
const [age, setAge] = React.useState(15) // Will be inferred as number
const [isHappy, setIsHappy] = React.useState(true) // Will be inferred as boolean
const [skills, setSkills] = React.useState(['Programming', 'Drawing']) // Will be inferred as an array
const [skills, setSkills] = React.useState([]) // Will be inferred as an array
const [customerData, setCustomerData] = React.useState({ firstName: 'Tom', lastName: 'Smith' }) // Will be inferred as an object
const [customerData, setCustomerData] = React.useState({}) // Will be inferred as an object
}
// Initialize useState hook without default value
const MyComponent = () => {
const [name, setName] = React.useState<string>() // set type to string
const [age, setAge] = React.useState<number>() // set type to number
const [isHappy, setIsHappy] = React.useState<boolean>() // set type to boolean
const [skills, setSkills] = React.useState<[]>() // set type to array
const [skills, setSkills] = React.useState<{}>() // set type to object
}
Example of useRef hook:
const MyComponent = () => {
// Initialize ref with null value
// and tell TypeScript the type of the HTML element
let textInputRef = React.useRef<HTMLInputElement>(null)
// Initialize ref for form element
let formRef = React.useRef<HTMLFormElement>(null)
const handleTextSave = () => {
// Make sure textInputRef input exists
if (textInputRef & textInputRef.current) {
// Get value of textInputRef input
const inputValue = textInputRef.current.value
}
}
return (
{/* Use ref for form initialized above */}
<form ref={formRef}>
<label>Your name:</label>
<input type="text" defaultValue="" ref={textInputRef}{/* Use textInputRef ref initialized above */} />
<button onClick={handleTextSave}>Save</button>
</form>
)
}
Example of useReducer hook:
// Create interface for app state
interface AppStateInterface {}
// Create interface for reducer actions
interface ActionInterface {
type: 'ACTION_ONE' | 'ACTION_TWO';
payload: string | boolean;
}
// Create reducer function
// use AppStateInterface and ActionInterface
// to annotate state and action parameters
// and set return type to AppStateInterface (reducer( ... ): AppStateInterface) { ... })
export function reducer(state: AppStateInterface, action: ActionInterface): AppStateInterface {
switch (action.type) {
case 'ACTION_ONE':
return {
...state,
one: action.payload // payload is string
}
case 'ACTION_TWO':
return {
...state,
two: action.payload // payload is boolean
}
default:
return state
}
}