😍React Context với TypeScript: Phần 4 - Tạo một context không có default và không có undefined check

https://codesandbox.io/p/sandbox/react-ts-context-no-default-8ch32?file=%2Fsrc%2Findex.tsx

src\App.tsx

import React from 'react';
// Step 1
export function createCtx<ContextType>() {
  const ctx = React.createContext<ContextType | undefined>(undefined);
  function useCtx() {
    const c = React.useContext(ctx);
    if (!c) throw new Error("useCtx must be inside a Provider with a value");
    return c;
  }
  return [useCtx, ctx.Provider] as const;
}
type ThemeContextType = {
  theme: string;
  setTheme: (value: string) => void;
};
const [useTheme, CtxProvider] = createCtx<ThemeContextType>();
// Step 2
type Props = {
  children: React.ReactNode;
};
export const ThemeProvider = ({ children }: Props) => {
  const [theme, setTheme] = React.useState("white");
  React.useEffect(() => {
    const currentTheme = "lightblue";
    setTheme(currentTheme);
  }, []);
  return <CtxProvider value={{ theme, setTheme }}>{children}</CtxProvider>;
};
// Step 3
const Header = () => {
  const { theme, setTheme } = useTheme();
  return (
    <div style={{ backgroundColor: theme }}>
      <select value={theme} onChange={e => setTheme(e.currentTarget.value)}>
        <option value="white">White</option>
        <option value="lightblue">Blue</option>
        <option value="lightgreen">Green</option>
      </select>
      <span>Hello!</span>
    </div>
  );
};
export const App = () => (
  <ThemeProvider>
    <Header />
  </ThemeProvider>
);
export default App;

Đây là bài đăng cuối cùng trong loạt bài đăng về React context với TypeScript. Trong bài đăng trước , chúng ta đã sử dụng một context trong một thành phần lớp. Trong bài đăng này, chúng ta sẽ tìm hiểu cách tạo một context mà không cần phải truyền default và sau đó thực hiện bất kỳ undefinedkiểm tra nào khi sử dụng context đó.

Vấn đề

Kiểu for createContextyêu cầu một giá trị mặc định được truyền vào, nhưng thường thì không hợp lý khi truyền giá trị mặc định. Vì vậy, cuối cùng chúng ta truyền undefinedgiá trị mặc định:

const ThemeContext = React.createContext<
  ThemeContextType | undefined
>(undefined);

… và sau đó kiểm tra undefinedmọi nơi chúng ta tiêu thụ nó:

const { theme, setTheme } = useTheme()!;

Một giải pháp nhanh chóng

Một giải pháp nhanh chóng cho vấn đề này là sử dụng toán tử khẳng định không null và loại bỏ undefinedkhỏi kiểu ngữ cảnh.

const ThemeContext = React.createContext<
  ThemeContextType
>(undefined!);

Điều này hoạt động tốt vì việc sử dụng mã không cần undefinedkiểm tra. Tuy nhiên, chúng ta vẫn đang truyền một giá trị mặc định.

Tạo một wrapper để tạo ngữ cảnh

Một giải pháp là tạo một lớp bao bọc xung quanh createContextđể xử lý giá trị mặc định và giá trị undefinedkiểm tra:

export function createCtx<ContextType>() {
  const ctx = React.createContext<
    ContextType | undefined
  >(undefined);
  function useCtx() {
    const c = React.useContext(ctx);
    if (!c)
      throw new Error(
        "useCtx must be inside a Provider with a value"
      );
    return c;
  }
  return [useCtx, ctx.Provider] as const;
}

Hàm này trước tiên tạo ngữ cảnh với kiểu chung được truyền vào với undefinedgiá trị mặc định.

Sau đó, một hàm lồng nhau được định nghĩa, hàm này sẽ bao bọc useContexthook. Một biến cđược gán cho giá trị trả về của useContexthook, là kiểu chung được truyền vào or undefined:

Sau đó chúng ta sẽ ném lỗi nếu clà falsy, lỗi này sẽ xử lý việc undefinedkiểm tra. Điều này có nghĩa là khi cđược trả về từ hàm lồng nhau, nó không thể undefinedvà chỉ là kiểu chung mà chúng ta đã truyền vào:

Lưu ý chúng ta cũng sử dụng khẳng định const ( as const) ở dòng cuối cùng để đảm bảo TypeScript suy ra kiểu tuple chứ không phải mảng các kiểu hợp nhất.

Tạo bối cảnh

Bây giờ chúng ta có thể sử dụng createCtxhàm của mình để tạo ngữ cảnh thay vì hàm của React createContext:

const [useTheme, CtxProvider] = createCtx<
  ThemeContextType
>();

Tạo nhà cung cấp

Hàm của chúng tôi createCtxtrả về một tuple, chứa một thành phần nhà cung cấp trong phần tử thứ hai ( CtxProvider). Chúng tôi có thể tạo thành phần nhà cung cấp cụ thể chứa trạng thái bắt buộc của mình:

export const ThemeProvider = ({
  children
}: Props) => {
  const [theme, setTheme] = React.useState(
    "white"
  );
  ...
  return (
    <CtxProvider value={{ theme, setTheme }}>      {children}
    </CtxProvider>  );
};

Sau đó có thể đặt nó vào vị trí thích hợp trong cây thành phần:

export const App = () => (
  <ThemeProvider>    <Header />
  </ThemeProvider>);

Tiêu thụ bối cảnh

Our createCtxcũng trả về một hook ( useTheme) trong phần tử đầu tiên của tuple. Chúng ta có thể sử dụng mà không cần phải thực hiện bất kỳ undefinedkiểm tra nào:

const Header = () => {
  const { theme, setTheme } = useTheme();  return (
    <div style={{ backgroundColor: theme }}>
      <select
        value={theme}
        onChange={e =>
          setTheme(e.currentTarget.value)
        }
      >
        <option value="white">White</option>
        <option value="lightblue">Blue</option>
        <option value="lightgreen">Green</option>
      </select>
      <span>Hello!</span>
    </div>
  );
};

Gọn gàng!

Có thể xem bản triển khai đầy đủ bằng cách nhấp vào liên kết bên dưới. Hãy thử và thay đổi giá trị chủ đề và xem màu nền thay đổi.

Last updated

Was this helpful?