使用React Hooks从Router获取props

5
我正在试着使用React Hooks重构我的代码,但我不太明白如何通过React Routers和Hooks将属性(props)传递给我的组件。
旧的(普通)React代码如下:
App.js
import React from 'react';
import { withRouter } from "react-router-dom";
import {Routes} from './routes/Routes';

function App() {
    const childProps={something: "else"};
    return (
        <div className="App">
            <Routes childProps={childProps} />
        </div>
    );
}

export default withRouter(App);

Routes.js

import {Switch, Route} from 'react-router-dom';
import Game from '../game/Game';
import Scenario from '../game/Scenario';

const CustomRoute = ({ component: C, props: cProps, ...rest }) =>
    <Route
        {...rest}
        render={(props) =>
            <C {...props} {...cProps} />
        }
    />;

export const Routes = ({childProps}) => 
    <Switch>
        <Route path="/" exact component={Game} props={childProps} />
        <CustomRoute path="/scenario/:id" exact component={Scenario} props={childProps}/>
    </Switch>

Game.js

import React from 'react';

const Game = () => {
  return (
    <div className="Game">
      <header className="Game-header">
        <a href="/scenario/0">
          START
        </a>
      </header>
    </div>
  );
};

export default Game;

Scenario.js

export default class Scenario extends Component {
    constructor(props) {
        super(props);

        this.state = {
            scenarios: null,
            scenarioId: null,
            currentScenario: null
        }
    }

    async componentDidMount() {
        const scenarioId = await this.props.match.params.id;
        const scenarios = await data.scenarios;
        this.setState({scenarios, scenarioId});
        this.getScenario();
    }

    getScenario = () => {
        this.state.scenarios.forEach((scenario) => {
            if (scenario.id === this.state.scenarioId) {
                const currentScenario = scenario;
                this.setState({currentScenario});
            }
        })
    }

    render() {
        return (
            <div>
                {this.state.currentScenario != null
                    ? this.state.currentScenario.options.length === 1
                        ? (
                            <div>
                                <div>{this.state.currentScenario.text}</div>
                                <div>{this.state.currentScenario.options[0].text}</div>
                                <a href="/">Go Back</a>
                            </div>
                        )
                        : (
                            <div>
                                <div>{this.state.currentScenario.text}</div>
                                <div>{this.state.currentScenario.options.map((option, index) => (
                                    <div key={index}>
                                        <a href={`/scenario/${option.to}`}>
                                            {option.text}
                                        </a>
                                    </div>
                                ))}</div>
                            </div>
                        )
                    : null
                }
            </div>
        );
    }
};

我在网上找到了一些代码,可以改变我从路由器获取props的方式:

HookRouter.js

import * as React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

const RouterContext = React.createContext(null);

export const HookedBrowserRouter = ({ children }) => (
  <BrowserRouter>
    <Route>
      {(routeProps) => (
        <RouterContext.Provider value={routeProps}>
          {children}
        </RouterContext.Provider>
      )}
    </Route>
  </BrowserRouter>
);

export function useRouter() {
  return React.useContext(RouterContext);
};

新的 App.js

import React from 'react';
import { withRouter } from "react-router-dom";
import {Routes} from './routes/Routes';
import {HookedBrowserRouter, useRouter} from './routes/HookRouter';

function App() {
    const childProps={something: "else"};
    return (
        <HookedBrowserRouter>
        <div className="App">
            <Routes childProps={childProps} />
        </div>
        </HookedBrowserRouter>
    );
}

export default withRouter(App);

我使用新的Scenario.js已经有了很大进展。
import React, { Component, useState, useEffect } from 'react';
import data from '../data/fake';
import {useRouter} from '../routes/HookRouter';

const RouterContext = React.createContext(null);

const HookSceneario = () => {
    const [scenarios, setScenarios] = useState(null);
    const [scenarioId, setScenarioId] = useState(null);
    const [currentScenario, setCurrentScenario] = useState(null);

    // Similar to componentDidMount and componentDidUpdate:
        // Update the document title using the browser API
        // console.log(React.useContext(RouterContext));

    useEffect(() => {
        console.log(scenarios);
    });

    return (
        <div>
            // ...
        </div>
    );
}

所以,useState可以替换类构造函数中的this.state,而useEffect则应该替换componentDidMount,但我找不到从路由中获取props的方法。


