web-dev-qa-db-ja.com

ReactReactフックを使用してルーターから小道具を取得する

私はtryingReact Hooksを使用してコードをリファクタリングしていますが、React)を介して小道具をコンポーネントに渡す方法がよくわかりませんフックを使用するルーター。

古い(通常の)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;

Scenery.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を置き換え、useEffectcomponentDidMountを置き換えることになっていますが、propsルーターから。

2
Viet

これはあなたがやろうとしていることをよく示していると思います。

覚えておいてください:

<Route>によってレンダリングされたコンポーネントは、常にrouteProps(一致、場所、履歴)にアクセスできます。

<Route ... component={Home}/>のように、componentプロップによってレンダリングされる場合、これは自動的に行われます。

renderpropによってレンダリングされる場合は、次のようにそれらを拡散する必要があります。

// 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のリンク


結果:

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;
7
cbdeveloper