将具有 React Context API 的函数传递给嵌套在树深处的 child 组件以更新状态值

Passing a function with React Context API to child component nested deep in the tree to update state value

我是第一次使用 React Context API。我有一个 JWT 令牌生成器,它接受 header、有效负载和秘密的输入。考虑到这一点,JWT 生成器组件嵌套在几个不同的组件中。生成JWT后,我想更新App,让JWT遍及所有不同的页面。

根据之前的工作,我尝试使用各种形式的 React Context 来实现这一点,但在尝试将某种 'setJWT' 函数传递给 JWT 时,我特别 运行 遇到了问题生成器,以便在用户成功通过身份验证时更新此状态值。

App.tsx (parent)

export default class App extends Component<AppProps> {
  constructor(props: AppProps) {
    super(props);
    this.state = {};
  }

  render(): JSX.Element {
    // const { user } = this.state;

    return (
      <>
        <BrowserRouter>
          <Header />
          <Box className="root">
            <div style={{ display: 'flex' }}>
              <Box className="nav">
                <NavBar headings={headings} subheadings={subheadings} />
              </Box>
              <Box
                style={{
                  marginLeft: '210px',
                  marginTop: '50px',
                  position: 'fixed',
                }}
              >
                <Content />
              </Box>
            </div>
          </Box>
        </BrowserRouter>
      </>
    );
  }
}

Authentication.tsx (child)

export interface PaneProps {
  headings: {
    key: number;
    name: string;
    pathname: string;
  }[];
  subheadings: {
    key: number;
    name: string;
    calls: string[];
  }[];
}

interface PageState {
  isLoggedIn: boolean;
}

export default class Authentication extends Component<unknown, PageState> {
  constructor(props: PaneProps) {
    super(props);
    this.state = {
      isLoggedIn: true,
    };
  }

  render(): JSX.Element {
    const { isLoggedIn } = this.state;
    return (
      <>
        <Box className="outer-box">
          <Grid container className="grid-container">
            <div className="some-page-wrapper">
              <div className="row">
                <div className="column">
                  <div className="text-column">
                    <AuthenticationText />
                  </div>
                </div>

                {(function () {
                  if (isLoggedIn) {
                    return (
                      <div className="column">
                        <div className="text-column">
                          <div className="sticky-div-bs">
                            <JWTGenerator />
                          </div>
                        </div>
                      </div>
                    );
                  }
                  return (
                    <div className="column">
                      <div className="bs-column">
                        <div className="sticky-div-bs">
                          <IFrameBS />
                        </div>
                      </div>
                    </div>
                  );
                })()}
              </div>
            </div>
          </Grid>
        </Box>
      </>
    );
  }
}

JWTGenerator.tsx (child-child)

export interface AppProps {
  headings?: {
    key: number;
    name: string;
    pathname: string;
  }[];
  subheadings?: {
    key: number;
    name: string;
    calls: string[];
  }[];
}

interface AppState {
  header: string;
  payload: string;
  secret: string;
  textAreaValue?: string;
}

export default class JWTGenerator extends Component<AppProps, AppState> {
  constructor(props: AppProps) {
    super(props);
    this.state = {
      header: JSON.stringify(
        {
          alg: 'HS256',
          typ: 'JWT',
        },
        null,
        2,
      ),
      payload: JSON.stringify(
        {
          iat: 1501768003,
          iss: 'periodicdev',
          sub: 'periodicadmin',
        },
        null,
        2,
      ),
      secret: '',
      textAreaValue: '',
    };
    this.onChangeHeader = this.onChangeHeader.bind(this);
    this.onChangePayload = this.onChangePayload.bind(this);
    this.onChangeSecret = this.onChangeSecret.bind(this);
  }

  onChangeHeader(newHeader: string): void {
    this.setState({ header: newHeader });
  }

  onChangePayload(newPayload: string): void {
    this.setState({ payload: newPayload });
  }

  onChangeSecret(newSecret: string): void {
    this.setState({ secret: newSecret });
  }

