Hoc (Higher Order Components) trong typescript
HOC được hiểu sơ qua như một nhà máy sản xuất component
Lời nói đầu
Xin chào các bạn, bài viết trước mình đã nói qua về HOC trong Javascript. Tuy nhiên HOC sẽ được viết như nào trong Typescript
Yêu cầu kiến thức cần có
- Javascript và cũng như chút hiểu biết về HOC trong js (tìm hiểu)
-
Typescrip và ý nghĩa của nó. Đặc biệt trong bài này là
Generic class
(tìm hiểu)
*NOTE: Sắp tới mình sẽ viết về Generic*
Hãy bắt đầu thông qua một số mục đích của HOC
Tiêm props mới vào component con
Đôi khi bạn muốn tiêm thêm prop từ một nơi nào đó, từ một global store (redux hoặc mobx), hoặc một provider. Và không muốn cứ truyền đi truyền lại props. Context là một lựa chọn tuyệt vời, NHƯNG những giá trị được truyền từ context chỉ được dùng trong render function. HOC sẽ cung cấp các giá trị đấy như là các props
Đầu tiên chúng ta sẽ định nghĩa cho inject props
interface WithThemeProps {
primaryColor: string;
}
Tiếp theo chúng ta viết một HOC như sau để dùng WithThemeProps
mà chúng ta định nghĩa ở trên
/**
* T là generic type đại diện cho type props của component con
*
* T extends WithThemeProps nghĩa là types của props
* con sẽ được mở rộng thêm bởi interface đã định nghĩa ở trên
*
* Do T mở rộng từ WithThemeProps sẽ ràng buộc cho prop truyền vào phải
* có một prop là primaryColor
*/
export function withTheme<T extends WithThemeProps = WithThemeProps>(
WrappedComponent: React.ComponentType<T> // Nhận vào một component (có thể là hook hoặc class với type là T)
) {
// Try to create a nice displayName for React Dev Tools.
const displayName =
WrappedComponent.displayName || WrappedComponent.name || "Component";
// Ở đây chúng ta tạo ra một component mới, và việc ```premade component``` sẽ được tiến hành ở đây
// Component mới có type của prop là T - đây là bước chúng ta gộp type của component con và HOC vào với nhau
const ComponentWithTheme = (props: T) => {
// Tiếp theo chúng ta có thể fecth dữ liệu hoặc truyền một giá trị props sẽ được thêm vào
const themeProps = { injectedProp: "injected" };
// Truyền prop được tiêm và prop được gộp lại
return <WrappedComponent {...themeProps} {...(props as T)} />;
};
ComponentWithTheme.displayName = `withTheme(${displayName})`;
return ComponentWithTheme;
}
Kế đó chúng ta định nghĩa một một component Button
và dùng HOC đã viết ở trên bọc ngoài
interface Props extends WithThemeProps {
children: React.ReactNode;
}
class MyButton extends React.Component<Props> {
render() {
return (
<>
<button style={{ color: this.props.primaryColor }}>
Button with color
</button>
<p>this Component is injected props: {this.props.injectedProp}</p>
</>
);
}
private someInternalMethod() {
// The theme values are also available as props here.
}
}
export default withTheme(MyButton);
Dùng như sau
// Lỗi: yêu cầu prop primaryColor
<ButtonWithTheme>This is a button</ButtonWithTheme>
// ok
<ButtonWithTheme primaryColor="red">This is a button</ButtonWithTheme>
Exclude một prop được chỉ định
Có hai component như sau:
type DogProps {
name: string
owner: string
}
function Dog({name, owner}: DogProps) {
return <div> Woof: {name}, Owner: {owner}</div>
}
type CatProps = {
lives: number;
owner: string;
};
function Cat({ lives, owner }: CatProps) {
return (
<div>
{" "}
Meow: {lives}, Owner: {owner}
</div>
);
}
Các bạn để ý là cả hai component trên đều có props là owner. Mỗi lần dùng component chúng ta đều phải viết như sau
<Dog name="fido" owner="swyx" />
<Cat lives={9} owner="swyx" />
Cách viết trên khiến việc trùng lặp code diễn ra thường xuyên, đã đến lúc HOC ra tay
/**
* Thú thật code này khó hiểu vãi beep =)))
* - function withOwner sẽ nhận vào một biến owner và trả lại một function mới
* - function mới này lại return lại một component :)))
*/
function withOwner(owner: string) {
return function <T extends { owner: string }>( // (1)
Component: React.ComponentType<T>
) {
return function (props: Omit<T, "owner">): JSX.Element { // (2)
const newProps = { ...props, owner } as T; // (3)
return <Component {...newProps} />;
};
};
}
(1) Đây là một Generic function
(2) Omit
là Utility Types
mà TS cung cấp để loại bỏ một type chỉ định. Việc loại bỏ như này sẽ cấm component mới không được nhận props là owner
(3) Gộp props component con với biến owner
truyền vào ở trên
Cách dùng HOC trên như sau
const OwnedCat = withOwner("Cuong")(Cat);
<OwnedCat lives={9} owner="swyx" />; // typeError
<OwnedCat lives={9} />; // OK
const OwnedDog = withOwner("John")(Dog);
<OwnedDog name="fido" owner="swyx" />; // typeError
<OwnedDog name="fido" />; // OK
Codesanboxes
Để tiện cho các bạn đọc và thực hành cũng như tìm hiểu, mình đã chuẩn bị codesandbox
Kết luận
Vậy là mình đã nói qua về HOC trong Typescript. Mỗi các viết đều có cái hay riêng. Tuy nhiên đặc điểm chung là cả hai đều rất khó học :)). Mình sẽ không ngạc nhiên nếu mọi người không hiểu :3
Nguồn
https://react-typescript-cheatsheet.netlify.app/docs/hoc/intro
Comments