Bài 18.3: Higher-Order Components trong TypeScript

Chào mừng bạn đến với bài viết tiếp theo trong series lập trình web front-end của chúng ta! Hôm nay, chúng ta sẽ lặn sâu vào một khái niệm quan trọng (mặc dù có phần "cũ" hơn so với Hooks hiện tại) trong thế giới React: Higher-Order Components (HOC). Đặc biệt, chúng ta sẽ xem xét cách sử dụng HOC một cách an toàn và hiệu quả với sức mạnh của TypeScript.

Higher-Order Component (HOC) là gì?

Trong React, HOC không phải là một API của React mà là một pattern (mẫu thiết kế) xuất phát từ bản chất thành phần của React.

Định nghĩa đơn giản nhất: Một Higher-Order Component là một hàm nhận vào một React Component và trả về một Component mới với các props hoặc hành vi bổ sung.

Nghe có vẻ trừu tượng? Hãy nghĩ theo cách này:

  • Component bình thường: Biến props thành UI. ((props) => UI)
  • HOC: Biến một component thành một component mới. ((Component) => NewComponent)

Mục đích chính của HOC là tái sử dụng logic giữa các component. Thay vì lặp lại cùng một đoạn code (ví dụ: logic fetch data, logic quản lý state, logic xử lý subscription) trong nhiều component khác nhau, chúng ta có thể gói gọn logic đó vào một HOC và áp dụng nó cho bất kỳ component nào cần đến.

Tại sao lại dùng HOC? (Và bối cảnh hiện tại)

Trước khi React Hooks ra đời, HOC là một trong những cách chính để chia sẻ stateful logic (logic liên quan đến state và lifecycle) giữa các component. Các lý do chính bao gồm:

  1. Tái sử dụng Logic: Tránh lặp code.
  2. Tách biệt Mối quan tâm (Separation of Concerns): Tách biệt logic quản lý dữ liệu hoặc hành vi khỏi logic hiển thị UI.
  3. Khả năng Cấu hình: HOC có thể nhận thêm các tham số để cấu hình hành vi của component mới trả về.

Tuy nhiên, với sự ra đời của React Hooks, nhiều trường hợp sử dụng HOC đã được thay thế bởi Custom Hooks, thường mang lại code dễ đọc, dễ hiểu và ít lồng ghép hơn. Mặc dù vậy, việc hiểu về HOC vẫn cực kỳ quan trọng vì:

  • Nhiều thư viện React phổ biến (ví dụ: Redux kết nối component với store trước đây dùng connect - một dạng HOC) vẫn sử dụng HOC hoặc có API dựa trên HOC.
  • Bạn có thể gặp và cần duy trì các codebase cũ sử dụng HOC.
  • Việc hiểu các pattern cũ giúp bạn đánh giá cao hơn các pattern mới.

Trong bài này, chúng ta sẽ tập trung vào cách xây dựng và sử dụng HOC, đặc biệt là cách TypeScript giúp chúng ta quản lý kiểu dữ liệu (props) một cách chặt chẽ.

Cấu trúc cơ bản của một HOC

Một HOC thường có dạng như sau:

import React, { ComponentType } from 'react';

// Kiểu dữ liệu cho các props mà HOC sẽ "tiêm" (inject) vào component gốc
interface InjectedProps {
  // Ví dụ: user: User | null;
  // isLoading: boolean;
}

// Kiểu dữ liệu cho các props mà component gốc *mong đợi nhận* từ bên ngoài HOẶC từ HOC
// (Thường là kết hợp các props ban đầu và props được inject)
type WrappedComponentProps = any; // Tạm thời dùng 'any'

