Trong lập trình chúng ta thường sẽ gặp phải một số vấn đề chung lặp đi lặp lại trong các dự án khác nhau và sẽ có những kỹ thuật (lời giải) tương tự nhau để giải quyết bài toán đó. Khái niệm pattern chỉ những “mẫu” lời giải đó, nó thường được tổng hợp và khuyên dùng trong lập trình để xử lý và tránh các lỗi có thể gặp phải. Với React, chúng ta có một vài pattern được khuyên sử dụng và bài viết hôm nay chúng ta cùng tìm hiểu về chúng cùng các ví dụ cụ thể nhé.
Higher-Order Function/Component
Higher-Order functions là một hàm nhận một hàm khác dưới dạng đối số hoặc trả về một hàm dưới dạng đầu ra. Chúng ta có ví dụ dưới đây:
function greaterThan(n) { return (m) => m > n; } const greaterThan10 = greaterThan(10); console.log(greaterThan10(11)); // true
Ở ví dụ trên, hàm greaterThan trả về 1 function so sánh giá trị truyền vào với đối số xác định trước, cụ thể với hàm greaterThan10 thì đối số là 10. Từ đó chúng ta có thể tạo ra các function khác nhau bằng cách call function greaterThan và đối số truyền vào. Đấy chính là lợi ích của higher-order function.
Từ khái niệm trên, trong React chúng ta cũng có khái niệm Higher-Order component (HOC), đó là 1 component mà:
- Nhận đầu vào 1 component và trả về 1 component mới
- Biến đổi 1 component thành 1 component khác
Chúng ta có ví dụ dưới đây:
const withColor = Element => props => <Element {...props} color="red" /> const Button = () => { return <button>My Button</button> } const ColoredButton = withColor(Button)
Ở đây chúng ta sử dụng HOC withColor để tạo ra component ColoredButton từ component Button với mục đích là set mặc định giá trị màu đỏ cho button đó.
Thực tế có rất nhiều thư viện sử dụng kỹ thuật này trong React, điển hình như Redux. Để có thể tạo ra những state dùng chung(store) và các action tác động lên toàn bộ component thì Redux tạo ra Provider để bọc toàn bộ các components trong ứng dụng, và đấy cũng chính là 1 HOC.
Tham khảo việc làm React mới nhất trên TopDev
State Hoisting
Bạn nào làm React chắc cũng đều nắm được về stateless và statefull component, hiểu đơn giản thì stateless sẽ không giữ biến state nào cả. Trong 1 project, việc tạo ra các stateless component được khuyến khích vì lúc đó bạn có thể yên tâm tái sử dụng hay xóa chúng đi mà không lo việc ảnh hưởng đến logic của toàn hệ thống. Chúng ta cùng xem xét bài toán dưới đây:
<div> <CarHeader /> <CarList /> </div>; const CarHeader = () => { const [cars, setCars] = useState([]); useEffect(() => { const cars = getData(); //this fuctional function calls an API setCars(cars); }, []); return <div> No of Cars: {cars.length}</div>; }; const CarList = () => { const [cars, setCars] = useState([]); useEffect(() => { const cars = getData(); //this fuctional function calls an API setCars(cars); }, []); return ( <> {cars.map((car) => ( <div>Car:{car.name}</div> ))} </> ); };
2 components CarHeader và CarList đều sử dụng cùng dữ liệu là danh sách car lấy từ API trả về, cách tốt hơn ở đây là sẽ khai báo state cars ở trong component cha, loại bỏ biến state trong 2 component con để biến chúng thành các stateless component.
useEffect(() => { const cars = getData(); //this fuctional function calls an API setCars(cars); }, []); return ( <div> <CarHeader cars /> <CarList cars /> </div> );
Kỹ thuật này gọi là state hoisting, ngoài việc tạo ra các staless component con, thì lúc này dữ liệu cũng được lấy từ 1 nguồn duy nhất (single source of truth) giúp các component đồng bộ với nhau, việc debug ứng dụng cũng trở nên dễ dàng hơn.
Ngoài việc đẩy data ra component cha để xử lý, thì chúng ta cũng sử dụng kỹ thuật này vơi các events trong các phần tử con có tương tác với người dùng, ví dụ như các thẻ Input.
const Name = ({ onChange }) => ( <input onChange={(e) => onChange(e.target.value)} /> ); const NameContainer = () => { const [name, setName] = useState(""); return ( <> <div>Your name: ${name}</div> <Name onChange={(newName) => setName(newName)} /> </> ); };
Trong ví dụ trên, thẻ input tạo ra trong component Name và chúng ta muốn lấy được dữ liệu ra trong component cha của nó, không tác động thêm state vào Name, cách giải quyết là đẩy event onChange ra ngoài để component cha (NameContainer) sử dụng, vẫn đảm bảo được tính “stateless” của Name.
Proxy Component
Proxy pattern là 1 thiết kế mẫu giúp bạn kiểm soát truy cập đến đối tượng ban đầu, giúp bạn hạn chế việc sai sót trong quá trình tái sử dụng component ban đầu cũng như tạo ra 1 component mới.
Ví dụ chúng ta có 1 component button được sử dụng trong App như sau:
<button type="button"> </button> // button type // 1. button // 2. reset // 3. submit
Nếu trong App của chúng ta sử dụng thẻ button này trực tiếp thì sẽ luôn phải khai báo type cho nó, bài toán đặt ra là nếu chúng ta không cho phép sử dụng type=submit trong ứng dụng của mình thì sẽ thế nào? Cách giải quyết là tạo ra 1 component mới với thuộc tính type=button để đảm bảo mỗi button được tạo ra luôn có chung thuộc tính type. Kỹ thuật này gọi là Proxy, nó giúp chúng ta đảm bảo tính nhất quán trong thuộc tính (attribute) của component, đồng thời hạn chế việc truy cập đến các thuộc tính khác trong component gốc.
const Button = (props) => { return <button type="button" {...props}></button>; }; return ( <> <Button /> <Button className="CTA">Send Money</Button> </> );
Chúng ta cũng có thể sử dụng kỹ thuật này cho các thuộc tính của style để tạo ra các Style component.
import classnames from "classnames"; const PrimaryBtn = (props) => <Btn {...props} primary />; const Btn = ({ className, primary, ...props }) => ( <button type="button" className={classnames("btn", primary && "btn-primary", className)} {...props} /> ); <button type="button" className="btn btn-primary"></button>;
3 cách khai báo code trên sẽ cùng cho ra 1 kết quả giống nhau, trong trường hợp này PrimaryBtn component được sử dụng như 1 Proxy cho component button với cách khai báo gọn và thống nhất hơn.
Kết bài
Trên đây là 3 patterns sử dụng trong lập trình React mà bạn sẽ thường xuyên gặp và áp dụng. Áp dụng đúng và hợp lý các patterns sẽ giúp code của bạn clear và tránh các lỗi có thể xảy ra; nó cũng sẽ giúp bạn nâng cao kỹ năng lập trình của mình. Hy vọng bài viết hữu ích dành cho bạn, hẹn gặp lại các bạn trong các bài viết tiếp theo của mình.
Tác giả: Phạm Anh Khoa
Xem thêm:
- Thông não Java Design Pattern – Dependency Injection
- Repository design pattern hoàn thiện trong Laravel
- Tìm hiểu Java Design Pattern – Service Locator
Xem thêm Việc làm IT hấp dẫn trên TopDev