reactjs 如何强制react上下文消费者返回非未定义的值,或者如果未提供值,则失败?

ryoqjall  于 2023-04-29  发布在  React
关注(0)|答案(1)|浏览(93)

在我的typescript/react应用程序中(其中设置了strict和strictNullChecks标志),我使用了一些react上下文。
我不能定义默认值,这就是为什么我允许undefined作为值:

const MyMandatoryContext = createContext<string | undefined>(undefined);

我的上下文设置相当高的是组件树(登录用户名,选定,主题,...),并且应该在我想要使用该值的任何地方设置。
为了减少冗余代码,我想安全地检查值的存在。
很容易使用自定义钩子:

const useMyMandatoryContext = ()=>{
  const context = useContext(MyMandatoryContext);
  if(context === undefined) throw new Error("Must provide a value with MyMandatoryContext.Provider");

  return context;
}

这样,我可以使用钩子来检索值,或者完全失败,而不必检查值的存在:

const MyFuncComponent : React.FC =()=>{
  const myMandatoryValue = useMyMandatoryContext(); // is string for sure
  return (<span>{myMandatoryValue.toUpperCase()}</span>)
}

但是,钩子在类组件中不可用(对于一些遗留代码,或者像ErrorBoundary这样的特定组件,仍然需要)
我可以消费并检查一个值的存在:

class MyClassComponent  extends Component {
  render () {
    return (
      <MyMandatoryContext.Consumer>
        {(myMandatoryValue) => {
          if(!myMandatoryValue) throw new Error("Must provide a value with MyMandatoryContext.Provider");
          return <span>{myMandatoryValue.toUpperCase()}</span>;
        }}
      </MyMandatoryContext.Consumer>
    )
  }
}

但是我需要在每次我想消费值的时候检查值,这在某种程度上是多余的。
有没有一种方法可以安全地(在类型方面)强制消费者获得不可空的值?
我试图破解React.Consumer类型,但我没有找到正确的方法:

const MyMandatoryContextConsumer : React.Consumer<string> = ({children})=>{
  return children(val=>{
    if(val === undefined) throw new Error("Must provide a value with MyMandatoryContext.Provider");

    return ???? // Not sure what to do here
  })
}

我为repro my needs创建了一个小沙箱:

import "./styles.css";
import { createContext, useContext, useState,Component} from "react";

const MyMandatoryContext = createContext<string | undefined>(undefined);

const useMyMandatoryContext = ()=>{
  const context = useContext(MyMandatoryContext);
  if(context === undefined) throw new Error("Must provide a value with MyMandatoryContext.Provider");

  return context;
}

const MyMandatoryContextConsumer : React.Consumer<string> = ({children})=>{
  return children(val=>{
    if(val === undefined) throw new Error("Must provide a value with MyMandatoryContext.Provider");

    return ???? // Not sure what to do here
  })
}

const MyFuncComponent : React.FC =()=>{
  const myMandatoryValue = useMyMandatoryContext(); // is string for sure
  return (<span>{myMandatoryValue.toUpperCase()}</span>)
}

class MyClassComponent  extends Component {
  render () {
    return (
      <MyMandatoryContext.Consumer>
        {(myMandatoryValue) => <span>{myMandatoryValue.toUpperCase()}</span>}
      </MyMandatoryContext.Consumer>
    )
  }
}

class MyClassComponent2  extends Component {
  render () {
    return (
      <MyMandatoryContext.Consumer>
        {(myMandatoryValue) => {
          if(!myMandatoryValue) throw new Error("Must provide a value with MyMandatoryContext.Provider");
          return <span>{myMandatoryValue.toUpperCase()}</span>;
        }}
      </MyMandatoryContext.Consumer>
    )
  }
}

export default function App() {
  const [message, setMessage] = useState<string | undefined>();

  return (
    <div className="App">
      <p>
        <input
          value={message}
          onChange={(evt) => setMessage(evt.target.value)}
        />
      </p>
      {message && (
        <MyMandatoryContext.Provider value={message}>
          <MyFuncComponent />
          <MyClassComponent />
          <MyClassComponent2 />
        </MyMandatoryContext.Provider>
      )}
    </div>
  );
}
knpiaxh1

knpiaxh11#

如果你想坚持使用类组件,建议在消费者周围制作一个 Package 器并使用它。

const MyMandatoryContextConsumer = ({
  children
}: {
  children: (val: string) => ReactNode;
}) => {
  return (
    <MyMandatoryContext.Consumer>
      {(val) => {
        if (val === undefined)
          throw new Error(
            "Must provide a value with MyMandatoryContext.Provider"
          );
        return children(val); 
      }}
    </MyMandatoryContext.Consumer>
  );
};

现在你可以在你的课堂上使用它:

class MyClassComponent extends Component {
  render() {
    return (
      <MyMandatoryContextConsumer>
        {(myMandatoryValue) => <span>{myMandatoryValue.toUpperCase()}</span>}
      </MyMandatoryContextConsumer>
    );
  }
}

Sandbox
注意:children属性是用string类型定义的。应该有更好的方法使其通用。

相关问题