{
  "version": 1,
  "locale": "vi",
  "updated": "2022-03-13",
  "page": {
    "type": "article",
    "title": "2 kĩ thuật đơn giản để optimize React render",
    "description": "Lễ được nghỉ dài ngày nhưng lại trúng mùa dịch COVID-19, cũng không được đi đâu chơi nên thôi ở nhà viết blog cho các bạn. Bài viết hôm nay sẽ chia sẻ cho các...",
    "url": "https://egany.com/blogs/ecommerce-cro/2-ki-thuat-don-gian-de-optimize-react-render/",
    "markdown_url": "https://egany.com/blogs/ecommerce-cro/2-ki-thuat-don-gian-de-optimize-react-render.md",
    "json_url": "https://egany.com/blogs/ecommerce-cro/2-ki-thuat-don-gian-de-optimize-react-render.json",
    "group": "ecommerce-cro",
    "author": "Thành Nguyễn",
    "published": "2021-05-03T10:09:39.000Z",
    "modified": "2022-03-13T02:47:34.000Z",
    "categories": [
      "Engineering",
      "Optimization"
    ]
  },
  "organization": {
    "name": "EGANY",
    "legal_name": "CÔNG TY CỔ PHẦN CÔNG NGHỆ EGANY",
    "alternate_name": "EGANY Technology JSC",
    "url": "https://egany.com/",
    "address": "77/6 Hồ Thị Kỷ, Phường Vườn Lài, TP. Hồ Chí Minh",
    "telephone": "+84934269700",
    "email": "support@egany.com",
    "tax_id": "0313000791",
    "opening_hours": "Thứ 2 - Thứ 6, 9:00 - 17:00",
    "same_as": [
      "https://www.facebook.com/egany.tech/",
      "https://www.youtube.com/user/eganydotcom",
      "https://zaloapp.com/qr/p/15np7bg4k8s47?src=qr"
    ]
  },
  "headings": [
    {
      "level": 1,
      "text": "2 kĩ thuật đơn giản để optimize React render",
      "id": "2-ki-thuat-don-gian-de-optimize-react-render"
    },
    {
      "level": 2,
      "text": "Giới thiệu",
      "id": "gioi-thieu"
    },
    {
      "level": 2,
      "text": "Chuẩn bị",
      "id": "chuan-bi"
    },
    {
      "level": 3,
      "text": "1\\. Xài gì truyền nấy",
      "id": "1-xai-gi-truyen-nay"
    },
    {
      "level": 3,
      "text": "2\\. Profiling với React Developer Tools",
      "id": "2-profiling-voi-react-developer-tools"
    },
    {
      "level": 2,
      "text": "Optimize",
      "id": "optimize"
    },
    {
      "level": 3,
      "text": "1\\. React.memo",
      "id": "1-reactmemo"
    },
    {
      "level": 3,
      "text": "2\\. React.useCallback",
      "id": "2-reactusecallback"
    },
    {
      "level": 2,
      "text": "Lời kết",
      "id": "loi-ket"
    }
  ],
  "links": [
    {
      "text": "`React.memo`",
      "url": "https://reactjs.org/docs/react-api.html#reactmemo"
    },
    {
      "text": "`React.useCallback`",
      "url": "https://reactjs.org/docs/hooks-reference.html#usecallback"
    },
    {
      "text": "_Tachyons CSS_",
      "url": "https://tachyons.io/"
    }
  ],
  "sections": [
    {
      "id": "hero",
      "heading": "2 kĩ thuật đơn giản để optimize React render",
      "content_markdown": "Lễ được nghỉ dài ngày nhưng lại trúng mùa dịch COVID-19, cũng không được đi đâu chơi nên thôi ở nhà viết blog cho các bạn. Bài viết hôm nay sẽ chia sẻ cho các bạn **2 kĩ thuật đơn giản để optimize React render**.",
      "links": []
    },
    {
      "id": "gioi-thieu",
      "heading": "Giới thiệu",
      "content_markdown": "Chắc hẳn một số bạn đã từng gặp những khung search \"giật lag\" hoặc các input text khi gõ chữ rồi nhưng vài giây sau mới hiện trên giao diện. Đó là vấn đề mà chúng ta sẽ khắc phục ngày hôm nay. Optimize React render có thể hiểu là việc loại bỏ các lượt render thừa, giúp tăng tốc độ của ứng dụng cũng như gia tăng trải nghiệm của người dùng.\n\nCác \"component\" thường được optimize là `form` (tất nhiên là tính luôn cả input bên trong) và table, đặc biệt là `datatable` với dữ liệu trích xuất từ API.\n\n**Lưu ý:** Chỉ optimize khi ứng dụng của bạn có dấu hiệu chậm. Lạm dụng optimize quá nhiều đôi khi sẽ phản tác dụng và làm ứng dụng của bạn chậm hơn so với lúc chưa optimize cũng như phát sinh một số lỗi đi kèm (do sơ xuất trong quá trình thực hiện), rất khó để debug.",
      "links": []
    },
    {
      "id": "chuan-bi",
      "heading": "Chuẩn bị",
      "content_markdown": "![optimize react demo](https://cdn.egany.com/images/blogs/2021/05/demo.png)\n\nCài đặt extension *React Developer Tools* (Firefox | Google Chrome) để kiểm tra và hỗ trợ quá trình optimize.\n\nCác kĩ thuật bao gồm:\n\n- Xài gì truyền nấy (phân tách component)\n- [`React.memo`](https://reactjs.org/docs/react-api.html#reactmemo)\n- [`React.useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback)\n\nGithub repo được sử dụng trong bài viết.\n\n- Nhánh `main`: code chưa optimize\n- Nhánh `optimized`: như tên nhánh\n\nRepo chủ yếu nhằm mục đích demo nên tổ chức khá đơn giản:\n\n- `components/index.js`: khai báo các component sẽ sử dụng trong trang, chủ yếu là form input được styled bằng [_Tachyons CSS_](https://tachyons.io/)\n- `pages/index.js`: trang chính, sử dụng các components đã build để dụng một form đơn giản bao gồm `name`, `email` và `button` để submit form\n\n### 1\\. Xài gì truyền nấy\n\nHạn chế việc truyền dư `props` cho component.\n\nMình gặp rất nhiều trường hợp (tất nhiên là có mình của \"ngày hôm qua\") truyền dư props cho component, đặc biệt là các `wrapper` component. Ví dụ\n\n![](https://cdn.egany.com/images/blogs/2021/05/component-700x326.png)\n\nNhư các bạn thấy thì component `Dashboard` chỉ sử dụng `isMobile`. Nó chủ yếu là trung gian để truyền `props` tới các component con, cụ thể là `SearchInput` và `Datatable`.\n\nĐể giải quyết vấn đề này, chúng ta có thể viết lại như sau:\n\n![](https://cdn.egany.com/images/blogs/2021/05/component-after-700x383.png)\n\n> Bên trên chỉ là code ví dụ để các bạn dễ hình dung, không có trong repo đâu nhé 🙂\n\nTuy không trực tiếp giải quyết vấn đề render thừa nhưng mình nghĩ đây là một thói quen tốt mà các bạn nên áp dụng. Tất nhiên mọi thứ luôn có 2 mặt của nó, nếu các bạn muốn tìm hiểu sâu hơn thì có thể sử dụng từ khóa `props drilling in react` để đọc thêm nhé.\n\n### 2\\. Profiling với React Developer Tools\n\n> Các bước hướng dẫn bên dưới chỉ áp dụng cho repo mẫu của mình. Các bạn tự điều chỉnh lại theo repo của các bạn nhé.\n\nStart ứng dụng ở `development` mode với lệnh `yarn dev`. Truy cập vào ứng dụng tại http://localhost:3000\n\nMở `Chrome DevTools` hoặc `Firefox Developer Tools` (phím tắt F12 hoặc right-click vào trang và chọn *Inspect*)\n\nChọn *Profiler* như hình:\n\n![](https://cdn.egany.com/images/blogs/2021/05/open-profiler.png)\n\nNhấn vào icon bánh răng (cog) và thiết lập như trong hình:\n\n![](https://cdn.egany.com/images/blogs/2021/05/view-setting-700x149.png)  \n![](https://cdn.egany.com/images/blogs/2021/05/highlight-render-700x308.png)  \n![](https://cdn.egany.com/images/blogs/2021/05/why-render-700x203.png)\n\nSau khi thiết lập, các bạn có thể click vào icon tròn màu xanh dương để bắt đầu profile.\n\n![](https://cdn.egany.com/images/blogs/2021/05/start-profile.png)\n\nĐể profile hiệu quả, bạn **chỉ nên nhập một input bạn nghi ngờ** là lý do làm cho ứng dụng bị chậm. Ở đây mình chọn input `name` (Họ và tên). Sau khi nhập xong, nhấn vào icon tròn màu đỏ để dừng profile.\n\n![](https://cdn.egany.com/images/blogs/2021/05/stop-profile-670x450.png)\n\nKết quả sẽ là một biểu đồ (chart) như hình, gọi là *Flamegraph chart*. Đây là biểu đồ thể hiện thời gian render của các component. Màu càng nhạt đồng nghĩa với việc component render càng ít (màu xám là không render, màu đỏ là render lâu nhất).\n\n![](https://cdn.egany.com/images/blogs/2021/05/unoptimized-profile-685x450.png)\n\nBạn có thể chuyển qua *Ranked chart* để xem chi tiết thứ hạng render (sắp xếp từ lâu nhất tới nhanh nhất từ trên xuống) cũng như chi tiết vì sao component render bằng cách hover lên component muốn xem.\n\n![](https://cdn.egany.com/images/blogs/2021/05/styled-input-profile-700x330.png)  \n![](https://cdn.egany.com/images/blogs/2021/05/button-profile-700x262.png)\n\nNhư các bạn thấy, tuy mình chỉ đổi duy nhất `name` (Họ và tên) nhưng `email` và các component khác cũng render theo.\n\nTrong demo thì việc này là chấp nhận được vì chủ yếu các component đều phục vụ mục đích styling, không xử lý logic quá nhiều. Thế nhưng trong thực tế sẽ xuất hiện một số component xử lý rất nhiều logic, thậm chí là có gọi API phía bên trong nên việc render dư thừa sẽ ảnh hưởng ít nhiều tới performance của trang.\n\nKhi đã xác định được nguyên nhân render của component, mình sẽ bắt đầu optimize.",
      "links": [
        {
          "text": "`React.memo`",
          "url": "https://reactjs.org/docs/react-api.html#reactmemo"
        },
        {
          "text": "`React.useCallback`",
          "url": "https://reactjs.org/docs/hooks-reference.html#usecallback"
        },
        {
          "text": "_Tachyons CSS_",
          "url": "https://tachyons.io/"
        }
      ]
    },
    {
      "id": "optimize",
      "heading": "Optimize",
      "content_markdown": "### 1\\. React.memo\n\n> Nếu sử dụng _Class Component_, bạn có thể sử dụng `Pure Component` để thay thế cho `React.memo`\n\n`React.memo` là higher-order component (HOC) phục vụ cho mục đích optimize performance. `React.memo` sẽ \"nhớ\" lần render cuối của component và `props` tại thời điểm đó. Chỉ khi `props` thay đổi thì `React.memo` mới thực hiện render lại component.\n\nMình sẽ áp dụng nó để optimize `Heading`, `StyledInput`, `StyledLabel` và `StyledButton`. Mình không áp dụng cho `Wrapper` hay `StyledForm` vì chúng là \"wrapper\" component, khi `children` thay đổi thì chúng sẽ render lại, mà tần suất thay đổi của các component bên trong khá là cao nên có \"nhớ\" thì cũng không có tác dụng gì.\n\n![](https://cdn.egany.com/images/blogs/2021/05/optimize-react-memo-1024x975.png)\n\nTiến hành profile lại sau khi optimize bằng `React.memo`, mình nhận thấy `Heading`, `StyledLabel` cũng như `StyledButton` không còn render nữa. Tốt hơn rất nhiều rồi phải không nào? Tuy nhiên, `email` input vẫn render vì props `onChange` thay đổi.\n\n![](https://cdn.egany.com/images/blogs/2021/05/after-memo-profile-700x337.png)\n\nTại sao `onChange` lại thay đổi?\n\n![](https://cdn.egany.com/images/blogs/2021/05/handle-email-change-700x397.png)\n\nVới đoạn code như trên, mỗi lần `Home` render lại, một function `handleEmailChange` (props `onChange`) lại được tạo ra khiến cho `StyledInput` của `email` bị thay đổi. Chúng ta có thể optimize vấn đề này bằng kĩ thuật tiếp theo: `React.useCallback`\n\n### 2\\. React.useCallback\n\nCũng giống như cách `React.memo` hoạt động, `React.useCallback` sẽ \"nhớ\" function thay vì lần cuối render. Nếu `React.memo` thay đổi dựa trên `props` thì `React.useCallback` sẽ thay đổi dựa trên danh sách *dependencies* được khai báo.\n\n`handleEmailChange` sẽ được viết lại như sau:\n\n![](https://cdn.egany.com/images/blogs/2021/05/optimize-react-use-callback-700x354.png)\n\nDo không phụ thuộc vào bất kỳ biến nào (từ props hoặc các thư viện bên ngoài, vd: `router`) nên *dependencies* của mình sẽ là danh sách rỗng `[]`. `setEmail` do là state nên sẽ không đổi, các bạn có thể khai báo hoặc không đều được. Sẵn tiện thì mình áp dụng luôn cho `handleNameChange` nhé.\n\nKết quả profile sau khi áp dụng `React.useCallback`:\n\n![](https://cdn.egany.com/images/blogs/2021/05/optimized-700x337.png)",
      "links": []
    },
    {
      "id": "loi-ket",
      "heading": "Lời kết",
      "content_markdown": "Chỉ với vài thao tác đơn giản, các bạn đã loại bỏ được khá nhiều lượt render thừa trong ứng dụng của mình. Ngoài các kĩ thuật được chia sẻ trong bài viết, các bạn có thể đọc thêm về React.useMemo hoặc debounce/throttle để optimize sâu hơn nữa nhé.\n\nHi vọng qua bài viết các bạn sẽ tự optimize performance cho các ứng dụng hiện tại của mình. Nếu có bất kì thắc mắc hoặc góp ý thì cứ comment lại bên dưới cho mình nhé. Đừng quên giữ gìn sức khỏe cho mình và người thân nhé trong lúc tình hình dịch đang diễn biến phức tạp.\n\nHappy hacking!\n\n> Mình cũng có viết chuỗi bài [Frontend Du Ký](https://egany.com/blogs/tag/frontend-du-ky/ \"Frontend Du Ký\"), kể về hành trình phát triển sản phẩm của team Frontend EGANY đã đi qua. Bạn cũng có thể ghé qua xem thử, mình tin nó sẽ giúp các bạn bớt \"giẫm mìn\" hơn rất nhiều \n> \n>  Nếu bạn thấy hành trình bọn mình thú vị và muốn cùng nhau viết tiếp thì [xem thêm tại đây](https://egany.com/blogs/front-end-engineer-reactjs-nextjs-typescript-svelte/ \"xem thêm tại đây\") nhé.",
      "links": []
    }
  ]
}