# 1. Cấu trúc react
## 1.1. Cấu trúc Project
```
- public: thư mục chứa resources của client
- libs: thư viện ngoài
- assets
- images
- json
- js
- css
- locales
- en
- vi
- index.html
- favicon.ico
- src: thư mục chứa source code
- assets: các tài nguyên import trực tiếp vào Project
- styles: thư mục chứa style của Project
- [Module1].[scss/css]
- ...
- application:
- configs: các cấu hình để khởi động Project, External Libs
- Constants.[js/ts/tsx]: các hằng số của Project
- apis: danh sách các api của Project
- [Module1]Api.[js/ts]
- index.[js/ts/tsx]: export các api
- routers: mapping các url và pages
- index.[js/ts/tsx]: export các router
- services: chứa business của application
- hooks: các thành phần tái sử dụng business, flow
- providers: cung cấp các thành phần sử dụng cho việc control flow
- pages: các trang dùng chung như Login, NotFound, ...
- index.[js/ts/tsx]: export các thành phần ra bên ngoài
- components: các thành phần tái sử dụng
- modules: các chức năng của Project dưới dạng các Modules
- [Module1]
- [Child-Module1]
- ...
- helpers: các tiện tích tái sử dụng dưới dạng function
- index.[js/ts/tsx]: application startup
- package.json
- ....
```
## 1.2. Cấu trúc Module trong Project
```
- [Module]
- [Child-Module]
- configs: các thành phần cấu hình của Module
- Constants.[js/ts/tsx]: các hằng số của Module
- defs:
- Model
- ColumnDef
- ...
- components: các thành phần tái sử dụng của Module
- services:
- hooks: các thành phần tái sử dụng business, flow của Local Module
- providers: các Local Provider của Module
- pages: các trang của Module
- index.[js/ts/tsx]: export các phần ra bên ngoài
```
> Ví dụ cho Module System > Menu
> {.is-info}
```
- src
- styles
- system
- Menu.scss
- application
- configs
- Constants.ts
- apis
- system
- MenuApi.ts
- index.ts
- modules
- system
- menu
- configs
- defs
- MenuColDef.ts
- MenuCreateModel.ts
- IMenuProp.ts
- IMenuItem.ts
- components
- MenuCreateForm.tsx
- pages
- MenuList.tsx
- index.tsx
```
> Ví dụ style Module Menu.scss với .[Tên Module] là prefix.
{.is-warning}
```scss=
// Menu.scss
.menu-list {
background: #ffffff;
padding: 10px;
}
.menu-create-form {
.form-title {
font-size: 14px;
font-weight: bold;
}
}
```
```typescript=
// MenuApi.ts
import { APP_API_PATH } from '~/application/configs/';
export const MENU_LIST_API = APP_API_PATH + '/menu/index';
// index.tsx
import IMenuProp from '../configs/IMenuProp.tsx';
import IMenuItem from '../configs/IMenuItem.tsx';
import MenuList from '../pages/MenuList.tsx';
export {
IMenuProp,
IMenuItem,
MenuList
};
```
> Các Module khi phụ thuộc nhau chỉ Import từ file index[.js/ts/tsx]
> {.is-warning}
```javascript=
// "utils/index.ts"
const NotifyHelper = {
notify: () => {...};
};
export {
NotifyHelper
};
// Import full path from file "modules/user/pages/create.tsx"
import { NotifyHelper } from '~/helpers';
const CreatePage = () => {
//...
};
export default CreatePage;
```
## 1.3. Cấu trúc File
### Header
Import theo Thứ tự:
- Các thư viện cài trong package.json
- Các Custom Component
- CSS/SCSS/assets
### Body
Khai báo main Class/Funtion
- Xử lý Prop, State, Ref
- Xử lý Effect
- Xử lý Event: onClick, onCreated...
- Xử lý Render
### Footer
Export: Luôn có 1 default export. Có thể export nhiều đối tượng biến, hàm.
- export default
- export {}
> Tham khảo [Pattern](/tech-docs/react/patterns) để viết Component tốt hơn.
> {.is-warning}
# 2. Quy tắc đặt tên
> - Interface: Thêm tiền tố "I" vào trước tên Component. Ví dụ: IMenuProp.ts,...
> - Type: Thêm tiền tố "T" vào trước tên Component. Ví dụ: TMenu.tsx
> - Tên Folder: CamelCase. Ví dụ: system > userManagement
> - Tên File: PascalCase. Ví dụ: UserManagement.tsx
> - Tên Class / Function Export: PascalCase. Ví dụ: MenuList, UserList...
> - Tên hook: CamelCase. Ví dụ: useCustomHook...
> - Tên hằng: ALL_UPPER_CASE. Ví dụ: MENU_API, PI,...
> - Tên variable, funtion local: CamelCase. Ví dụ: menus, menuStore, renderMenuList...
> - Tên file trong CSS/SCSS: PascalCase. Ví dụ: Menu.scss
> - Tên class trong CSS/SCSS: KebabCase. Ví dụ: .menu-list...
# 3. Common
## 3.1. Prop is Immutable: Không thay đổi dữ liệu truyền vào dưới dạng Prop. Khi cần đổi dữ liệu, Clone dữ liệu sử dụng Lodash(cloneDeep, assign) và cập nhật lại State.
- Bad
```typescript=
const FilterMenu = (menus: IMenuItem[]) => {
return _.filter(menus, function(menu: IMenuItem) {
if (_.isUndefined(menu.name) || _.isNull(menu.name)) {
menu.name = 'Awesome';
}
return _.isEqual(menu.hasRight, true);
});
}
```
- Good
```typescript=
// Khi đổi dữ liệu của Prop menus, các hàm có sử dụng biến này sẽ có nguy cơ bị lỗi
const FilterMenu = (menus: IMenuItem[]) => {
const resultMenus: IMenuItem[] = [];
// Duyệt qua các phần tử của Menu
_.forEach(menus, function(menu: IMenuItem) {
if (_.isEqual(menu.hasRight, true)) {
// Tạo Clone menu
let cloneMenu: IMenuItem = _.cloneDeep(menu);
// Có thể sử dụng toán tử ... để tối ưu
// let cloneMenu: IMenuItem = {...menu};
if (_.isUndefined(menu.name) || _.isNull(menu.name)) {
_.assign(cloneMenu, {name: 'Awesome'});
}
// Thêm vào mảng kết quả
resultMenus.push(cloneMenu);
}
}
return resultMenus;
});
}
```
## 3.2. Không nên gộp chung Business vào Component mà tách ra Container (vật chứa) và Custom Hook (flow/business)
- Bad
```typescript=
// Viết Business trong User.tsx
const User = () => {
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const [isLoadingDev, setIsLoadingDev] = useState(true);
const fetchDevs = async () => {
console.log("this might take some time....");
await delay(4000);
setIsLoadingDev(false);
console.log("Done!");
};
useEffect(() => {
fetchDevs();
}, []);
// return ...
};
```
- Good
```javascript=
// useLoading.tsx
const useLoading = (action) => {
const [loading, setLoading] = useState(false);
const doAction = (...args) => {
setLoading(true);
// Xử lý Business
return action(...args).finally(() => setLoading(false));
};
return [doAction, loading];
};
// User.tsx
const User = () => {
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const fetchDevs = async () => {
console.log("this might take some time....");
await delay(4000);
console.log("Done!");
};
const [getDev, isLoadingDev] = useLoading(fetchDevs);
useEffect(() => {
getDev();
}, []);
return (
<>
{isLoadingDev && <Loading />}
</>
);
};
```
> Tham khảo: [Custom reusable Hooks](https://blog.logrocket.com/advanced-react-hooks-creating-custom-reusable-hooks/)
# 4. Promise, Async/await
## 4.1. Sử dụng Axios.all hoặc Promise.all để call nhiều API, try..catch nếu xài async/await.
- Bad
```typescript=
const User = () => {
const fetchData = async () => {
const users = await axios.get('/api/users');
const rights = await axios.get('/api/rights');
return { users, rights };
};
//...
return (
<Grid />
);
};
```
- Good
```typescript=
const User = () => {
const fetchData = async () => {
try {
const [resUser, resRight] = await axios.all([
axios.get('/api/users'),
axios.get('/api/rights')
]);
return { users: resUser?.data, rights: resRight?.data };
} catch (ex: Exception) {
NotifyUtil.notify(ex.message);
}
};
//...
return (
<Grid />
);
};
```