首页 > 解决方案 > 如何在组件中使用 useHook,而不是将其返回的值作为 prop 传递给使用 react 和 typescript 的组件?

问题描述

我想在组件本身中使用 useHook,而不是使用 react 和 typescript 将其作为道具传递给每个组件。

我想做什么?我有一个名为 useRefresh 的 useHook,它返回 isLoading 状态。此 isLoading 状态用于在每个页面中显示加载指示器。

所以我有三个页面,只要这个 isLoading 为真,就应该在这些页面中显示一个加载指示器。

下面是我的代码,

function App(){
    const user = useGetUser(); 
    return (
        <Router>
            <Switch>
                <Route
                    path="/"
                    render={props: any => (
                        user ? (<Main {...props} />) : (
                            <LoginPage/>
                        );
                    )}
                </Route>
            </Switch>
        </Router>
    );
}

export function useLoad() {
    const { refetch: refetchItems } = useGetItems();
    const { refetch: refetchOwnedItems } = useListOwnedItems();
    return async function() {
        await refreshCompany();
        refetchItems();
        refetchOwnedItems();
    };  
}

function useAnother(Id: string) {
    const [compId, setCompId] = React.useState(undefined);
    const [isLoading, setIsLoading] = React.useState(false);
    const comp = useCurrentComp(Id);
    const load = useLoad();
    if (comp && comp.id !== compId) {
        setCompId(comp.id);
        const prevCompId = compId !== undefined;
        if (prevCompId) {
            setIsLoading(true);
            load().then(() => {
                setIsLoading(false);
            });
        }
    }
}


function Main ({user}: Props) {
    useAnother(user.id);
    return (
        <Router>
            <Switch>
                <Route 
                    path="/" 
                    render={routeProps => (
                        <FirstComp {...routeProps} />
                    )}
                />
                <Route 
                    path="/items" 
                    render={routeProps => (
                        <SecondComp {...routeProps} />
                    )}
                />
                //many other routes like these
            </Switch>
        </Router>
    );
}


function FirstComp () {
    return(
        <Wrapper>
            //some jsx
        </Wrapper>
    );
}

function SecondComp () {
    return(
        <Wrapper>
            //some jsx
        </Wrapper>
    );
}

现在我想将 isLoading 状态传递给 Main 组件中的每个组件......所以我像下面这样传递了它,

function Main ({user}: Props) {
    const isLoading = useAnother(user.id); //fetching isLoading here from useHook
    return (
        <Router>
            <Switch>
                <Route 
                    path="/" 
                    render={routeProps => (
                        <FirstComp isLoading={isLoading} {...routeProps} />
                    )}
                />
                <Route 
                    path="/items" 
                    render={routeProps => (
                        <SecondComp isLoading={isLoading} {...routeProps} />
                    )}
                />
                //many other routes like these
            </Switch>
        </Router>
    );
}


function FirstComp ({isLoading}: Props) {
    return(
        <Wrapper>
             displayIndicatorWhen(isLoading);
            //some jsx
        </Wrapper>
    );
}

function SecondComp ({isLoading}: Props) {
    return(
        <Wrapper>
            displayIndicatorWhen(isLoading);
            //some jsx
        </Wrapper>
    );
}

这行得通。但对我来说似乎不是正确的方法.. 我不想将此 isLoading 状态作为道具传递给这些组件中的每一个。其中有10多个。

有没有其他方法可以做到这一点。有人可以帮我解决这个问题。谢谢。

标签: reactjstypescript

解决方案


最常见的解决方案是创建一个包含整个组件树的上下文。此上下文包含您的钩子拉入的状态

////LoadingContext.tsx
const LoadingContext =  createContext();

const LoadingContextProvider = () => {
    const [isLoading, setIsLoading] = useState(false);

    return (
        <LoadingContextProvider.Provider
            value={{
               isLoading,
               setIsLoading
            }}
        />
    )

}

export const useLoading = () => useContext(LoadingContext);

您需要围绕将要调用的任何内容包装上下文useLoading

import { LoadingContextProvider } from './LoadingContext' //or wherever this is relative to Main.tsx 

<LoadingContextProvider>
    <Router>
       ...(router stuff)
    </Router>
</LoadingContextProvider>

现在您可以在较低级别的组件中调用 useLoading。

//in another file defining a lower-level component:
import { useLoading } from '../../LoadingContext' //or wherever the context stuff is relative to this component definition


const FirstComp = () => 
    const [isLoading, setIsLoading] = useLoading();

    const handleClick = () => {
        setIsLoading(true);
        callMyApi().then(() => setIsLoading(false));
    } 

    if(isLoading){
        return <LoadingGif />
    }
    else{
        return <div onClick={handleClick}>Click me!</div>
    }
)}

推荐阅读