1
你在 <Scenario/> 的子组件中需要 routeProps 吗?因为你的渲染方式是在 <Route> 组件中渲染的,而你是通过 render={(routeProps) => <C {...routeProps)/> 传递 routeProps 的。请注意,我已将其重命名为 routeProps,以明确 render 属性中可用的 props 对象即为 routeProps(匹配、位置和历史记录)。因此,<Scenario/> 已经可以访问 routeProps - cbdeveloper
感谢@cbdev420的建议。所以你就是写那段代码的人。我的问题是如何使用Hooks将Scenario内的这些props作为函数调用。似乎我不能像使用类一样console.log任何props。 - Viet
我认为异步生命周期方法不是一个好主意... 我可以创建一个单独的异步函数并将逻辑移动到那里。然后在componentDidMount中调用此函数,例如getScenarion。 - Bonjov
@Bonjov 感谢您的建议。除非我误解了您的评论,否则上面的代码似乎已经实现了您的建议:在componentDidMount中调用异步函数。 - Viet
1个回答

11

我认为这很好地阐述了你想要做的事情:

记住:

<Route>呈现的组件始终可以访问routeProps(匹配、位置和历史记录)。

如果它是通过component prop进行呈现的,如<Route ... component={Home}/>,则这是自动的。

如果它是通过render prop进行呈现的,则需要将它们传递扩展开,例如:

// You can spread routeProps to make them available to your rendered Component
const FadingRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={routeProps => (
    <FadeIn>
      <Component {...routeProps}/>
    </FadeIn>
  )}/>
)

在 CodeSandbox 上查看链接


结果:

图片描述


完整代码:

index.js

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import AllRoutes from "./AllRoutes";

function App() {
  return (
    <Router>
      <AllRoutes />
    </Router>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

AllRoutes.js

import React from "react";
import { Switch, Route } from "react-router-dom";
import Home from "./Home";
import Component1 from "./Component1";

function AllRoutes() {
  return (
    <Switch>
      <Route exact path="/" component={Home} />
      <Route exact path="/comp1" component={Component1} />
    </Switch>
  );
}

export default AllRoutes;

首页.js

import React from "react";
import { Link } from "react-router-dom";

function Home(props) {
  return (
    <div>
      I am HOME component
      <ul>
        <li>
          <Link to={"/comp1"}>Component1</Link>
        </li>
      </ul>
      I have access to routeProps: YES
      <br />
      Because I'm directly rendered from a Route
      <ul>
        <li>{"props.match:" + props.match.toString()}</li>
        <li>{"props.location:" + props.location.toString()}</li>
        <li>{"props.history:" + props.history.toString()}</li>
      </ul>
    </div>
  );
}

export default Home;

组件1.js

import React from "react";
import { Link } from "react-router-dom";
import Component1Child from "./Component1Child";
import RouterContext from "./RouterContext";

function Component1(props) {
  const routeProps = {
    match: props.match,
    history: props.history,
    location: props.location
  };

  return (
    <RouterContext.Provider value={routeProps}>
      <div>
        <b>I am Component1</b>
        <ul>
          <li>
            <Link to={"/"}>Home</Link>
          </li>
        </ul>
        I have access to routeProps: YES
        <br />
        Because I'm directly rendered from a Route.
        <br />
        And I automatically 'inherit' them when I'm rendered through the Route
        'component' prop
        <ul>
          <li>{"props.match:" + props.match.toString()}</li>
          <li>{"props.location:" + props.location.toString()}</li>
          <li>{"props.history:" + props.history.toString()}</li>
        </ul>
        <Component1Child />
      </div>
    </RouterContext.Provider>
  );
}

export default Component1;

组件1子组件.js

import React from "react";
import Component1GrandChild from "./Component1GrandChild";

function Component1Child(props) {
  return (
    <div>
      <b>I am Component1Child</b> <br />
      <br />
      I have access to routeProps: NO
      <br />
      Because I'm NOT directly rendered from a Route.
      <br />I am rendered by Componen1 and routeProps are not automatically
      passed down.
      <ul>
        <li>{"props.match:" + props.match}</li>
        <li>{"props.location:" + props.location}</li>
        <li>{"props.history:" + props.history}</li>
      </ul>
      <Component1GrandChild />
    </div>
  );
}

export default Component1Child;

组件1的子孙组件.js

import React from "react";
import useRouteProps from "./useRouteProps";

function Component1GrandChild(props) {
  const [match, location, history] = useRouteProps();
  return (
    <div>
      <b>I am Component1GrandChild</b> <br />
      <br />
      I have access to routeProps: YES
      <br />
      Because I'm consuming the routeProps provided by Component1 (which is the
      one directly rendered by the Route)
      <br /> And I'm consuming that through a custom hook called useRouteProps.
      <br />I am rendered by Componen1 and routeProps are not automatically
      passed down.
      <ul>
        <li>{"props.match:" + match}</li>
        <li>{"props.location:" + location}</li>
        <li>{"props.history:" + history}</li>
      </ul>
    </div>
  );
}

export default Component1GrandChild;

RouterContext.js

import React from "react";

const RouterContext = React.createContext(null);

export default RouterContext;

useRouteProps.js

import { useContext } from "react";
import RouterContext from "./RouterContext";

function useRouteProps() {
  const routeProps = useContext(RouterContext);
  return [routeProps.match, routeProps.location, routeProps.history];
}

export default useRouteProps;

非常感谢您详细的回答。请让我实现您的代码并与您联系(将其标记为正确)。 - Viet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接