  onChangeTextArea(header: string, payload: string, secretKey: string): void {
    const cleanHeader = btoa(header.replace(/\s/g, ''));
    const cleanPayload = btoa(payload.replace(/=/g, '').replace(/\s/g, ''));
    const baseMessage = `${cleanHeader}.${cleanPayload.replace(/=/g, '')}`;
    const secret = CryptoJS.HmacSHA256(baseMessage, secretKey)
      .toString(CryptoJS.enc.Base64)
      .replace(/=/g, '')
      .replace(/\+/g, '-')
      .replace(/\//g, '_');
    this.setState({
      textAreaValue: `${cleanHeader}.${cleanPayload.replace(
        /=/g,
        '',
      )}.${secret}`,
    });
  }

  render(): JSX.Element {
    const { header, payload, secret, textAreaValue } = this.state;
    return (
      <>
        <div className="main-container-jwt">
          <div className="top-block-jwt">
            <Box className="top-bar-text-jwt">
              <i className="white">Example Call</i>
            </Box>
          </div>

          <div className="example-block-jwt">
            <div className="header-example-jwt">
              <AceEditor
                placeholder="Header"
                value={header}
                onChange={(e) => this.onChangeHeader(e)}
                onInput={(e: any) =>
                  this.onChangeTextArea(header, payload, secret)
                }
                // mode="json"
                theme="xcode"
                name="ace-editor"
                fontSize={11}
                showPrintMargin
                wrapEnabled
                showGutter
                setOptions={{
                  highlightGutterLine: false,
                  highlightActiveLine: false,
                  enableBasicAutocompletion: true,
                  enableLiveAutocompletion: true,
                  enableSnippets: false,
                  showLineNumbers: false,
                  tabSize: 2,
                  useWorker: false,
                }}
                style={{
                  color: 'red',
                  position: 'relative',
                  width: '100%',
                  height: '100%',
                  maxHeight: '100px',
                }}
              />
            </div>
            <div className="payload-example-jwt">
              <AceEditor
                placeholder="Payload"
                value={payload}
                onChange={(e: any) => this.onChangePayload(e)}
                onInput={(e: any) =>
                  this.onChangeTextArea(header, payload, secret)
                }
                // mode="json"
                theme="xcode"
                name="ace-editor"
                fontSize={11}
                showPrintMargin
                wrapEnabled
                showGutter
                setOptions={{
                  highlightGutterLine: false,
                  highlightActiveLine: false,
                  enableBasicAutocompletion: true,
                  enableLiveAutocompletion: true,
                  enableSnippets: false,
                  showLineNumbers: false,
                  tabSize: 2,
                  useWorker: false,
                }}
                style={{
                  color: 'blue',
                  position: 'relative',
                  width: '100%',
                  height: '100%',
                  maxHeight: '100px',
                }}
              />
            </div>
            <div className="secret-example-jwt">
              <AceEditor
                placeholder="Secret"
                value={secret}
                onChange={(e: any) => this.onChangeSecret(e)}
                onInput={(e: any) =>
                  this.onChangeTextArea(header, payload, secret)
                }
                // mode="json"
                theme="xcode"
                name="ace-editor"
                fontSize={11}
                showPrintMargin
                wrapEnabled
                showGutter
                setOptions={{
                  highlightGutterLine: false,
                  highlightActiveLine: false,
                  enableBasicAutocompletion: true,
                  enableLiveAutocompletion: true,
                  enableSnippets: false,
                  showLineNumbers: false,
                  tabSize: 2,
                  useWorker: false,
                }}
                style={{
                  color: 'green',
                  position: 'relative',
                  width: '100%',
                  height: '100%',
                  maxHeight: '100px',
                }}
              />
            </div>

            <div className="spacer">
              <Button
                // onClick={() =>
                //   this.setState((prev) => ({
                //     ...prev,
                //     textAreaValue: prev.value,
                //   }))
                // }
                onClick={(e: any) =>
                  this.onChangeTextArea(header, payload, secret)
                }
                className="try-it-button"
                style={{
                  backgroundColor: '#533cf8',
                  color: 'white',
                  borderRadius: 0,
                  fontSize: 13,
                  fontWeight: 200,
                }}
              >
                Make Call
              </Button>
              <div className="spacer-text-div">
                auto-update 'fat', alphabetize payload, and make the example
                call above
              </div>
            </div>

            <div className="header-2-jwt">
              <i className="white">Example Response</i>
            </div>

            <div className="textarea-example-jwt">
              <AceEditor
                // placeholder="Enter a call here..."
                value={textAreaValue || ''}
                readOnly
                // mode="json"
                theme="twilight"
                name="ace-editor"
                fontSize={11}
                showPrintMargin
                wrapEnabled
                setOptions={{
                  highlightGutterLine: false,
                  highlightActiveLine: false,
                  enableBasicAutocompletion: true,
                  enableLiveAutocompletion: true,
                  enableSnippets: false,
                  showLineNumbers: false,
                  tabSize: 2,
                  useWorker: false,
                  indentedSoftWrap: false,
                  wrapEnabled: true,
                }}
                style={{
                  position: 'relative',
                  width: '100%',
                  height: '100%',
                }}
              />
            </div>

            <div className="bottom-block-jwt" />
          </div>
        </div>
      </>
    );
  }
}

我需要做的是在我的应用程序中传递这个 jwt 令牌,以确保 API 用户的身份验证。非常感谢任何 suggestions/help。谢谢。

为此,您需要在应用的顶层初始化一些状态以设置 JWT。当然这个状态一开始会是null,因为按照你说的,需要把JWT设置下来几children。但是你仍然需要在顶部初始化它以使用ContextAPI,所以Context可以被所有children访问。

首先创建一些用户上下文:

import { createContext } from 'react'; 

export const UserContext = createContext(null);

然后在 App.js 中创建一些状态并将其放入上下文中,以便可以将其传递给将更新 JWT 的 child。请务必从 useState.

传递 setJwt 函数
const [jwt, setJwt] = useState(null);

    return (
     <UserContext.Provider value={{ jwt, setJwt }}>
        <BrowserRouter>
          <Header />
          <Box className="root">
            // etc. your app here
          </Box>
        </BrowserRouter>
     </UserContext.Provider>)

然后您可以通过从上下文中检索它来访问 JWTGenerator.tsx (child-child) 中的该函数。

const { setJwt } = useContext(UserContext)

这将允许您在此 child 中设置 JWT。并且因为 JWT 也在上下文中传递,一旦它在 JWTGenerator.tsx.

中更新,任何其他 child 组件都可以访问该状态值

这解释了如何使用此模式在上下文中详细设置用户,尽管在此示例中将用户设置在顶层,但它仍然是相同的模式:https://jawblia.medium.com/react-template-for-jwt-authentication-with-private-routes-and-redirects-f77c488bfb85我也会推荐 React当然是文档,Ben Awad 在 YouTube 上有一个关于这个的视频教程。