Xin chào tất cả các bạn, mình là Quân, sau nhiều ngày vắng bóng để reset lại bản thân cũng như chuẩn bị cho dự án sắp tới, hôm nay mình sẽ khởi động một series mới về ReactJS. Và bài đầu tiên này mình sẽ giải thích rõ ràng chi tiết về React Hooks, một công nghệ mới của ReactJS mà theo mình nghĩ, bất kể ai dù mới tiếp cận React hay là đã làm việc với React rồi thì cũng nên update nhé.
“Bài này nằm trong loạt bài Lập Trình ReactJS (Hooks) & Xây dựng ứng dụng Todo List hoàn chỉnh (đang cập nhật) trên trang blog chính thức trungquandev.com“
Những nội dung có trong bài:
- React Hooks là gì?
- Những lợi ích của Hooks (Functional Component) so với Class Component.
2.1. React.createClass
2.2. React.Component
2.3. React Hooks & Functional Component - Hooks có thể thay thế Redux không?
1. React Hooks là gì?
Đầu tiên, mình sẽ để nguyên câu giới thiệu Hooks trên trang chủ của ReactJS nhé:
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
reactjs.org
Mình tạm dịch:
Hooks là một sự bổ sung mới kể từ phiên bản React 16.8 trở về sau. Chúng nó (ở đây là các Hook APIs) cho phép bạn sử dụng trạng thái (State) cũng như các tính năng khác của React mà không cần phải viết một Class.
trungquandev.com
Ok ngay khi đọc xong cái giới thiệu thì các bạn cũng đủ hình dung được là thằng React Hooks này sinh ra để thay thế dần cách viết code Class Component rồi phải không?
Lưu ý ở trên mình có dùng từ “thay thế dần” chứ không phải là thay thế hoàn toàn nhé, bởi vì đơn giản là nếu các bạn hiện tại đang có sẵn một dự án ReactJS code theo kiểu Class Component rồi thì các bạn vẫn có thể thoải mái sử dụng Hooks trong dự án đó, chỉ cần đảm bảo version của React, React DOM cũng như các dependencies khác có liên quan được cập nhật mới nhất là oke.
Rồi, mình nghĩ là về khái niệm chỉ nên ngắn gọn rõ ràng như vậy thôi. Để trong phần tiếp theo đây, chúng ta sẽ đi phân tích sâu hơn về những lợi ích của Hooks – Functional Components so với Class Components xem tại sao nó lại được sinh ra nhé.
2. Những lợi ích của Hooks (Functional Component) so với Class Component.
Để cho các bạn có một cái nhìn thú vị và đầy đủ nhất, thì hướng tiếp cận mà mình sẽ mang đến cho các bạn ngày hôm nay đó là đi dạo một vòng lịch sử cách viết code React từ đời đầu trở đi cho tới ngày hôm nay nhé. Từ đó các bạn cũng sẽ dễ dàng nắm bắt được lợi ích của thằng React Hooks hơn.
Xuyên suốt dòng lịch sử của React này, mình sẽ sử dụng các đoạn ví dụ code mà chúng nó cùng để thực hiện một tính năng giống nhau đó là: “Gọi API để lấy thông tin một con mèo, lưu vào State và hiển thị thông tin con mèo đó ra trình duyệt” nhé. Đại loại thì kết quả của những đoạn code đó sẽ đơn giản là render ra 2 dòng này:
2.1. React.createClass
Đây là cách đầu tiên mà React dùng để tạo Class, tất cả các dữ liệu, các hàm mà chúng ta sử dụng để tạo Component như getInitialState, componentDidMount, componentDidUpdate, render…vv thì đều thuộc một Object và được truyền vào hàm createClass như một tham số.
/** * Created by trungquandev.com's author on 02/19/2021. */ import React from "react" import ReactDOM from "react-dom" const CatComponent = React.createClass({ getInitialState() { return { currentCat: null } }, componentDidMount() { this.getCat(this.props.id) }, componentDidUpdate(prevProps) { if (prevProps.id !== this.props.id) { this.getCat(this.props.id) } }, getCat(id) { // Mình sẽ giả lập gọi api get cat bằng hàm setTimeout nhé, hành động thực tế ở đây là sẽ phải gọi api để lấy thông tin con mèo theo id nha // Và Sau khi lấy được thông tin mèo từ api về rồi thì lưu lại vào State setTimeout(() => { this.setState({ currentCat: { name: 'trungquandev', type: 'Vietnamese cat' } }) }, 1000) }, render() { const { currentCat } = this.state if (!currentCat) return null // or return a loading return ( <div className="cat-details"> <p className="name">Name: <strong>{currentCat.name}</strong></p> <p className="type">Type: <strong>{currentCat.type}</strong></p> </div> ) } }) const rootElement = document.getElementById("root") ReactDOM.render(<CatComponent />, rootElement)
Lý do mà ban đầu React sử dụng API createClass là bởi vì tại thời điểm đó Javascript chưa có định nghĩa Class. Về sau này từ ES6 trở đi, Javascript mới bắt đầu giới thiệu từ khóa Class. Và dĩ nhiên React đã update ngay sau đó. Lúc này chúng ta sẽ tiếp tục đến với React.Component
2.2. React.Component
We figured that we’re not in the business of designing a class system. We just want to use whatever is the idiomatic JavaScript way of creating classes.
reactjs.org v0.13.0 Release
Câu quote ở trên là mình lấy nguyên gốc tiếp từ trang chủ React nhé.
Kể từ phiên bản 0.13.0 thì React đã giới thiệu React.Component cho phép mọi người tạo component từ Native Class của Javascript ES6 như mình đã nói ở trên. Chúng ta sẽ viết lại ví dụ code như sau:
/** * Created by trungquandev.com's author on 02/19/2021. */ import React from "react" import ReactDOM from "react-dom" class CatComponent extends React.Component { constructor(props) { super(props) this.state = { currentCat: null } this.getCat = this.getCat.bind(this) } componentDidMount() { this.getCat(this.props.id) } componentDidUpdate(prevProps) { if (prevProps.id !== this.props.id) { this.getCat(this.props.id) } } getCat(id) { // Mình sẽ giả lập gọi api get cat bằng hàm setTimeout nhé, hành động thực tế ở đây là sẽ phải gọi api để lấy thông tin con mèo theo id nha // Và Sau khi lấy được thông tin mèo từ api về rồi thì lưu lại vào State setTimeout(() => { this.setState({ currentCat: { name: "trungquandev", type: "Vietnamese cat" } }) }, 1000) } render() { const { currentCat } = this.state if (!currentCat) return null // or return a loading return ( <div className="cat-details"> <p className="name">Name: <strong>{currentCat.name}</strong></p> <p className="type">Type: <strong>{currentCat.type}</strong></p> </div> ) } } const rootElement = document.getElementById("root") ReactDOM.render(<CatComponent />, rootElement)
Đến đây thì các bạn có thể thấy rằng code React theo kiểu Class Component cũng đã khá clear rõ ràng, đi theo đúng chuẩn ES6 mới của Javascript rồi phải không? Nhưng dĩ nhiên là nó vẫn còn một số hạn chế kiểu như thế này:
- Đầu tiên phải kể đến thằng constructor(props) sau đây:
constructor (props) { super(props) // @@ ... }
Với việc chúng ta sử dụng Class Component và khởi tạo State trong hàm khởi tạo constructor thì vô hình chung theo chuẩn kỹ thuật ECMAScript, khi bạn muốn extends (thừa kế, mở rộng) ra một Class con khác (trong trường hợp này là extends React.Component) thì bạn luôn luôn phải gọi super() đầu tiên rồi mới có thể sử dụng được. Và đặc biệt riêng đối với React là bạn lúc nào cũng phải truyền thằng props cho hàm super hầu như ở mọi Component. =))))
- Vấn đề thứ hai là dòng Binding này:
constructor (props) { ... this.getCat = this.getCat.bind(this) // @@ }
Khi sử dụng createClass thì React sẽ tự động bind các methods vào instance của component. Còn ở React.Component, chúng ta phải thêm dòng this rồi .bind như trên. Điều này khiến cho rất nhiều các dev trên khắp thế giới không hiểu được từ khóa “this” hoạt động như thế nào. Họ chỉ đơn giản hiểu là phải có cái dòng bind kia trong hàm khởi tạo constructor thì code mới chạy được thay vì chỉ cần nhớ mỗi method, nếu không thì thường là bạn sẽ gặp phải cái lỗi kiểu kiểu này “Can not read property … of undefined.”
Oke mình viết đến đây và cũng đoán được là sẽ có nhiều bạn đang nheo mắt nhăn trán lại và cảm thấy 2 cái lý do ở trên kia của mình có vấn đề gì đâu nhỉ, nó vẫn đúng mà?
trungquandev
Ừ thì mình công nhận luôn là đúng, chẳng có gì sai trái ở đây cả nhé =))) Chính dự án React mà mình đang làm ở công ty cũng phải viết Class Component và các thứ code như ở trên đây :v
Nhưng có một khía cạnh mấu chốt mà mình muốn các bạn hiểu ở đây đó là: Đối với một lập trình viên thì việc viết code dư thừa như trên sẽ vẫn là một vấn đề phiền toái khó chịu nếu như ngày nào bạn cũng phải viết đi viết lại những cái đó, không chỉ một lần đâu mà còn là rất rất nhiều lần tùy thuộc vào độ to của dự án mà bạn đang làm nhé. Với cá nhân mình thì mình thấy code như thế vừa mệt vừa rườm rà lại còn dài dòng nữa.
Dĩ nhiên là luôn có cách để tối ưu các trường hợp ở trên, ví dụ như dùng Class Fields để không phải viết constructor như này:
class CatComponent extends React.Component { state = { currentCat: null } ... }
Hoặc sử dụng kiểu khai báo Arrow Function để giải quyết vấn đề Binding:
class CatComponent extends React.Component { ... // arrow function getCat = (id) => { // your logic code... } }
Nhưng mà mọi chuyện chưa có dừng lại đâu, còn một cái vấn đề thứ 3 khá khó chịu nữa đó là chúng ta đang bị Duplicate Logic ở 2 thằng componentDidMount và componentDidUpdate hoặc sẽ còn có thêm cả cái componentWillReceiveProps nữa nếu bạn nào đã từng sử dụng tới nó sẽ biết.
Trong ví dụ nhỏ ở trên của mình thì code chưa có gì nhiều, chỉ mới duplicate có 2 lần cái dòng gọi api this.getCat(this.props.id) nhưng với những Class thực tế có nhiều thứ phải xử lý, thì việc bị duplicate logic như vậy là một sự khó chịu khủng khiếp đấy chứ không phải đơn giản đâu nhé =)))
Ngoài ra ví dụ bây giờ chúng ta cần làm một Component khác và cũng muốn dùng logic của state “currentCat” thì sao? Thông thường thì chúng ta sẽ phải clone lại đống logic xử lý của CatComponent sang bên Component mới kia hoặc có một cách khác hay hơn là tạo một HOC (Higher-Order Component) để đóng gói toàn bộ logic của CatComponent lại, sau đó pass thằng state “currentCat” như là một props để cho những thằng component khác có thể gọi đến và sử dụng.
Nhưng mà đây là một quá trình viết code trông rất phức tạp và khó đọc, hay nói ngắn gọn là hại não nhé. Nên mình cũng lười không viết thêm mấy đoạn code vào đây cho đỡ dài dòng luôn 😀
Dev đơn giản thôi, để cho bộ não được thư giãn chút đi nhé =))
trungquandev
2.3. React Hooks & Functional Component
Từ những hạn chế mà mình đã tìm hiểu và giải thích ở trên cũng như trong quá trình đi làm thực tế chính mình gặp phải và tất nhiên là mình không phải người duy nhất cảm thấy khó chịu, mà còn cả một phần cộng đồng React Developer trên thế giới cũng thấy như vậy. Đó cũng là lý do mà React Team đã cho ra đời đứa con mới: Hooks xuất hiện.
Trước tiên mình có sưu tầm được một cái quote khá hay như thế này:
Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.
John Carmack
Đôi khi, việc triển khai code đơn giản và thanh lịch chỉ đơn giản là một function. Không phải method, không phải class, cũng không phải framework, chỉ đơn giản là một function.
trungquandev translate :))
Kể từ phiên bản React v16.8.0, React cung cấp cho chúng ta các hooks như useState, useEffect, useContext, useReducer…vv hoặc bạn cũng có thể tự tạo được custom hook cho riêng mình để giải quyết những vấn đề hạn chế của Class Component cũng như tiếp cận cách viết code theo hướng Functional component dễ dàng hơn.
Mình sẽ đi sâu vào lần lượt các hooks trên ở trong những bài viết sau, còn bây giờ chúng ta sẽ đi viết lại ví dụ code của ngày hôm nay với hooks xem điều kỳ diệu gì sẽ xảy ra nhé 😀
/** * Created by trungquandev.com's author on 02/19/2021. */ import React, { useState, useEffect } from "react" import ReactDOM from "react-dom" function CatComponent(props) { const [currentCat, setCurrentCat] = useState(null) useEffect(() => { function getCat(id) { // Giả lập hành động call api return new Promise((resolve, reject) => { setTimeout(() => resolve({ name: "trungquandev", type: "Vietnamese cat" }), 1000) }) } getCat(props.id).then(cat => setCurrentCat(cat)).catch(console.error) }, [props.id]) if (!currentCat) return null // or return a loading return ( <div className="cat-details"> <p className="name">Name: <strong>{currentCat.name}</strong></p> <p className="type">Type: <strong>{currentCat.type}</strong></p> </div> ) } const rootElement = document.getElementById("root") ReactDOM.render(<CatComponent />, rootElement)
Ở đoạn code trên mình có sử dụng 2 hooks là useState() và useEffect(), điều đầu tiên mà các bạn có thể dễ dàng nhận thấy nhất là code đã ngắn gọn nhẹ đi rất nhiều phải không?
Tiếp theo là không còn constructor, không còn bingding và tuyệt vời hơn nữa là logic code không còn bị duplicate như ban đầu sử dụng Class Component.
Còn về vấn đề chúng ta muốn dùng logic của state “currentCat” ở nhiều Component khác thì sao?
Đơn giản là chúng ta sẽ viết lại function CatComponent thành một custom hook riêng ví dụ như useCatComponent() chẳng hạn. Đón chờ bài hướng dẫn của mình về thằng custom hook này ở những post tiếp theo nhé 😀
(Ngoài lề quen thuộc: Cảnh báo này dành cho bất kể trang web nào khác mà có ý định copy bài không phải của các bạn thì vui lòng tôn trọng người viết bài chân chính, tuyệt đối không được xào nấu, chỉnh sửa linh tinh bài viết của mình cụ thể là không được xóa những liên kết (link) cũng như tự ý xóa các câu thoại của mình trong toàn bộ bài viết rồi post lại lên trang của các bạn như kiểu đây là bài của các bạn vậy, nếu tham khảo thì hãy để lại liên kết nguồn rõ ràng từ trang trungquandev, mình sẽ thường xuyên dùng tool để check, và nếu phát hiện ra thì cứ đơn giản là chắc chắn sẽ ăn report DMCA nhé.)
3. Hooks có thể thay thế Redux không?
Thực ra thì đây là một câu hỏi thú vị mà mình đã từng nghe qua nên mình xếp nó riêng thành một phần trong bài viết này vì nhiều bạn chưa biết có thể sẽ nhầm lẫn.
Câu trả lời là không nhé, Hooks không thay thế Redux mà chúng nó có thể kết hợp cùng với nhau trong một dự án để đạt được hiệu quả tốt nhất.
Component state for component state, Redux for application state.
Mình sẽ không dịch câu trên sang tiếng Việt để giữ cho nó đúng với bản chất nhất có thể. Các bạn có thể tham khảo thêm về Hooks cũng như Redux ở một bài viết cực kỳ thú vị này nhé, hoá ra trên thế giới cũng có rất nhiều người để ý tới vấn đề này :)))
Link bài viết: Do React Hooks Replace Redux?
Ok vậy là kết thúc bài hôm nay chúng ta đã cùng nhau đi tìm hiểu về React Hooks, Class Component, Functional Component cũng như dạo qua một chút về lịch sử của React, từ đó hiểu được những hạn chế của Class Component để rồi Hooks ra đời.
Có thể trong tương lai sẽ có một thứ gì đó hay hơn Hooks của hiện tại, đó là điều tất yếu thôi vì công nghệ luôn được cập nhật, thay đổi để trở nên hoàn hảo hơn cũng như con người chúng ta mỗi ngày đều học hỏi để trở nên tốt hơn phiên bản ngày của hôm qua vậy. Điều quan trọng là chúng ta có sẵn sàng tiếp nhận sự thay đổi không hay vẫn sẽ cố hữu muốn ở lại. Đó là quyết định của chính bạn.
Và nếu thấy bài viết này bổ ích, các bạn có thể ủng hộ mình bằng cách like page trungquandev trên facebook và subscribe kênh youtube của mình nhé, link mình sẽ để bên dưới, trong năm 2021 này mình sẽ cố gắng để xuất bản một khoá học MERN Stack chất lượng lên youtube và đặc biệt là miễn phí tới cộng đồng.
Facebook: https://facebook.com/trungquandev
Youtube: Trungquandev Official on Youtube
Cảm ơn các bạn đã dành thời gian đọc bài viết.
Xin chào và hẹn gặp lại các bạn ở những bài viết tiếp theo.
Best Regards – Trung Quân – Green Cat
Mình có tham khảo kiến thức từ một số nguồn bên dưới để biên soạn bài viết này theo văn phong của mình, các bạn có thể tham khảo thêm để trau dồi kỹ năng tiếng Anh nhé:
https://reactjs.org/docs/hooks-intro.html
https://reactjs.org/blog/2015/01/27/react-v0.13.0-beta-1.html
https://ui.dev/why-react-hooks/
“Thanks for awesome knowledges.”
“ From author: trungquandev ”