Redux 和 React-Router:动态路由器不工作

Posted

技术标签:

【中文标题】Redux 和 React-Router:动态路由器不工作【英文标题】:Redux & React-Router: Dynamic Router Not Working 【发布时间】:2016-03-16 10:39:55 【问题描述】:

这在 Alt.js 上运行良好,然后我切换到了 redux。我真的很难把头绕在 redux 上。

错误:Invariant Violation:对象作为 React 子项无效(发现:对象带有键 id, doc…

我正在使用:

"react": "^0.14.3",
"react-dom": "^0.14.3",
"react-redux": "^4.0.0",
"react-router": "^1.0.2",
"redux": "^3.0.4"

任何人都可以看到为什么我收到错误并且没有显示任何组件吗?

import React,  Component, PropTypes  from 'react';
import Router,  Route  from 'react-router';
// redux
import  connect  from 'react-redux';
import  fetchNavItemsIfNeeded  from '../redux/actions/nav-item-actions';

class Routes extends Component 

  constructor(props) 
    super(props);
  

  componentWillMount() 
    const  dispatch  = this.props;
    dispatch(fetchNavItemsIfNeeded('7B3E7eWWPizd11n'));
  

  fetchMenuSystem(data) 
    const self = this;
    const currRoutesState = this.props.navItems;
    const routes = data === undefined ? this.props.navItems : data;
      routes.map((route) => 
        // set paths up first
        let currPaths = [];
        if (route.paths !== undefined) 
          currPaths = route.paths;
         else 
          currPaths.push(route.linkTo);
        
        // Components - first check for ecomMods
        let currComponent;
        if (route.ecomMod !== undefined) 
          currComponent = require('../components/eCommerce/' + (route.ecomMod).toLowerCase());
          // clear out currPath if this is an ecom Module
          // and start a new currPaths array
          currPaths = [];
          if (route.parentId === null) 
            currPaths.push(route.ecomMod);
           else 
            currPaths.push(route.ecomMod + '/:id');
          
         else 
          currComponent = require('../components/pages/');
        

        currPaths.map((currPath) => 
          const props =  key: currPath, path: currPath, component: currComponent ;
          currRoutesState.push(<Route  ...props  />);
        );

        if (route.childNodes !== undefined) 
          self.fetchMenuSystem(route.childNodes);
        
      );
    return currRoutesState;
  

  fetchRoutes() 
    const result = this.fetchMenuSystem();
    const clientId = this.props.clientId;
    return (
      <Route clientId= clientId  component= require('../components/APP') >
         result 
        <Route path="*" component= require('../components/pages/Not-Found') />
      </Route>
    );
  

  render() 
    if (!this.props.navItems) return <div>Loading ...</div>;
    const routerProps = 
      routes: this.fetchRoutes(),
      createElement: (component, props) => 
        return React.createElement(component,  ...props );
      
    ;
    return (
      <div>
        <Router  ...routerProps  history= this.props.history />
      </div>
    );
  


Routes.propTypes = 
  clientId: PropTypes.string.isRequired,
  dispatch: PropTypes.func.isRequired,
  error: PropTypes.object,
  history: PropTypes.object.isRequired,
  navItems: PropTypes.array.isRequired
;

function mapStateToProps(state) 
  const  navItemsPerClient  = state;
  if (!navItemsPerClient) 
    return 
      navItems: []
    ;
  
  return 
    navItems: navItemsPerClient.navItems
  ;


export default connect(mapStateToProps)(Routes);

我知道很多代码,但是数据显示出来了,而且确实是 result in this.fetchRoutes() 这给了我问题。没有它 一切正常,但当然有 95% 的路线。

【问题讨论】:

【参考方案1】:

如果有人感兴趣,这里有一个带有 redux 的动态反应路由器。 [世界上有太多的待办事项示例,如果我不是总是想出真实世界示例的人,那就太好了。]

index.js

import 'babel-core/polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import Routes from './router/routes';
// redux
import  Provider  from 'react-redux';
import configureStore from './store/configureStore';
// styles
import './index.css';   

// Setting up entire state 'schema' at inception
const store = configureStore();

ReactDOM.render(
  <Provider store= store >
    <Routes />
  </Provider>,
  document.getElementById('root')
);

routes.js

import React,  Component, PropTypes  from 'react';
import  Router, Route, IndexRoute  from 'react-router';

// redux
import  connect  from 'react-redux';
import  fetchNavItemsIfNeeded  from '../actions/nav-items-actions';

// history
import createBrowserHistory from 'history/lib/createBrowserHistory';
const history = createBrowserHistory();

import App from '../containers/app/App';
import Home from '../containers/home/Home';
import NotFound from '../containers/misc/NotFound';

class Routes extends Component 

  constructor() 
    super();
    this.state = 
      routes: []
    ;
  

  fetchMenuSystem(data) 
    const self = this;
    const currRoutesState = this.state.routes;
    const routes = data === undefined ? this.props.navItems : data;

    routes.map((route) => 
      // set paths up first
      let currPaths = [];
      if (route.paths !== undefined) 
        currPaths = route.paths;
       else 
        currPaths.push(route.linkTo);
      
      // Components - first check for ecomMods
      let currComponent;
      if (route.ecomMod !== undefined) 
        currComponent = require('../containers/' + route.ecomMod);
        // clear out currPath if this is an ecom Module
        // and start a new currPaths array
        currPaths = [];
        if (route.parentId === null) 
          currPaths.push(route.ecomMod);
         else 
          currPaths.push(route.ecomMod + '/:id');
        
       else 
        currComponent = require('../containers/' + route.component);
      

      currPaths.map((currPath, idx) => 
        const props =  key: idx, path: currPath, component: currComponent ;
        currRoutesState.push(<Route  ...props  />);
      );

      if (route.childNodes !== undefined) 
        self.fetchMenuSystem(route.childNodes);
      
    );
    return currRoutesState;
  

  componentDidMount() 
    const  dispatch  = this.props;
    const clientId = '7B3E7eWWPizd11n';
    dispatch(fetchNavItemsIfNeeded(clientId));
  

  render() 
    if (!this.props.navItems) return <div>Loading ...</div>;
    return (
      <Router history= history >
        <Route path="/" component= App >
          <IndexRoute component= Home />
           this.fetchMenuSystem() 
          <Route path="*" component= NotFound />
        </Route>
      </Router>
    );
  


function mapStateToProps(state) 
  const  navItemsPerClient  = state;
  if (!navItemsPerClient) 
    return 
      isFetching: false,
      didInvalidate: false,
      navItems: [],
      error: null
    ;
  

  return 
    error: navItemsPerClient.error,
    isFetching: navItemsPerClient.isFetching,
    didInvalidate: navItemsPerClient.didInvalidate,
    navItems: navItemsPerClient.navItems
  ;


Routes.propTypes = 
  dispatch: PropTypes.func.isRequired,
  navItems: PropTypes.array
;

export default connect(mapStateToProps)(Routes);

nav-items-actions.js

import 'isomorphic-fetch';
import  checkStatus, parseJSON  from './utils';

export const INVALIDATE_NAV_ITEMS = 'INVALIDATE_NAV_ITEMS';   
export const NAV_ITEMS_REQUEST = 'NAV_ITEMS_REQUEST';
export const NAV_ITEMS_SUCCESS = 'NAV_ITEMS_SUCCESS';
export const NAV_ITEMS_FAILURE = 'NAV_ITEMS_FAILURE';

export function invalidateNavItems() 
  return 
    type: INVALIDATE_NAV_ITEMS
  ;


function navItemsRequest() 
  return 
    type: NAV_ITEMS_REQUEST
  ;


function navItemsSuccess(payload) 
  return 
    type: NAV_ITEMS_SUCCESS,
    navItems: payload.navItems
  ;


function navItemsFailure(error) 
  return 
    type: NAV_ITEMS_FAILURE,
    error
  ;
  

export function fetchNavItems(clientId) 
  const API_URL = (`../data/$clientId/navigation/navigation.json`);
  return dispatch => 
    dispatch(navItemsRequest());
    return fetch(API_URL)
      .then(checkStatus)
      .then(parseJSON)
      .then(json => dispatch(navItemsSuccess(json)))
      .catch(function(error) 
        const response = error.response;
        if (response === undefined) 
          dispatch(navItemsFailure(error));
         else 
          parseJSON(response)
            .then(function(json) 
              error.status = response.status;
              error.statusText = response.statusText;
              error.message = json.message;
              dispatch(navItemsFailure(error));
            );
        
      );
  ;


function shouldFetchNavItems(state) 
  // Check cache first
  const navItems = state.navItemsPerClient;
  if (!navItems || navItems.length === undefined) 
    // Not cached, should fetch
    return true;
  

  if (navItems.isFetching) 
    // Shouldn't fetch since fetching is running
    return false;
  

  // Should fetch if cache was invalidate
  return navItems.didInvalidate;


export function fetchNavItemsIfNeeded(clientId) 
  return (dispatch, getState) => 
    if (shouldFetchNavItems(getState())) 
      return dispatch(fetchNavItems(clientId));
    
  ;


utils.js

export function checkStatus(response) 
  if (!response.ok)    // (response.status < 200 || response.status > 300)
    const error = new Error(response.statusText);
    error.response = response;
    throw error;
  
  return response;


export function parseJSON(response) 
  return response.json();


nav-items-reducer.js

import  
         INVALIDATE_NAV_ITEMS, NAV_ITEMS_REQUEST, 
         NAV_ITEMS_SUCCESS, NAV_ITEMS_FAILURE 
        from '../actions/nav-items-actions';

function navItems(state = 
  isFetching: false,
  didInvalidate: false,
  navItems: [],
  error: null
, action) 
  switch (action.type) 
  case INVALIDATE_NAV_ITEMS:
    return Object.assign(, state, 
      didInvalidate: true
    );
  case NAV_ITEMS_REQUEST:
    return Object.assign(, state, 
      isFetching: true,
      didInvalidate: false
    );
  case NAV_ITEMS_SUCCESS:
    return Object.assign(, state, 
      isFetching: false,
      didInvalidate: false,
      navItems: action.navItems,
      error: null
    );
  case NAV_ITEMS_FAILURE:
    return Object.assign(, state, 
      isFetching: false,
      didInvalidate: false,
      error: action.error
    );
  default:
    return state;
  


export function navItemsPerClient(state =  , action) 
  switch (action.type) 
  case INVALIDATE_NAV_ITEMS:
  case NAV_ITEMS_REQUEST:
  case NAV_ITEMS_SUCCESS:
  case NAV_ITEMS_FAILURE:
    return navItems(state, action);
  default:
    return state;
  


configure-store.js

import  createStore, applyMiddleware, combineReducers  from 'redux';
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import auth from '../reducers/auth-reducer';
import  navItemsPerClient  from '../reducers/nav-items-reducer';

const logger = createLogger();
const reducer = combineReducers(
  
    auth,
    navItemsPerClient
  
);

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware,
  logger
)(createStore);