// Định nghĩa HOC: là một hàm nhận vào Component gốc
const withSomething = <P extends WrappedComponentProps>(
  WrappedComponent: ComponentType<P> // Component gốc, mong đợi props kiểu P
): ComponentType<Omit<P, keyof InjectedProps>> => { // Trả về Component mới, mong đợi props *không* bao gồm InjectedProps

  // Component mới được trả về bởi HOC
  const ComponentWithSomething = (props: Omit<P, keyof InjectedProps>) => {
    // --- Logic của HOC ---
    // Ở đây, HOC có thể quản lý state, side effects (useEffect), context, ...
    // Dựa trên logic này, HOC sẽ tạo ra các props cần "tiêm" vào WrappedComponent

    const injectedProps: InjectedProps = {
      // ... tính toán các giá trị cho injectedProps ...
    };

    // Kết hợp props nhận từ bên ngoài (props) và props được inject (injectedProps)
    // và truyền xuống WrappedComponent
    const mergedProps = {
      ...props,
      ...injectedProps,
    } as P; // Ép kiểu về P, giả định WrappedComponent sẽ nhận được đầy đủ props cần thiết

    // Render WrappedComponent với các props đã được xử lý
    return <WrappedComponent {...mergedProps} />;
  };

  // Đặt tên hiển thị cho Component mới để dễ debug trong React DevTools
  ComponentWithSomething.displayName = `WithSomething(${getDisplayName(WrappedComponent)})`;

  return ComponentWithSomething;
};

// Hàm helper để lấy tên hiển thị của component
function getDisplayName(WrappedComponent: ComponentType<any>) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

export default withSomething;

Trong cấu trúc trên:

  • WrappedComponent: Là component ban đầu bạn muốn "nâng cấp".
  • HOC (withSomething): Là hàm nhận WrappedComponent làm đối số.
  • ComponentWithSomething: Là component mới mà HOC trả về. Component này sẽ render WrappedComponent bên trong nó, nhưng trước đó, nó sẽ xử lý logic nào đó và "tiêm" thêm props vào WrappedComponent.
  • TypeScript và Prop Types: Đây là phần quan trọng! Chúng ta cần quản lý kiểu dữ liệu của props một cách cẩn thận:
    • InjectedProps: Các props mà HOC sẽ tự cung cấp.
    • WrappedComponentProps: Các props tổng cộngWrappedComponent mong đợi (bao gồm cả những props nhận từ bên ngoài và những props được HOC inject).
    • Kiểu trả về của HOC: ComponentType<Omit<P, keyof InjectedProps>>. Điều này có nghĩa là component mới trả về sẽ chỉ cần nhận các props không nằm trong InjectedProps từ bên ngoài, vì phần InjectedProps sẽ được HOC lo liệu. Utility type Omit giúp chúng ta loại bỏ các key cụ thể khỏi một kiểu dữ liệu.
Ví dụ Minh họa 1: Thêm Prop Mặc định hoặc Tính toán

Giả sử chúng ta có một component đơn giản hiển thị tên người dùng, nhưng muốn có một giá trị mặc định nếu tên không được cung cấp từ bên ngoài.

Component gốc (DisplayName.tsx):

import React from 'react';

interface DisplayNameProps {
  name: string;
  fontSize?: number; // Có thể có thêm các props khác
}

const DisplayName: React.FC<DisplayNameProps> = ({ name, fontSize = 16 }) => {
  return (
    <p style={{ fontSize: fontSize }}>
      Tên: **{name}**
    </p>
  );
};

export default DisplayName;

HOC (withDefaultName.tsx):

HOC này sẽ kiểm tra prop name và cung cấp giá trị mặc định nếu nó undefined hoặc null.

import React, { ComponentType } from 'react';

// Các props mà HOC này sẽ inject (hoặc đảm bảo có giá trị)
interface InjectedNameProp {
  name: string; // HOC sẽ đảm bảo prop 'name' có giá trị
}

// HOC nhận vào component mong đợi props kiểu P (bao gồm cả 'name')
// và trả về component mới chỉ cần các props của P *trừ đi* 'name'
const withDefaultName = <P extends InjectedNameProp>(
  WrappedComponent: ComponentType<P>
): ComponentType<Omit<P, 'name'> & Partial<InjectedNameProp>> => {
  // Component mới trả về bởi HOC
  const ComponentWithDefaultName = (
    props: Omit<P, 'name'> & Partial<InjectedNameProp> // Component mới chỉ cần các props khác name, và name là tùy chọn
  ) => {
    // Lấy prop name từ props nhận vào, cung cấp giá trị mặc định
    const name = props.name ?? 'Người dùng ẩn danh';

    // Kết hợp các props khác và prop name đã được xử lý
    // Ép kiểu về P, giả định WrappedComponent sẽ nhận được đầy đủ props cần thiết (bao gồm name)
    const componentProps = { ...props, name } as P;

    // Render component gốc với props đã xử lý
    return <WrappedComponent {...componentProps} />;
  };

  // Đặt tên hiển thị để dễ debug
  ComponentWithDefaultName.displayName = `WithDefaultName(${getDisplayName(WrappedComponent)})`;

  return ComponentWithDefaultName;
};

