📘 TypeScript with ReactJS All in One ⚛️

0 0 0

Người đăng: Truong Phung

Theo Viblo Asia

Following is an example that helps us quickly review common TypeScript features like Enums, Interfaces, Generics, Type Annotations, Intersection Type, Union Type, Type Guard....

1. TypeScript Page (TypeScriptPage.tsx)

import React, { useState, useEffect, useReducer } from 'react';
import styles from './TypeScriptPage.module.css'; // Enum for user roles
enum UserRole { Admin = 'Admin', User = 'User', Guest = 'Guest',
} // Interface for base user data
interface User { id: number; name: string; email?: string; // Optional property example role: UserRole;
} // AdminUser type using intersection to add permissions to User
type AdminUser = User & { permissions: string[] };
// Above code equivalent to following
// interface AdminUser extends User {
// permissions: string[]; // Specific to AdminUser
// } // Union type for users with different possible structures
type UserType = User | AdminUser; // Type Guard to check if a user is an AdminUser
const isAdminUser = (user: UserType): user is AdminUser => { return (user as AdminUser).permissions !== undefined;
}; // Type alias for a list of users
type UserList = User[]; // Utility types examples
type UserWithoutEmail = Omit<User, 'email'>; // Omits the email property
type UserContactInfo = Pick<User, 'id' | 'name'>; // Picks only the id and name properties
type PartialUser = Partial<User>; // Makes all properties of User optional // Generic function to fetch data, using generics for flexible return types with overloads and default parameter
async function fetchData<T>(url: string, options: RequestInit = {}): Promise<T> { const response = await fetch(url, options); if (!response.ok) { throw new Error('Network response was not ok'); } return response.json();
} // Action type for useReducer with different payloads
type UserAction = | { type: 'add'; payload: User } | { type: 'remove'; id: number } | { type: 'update'; payload: { id: number; updates: PartialUser } }; // Reducer function for managing a list of users, demonstrating typed reducer usage
const userReducer = (state: User[], action: UserAction): User[] => { switch (action.type) { case 'add': return [...state, action.payload]; case 'remove': return state.filter(user => user.id !== action.id); case 'update': return state.map(user => user.id === action.payload.id ? { ...user, ...action.payload.updates } : user ); default: return state; }
}; const TypeScriptPage: React.FC = () => { const [users, dispatch] = useReducer(userReducer, []); // Using useReducer for state management const [loading, setLoading] = useState<boolean>(true); const [error, setError] = useState<string | null>(null); const [newUser, setNewUser] = useState<{ name: string; email: string; role: UserRole }>({ name: '', email: '', role: UserRole.Guest, }); const [validationError, setValidationError] = useState<string | null>(null); useEffect(() => { fetchData<UserList>('https://jsonplaceholder.typicode.com/users') .then(data => { // Simulate user roles and create AdminUser type users const usersWithRoles: UserType[] = data.map((user, index) => index % 2 === 0 ? { ...user, role: UserRole.User } : { ...user, role: UserRole.Admin, permissions: ['read', 'write'] } ); usersWithRoles.forEach(user => dispatch({ type: 'add', payload: user })); // Adding users to the reducer setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, []); if (loading) { return <div className={styles.loading}>Loading...</div>; } if (error) { return <div className={styles.error}>Error: {error}</div>; } // Example of Partial: updating only a subset of user properties const updateUser = (id: number, updates: PartialUser) => { dispatch({ type: 'update', payload: { id, updates }, }); }; const handleAddUser = () => { if (!newUser.name || !newUser.email) { setValidationError('Name and email are required.'); return; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(newUser.email)) { setValidationError('Please enter a valid email.'); return; } const newUserId = users.length ? Math.max(...users.map(user => user.id)) + 1 : 1; const userToAdd: User = { id: newUserId, name: newUser.name, email: newUser.email, role: newUser.role, }; dispatch({ type: 'add', payload: userToAdd }); setNewUser({ name: '', email: '', role: UserRole.Guest }); setValidationError(null); }; return ( <div className={styles.container}> <h1 className={styles.title}>TypeScript Advanced Features Page</h1> {/* Demonstrate Omit utility */} <div> <h2 className={styles.subtitle}>Omit User Example</h2> {/* Example: Email omitted from user */} <code>{JSON.stringify({ id: 1, name: "User without email", role: UserRole.User } as UserWithoutEmail)}</code> </div> <div className={styles.addUserForm}> <h2 className={styles.subtitle}>Add New User</h2> <input type="text" placeholder="Name" value={newUser.name} onChange={(e) => setNewUser({ ...newUser, name: e.target.value })} className={styles.input} /> <input type="email" placeholder="Email" value={newUser.email} onChange={(e) => setNewUser({ ...newUser, email: e.target.value })} className={styles.input} /> <select value={newUser.role} onChange={(e) => setNewUser({ ...newUser, role: e.target.value as UserRole })} className={styles.select} > <option value={UserRole.Guest}>Guest</option> <option value={UserRole.User}>User</option> <option value={UserRole.Admin}>Admin</option> </select> <button onClick={handleAddUser} className={styles.button}> Add User </button> {validationError && <div className={styles.error}>{validationError}</div>} </div> <ul className={styles.userList}> {users.map(user => ( <li key={user.id} className={styles.userItem}> {user.name} ({user.email ?? 'N/A'}) - Role: {user.role} {isAdminUser(user) && ( <div className={styles.permissions}>Permissions: {user.permissions.join(', ')}</div> )} {/* Example using Pick to show only selected properties */} <div> <strong>User Preview:</strong>{' '} {JSON.stringify({ id: user.id, name: user.name } as UserContactInfo)} </div> {/* Update user role using Partial example */} <button onClick={() => updateUser(user.id, { role: UserRole.Guest })} className={`${styles.button} ${styles.changeRoleButton}`}> Change Role to Guest </button> <button onClick={() => dispatch({ type: 'remove', id: user.id })} className={`${styles.button} ${styles.removeUserButton}`}> Remove User </button> </li> ))} </ul> </div> );
}; export default TypeScriptPage;

