【问题标题】:How to use context api with react router v4?如何将上下文 api 与反应路由器 v4 一起使用?
【发布时间】:2018-10-13 19:39:04
【问题描述】:

我正在我的应用程序上尝试使用 React 16.3 中的新上下文 API 进行一些测试,但我不明白为什么我的重定向永远不起作用。

<ContextA>
  <Switch>
    <Route exact path='/route1' component={ Component1 } />
    <ContextB>
      <Route exact path='/route2' component={ Component2 } />
      <Route exact path='/route3' component={ Component3 } />
    </ContextB>
    <Redirect from='/' to='/route1' />
  </Switch>
</ContextA>

我不想让我的 ContextB 可用于所有路线,只有 2 和 3 条。我该怎么做?

【问题讨论】:

    标签: reactjs react-router


    【解决方案1】:

    看起来&lt;Switch&gt; 应该只有&lt;Route&gt;&lt;Redirect &gt; 组件作为直接子级。 (source)

    我想这就是为什么你的Redirect 不能工作的原因,因为你使用ContextB 作为Switch 孩子。

    最简单但重复的解决方案可能是将您的 ContextB 作为您想要的每个 &lt;Route&gt; 的子代:

    注意:这些解决方案假设您为 Context 组件分配了默认值,如下所示:const MyContext = React.createContext(defaultValue);

    <Route exact path='/route2'>
      <ContextB.Provider>
        <Component1 />
      </ContextB.Provider>
    </Route>
    

    您甚至可以为此创建一个ContextRoute 组件:

    import React from 'react';
    import { Route } from 'react-router-dom';
    
    const ContextRoute = ({ contextComponent, component, ...rest }) => {
      const { Provider } = contextComponent;
      const Component = component;
    
      return (
        <Route {...rest}>
          <Provider>
            <Component />
          </Provider>
        </Route>
      );
    };
    
    export default ContextRoute;
    

    然后将其用作Route:

    <ContextA>
      <Switch>
        <Route exact path='/route1' component={ Component1 } />
        <ContextRoute exact path='/route2' contextComponent={ContextB} component={ Component2 } />
        <ContextRoute exact path='/route3' contextComponent={ContextB} component={ Component3 } />
        <Redirect from='/' to='/route1' />
      </Switch>
    </ContextA>
    

    通过此解决方案,您可以在嵌套组件中将上下文与渲染道具一起使用:

    return (
      <ContextB.Consumer>
        {value => <div>{value}</div>}
      </ContextB.Consumer>
    );
    

    但我们可以想象更多的解决方案,例如 HOC,将上下文值直接传递给路由组件道具等...

    【讨论】:

    • 成功了,非常感谢!现在我可以更好地理解架构了。
    • 似乎上下文 B 的状态会丢失,但是如果您从 /route2 或 /route3 转到 /route1。似乎通常最好将根放在某个地方,然后在需要它的那些组件中抓取它?
    • @jones 我同意你的做法,但我想这取决于每个应用的目标。
    • 阅读下面的答案,了解为什么这种方法不能真正按预期工作:stackoverflow.com/a/61824692/4470169
    【解决方案2】:

    作为对其他人的警告,接受的答案并不真正起作用,正如您对原始(非工作)概念所期望的那样:

    // This comes from the original question (doesn't work as-is!)
    <ContextA>
      <Switch>
        <Route exact path='/route1' component={ Component1 } />
          <ContextB>
            <Route exact path='/route2' component={ Component2 } />
            <Route exact path='/route3' component={ Component3 } />
          </ContextB>
          <Redirect from='/' to='/route1' />
       </Switch>
    </ContextA>
    

    在那里,/route2/route3 共享一个上下文,期望应该是:

    1. 在路由转换之间保持状态。
    2. 如果 Component2Component3 更新上下文,则更改应反映给另一个。

    对于公认的解决方案,以上都不是真的。

    【讨论】:

    • 你说得对,很好。即使您的答案不是一个真正的(也许在已接受的答案中进行评论或编辑会更合适?)。使其工作的一种方法是将 ContextB 提供程序提升到 Switch 组件的上方,并在路线 2 和 3 上使用消费者。我会在有时间时尝试更新答案
    • 我仍在寻找解决方案,但还没有弄清楚。 @Nenu 我对你关于提升上下文提供者的理论感兴趣——你解决过这个问题吗?
    【解决方案3】:

    在路由中使用渲染方法。这将解决您的问题,就像我在我的应用程序中所做的那样。

    <Route
      path="/"
      render={(props) => (
        <ContextB>
          <Component2 {...props} />
        </ContextB>
      )}
    />
    

    【讨论】:

      【解决方案4】:

      感谢您的帮助,我在商店环境中使用了它并且有效! (createContext 和 useReducer)

      import React, {createContext, useReducer} from "react";
      import Reducer from './reducer.js'
      
      const initialState = {
          /* */
      };
      const Store = ({children}) => {
          const [state, dispatch] = useReducer(Reducer, initialState);
      
          return (
              <Context.Provider value={[state, dispatch]}>
                  {children}
              </Context.Provider>
          )
      };
      
      export const Context = createContext(initialState);
      export default Store;
      

      /* 在 App.js*/

      const App = () => {
      
          return (
          <BrowserRouter>
                  <Switch>
                  <Route exact path="/" render={ (props)=> <Store> <Home {...props} /> </Store> } />
                  <Route exact path="/about" render={ (props)=> <Store> <About {...props} /> </Store> } />
                  <Route exact path="/login" render={ (props)=> <Store> <Login {...props} /> </Store> } />
                  </Switch>
      
          </BrowserRouter>
          );
      };
      

      【讨论】:

        【解决方案5】:

        简单的解决方案:

        <ContextA>
            <Switch>
                <Route exact path='/' render={ () => <Redirect to='/route1' /> } />
                <Route path='/route1' component={ Component1 } />
            </Switch>
            <ContextB>
                <Switch>
                    <Route path='/route2' component={ Component2 } />
                    <Route path='/route3' component={ Component3 } />
                </Switch>        
            </ContextB>        
        </ContextA>
        

        【讨论】:

        • @impulsgraw 会的。您只需在路由声明底部的 switch 包装器中声明错误路由处理程序。
        • 不,不会的。如果有多个交换机,我应该在哪个交换机中包含错误路由,这样路由器就不会一直显示它?
        • 由于 Switch 包装器是嵌套的,因此您必须有条件地呈现您的错误路由处理程序。您可以定义一个自定义函数,循环遍历所有静态路径并返回一个布尔值。如果未找到匹配项,则使用它有条件地呈现您的错误路由处理程序。即 ``` { !isMatch() && ```
        • 您建议的方法显然是一种臭名昭著的解决方法。事实上,您所说的意思是制作自己的 Switch 实现而不是内置的实现,我认为这是这里唯一可用的选项。
        • 限制自己严格遵循每个用例的 api 实现是不切实际和理想的。这就是为什么您必须创建一个符合逻辑的工作来实现您的预​​期结果。
        猜你喜欢
        • 2018-10-10
        • 2017-12-21
        • 2021-01-22
        • 2018-01-26
        • 2019-07-17
        • 2017-10-21
        • 2017-12-06
        • 2017-08-10
        • 2018-08-03
        相关资源
        最近更新 更多