// Helper function (tái sử dụng)
function getDisplayName(WrappedComponent: ComponentType<any>) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

export default withDefaultName;

Giải thích code:

  • InjectedNameProp: Định nghĩa prop name: string mà HOC sẽ đảm bảo có.
  • Kiểu trả về ComponentType<Omit<P, 'name'> & Partial<InjectedNameProp>>: Đây là điểm mấu chốt. Nó nói rằng component mới sẽ nhận vào các props gốc của WrappedComponent (P), trừ đi prop name (Omit<P, 'name'>), nhưng vẫn chấp nhận prop name như một prop tùy chọn (Partial<InjectedNameProp>) nếu người dùng vẫn muốn truyền nó từ bên ngoài (lúc này giá trị truyền từ ngoài sẽ được ưu tiên, hoặc HOC sẽ xử lý logic kết hợp). Cách dùng Partial ở đây giúp linh hoạt hơn trong việc sử dụng.
  • Bên trong ComponentWithDefaultName: Chúng ta lấy props.name và sử dụng nullish coalescing operator (??) để gán giá trị mặc định 'Người dùng ẩn danh' nếu props.namenull hoặc undefined.
  • { ...props, name } as P;: Chúng ta tạo một object props mới bao gồm tất cả các props nhận từ bên ngoài (...props) và ghi đè (hoặc thêm vào) prop name đã được xử lý. Sau đó, ép kiểu object này thành P - kiểu props mà WrappedComponent mong đợi.

Cách sử dụng (App.tsx hoặc component cha):

import React from 'react';
import DisplayName from './DisplayName';
import withDefaultName from './withDefaultName';

// Tạo component mới bằng cách áp dụng HOC
const DisplayNameWithDefault = withDefaultName(DisplayName);

const App: React.FC = () => {
  return (
    <div>
      <h2> dụ HOC: Thêm Prop Mặc định</h2>

      <h3>Component gốc:</h3>
      {/* Component gốc cần truyền prop 'name' */}
      <DisplayName name="Alice" />
      {/* <DisplayName /> // Lỗi TypeScript: Property 'name' is missing */}

      <h3>Component qua HOC:</h3>
      {/* Component qua HOC, prop 'name' là tùy chọn */}
      <DisplayNameWithDefault name="Bob" /> {/* Truyền name từ ngoài */}
      <DisplayNameWithDefault /> {/* Không truyền name, HOC cung cấp mặc định */}
      <DisplayNameWithDefault fontSize={20} /> {/* Vẫn truyền được các props khác */}
    </div>
  );
};

export default App;

Giải thích cách dùng:

  • Chúng ta gọi withDefaultName(DisplayName) để nhận về component mới DisplayNameWithDefault.
  • DisplayName gốc bắt buộc phải nhận prop name. Nếu không truyền, TypeScript sẽ báo lỗi.
  • DisplayNameWithDefault (component đã qua HOC) thì prop nametùy chọn (nhờ kiểu Partial<InjectedNameProp> trong định nghĩa của HOC). Nếu không truyền, HOC sẽ tự cung cấp giá trị mặc định. Các props khác của DisplayName (như fontSize) vẫn được truyền xuống bình thường.

Đây là một ví dụ đơn giản nhưng cho thấy cách HOC có thể thay đổi giao diện props mà component yêu cầu từ bên ngoài, đồng thời thêm logic xử lý.

Ví dụ Minh họa 2: Fetching Data với HOC

Một trường hợp phổ biến khác cho HOC là xử lý logic tải dữ liệu. HOC sẽ quản lý trạng thái loading, error và dữ liệu, sau đó truyền chúng xuống component gốc.

Component gốc (PostDetail.tsx):

Component này chỉ quan tâm đến việc hiển thị dữ liệu post, trạng thái loading và lỗi.

import React from 'react';

// Props mà component này mong đợi nhận *sau khi đã qua HOC*
interface PostDetailProps {
  post: { id: number; title: string; body: string } | null;
  isLoading: boolean;
  error: string | null;
  // Có thể có thêm các props khác không liên quan đến dữ liệu fetch
  additionalProp?: string;
}

