Trong bài trước chúng ta đã cùng nhau tìm hiểu về hàm useState và useEffect, trong bài này Dũng sẽ cùng các bạn tìm hiểu về các hàm useContext nhé.

Vấn đề

Ví dụ chúng ta có một lướp DataStore thế này:

class DataStore {
    private map = new Map<string, any>();

    public set(key: string, value: any) {
        this.map.set(key, value);
    }

    public get<T>(key: string): T {
        return this.map.get(key);
    }

    public toString() {
        return JSON.stringify(this.map);
    }
}

Từ lớp này chúng ta sẽ tạo ra một đối tượng dataStore dùng chung cho Component1, và Component5 như sau:

import { useEffect, useState } from "react";

class DataStore {
    private map = new Map<string, any>();

    public set(key: string, value: any) {
        this.map.set(key, value);
    }

    public get<T>(key: string): T {
        return this.map.get(key);
    }

    public toString() {
        return JSON.stringify(this.map);
    }
}

function Component1() {
    const [dataStore, setDataStore] = useState(new DataStore());
  
    return (
      <>
        <h1>{`dataStore ${dataStore.toString()}!`}</h1>
        <Component2 dataStore={dataStore} />
      </>
    );
  }
  
  function Component2({ dataStore }: { dataStore: DataStore }) {
    return (
      <>
        <h1>Component 2</h1>
        <Component3 dataStore={dataStore} />
      </>
    );
  }
  
  function Component3({ dataStore }: { dataStore: DataStore }) {
    return (
      <>
        <h1>Component 3</h1>
        <Component4 dataStore={dataStore} />
      </>
    );
  }
  
  function Component4({ dataStore }: { dataStore: DataStore }) {
    return (
      <>
        <h1>Component 4</h1>
        <Component5 dataStore={dataStore} />
      </>
    );
  }
  
  function Component5({ dataStore }: { dataStore: DataStore }) {
    useEffect(() => {
        dataStore.set("currentComponent", "Component5");
    }, [dataStore]);
    return (
      <>
        <h1>Component 5</h1>
        <h2>{`dataStore ${dataStore.toString()} again!`}</h2>
      </>
    );
  }

export default Component1;

Bạn có thấy vấn đề ở đây không? Đó chính là việc chúng ta phải lặp đi lặp lại việc truyền đối tượng dataStore xuyên suốt các component mặc dù chỉ có 2 component là sử dụng, điều này làm cho mã nguồn bị lặp và vi phạm vào nguyên tắc don’t repeat yourself trong lập trình.

Sử dụng useContext để giải quyết vấn đề

Để giải quyết vấn đề kể trên thì ReactJS đã cung cấp cho chúng ta một cơ chế để cho phép chia sẻ các giá trị hoặc đối tượng sử dụng chung bằng cách sử dụng khái niêm context với các bước như sau:

  1. Tạo ra một đối tượng Context bằng cách sử dụng hàm createContext.
  2. Bọc lại các thành phần con với đối tượng Context.Provider.
  3. Sử dụng hàm useContext để lấy ra giá trị hoặc đối tượng được chia sẻ.
    Với cách làm này chúng ta có thể cập nhật lại mã nguồn ở trên như sau:
import { useEffect, useState, createContext, useContext, ReactNode } from "react";

class DataStore {
    private map = new Map<string, any>();

    public set(key: string, value: any) {
        this.map.set(key, value);
    }

    public get<T>(key: string): T {
        return this.map.get(key);
    }

    public toString() {
        return JSON.stringify(this.map);
    }
}

const defaultDataStore = new DataStore();
const DataStoreContext = createContext<DataStore>(defaultDataStore);

function DataStoreProvider({ value = defaultDataStore, children }: { value?: DataStore, children: ReactNode }) {
    return (
      <DataStoreContext.Provider value={value}>
        {children}
      </DataStoreContext.Provider>
    );
  }

const useDataStoreContext = (): DataStore => {
    const context = useContext(DataStoreContext);
    if (!context) {
      throw new Error('useDataStoreContext must be used within a DataStoreProvider');
    }
    return context;
  };

function Component1() {
    const [dataStore, setDataStore] = useState(new DataStore());
  
    return (
      <DataStoreProvider value={dataStore}>
        <h1>{`dataStore ${dataStore}!`}</h1>
        <Component2 />
      </DataStoreProvider>
    );
  }
  
  function Component2() {
    return (
      <>
        <h1>Component 2</h1>
        <Component3 />
      </>
    );
  }
  
  function Component3() {
    return (
      <>
        <h1>Component 3</h1>
        <Component4 />
      </>
    );
  }
  
  function Component4() {
    return (
      <>
        <h1>Component 4</h1>
        <Component5 />
      </>
    );
  }
  
  function Component5() {
    const dataStore = useDataStoreContext();

    useEffect(() => {
        dataStore.set("currentComponent", "Component5");
    }, [dataStore]);
    return (
      <>
        <h1>Component 5</h1>
        <h2>{`dataStore ${dataStore.toString()} again!`}</h2>
      </>
    );
  }

export default Component1;

Trong mã nguồn này chúng ta đã:

  1. Sử dụng hàm createContext để tạo ra đối tượng DataStoreContext.
  2. Tạo ra component DataStoreProvider sử dụng DataStoreContext.Provider và bọc lại component con.
  3. Tạo ra hàm tiện ích useDataStoreContext sử dụng hàm useContext(DataStoreContext) cho ngắn gọn.
  4. Thay đổi Component1 để sử dụng DataStoreProvider bọc lại các thành phần con.
  5. Loại bỏ việc gán dataStore qua các thành phần con.
  6. Trong Component5 chúng ta sử dụng hàm useDataStoreContext để lấy ra dataStore.
    Như chúng ta thấy các mã nguồn trùng lặp đã bị loại bỏ và mã nguồn của chúng ta đã trở nên trong sáng hơn.
    Tuy nhiên chúng ta cũng không nên lạm dụng phương pháp này vì nó sẽ làm cho các thành phần con bị đẩy sâu vào bên trong và có thể gây rối loạn khi quản lý trạng thái.

Tổng kết

Như vậy chúng ta đã cùng nhau tìm hiểu về hàm useContext của React Hooks, các hàm còn lại chúng ta sẽ tìm hiểu trong bài sau nhé.

Sách tham khảo

Bạn có thể tìm hiểu thêm về nguyên tắc don’t repeat yourself trong cuốn những nguyên tắc sống còn trong lập trình, và đừng quên nhập mã Tech10 để được giảm giá 10% nhé.


Cám ơn bạn đã quan tâm đến bài viết này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a