8cadd00d34
Created by Github action --------- Co-authored-by: Crowdin Bot <support+bot@crowdin.com> Co-authored-by: github-actions <github-actions@twenty.com>
334 lines
12 KiB
Plaintext
334 lines
12 KiB
Plaintext
---
|
|
title: Các thực tiễn tốt nhất
|
|
image: '"/images/user-guide/tips/light-bulb.png"'
|
|
---
|
|
|
|
<Frame>
|
|
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
|
</Frame>
|
|
|
|
Tài liệu này phác thảo các thực tiễn tốt nhất bạn nên tuân theo khi làm việc trên giao diện người dùng.
|
|
|
|
## Quản lý trạng thái
|
|
|
|
React và Recoil xử lý quản lý trạng thái trong mã nguồn.
|
|
|
|
### Sử dụng `useRecoilState` để lưu trữ trạng thái
|
|
|
|
Thực hành tốt nhất là tạo nhiều atom khi bạn cần lưu trữ trạng thái của mình.
|
|
|
|
<Warning>
|
|
|
|
Tốt hơn là sử dụng thêm atom thay vì cố gắng quá ngắn gọn với truyền props.
|
|
|
|
</Warning>
|
|
|
|
```tsx
|
|
export const myAtomState = atom({
|
|
key: 'myAtomState',
|
|
default: 'default value',
|
|
});
|
|
|
|
export const MyComponent = () => {
|
|
const [myAtom, setMyAtom] = useRecoilState(myAtomState);
|
|
|
|
return (
|
|
<div>
|
|
<input
|
|
value={myAtom}
|
|
onChange={(e) => setMyAtom(e.target.value)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Không sử dụng `useRef` để lưu trữ trạng thái
|
|
|
|
Tránh sử dụng `useRef` để lưu trữ trạng thái.
|
|
|
|
Nếu bạn muốn lưu trữ trạng thái, bạn nên sử dụng `useState` hoặc `useRecoilState`.
|
|
|
|
Xem [cách quản lý việc tải lại](#managing-re-renders) nếu bạn cảm thấy cần `useRef` để ngăn ngừa một số lỗi tải lại xảy ra.
|
|
|
|
## Quản lý tải lại
|
|
|
|
Tải lại có thể khó quản lý trong React.
|
|
|
|
Dưới đây là một số quy tắc để tránh tải lại không cần thiết.
|
|
|
|
Hãy luôn nhớ rằng bạn có thể tránh được tải lại bằng cách hiểu rõ nguyên nhân của chúng.
|
|
|
|
### Làm việc ở cấp độ gốc
|
|
|
|
Tránh tải lại trong các tính năng mới được dễ dàng hơn bằng cách loại bỏ chúng ở cấp độ gốc.
|
|
|
|
Thành phần phụ `PageChangeEffect` chứa chỉ một `useEffect` giữ mọi logic để thực thi khi thay đổi trang.
|
|
|
|
Bằng cách đó, bạn biết rằng chỉ có một nơi có thể kích hoạt tải lại.
|
|
|
|
### Luôn suy nghĩ kỹ trước khi thêm `useEffect` vào mã nguồn của bạn
|
|
|
|
Tải lại thường do `useEffect` không cần thiết gây ra.
|
|
|
|
Bạn nên cân nhắc liệu bạn có cần `useEffect`, hoặc nếu bạn có thể di chuyển logic vào một hàm xử lý sự kiện.
|
|
|
|
You'll find it generally easy to move the logic in a `handleClick` or `handleChange` function.
|
|
|
|
Bạn cũng có thể tìm thấy chúng trong các thư viện như Apollo: `onCompleted`, `onError`, vv.
|
|
|
|
### Sử dụng thành phần cùng cấp để tách `useEffect` hoặc logic lấy dữ liệu
|
|
|
|
Nếu bạn cảm thấy cần thêm `useEffect` vào thành phần gốc của bạn, bạn nên cân nhắc tách nó thành một thành phần phụ.
|
|
|
|
Bạn có thể áp dụng tương tự cho logic lấy dữ liệu, với các hook của Apollo.
|
|
|
|
```tsx
|
|
// ❌ Bad, will cause re-renders even if data is not changing,
|
|
// because useEffect needs to be re-evaluated
|
|
export const PageComponent = () => {
|
|
const [data, setData] = useRecoilState(dataState);
|
|
const [someDependency] = useRecoilState(someDependencyState);
|
|
|
|
useEffect(() => {
|
|
if(someDependency !== data) {
|
|
setData(someDependency);
|
|
}
|
|
}, [someDependency]);
|
|
|
|
return <div>{data}</div>;
|
|
};
|
|
|
|
export const App = () => (
|
|
<RecoilRoot>
|
|
<PageComponent />
|
|
</RecoilRoot>
|
|
);
|
|
```
|
|
|
|
```tsx
|
|
// ✅ Good, will not cause re-renders if data is not changing,
|
|
// because useEffect is re-evaluated in another sibling component
|
|
export const PageComponent = () => {
|
|
const [data, setData] = useRecoilState(dataState);
|
|
|
|
return <div>{data}</div>;
|
|
};
|
|
|
|
export const PageData = () => {
|
|
const [data, setData] = useRecoilState(dataState);
|
|
const [someDependency] = useRecoilState(someDependencyState);
|
|
|
|
useEffect(() => {
|
|
if(someDependency !== data) {
|
|
setData(someDependency);
|
|
}
|
|
}, [someDependency]);
|
|
|
|
return <></>;
|
|
};
|
|
|
|
export const App = () => (
|
|
<RecoilRoot>
|
|
<PageData />
|
|
<PageComponent />
|
|
</RecoilRoot>
|
|
);
|
|
```
|
|
|
|
### Sử dụng trạng thái họ recoil và bộ chọn họ recoil
|
|
|
|
Trạng thái họ recoil và bộ chọn là cách tuyệt vời để tránh tải lại.
|
|
|
|
Chúng hữu ích khi bạn cần lưu trữ một danh sách các mục.
|
|
|
|
### Bạn không nên sử dụng `React.memo(MyComponent)`
|
|
|
|
Tránh sử dụng `React.memo()` vì nó không giải quyết nguyên nhân của việc tải lại, mà thay vào đó ngắt chuỗi tải lại, điều này có thể dẫn đến hành vi bất ngờ và làm mã rất khó kết cấu lại.
|
|
|
|
### Giới hạn việc sử dụng `useCallback` hoặc `useMemo`
|
|
|
|
Chúng thường không cần thiết và sẽ làm cho mã khó đọc và bảo trì hơn đối với một lợi ích về hiệu suất không đáng kể.
|
|
|
|
## Console.logs
|
|
|
|
Các câu lệnh `console.log` có giá trị trong quá trình phát triển, cung cấp cái nhìn ngay lập tức về giá trị biến và luồng mã. Nhưng, để lại chúng trong mã sản xuất có thể dẫn đến một số vấn đề:
|
|
|
|
1. **Hiệu suất**: Ghi log quá nhiều có thể ảnh hưởng đến hiệu suất chạy, đặc biệt là trên ứng dụng phía máy tính khách.
|
|
|
|
2. **Bảo mật**: Ghi log dữ liệu nhạy cảm có thể tiết lộ thông tin quan trọng với ai đó kiểm tra bảng điều khiển của trình duyệt.
|
|
|
|
3. **Sạch sẽ**: Điền đầy bảng điều khiển với các bản ghi có thể làm mờ các cảnh báo hoặc lỗi quan trọng cần thấy bởi các nhà phát triển hoặc công cụ.
|
|
|
|
4. **Sự chuyên nghiệp**: Người dùng cuối hoặc khách hàng kiểm tra bảng điều khiển và thấy hàng loạt các câu lệnh ghi có thể nghi ngờ về chất lượng và độ hoàn thiện của mã.
|
|
|
|
Hãy chắc chắn rằng bạn xóa tất cả `console.logs` trước khi đưa mã lên sản xuất.
|
|
|
|
## Đặt tên
|
|
|
|
### Đặt tên biến
|
|
|
|
Tên biến cần miêu tả chính xác mục đích hoặc chức năng của biến.
|
|
|
|
#### Vấn đề với tên chung
|
|
|
|
Tên chung trong lập trình không lý tưởng vì chúng thiếu tính cụ thể, dẫn đến mơ hồ và giảm khả năng đọc của mã. Tên như vậy không truyền đạt mục đích của biến hoặc hàm, làm cho nó khó khăn cho nhà phát triển để hiểu ý định của mã mà không cần điều tra sâu hơn. Điều này có thể dẫn đến thời gian gỡ lỗi gia tăng, dễ xảy ra lỗi hơn và khó khăn trong bảo trì và cộng tác. Trong khi đó, việc đặt tên mô tả sẽ làm cho mã tự giải thích và dễ dàng điều hướng, cải thiện chất lượng mã và năng suất của nhà phát triển.
|
|
|
|
```tsx
|
|
// ❌ Bad, uses a generic name that doesn't communicate its
|
|
// purpose or content clearly
|
|
const [value, setValue] = useState('');
|
|
```
|
|
|
|
```tsx
|
|
// ✅ Good, uses a descriptive name
|
|
const [email, setEmail] = useState('');
|
|
```
|
|
|
|
#### Một số từ cần tránh trong tên biến
|
|
|
|
- giả
|
|
|
|
### Bộ xử lý sự kiện
|
|
|
|
Tên của các bộ xử lý sự kiện nên bắt đầu với `handle`, trong khi `on` là một tiền tố dùng để đặt tên sự kiện trong các props của thành phần.
|
|
|
|
```tsx
|
|
// ❌ Bad
|
|
const onEmailChange = (val: string) => {
|
|
// ...
|
|
};
|
|
```
|
|
|
|
```tsx
|
|
// ✅ Good
|
|
const handleEmailChange = (val: string) => {
|
|
// ...
|
|
};
|
|
```
|
|
|
|
## Props tùy chọn
|
|
|
|
Tránh đưa giá trị mặc định cho một thuộc tính tùy chọn.
|
|
|
|
**VÍ DỤ**
|
|
|
|
Hãy xem thành phần `EmailField` định nghĩa dưới đây:
|
|
|
|
```tsx
|
|
type EmailFieldProps = {
|
|
value: string;
|
|
disabled?: boolean;
|
|
};
|
|
|
|
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
|
|
<TextInput value={value} disabled={disabled} fullWidth />
|
|
);
|
|
```
|
|
|
|
**Sử dụng**
|
|
|
|
```tsx
|
|
// ❌ Bad, passing in the same value as the default value adds no value
|
|
const Form = () => <EmailField value="username@email.com" disabled={false} />;
|
|
```
|
|
|
|
```tsx
|
|
// ✅ Good, assumes the default value
|
|
const Form = () => <EmailField value="username@email.com" />;
|
|
```
|
|
|
|
## Thành phần là props
|
|
|
|
Hãy cố gắng truyền càng nhiều càng tốt các thành phần chưa khởi tạo như props, để các con có thể tự quyết định props nào cần truyền.
|
|
|
|
Ví dụ phổ biến nhất cho điều đó là các thành phần biểu tượng:
|
|
|
|
```tsx
|
|
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
|
|
|
|
// In MyComponent
|
|
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
|
|
const theme = useTheme();
|
|
|
|
return (
|
|
<div>
|
|
<MyIcon size={theme.icon.size.md}>
|
|
</div>
|
|
)
|
|
};
|
|
```
|
|
|
|
Để React hiểu rằng thành phần là một thành phần, bạn cần sử dụng PascalCase, để sau đó khởi tạo với `<MyIcon>`
|
|
|
|
## Prop Drilling: Hãy Giữ Nó Ở Mức Tối Thiểu
|
|
|
|
Prop drilling, trong ngữ cảnh React, đề cập đến thực tiễn truyền biến trạng thái và các setter của chúng qua nhiều lớp thành phần, thậm chí nếu các thành phần trung gian không sử dụng chúng. Mặc dù đôi khi cần thiết, việc truyền prop quá mức có thể dẫn đến:
|
|
|
|
1. **Giảm Khả Năng Đọc**: Việc lần theo nguồn gốc hoặc nơi mà một prop được sử dụng có thể trở nên phức tạp trong một cấu trúc thành phần lồng ghép sâu.
|
|
|
|
2. **Thách Thức Bảo Trì**: Thay đổi trong cấu trúc prop của một thành phần có thể yêu cầu điều chỉnh ở nhiều thành phần khác, ngay cả khi chúng không sử dụng trực tiếp prop đó.
|
|
|
|
3. **Giảm Tính Tái Sử Dụng Thành Phần**: Một thành phần nhận nhiều props chỉ để truyền chúng xuống dưới trở nên ít sử dụng chung và khó tái sử dụng trong các ngữ cảnh khác nhau hơn.
|
|
|
|
Nếu bạn cảm thấy rằng bạn đang sử dụng quá mức prop drilling, hãy xem [các thực hành quản lý trạng thái tốt nhất](#state-management).
|
|
|
|
## Nhập khẩu
|
|
|
|
Khi nhập, hãy chọn các biệt danh được chỉ định thay vì xác định đường dẫn đầy đủ hoặc tương đối.
|
|
|
|
**Biệt danh Xử lý**
|
|
|
|
```js
|
|
{
|
|
alias: {
|
|
"~": path.resolve(__dirname, "src"),
|
|
"@": path.resolve(__dirname, "src/modules"),
|
|
"@testing": path.resolve(__dirname, "src/testing"),
|
|
},
|
|
}
|
|
```
|
|
|
|
**Sử dụng**
|
|
|
|
```tsx
|
|
// ❌ Bad, specifies the entire relative path
|
|
import {
|
|
CatalogDecorator
|
|
} from '../../../../../testing/decorators/CatalogDecorator';
|
|
import {
|
|
ComponentDecorator
|
|
} from '../../../../../testing/decorators/ComponentDecorator';
|
|
```
|
|
|
|
```tsx
|
|
// ✅ Good, utilises the designated aliases
|
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
|
```
|
|
|
|
## Xác Thực Schema
|
|
|
|
[Zod](https://github.com/colinhacks/zod) là bộ xác thực schema cho các đối tượng chưa được định dạng:
|
|
|
|
```js
|
|
const validationSchema = z
|
|
.object({
|
|
exist: z.boolean(),
|
|
email: z
|
|
.string()
|
|
.email('Email must be a valid email'),
|
|
password: z
|
|
.string()
|
|
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
|
|
})
|
|
.required();
|
|
|
|
type Form = z.infer<typeof validationSchema>;
|
|
```
|
|
|
|
## Thay Đổi Đột Phá
|
|
|
|
Luôn thực hiện thử nghiệm thủ công kỹ lưỡng trước khi tiến hành để đảm bảo rằng các sửa đổi không gây rối loạn ở nơi khác, do các thử nghiệm chưa được tích hợp rộng rãi.
|
|
|