const PostDetail: React.FC<PostDetailProps> = ({ post, isLoading, error, additionalProp }) => {
  if (isLoading) return <p>Đang tải bài viết...</p>;
  if (error) return <p>Lỗi: {error}</p>;
  if (!post) return <p>Không tìm thấy bài viết.</p>;

  return (
    <div>
      <h3>{post.title}</h3>
      <p>{post.body}</p>
      {additionalProp && <p>Prop bổ sung: {additionalProp}</p>}
    </div>
  );
};

export default PostDetail;

HOC (withPostData.tsx):

HOC này sẽ nhận prop postId từ bên ngoài, fetch dữ liệu tương ứng và truyền post, isLoading, error xuống PostDetail.

import React, { ComponentType, useEffect, useState } from 'react';

// Props mà HOC cần nhận từ bên ngoài để hoạt động
interface WithPostDataExternalProps {
  postId: number;
}

// Props mà HOC sẽ inject vào component gốc
interface InjectedPostProps {
  post: { id: number; title: string; body: string } | null;
  isLoading: boolean;
  error: string | null;
}

// Kiểu kết hợp của các props mà WrappedComponent mong đợi
// Nó mong đợi cả InjectedPostProps VÀ các props khác (nếu có) từ bên ngoài không liên quan đến HOC
// Ví dụ: PostDetailProps = InjectedPostProps & { additionalProp?: string }
type WrappedComponentProps<P> = P & InjectedPostProps;


const withPostData = <P extends {}>( // P là kiểu của các props *không* được inject bởi HOC
  WrappedComponent: ComponentType<WrappedComponentProps<P>> // Component gốc mong đợi các props kết hợp
): ComponentType<P & WithPostDataExternalProps> => { // Component mới chỉ cần các props gốc P và postId từ bên ngoài

  const ComponentWithPostData = (props: P & WithPostDataExternalProps) => {
    const { postId, ...restProps } = props; // Tách postId ra, giữ lại các props khác

    const [post, setPost] = useState<InjectedPostProps['post']>(null);
    const [isLoading, setIsLoading] = useState(true);
    const [error, setError] = useState<string | null>(null);

    useEffect(() => {
      setIsLoading(true);
      setError(null);
      setPost(null); // Reset post khi postId thay đổi

      // Simulate fetching data
      fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
        .then(res => {
          if (!res.ok) throw new Error('Failed to fetch post');
          return res.json();
        })
        .then(data => setPost(data))
        .catch(err => setError(err.message))
        .finally(() => setIsLoading(false));
    }, [postId]); // Dependency array: re-run effect when postId changes

    // Các props mà HOC inject
    const injectedProps: InjectedPostProps = { post, isLoading, error };

    // Kết hợp các props nhận từ bên ngoài (trừ postId) và props được inject
    // Ép kiểu về WrappedComponentProps<P>, giả định WrappedComponent có thể xử lý
    const componentProps = { ...restProps, ...injectedProps } as WrappedComponentProps<P>;


    // Render component gốc với props đã xử lý
    return <WrappedComponent {...componentProps} />;
  };

  ComponentWithPostData.displayName = `WithPostData(${getDisplayName(WrappedComponent)})`;

  return ComponentWithPostData;
};

// Helper function (tái sử dụng)
function getDisplayName(WrappedComponent: ComponentType<any>) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

export default withPostData;

Giải thích code:

  • WithPostDataExternalProps: Định nghĩa prop postId mà component sau HOC cần nhận.
  • InjectedPostProps: Định nghĩa các props (post, isLoading, error) mà HOC sẽ tạo ra và truyền xuống.
  • WrappedComponentProps<P>: Đây là kiểu props mà WrappedComponent (component gốc) mong đợi. Nó là sự kết hợp của InjectedPostProps và bất kỳ props nào khác (P) mà component gốc có thể nhận từ bên ngoài mà HOC không đụng vào (ví dụ: additionalProp).
  • Kiểu trả về của HOC: ComponentType<P & WithPostDataExternalProps>. Điều này cho biết component mới được tạo ra bởi HOC sẽ nhận vào các props gốc P (ví dụ: additionalProp) prop postId.
  • Bên trong ComponentWithPostData: Chúng ta sử dụng useStateuseEffect để quản lý trạng thái fetching dữ liệu, hoàn toàn giống như cách bạn làm trong một functional component thông thường hoặc custom Hook.
  • const { postId, ...restProps } = props;: Chúng ta tách prop postId (mà HOC cần) khỏi các props còn lại (restProps) mà component gốc có thể cần.
  • const componentProps = { ...restProps, ...injectedProps } as WrappedComponentProps<P>;: Kết hợp các props còn lại (restProps) với các props mà HOC đã inject (injectedProps) và truyền xuống WrappedComponent. Ép kiểu đảm bảo TypeScript hiểu rằng tập hợp props này khớp với những gì WrappedComponent mong đợi.

