反应警告:失败的上下文类型:所需的上下文“路由器”未在“组件”中指定

Posted

技术标签:

【中文标题】反应警告:失败的上下文类型:所需的上下文“路由器”未在“组件”中指定【英文标题】:React Warning: Failed Context Types: Required context `router` was not specified in `Component` 【发布时间】:2015-07-26 01:07:48 【问题描述】:

我正在尝试测试一个需要 react-router 与 app.js 分开的 React 组件。

我有一个使用 mixin Router.Navigation 进行重定向的组件,如下所示:

var React = require('react'),
    Router = require('react-router');

var Searchbar = React.createClass(

  mixins : [Router.Navigation],

  searchTerm: function(e) 
    if (e.keyCode !== 13) 
      return;
    

    this.context.router.transitionTo('/someRoute/search?term=' + e.currentTarget.value)
  ,

  render: function() 
    return (
      <div className="searchbar-container">
        <input type="search" placeholder="Search..." onKeyDown=this.searchTerm />
      </div>
    )
  
);

module.exports = Searchbar;

我试图为此编写一个测试,但碰壁了。除了我无法测试 transitionTo 是否按预期工作之外,我还在 Jest 测试中遇到了以下错误消息:

警告:上下文类型失败:router 未在 Searchbar 中指定所需的上下文。

有谁知道我如何摆脱警告和奖励问题,如何测试转换是否按预期工作?

我已经对此和 Github 上的对话进行了研究:https://github.com/rackt/react-router/issues/400 是我发现的最接近该问题的。看起来我需要单独导出路由器,但是在没有警告的情况下运行组件测试似乎有很多开销https://github.com/rackt/react-router/blob/master/docs/guides/testing.md

真的是这样吗?

【问题讨论】:

您找到解决方案了吗? 没有,我已经成功了,但我仍在努力。这不是特别紧急,因为它们只是警告消息,但它们很烦人。不过我会整理一下。 嗨@Tom 我终于找到了解决方案,因为 BinaryMuse 的最后一段让我走上了正轨。我用代码发布了自己的答案。 谢谢你让我知道伙计! 【参考方案1】:

在 React Router 的 0.13 版本中,mixin NavigationState 已被弃用。相反,它们提供的方法存在于对象this.context.router 上。这些方法不再被弃用,但如果您明确使用this.context.router,则不需要mixin(但您需要直接声明contextTypes);或者,您可以使用 mixin,但不需要直接使用 this.context.router。 mixin 方法将为您访问它。

在任何一种情况下,除非你通过 React Router 渲染你的组件(通过Router#run),router 对象不会提供给上下文,当然你不能调用转换方法。这就是警告告诉你的——你的组件期望路由器被传递给它,但它找不到它。

要单独测试(不创建路由器对象或通过Router#run 运行组件),您可以将模拟的router 对象放在组件上下文的正确位置,并测试您调用transitionTo使用正确的值。

【讨论】:

这个答案也被弃用了:github.com/rackt/react-router/blob/master/UPGRADE_GUIDE.md 像社区中的许多其他人一样,我们误解了“mixins 正在消失”的情绪。 Mixins 和 React.createClass 不会很快消失,而且绝对不会消失,直到 ES6 类有更好的答案来取代 mixins 所做的事情(比如装饰器)。 这个答案的最后一段给了我解决方案。有关详细信息,请参阅我自己的答案。 新的升级指南在这里github.com/rackt/react-router/blob/latest/upgrade-guides/…【参考方案2】:

因为 router 严重依赖于 React 鲜为人知的 context 功能,所以您需要像 here 中描述的那样对它进行存根

var stubRouterContext = (Component, props, stubs) => 
  return React.createClass(
    childContextTypes: 
      makePath: func,
      makeHref: func,
      transitionTo: func,
      replaceWith: func,
      goBack: func,
      getCurrentPath: func,
      getCurrentRoutes: func,
      getCurrentPathname: func,
      getCurrentParams: func,
      getCurrentQuery: func,
      isActive: func,
    ,

    getChildContext () 
      return Object.assign(
        makePath () ,
        makeHref () ,
        transitionTo () ,
        replaceWith () ,
        goBack () ,
        getCurrentPath () ,
        getCurrentRoutes () ,
        getCurrentPathname () ,
        getCurrentParams () ,
        getCurrentQuery () ,
        isActive () ,
      , stubs);
    ,

    render () 
      return <Component ...props />
    
  );
;

并像这样使用:

var stubRouterContext = require('./stubRouterContext');
var IndividualComponent = require('./IndividualComponent');
var Subject = stubRouterContext(IndividualComponent, someProp: 'foo');
React.render(<Subject/>, testElement);

【讨论】:

【参考方案3】:

这是我的 Jest 文件,可以完整回答这个问题。 BinaryMuse 的最后一段让我走上了正轨,但我发现代码示例总是最有帮助的,所以在这里供将来参考。

jest.dontMock('./searchbar');

describe('Searchbar', function() 
  var React = require('react/addons'),
      Searchbar = require('../../components/header/searchbar'),
      TestUtils = React.addons.TestUtils;

  describe('render', function() 
    var searchbar;

    beforeEach(function() 
      Searchbar.contextTypes = 
        router: function() 
          return 
            transitionTo: jest.genMockFunction()
          ;
        
      ;

      searchbar = TestUtils.renderIntoDocument(
        <Searchbar />
      );
    );

    it('should render the searchbar input', function() 
      var searchbarContainer = TestUtils.findRenderedDOMComponentWithClass(searchbar, 'searchbar-container');

      expect(searchbarContainer).toBeDefined();
      expect(searchbarContainer.props.children.type).toEqual('input');
    );

  );

);

希望这对将来的其他人有所帮助。

【讨论】:

【参考方案4】:

我的回答不是专门针对 Jest 的,但它可能会帮助遇到同样问题的人。 我创建了一个类来包装routercontext

然后在您的测试中添加&lt;ContextWrapper&gt;&lt;YourComponent/&gt;&lt;/ContextWrapper&gt;

包装其他内容(例如ReactIntl)会很有用。

请注意,您将失去使用浅渲染的可能性,但ReactIntl 已经是这种情况。

希望对某人有所帮助。


ContextWrapper.js

import React from 'react';

export default React.createClass(
    childContextTypes: 
        router: React.PropTypes.object
    ,

    getChildContext () 
        return 
            router: 
        ;
    ,

    render () 
        return this.props.children;
    
);

【讨论】:

以上是关于反应警告:失败的上下文类型:所需的上下文“路由器”未在“组件”中指定的主要内容,如果未能解决你的问题,请参考以下文章

警告:子上下文类型失败:提供给“CellRenderer”的“数字”类型的子上下文“virtualizedCell.cellKey”无效,应为“字符串”

如何模拟反应路由器上下文

反应路由器dom v4 |上下文路由器和路由未定义

如何将上下文 api 与反应路由器 v4 一起使用?

未捕获的错误:在路由器上下文之外呈现的 <Link> 无法导航。(…)反应

创建Dialog所需的上下文为什么必须是Activity?