2. Style (TypeScriptPage.module.css)

Let's create a TypeScriptPage.module.css file for styling and then apply these styles within TypeScriptPage. Here’s how you can do it:

/* Main container for the page */
.container { max-width: 800px; margin: 0 auto; padding: 20px; font-family: Arial, sans-serif;
} /* Title styling */
.title { color: #2c3e50; font-size: 2rem; margin-bottom: 20px; text-align: center;
} /* Subtitle for section headings */
.subtitle { color: #34495e; font-size: 1.5rem; margin-top: 20px; margin-bottom: 10px;
} /* User list container */
.userList { list-style-type: none; padding: 0; margin-top: 20px;
} /* Individual user item */
.userItem { padding: 15px; margin: 10px 0; border: 1px solid #bdc3c7; border-radius: 8px; background-color: #ecf0f1;
} /* Style for permissions for admin users */
.permissions { color: #16a085; font-weight: bold; margin-top: 5px;
} /* Button styling */
.button { margin-right: 10px; padding: 8px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9rem;
} /* Button for changing role */
.changeRoleButton { background-color: #3498db; color: white;
} .changeRoleButton:hover { background-color: #2980b9;
} /* Button for removing user */
.removeUserButton { background-color: #e74c3c; color: white;
} .removeUserButton:hover { background-color: #c0392b;
} /* Loading and error messages */
.loading, .error { text-align: center; font-size: 1.2rem;
} .loading { color: #3498db;
} .error { color: #e74c3c;
} /* Add User Form styling */
.addUserForm { margin-top: 20px; padding: 20px; border: 1px solid #bdc3c7; border-radius: 8px; background-color: #f8f9fa;
} .addUserForm input,
.addUserForm select { width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #bdc3c7; border-radius: 4px;
} .addUserForm button { background-color: #2ecc71; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer;
} .addUserForm button:hover { background-color: #27ae60;
}

