如何在 React CreateContext 中正确键入对象初始值

How to type properly object initial value in React CreateContext

我应该如何在 React Create Context 中正确输入 constance trainsDetails 的初始值,这是一个具有不同属性的对象。 TrainsDetails 是从端点获取的单个对象,其值在下面以 TrainsDetailsResponseType 类型提供:

type TrainsDetailsResponseType = {
    trainId: string;
    trainNo: string;
    lineId: string;
    route: string;
    lineKmPosition: string;
    speed: number;
    deviationFromTimetable: number;
    mileage: number;
    lastInspectionDate: string;
    nextInspectionDate: string;
    state: string;
};

这是 TrainsContextValues 的接口:

interface TrainsContextValues {
    trainsList: TrainsListResponseType[];
    trainsDetails: TrainsDetailsResponseType;
    handlePushToTrainsDetails: (arg: string) => void;
    trainId: string;
}

我应该如何正确输入 trainsDetails 的初始值?

export const TrainsContext = createContext<TrainsContextValues>({
    trainsList: [],
    trainsDetails: ,
    handlePushToTrainsDetails: () => undefined,
    trainId: '',
});

在这里您还可以看到我如何在 useState 挂钩中输入 trainsDetails

 const [trainsDetails, setTrainsDetails] = useState<TrainsDetailsResponseType>();

如您在示例中所见,状态值类型为 TrainsDetailsResponseType | undefined:

// typeof trainsDetails = TrainsDetailsResponseType | undefined
const [trainsDetails, setTrainsDetails] = useState<TrainsDetailsResponseType>();

您可以在 React 上下文值中为 trainsDetails 指定类似的类型:

interface TrainsContextValues {
  trainsList: TrainsListResponseType[];

  // optional type (?) or TrainsDetailsResponseType | undefined
  trainsDetails?: TrainsDetailsResponseType;

  handlePushToTrainsDetails: (arg: string) => void;
  trainId: string;
}

export const TrainsContext = React.createContext<TrainsContextValues>({
  trainsList: [],
  trainsDetails: undefined,
  handlePushToTrainsDetails: () => undefined,
  trainId: "",
});

这是正确的,因为在从端点获取数据之前,您确实没有 trainsDetails 的数据。


已更新。长答案

重要的是,您传递给 createContext 的参数不是上下文的 initialValue,而是 defaultValuedefaultValue 参数仅在组件在树中没有其上方的匹配提供者时使用。

您有两种使用上下文的场景:

1. - 您允许使用上下文,直到收到所有必要的数据。在这种情况下,如果没有数据,您应该提供回退:

interface TrainsContextValues {
  trainsList: TrainsListResponseType[];
  trainsDetails?: TrainsDetailsResponseType;
  handlePushToTrainsDetails: (arg: string) => void;
  trainId: string;
}

export const TrainsContext = React.createContext<TrainsContextValues>({
  trainsList: [],
  trainsDetails: undefined,
  handlePushToTrainsDetails: () => {},
  trainId: "",
});

const Parent = () => {
  const [trainsDetails, setTrainsDetails] =
    useState<TrainsDetailsResponseType>();

  useEffect(() => {
    const getTrainsDetail = async () => {
      try {
        const response = await fetch("your-api");
        const data: TrainsDetailsResponseType = await response.json();

        setTrainsDetails(data);
      } catch {
        // your logic for the failed request
      }
    };

    getTrainsDetail();
  }, []);

  const contextValue = useMemo(
    () => ({
      // undefined | data from api
      trainsDetails,

      // replace to actually
      trainsList: [],
      handlePushToTrainsDetails: () => {},
      trainId: "",
    }),
    [trainsDetails]
  );

  return (
    <TrainsContext.Provider value={contextValue}>
      <Child />
    </TrainsContext.Provider>
  );
};

const Child = () => {
  const { trainsDetails } = useContext(TrainsContext);

  // technically, the component can be rendered out of context and it should be ready for this
  if (!trainsDetails) {
    return <FallbackComponent />;
  }

  return <TrainsDetailsComponent data={trainsDetails} />;
};

2. - 您不允许在没有真实数据的情况下使用上下文。 WARNING - 这是一个非常严格的模式,仅在应用程序不能不应该时使用使用没有真实数据的上下文:

interface TrainsContextValues {
  trainsList: TrainsListResponseType[];
  trainsDetails: TrainsDetailsResponseType;
  handlePushToTrainsDetails: (arg: string) => void;
  trainId: string;
}

export const TrainsContext = React.createContext<TrainsContextValues | null>(
  null
);

const useTrainsContext = (): TrainsContextValues => {
  const data = useContext(TrainsContext);

  if (!data) {
    throw new Error("could not find trains context value");
  }

  return data;
};

const Parent = () => {
  const [trainsDetails, setTrainsDetails] =
    useState<TrainsDetailsResponseType>();

  useEffect(() => {
    const getTrainsDetail = async () => {
      try {
        const response = await fetch("your-api");
        const data: TrainsDetailsResponseType = await response.json();

        setTrainsDetails(data);
      } catch {
        // your logic for the dropped request
      }
    };

    getTrainsDetail();
  }, []);

  const contextValue = useMemo(
    () =>
      trainsDetails
        ? {
            trainsDetails,
            trainsList: [],
            handlePushToTrainsDetails: () => {},
            trainId: "",
          }
        : null,
    [trainsDetails]
  );

  if (!contextValue) {
    // some fallback for empty trainsDetails
    return <Loading />;
  }

  return (
    <TrainsContext.Provider value={contextValue}>
      <Child />
    </TrainsContext.Provider>
  );
};

const Child = () => {
  const { trainsDetails } = useTrainsContext();

  return <TrainsDetailsComponent data={trainsDetails} />;
};

第二种情况是特定的,仅当上下文中的数据丢失是组件组成中的逻辑错误时才使用。并且用户无法纠正这种情况。例如,在 redux 中缺少商店提供者。