Cách sử dụng (App.tsx hoặc component cha):

import React from 'react';
import PostDetail from './PostDetail';
import withPostData from './withPostData';

// Áp dụng HOC cho component PostDetail
const PostDetailWithData = withPostData(PostDetail);

const App: React.FC = () => {
  return (
    <div>
      <h2> dụ HOC: Fetching Data</h2>

      {/* Component sau HOC chỉ cần truyền postId */}
      <PostDetailWithData postId={1} /> {/* Sẽ fetch và hiển thị bài viết 1 */}
      <PostDetailWithData postId={2} additionalProp="Chào từ HOC!" /> {/* Truyền postId và prop bổ sung */}
      <PostDetailWithData postId={999} /> {/* Sẽ fetch và có thể báo lỗi hoặc không tìm thấy */}
    </div>
  );
};

export default App;

Giải thích cách dùng:

  • Chúng ta dùng withPostData(PostDetail) để tạo ra PostDetailWithData.
  • PostDetail gốc mong đợi post, isLoading, error, additionalProp.
  • PostDetailWithData (component sau HOC) chỉ cần nhận postIdadditionalProp từ bên ngoài. Các props post, isLoading, error sẽ được HOC cung cấp một cách "ma thuật".

Ví dụ này minh họa cách HOC có thể trừu tượng hóa hoàn toàn logic fetching data, giúp component gốc trở nên "ngu ngốc" hơn (chỉ quan tâm đến việc hiển thị).

Ví dụ Minh họa 3: Xử lý Subscription (Tổng quan)

Một use case khác là quản lý việc đăng ký/hủy đăng ký các nguồn dữ liệu (như WebSocket, các store bên ngoài React). HOC có thể quản lý vòng đời của subscription và truyền dữ liệu mới nhất xuống component.

Component gốc (RealtimeDisplay.tsx):

Component này chỉ hiển thị dữ liệu nhận được.

import React from 'react';

interface RealtimeDisplayProps {
  data: any; // Dữ liệu từ subscription
  // props khác...
}

