Dùng React useEffect “đúng chuẩn”

Lời mở đầu

Chào các bạn, chắc hẳn ai trong các React Developer cũng biết đến một react-hook, một hook rất đặc biệt, một khái niệm mà nhiều người cần bán tán và ngay trong tiêu đề của bài blog cũng đã nói lên tên của hook đó, đó chính là useEffect . Và hôm nay chúng ta hay cũng đâm sâu vào nó nhé. Let’s go!!!

useEffect là một React API, và được xây dựng lên cùng với bộ React core. Theo như tiêu đề là “best practices” thường được hiểu là luyện tập, ôn luyện một cách tốt nhất hay sử dụng sao cho đúng, hợp lý, không bị “ngốc” ?. Với một tiêu đề nghe có vẻ “only for newbie” nhưng ở bài blog này, chúng ta sẽ không show ra các case sử dụng useEffect trong react app mà là để “avoid using useEffect in our react application”. Oh tại sao ta lại avoid một API mà do chính React team tạo ra. Chúng ta cùng deep dive nào!!!

Trước khi mình đi vào các case không cần sử dụng useEffect thì cùng đảo qua lại chút một concept mà tôi đoán ai trong các React developer nên/phải biết, đó chính là React-lifecycle.

Mình có lụm được ở đây một sơ đồ để biểu diễn về lifecycle của React

Sơ đồ này được thiết kế bởi chính một thành viên trong React Dev team reference

Chúng ta có thể dễ dàng nhìn thấy quá trình Render và Commit ở phía bên trái và trong mỗi quá trình đó thì ở bên trong component thực hiện hành động gì.

Sẽ khá là mất thời gian để chúng ta có thể quét lại được từng bước trong từng quá trình một, nên ở đây mình có thiết kế ra một bản “lite” để đơn giản hoá hơn sơ đồ về lifecycle.

Component cũng giống như chúng ta vậy. Có giai đoạn sinh ra (componentDidMount), giai đoạn lớn lên (componentDidUpdate) và cuối cùng là giai đoạn “đăng xuất” (componentWillUnmount). Vậy chúng ta có thể theo dõi, nắm bắt được quá trình component mutate này hay không? May mắn thay chính react-hook mà chúng ta đang nhắc tới có thể lắng nghe/theo dõi được sự thay đổi này. Một hook khá là tiềm năng và mạnh mẽ đúng không?

Bên cạnh những lợi ích vốn có mà hook này mang lại thì vẫn tiềm ẩn những vấn đề liên quan đến perfomance của app. Effects là một “lối thoát” từ kiến trúc React ở đây mình gọi là “con cừu đen”, nghe có vẻ lú đúng không? Hiểu nôm na ở đây có nghĩa là Effects đi ngược lại với kiến trúc React. Effect làm bạn bước ra khỏi React và sự đồng bộ components như một vài hệ thống bên ngoài như non-React widget, network, hay DOM Brower. Đọc thêm

Cùng quay lại vấn đề chính nào, ở đây mình sẽ đề cập đến một số cách mà mình có thể avoid được useEffect trong react app mà mình thấy khá hữu hiệu và thông dụng đối với mình.

Cập nhật state dựa trên props hoặc state

Giả định rằng, chúng ta có một component cùng với biến state lần lượt là firstNamelastName. Chúng ta muốn output ra một fullName từ 2 state khởi tạo trên bằng cách nối chúng vào với nhau. Hơn thế, chúng ta có thể muốn fullName được tự động update mỗi khi firstName hoặc lastName thay đổi. Bản năng của chúng ta trước tiên có thể thêm một fullName là một biến state và cập nhật chúng ở trong Effect.

Đoạn code trên có vẻ phức tạp hơn là cần thiết. Việc dùng effect ở đây cũng không thật sự hiểu quả không? Nó làm cho quá trình render chạy cùng với giá trị cũ của fullName, rồi sau đó mới re-renders cùng giá trị đã được update. Chúng ta có thể viết code một cách smart hơn, đơn giản hơn là chúng ta có thể remove chính state đó và thay bằng một normal variable.

Thay đổi state khi một prop thay đổi

Giả sử ta có một <Bar/> ****component và một state của component đó phụ thuộc vào props của component cha truyền xuống. Trong trường hợp dưới đây thì <Bar/> ****nhận vào count props. Và ở component <Boo/> ****ta có một state là count và một button để tăng giá trị của count lên một đơn vị.