export default function configureStore(initialState) 
  return createStoreWithMiddleware(reducer, initialState);


navigation.json


  "incomplete_results": false,
  "navItems": [
    
      "linkTo": "/about",
      "component": "about/About",
      "childNodes": [
        
          "linkTo": "/proforma",
          "component": "about/ProForma"
        
      ]
    ,
    
      "linkTo": "/login",
      "component": "login/Login"
    ,
    
      "linkTo": "/none",
      "component": "misc/RestrictPage",
      "childNodes": [
        
          "linkTo": "/users",
          "component": "user/UsersPage"
        ,
        
          "linkTo": "/repos",
          "component": "repo/ReposPage"
        
      ]
    
  ]


package.json

"dependencies": 
    "body-parser": "^1.14.1",
    "classnames": "^2.2.0",
    "express": "^4.13.3",
    "fixed-data-table": "^0.5.0",
    "history": "^1.13.0",
    "isomorphic-fetch": "^2.1.1",
    "lodash": "^3.10.1",
    "react": "^0.14.3",
    "react-dom": "^0.14.3",
    "react-redux": "^4.0.0",
    "react-router": "^1.0.2",
    "redux": "^3.0.4",
    "redux-logger": "^2.0.4",
    "redux-thunk": "^1.0.0"
  ,
  "devDependencies": 
    "babel": "^5.8.29",
    "babel-core": "^5.8.33",
    "babel-eslint": "^4.1.5",
    "babel-loader": "^5.3.2",
    "eslint": "^1.9.0",
    "eslint-config-airbnb": "^1.0.0",
    "eslint-loader": "^1.1.1",
    "eslint-plugin-react": "^3.8.0",
    "file-loader": "^0.8.4",
    "raw-loader": "^0.5.1",
    "redbox-react": "^1.1.1",
    "rimraf": "^2.4.3",
    "stats-webpack-plugin": "^0.2.2",
    "style-loader": "^0.13.0",
    "url-loader": "^0.5.6",
    "webpack": "^1.12.4",
    "webpack-dev-middleware": "^1.2.0",
    "webpack-hot-middleware": "^2.4.1"
  

【讨论】:

【参考方案2】:

react-router v1.0 及更高版本中,您需要这样做:

import  Router, Route  from 'react-router';

【讨论】:

我正在使用 react-router 1.0.2 这适用于 1.0 以上的任何版本,即 1.0.2 也是如此。

以上是关于Redux 和 React-Router:动态路由器不工作的主要内容,如果未能解决你的问题,请参考以下文章

(通用 React + redux + react-router)如何避免在初始浏览器加载时重新获取路由数据?

react-router搭配react-redux无法监听路由变化的问题

在将 react-router 5 与 redux 7 一起使用时,react-router <Link> 在转到新路由后不会重置状态

如何用 redux 和 react-router 组织状态?

为啥 react-router 在调度时会自动返回上一个路由

Redux 中的 React Router 路由参数