3. Summary

Here’s a breakdown of what’s included

  1. Enums: Demonstrates using an enum to define user roles.
  2. Interfaces: Shows a clear example of defining an interface (User) for structured typing.
  3. Generics: The fetchData<T> function uses generics to make it reusable for various types.
  4. Type Annotations: Type annotations for state (users, loading, error) , component (React.FC)...
  5. Error Handling with Promises: Good use of error handling when fetching data.
  6. React with TypeScript: TypeScript integration in a React component.
  7. Optional Properties: The email field in User is optional, shown with email?: string.
  8. Intersection Type (AdminUser): Combines User with additional properties (permissions) for admin-specific features.
  9. Union Type and Type Guard (isAdminUser): UserType can be either User or AdminUser, with a type guard to check if a user has admin permissions.
  10. Utility Types:
  • Omit: UserWithoutEmail excludes email from User.
  • Pick: UserContactInfo selects only id and name from User.
  • Partial: PartialUser makes all User properties optional, used in the updateUser function to apply partial updates.
  1. useReducer and Typed Actions: userReducer manages the users state, allowing complex actions (add, remove,update) with a strongly-typed UserAction union.
  2. Type Assertions: In updateUser, payload is cast to User to match the userReducer requirements after partial updates.
  3. Conditional Rendering with Optional Chaining: Optional chaining is useful for handling deeply nested objects without worrying about null or undefined.
  4. Add User Form: a form is added to input the new user's details. The handleAddUser function validates the input and dispatches an action to add the new user to the state. The validationError state is used to display any validation errors.

This example demonstrates common TypeScript functionality and give you a broad showcase of the language’s features.

If you found this helpful, let me know by leaving a 👍 or a comment!, or if you think this post could help someone, feel free to share it! Thank you very much! 😃

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Typescript - Sự khác nhau giữa Typescript và Javascript

Typescript là gì. TypeScript là một ngôn ngữ giúp cung cấp quy mô lớn hơn so với JavaScript.

0 0 525

- vừa được xem lúc

Bạn đã biết các tips này khi làm việc với chuỗi trong JavaScript chưa ?

Hi xin chào các bạn, tiếp tục chuỗi chủ đề về cái thằng JavaScript này, hôm nay mình sẽ giới thiệu cho các bạn một số thủ thuật hay ho khi làm việc với chuỗi trong JavaScript có thể bạn đã hoặc chưa từng dùng. Cụ thể như nào thì hãy cùng mình tìm hiểu trong bài viết này nhé (go).

0 0 433

- vừa được xem lúc

Một số phương thức với object trong Javascript

Trong Javascript có hỗ trợ các loại dữ liệu cơ bản là giống với hầu hết những ngôn ngữ lập trình khác. Bài viết này mình sẽ giới thiệu về Object và một số phương thức thường dùng với nó.

0 0 153

- vừa được xem lúc

Tìm hiểu về thư viện axios

Giới thiệu. Axios là gì? Axios là một thư viện HTTP Client dựa trên Promise.

0 0 145

- vừa được xem lúc

Imports và Exports trong JavaScript ES6

. Giới thiệu. ES6 cung cấp cho chúng ta import (nhập), export (xuất) các functions, biến từ module này sang module khác và sử dụng nó trong các file khác.

0 0 110

- vừa được xem lúc

Bài toán đọc số thành chữ (phần 2) - Hoàn chỉnh chương trình dưới 100 dòng code

Tiếp tục bài viết còn dang dở ở phần trước Phân tích bài toán đọc số thành chữ (phần 1) - Phân tích đề và những mảnh ghép đầu tiên. Bạn nào chưa đọc thì có thể xem ở link trên trước nhé.

0 0 245