React使用React Hooks从Router获取道具

时间:2019-06-22 16:30:20

标签: javascript reactjs react-router react-hooks

我正在尝试使用React Hooks重构代码,但我真的不理解如何使用Hooks通过React Routers将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>
        );
    }
};

所以我在网上找到了这段代码,它将改变我从路由器获得道具的方式:

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,但我找不到获取{{1 }}。

1 个答案:

答案 0 :(得分:1)

我认为这很好地说明了您正在尝试做什么:

记住:

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

如果它是由component道具渲染的,例如<Route ... component={Home}/>,这是自动的。

如果它是由render道具渲染的,则需要对其进行传播,如下所示:

// 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>
  )}/>
)

Link on CodeSandbox


结果:

enter image description here


完整代码:

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;

Home.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;

Component1.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;

Component1Child.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;

Component1GrandChild.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;