const RealtimeDisplay: React.FC<RealtimeDisplayProps> = ({ data }) => {
  return (
    <div>
      <h4>Dữ liệu thời gian thực:</h4>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

export default RealtimeDisplay;

HOC (withSubscription.tsx - Sketch):

import React, { ComponentType, useEffect, useState } from 'react';

// Giả định có một service cung cấp API subscription
interface SomeDataService {
    subscribe: (callback: (data: any) => void) => () => void; // subscribe trả về hàm unsubscribe
    getData: () => any; // Lấy dữ liệu ban đầu
}

// Props mà HOC sẽ inject
interface InjectedSubscriptionProps {
  data: any;
}

// HOC nhận service và component gốc
const withSubscription = <P extends {}>( // P là props không được inject
  WrappedComponent: ComponentType<P & InjectedSubscriptionProps>,
  dataService: SomeDataService
): ComponentType<P> => { // Component mới chỉ cần props gốc P

  const ComponentWithSubscription = (props: P) => {
    const [data, setData] = useState(dataService.getData()); // Lấy dữ liệu ban đầu

    useEffect(() => {
      // Đăng ký nhận cập nhật
      const unsubscribe = dataService.subscribe(newData => {
        setData(newData); // Cập nhật state khi có dữ liệu mới
      });

      // Cleanup: Hủy đăng ký khi component unmount
      return () => {
        unsubscribe();
      };
    }, [dataService]); // Dependency: re-run if dataService instance changes

    // Inject data prop vào component gốc
    const componentProps = { ...props, data } as P & InjectedSubscriptionProps;


    return <WrappedComponent {...componentProps} />;
  };

  ComponentWithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;

  return ComponentWithSubscription;
};

// Helper function (tái sử dụng)
function getDisplayName(WrappedComponent: ComponentType<any>) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

// export default withSubscription; // Export hàm HOC

Giải thích code sketch:

  • HOC nhận thêm một tham số là dataService.
  • Sử dụng useState để lưu trữ dữ liệu hiện tại và useEffect để quản lý lifecycle của subscription.
  • Trong useEffect, gọi dataService.subscribe và cung cấp một callback để cập nhật state khi dữ liệu thay đổi.
  • Hàm cleanup trong useEffect (được trả về) gọi unsubscribe để tránh memory leaks khi component bị hủy.
  • Cuối cùng, truyền data từ state xuống WrappedComponent như một prop.

Đây là một mô hình rất phổ biến của HOC: quản lý side effect và truyền kết quả của side effect (dữ liệu) xuống component hiển thị.

TypeScript và HOCs: Quản lý Props

Như bạn đã thấy trong các ví dụ, việc sử dụng TypeScript với HOC đòi hỏi sự cẩn thận trong việc định nghĩa kiểu dữ liệu cho props. Các utility type như ComponentType, Omit, và Partial trở nên rất hữu ích.

  • ComponentType<P>: Đại diện cho một React component (functional hoặc class) mà mong đợi props kiểu P.
  • Omit<T, K>: Tạo ra một kiểu mới từ kiểu T bằng cách loại bỏ các key được chỉ định trong K. Chúng ta dùng nó để mô tả các props mà component sau HOC cần nhận (là các props mà component gốc cần, trừ đi những props do HOC inject).
  • Partial<T>: Tạo ra một kiểu mới từ kiểu T trong đó tất cả các thuộc tính đều là tùy chọn. Chúng ta có thể dùng nó để chỉ ra rằng một prop nào đó mà HOC inject có thể vẫn được chấp nhận nếu truyền từ bên ngoài, nhưng không bắt buộc.

Việc xác định rõ đâu là props HOC cần từ bên ngoài (WithPostDataExternalProps), đâu là props HOC inject (InjectedPostProps), và đâu là props mà component gốc mong đợi tổng cộng (WrappedComponentProps) là chìa khóa để sử dụng HOC an toàn với TypeScript.

Nhược điểm của HOCs (Và Tại sao Hooks Thường Được Ưu Tiên Hiện Nay)

Mặc dù mạnh mẽ, HOCs cũng có những hạn chế:

  1. Xung đột tên Prop (Prop Name Collisions): Nếu một HOC inject một prop có tên trùng với một prop mà component gốc hoặc một HOC khác cần/cung cấp, có thể gây ra lỗi hoặc hành vi không mong muốn. Cần phải đặt tên props cẩn thận hoặc cung cấp tùy chọn cấu hình tên prop trong HOC.
  2. "Wrapper Hell" và Lồng ghép sâu: Khi một component được bọc bởi nhiều HOC, cấu trúc cây component trong React DevTools trở nên rất sâu và khó đọc (withA(withB(withC(MyComponent)))). Điều này gây khó khăn cho việc debug và kiểm tra cấu trúc.
  3. Khó khăn trong việc Debug nguồn gốc Props: Khi một component nhận props từ nhiều HOC khác nhau, việc truy ngược lại xem prop nào đến từ đâu có thể phức tạp hơn.
  4. Không dễ dàng truy cập vào Component gốc: Bên trong HOC, bạn không có quyền truy cập trực tiếp vào instance (đối với class components) hoặc functional component ban đầu, điều này hạn chế khả năng gọi methods hoặc truy cập refs một cách dễ dàng (đòi hỏi Forwarding Refs phức tạp hơn).

React Hooks ra đời để giải quyết nhiều vấn đề này. Custom Hooks cho phép chúng ta tái sử dụng logic stateful trực tiếp bên trong functional components mà không cần thêm lớp bọc nào cả. Logic được đóng gói trong một hàm và bạn chỉ cần gọi hàm đó trong component của mình. Điều này dẫn đến cây component phẳng hơn và luồng dữ liệu dễ theo dõi hơn.

Tuy nhiên, như đã đề cập, HOC vẫn là một phần của hệ sinh thái React và hiểu về chúng là kỹ năng có giá trị. Đặc biệt khi làm việc với các thư viện hoặc codebase hiện có.

Comments

There are no comments at the moment.