reactjs 在Typescript中使用useContext传递状态

z31licg0  于 2023-01-08  发布在  React
关注(0)|答案(2)|浏览(178)

我尝试使用useContext钩子将state和setState传递给一个子组件,但是当我尝试在provider的value参数中传递[state,setState]时,我得到了一个ts错误。我的代码如下:

export interface IProviderProps {
  children?: any;
}

const initialState = {
  state: Object,
  setState: () => {},
};

export const AppContext = createContext(initialState);

export const AppProvider = (props: IProviderProps) => {
  const [state, setState] = useState([{ isMenuOpen: false, isSideOpen: false }]);

  return <AppContext.Provider value={[state, setState]}>{props.children}</AppContext.Provider>;
};

我在设置initialState的值变量上遇到错误。
index.d.ts(290, 9): The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps<{ state: ObjectConstructor; setState: () => void; }>'
我如何设置初始状态以允许我传递state和useState变量?

hjzp0vay

hjzp0vay1#

类型脚本从给定的initialStatecreateContext推断AppContext类型。
AppContext.Provider需要一个与上述类型匹配的value属性。因此,由createContext示例化的类型确定了使用组件可以使用的上下文形状。

哪里出了问题?

initialState获取以下推断类型:

{ state: ObjectConstructor; setState: () => void; }

Object传递给state意味着,你期望的是ObjectConstructor--而不是你真正想要的。对于setState: () => {},组件不能用state参数调用这个函数。还请注意,useState初始值当前被 Package 在一个额外的数组[{...}]中。
总之,[state, setState]参数与AppContext.Provider值属性不兼容。
溶液
我们假设,您想要的状态形状如下所示:

type AppContextState = { isMenuOpen: boolean; isSideOpen: boolean }
// omitting additional array wrapped around context value

则具有适当类型的初始状态是(playground):

// renamed `initialState` to `appCtxDefaultValue` to be a bit more concise
const appCtxDefaultValue = {
  state: { isMenuOpen: false, isSideOpen: false },
  setState: (state: AppContextState) => {} // noop default callback
};

export const AppContext = createContext(appCtxDefaultValue);

export const AppProvider = (props: IProviderProps) => {
  const [state, setState] = useState(appCtxDefaultValue.state);

  return (
    // memoize `value` to optimize performance, if AppProvider is re-rendered often 
    <AppContext.Provider value={{ state, setState }}>
      {props.children}
    </AppContext.Provider>
  );
};

具有自己的上下文值类型(playground)的更显式的变体:

import { Dispatch, SetStateAction, /* and others */ } from "react";

type AppContextValue = {
  state: AppContextState;
  // type, you get when hovering over `setState` from `useState`
  setState: Dispatch<SetStateAction<AppContextValue>>;
};

const appCtxDefaultValue: AppContextValue = {/* ... */};

讨论其他选择

完全删除上下文默认值(playground)

export const AppContext = React.createContext<AppContextValue | undefined>(undefined);

export const AppProvider = (props: IProviderProps) => {
    const [state, setState] = useState({ isMenuOpen: false, isSideOpen: false });
    // ... other render logic
};

为了防止客户端现在必须检查undefined,请提供一个自定义Hook:

function useAppContext() {
    const ctxValue = useContext(AppContext)
    if (ctxValue === undefined) throw new Error("Expected context value to be set")
    return ctxValue // now type AppContextValue
    // or provide domain methods instead of whole context for better encapsulation
}

const Client = () => {
    const ctxVal = useAppContext() // ctxVal is defined, no check necessary!
}

切换到useReducer和/或自定义useAppContext挂钩

考虑用useReducer替换useState,并将dispatch函数传递给组件,这将提供更好的封装,因为状态操作逻辑现在集中在一个纯Reducer中,子组件 * 不能 * 再通过setState直接操作它。
另一个将UI逻辑从域逻辑中分离出来的好方法是提供一个自定义的useAppContext Hook,而不是使用useContext(AppContext)--参见前面的示例。现在useAppContext可以提供一个更窄的API,而无需发布整个上下文。

rqenqsqc

rqenqsqc2#

在我看来,一个令人满意且简单的解决方案是创建一个额外的函数,它可以setState。

export const AuthContext = createContext(
    { 
    checked: true,
    loggedIn: false, 
    changeLoggedIn: (logged: boolean) => {} 
    });

const AuthContextProvider = (props: { children: React.ReactElement }) => {
    const [checked, setchecked] = React.useState(true);
    const [loggedIn, setloggedIn] = React.useState(false);

    const changeLoggedIn = (logged: boolean) => {
        setloggedIn(logged);
    }

    return (
        <AuthContext.Provider value={{ loggedIn, checked, changeLoggedIn }}>
            {props.children}
        </AuthContext.Provider>
    )
}

export default AuthContextProvider

相关问题