React 使用 React Hooks 从路由器获取道具

Posted

技术标签:

【中文标题】React 使用 React Hooks 从路由器获取道具【英文标题】:React get props from Router with React Hooks 【发布时间】:2019-11-05 02:49:49 【问题描述】:

我正在尝试使用 React Hooks 重构我的代码,但我真的不明白如何使用 Hooks 通过 React 路由器将 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.stateuseEffect 应该替换componentDidMount,但我找不到从路由器获取props 的方法。

【问题讨论】:

&lt;Scenario/&gt; 的孩子是否需要 routeProps?因为你这样做的方式,因为场景是由&lt;Route&gt; 组件呈现的,而你在render=(routeProps) =&gt; &lt;C ...routeProps)/&gt; 中传递routeProps。请注意,我已重命名为routeProps,以明确render 道具中可用的props 对象是routeProps(匹配、位置和历史记录)。因此&lt;Scenario/&gt; 已经可以访问routeProps 感谢@cbdev420 的建议。所以你是写那段代码的人。我的问题是如何将Scenario 中的这些道具称为带有 Hooks 的函数。看来我不能 console.log 任何道具,比如类。 我认为异步生命周期方法是个坏主意...我可以制作单独的异步函数并将这个逻辑移到那里。然后你在 componentdidmount 中调用这个函数,比如 getScenario @Bonjov 谢谢你的建议。除非我误解了您的评论,否则上面的代码似乎符合您的建议:在 componentDidMount 中调用异步函数。 【参考方案1】:

我认为这很好地说明了您正在尝试做的事情:

记住:

&lt;Route&gt; 渲染的组件始终可以访问routeProps(匹配、位置和历史记录)。

如果它是由 component 属性渲染的,就像在 &lt;Route ... component=Home/&gt; 中一样,这是自动的。

如果它是由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


结果:


完整代码:

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;

使用RouteProps.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;

【讨论】:

非常感谢您的详细回答。请让我实现您的代码并回复您(标记为正确)。

以上是关于React 使用 React Hooks 从路由器获取道具的主要内容,如果未能解决你的问题,请参考以下文章

从0搭建React+antd+TypeScript+Umi Hooks+Mobx前端框架

React Redux with hooks - 身份验证和受保护的路由

React hooks - useState() 中的状态在路由更改时不会重置

无法获取 API URL 以重新路由到正确的地址:React Hooks

React拓展 - setState - 路由组件懒加载 - Hooks - Fragment - Context - PureComponent - 插槽 - 错误边界 - 组件通信方式总结(代码片

React从Class方式转Hooks