# 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 /> ); }; ```