Trong đoạn code trên các bạn có nhận ra rằng có vấn đề gì ở đây không ạ? Hãy vào đây rồi thử bấm “Click” xem liệu count có được cộng thêm không nhé. Và… không có sự thay đổi nào ở đây, tại sao lại vậy nhỉ? Initial tempCount chỉ nhận giá trị count được truyền vào lần đầu tiên là giá trị khởi tạo/đầu tiên thôi, những lần update sau của count thì biến tempCount sẽ không tự thay đổi mà mình sẽ cần phải dùng setTempCount để update theo.

Để component <Boo/> để update biến tempCount mỗi khi count thay đổi thì chúng ta cần dùng thêm useEffect với dependencies là count props để mỗi khi count thay đổi thì ta sẽ dùng setTempCount để update lại tempCount. Cảm giác useEffect là một thứ tà đạo gì đó rất dễ khiến chúng ta cầm và dùng nó. Cùng thử lại tại đây nào!

Okay vậy vấn đề state không update của Bar đã được xử lý. Nhưng mà cách này có thể chưa hẳn là một cách tối ưu nhất. Vì chúng ta đang tránh việc sử dụng useEffect nên mình sẽ không đi theo hướng này nữa. Vậy làm cách nào? How? Gợi ý cho bạn là có từ khoá “key”, nhớ từ khoá này chứ? Chúng ta thường dùng key là một props truyền vào component mỗi lần map một mảng để rồi render ra từng element của mảng đó ra. Đọc thêm

Cùng mình nhắc lại đôi chút về công dụng từ khoá key trong React. “Key” là một props đặc biệt được React define ra, chiếc “chìa khoá” này giúp React có thể tối ưu được về hiệu suất việc render lẫn re-render. Component update được dựa trên thuật toán Diffing Algorithm thuộc chuyên đề Reconciliation để tìm ra những sự khác biệt trong một DOM tree để rồi từ đó chỉ cần update những thứ mà cần thay đổi và giữ nguyên những element vốn có.

Ở đây, ta có một ví dụ đơn giản để dễ hình dung hơn. Trong ul có một list các li gồm các tên người. Giả sử khi chúng ta thêm một người vào list đó thì ở đây thì cả cái list đó sẽ bị re-render thay vì render mỗi phần tử mới được thêm vào. Và ở đây “key” sẽ là giải pháp để xử lý việc re-render “thừa”. Khi mà các phần tử con có props là key, React sử dụng những key này để kiểm tra xem những phần tử con trong tree gốc có khớp với những phần tử con trong sub-tree hay không. Ví dụ, việc thêm “key” vào ví dụ mà không hiệu quả ở trên có thể làm cho sự chuyển đổi của tree có hiệu quả hơn:

Bây giờ React biết rằng phần tử mà có key là “2014” là phần tử mới nên sẽ chỉ render phần tử đó ra thôi.

Khá là hay đúng không? React sẽ dựa vào key này để so sánh xem trong các list này có element nào có sự thay đổi hay được thêm mới để rồi update theo một cách optimize nhất.

Bây giờ cùng quay lại vấn đề ban đầu nào. Vậy làm sao chúng ta có thể dùng “key” để thay thế useEffect ở đây trong trường hợp này. Đơn giản thôi. Chúng ta chỉ cần truyền key với giá trị là count luôn.

Bằng cách này <Boo/> sẽ nhận thấy sự thay đổi xong rồi sẽ unmount component xong rồi re-mount lại component. Công việc sẽ đảm bảo việc tempCount luôn được lấy giá trị mới nhất.

Vậy là chúng ta đã remove được useEffect ra khỏi đoạn code và đoạn code nhìn gọn gàng hơn. Tuy nhiên cách này không hoàn toàn thay thế được useEffect trong mọi trường hợp vì cơ chế mutate của component <Bar/> này là hoàn toàn khác nhau. Với việc sử dụng effect thì component sẽ được unmount → update. Còn với việc sử dụng key thì component sẽ được re-mount lại hoàn toàn. Vậy nên chúng ta cũng cần phải đánh giá thêm rằng cách nào sẽ phù hợp hơn trong tình huống mà chúng ta gặp phải.

Lời kết

Effect thực sự là một concept mà có thể ai trong các React Dev đều đã biết, nhưng để có thể hiểu và áp dụng nó một cách có thể kiểm soát, hiệu quả và tối ưu nhất thì… xem tại đây nhé ?

LongPC

Comments

Let’s make a great impact together

Be a part of BraveBits to unlock your full potential and be proud of the impact you make.