使用 redux 的服务器端呈现和 MapStateToProp 内容上的 immutable.js 错误

Server-side rendering with redux and immutable.js errors on MapStateToProp contents

我目前正在使用 react v0.14redux v3.0immutable v3.7.6 进行服务器渲染,但我 运行 遇到了一些问题。每当我转到包含 mapStateToProps 的应用程序页面时,我都会在控制台中收到一条错误消息,具体取决于 Uncaught TypeError: e.dashboard.shoppingCart.get is not a function 之类的状态部分。

dashboard.shoppingCart.get 是我在 mapStateToProps 中设置的状态值,.get() 指的是 [=24= 上的 immutable.js 方法].我不确定是什么导致了这个错误,它杀死了应用程序中的任何 javascript,没有任何工作。

服务器

import http from 'http';
import React from 'react';
import {renderToString} from 'react-dom/server';
import { match, RoutingContext } from 'react-router';
import {Provider} from 'react-redux';
import configureStore from './../common/store/store.js';

import fs from 'fs';
import { createPage, write, writeError, writeNotFound, redirect } from './server-utils.js';
import routes from './../common/routes/rootRoutes.js';

const PORT = process.env.PORT || 4000;



function renderApp(props, res) {
  var store = configureStore();
  var markup = renderToString(
    <Provider store={store}>
      <RoutingContext {...props}/>
    </Provider>
  );
  const initialState = store.getState();
  var html = createPage(markup, initialState);
  write(html, 'text/html', res);
}

http.createServer((req, res) => {

  if (req.url === '/favicon.ico') {
    write('haha', 'text/plain', res);
  }

  // serve JavaScript assets
  else if (/__build__/.test(req.url)) {
    fs.readFile(`.${req.url}`, (err, data) => {
      write(data, 'text/javaScript', res);
    })
  }

  // handle all other urls with React Router
  else {
    match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
      if (error)
        writeError('ERROR!', res);
      else if (redirectLocation)
        redirect(redirectLocation, res);
      else if (renderProps)
        renderApp(renderProps, res);
      else
        writeNotFound(res);
    });
  }

}).listen(PORT)
console.log(`listening on port ${PORT}`)

客户

import React from 'react';
import { match, Router } from 'react-router';
import { render } from 'react-dom';
import { createHistory } from 'history';
import routes from './../common/routes/rootRoutes.js';
import {Provider} from 'react-redux';
import configureStore from './../common/store/store.js';
import './../common/styles/main.scss';


const { pathname, search, hash } = window.location;
const location = `${pathname}${search}${hash}`;

const initialState = window.__INITIAL_STATE__;
const store = configureStore(initialState);



// calling `match` is simply for side effects of
// loading route/component code for the initial location
match({ routes, location }, () => {
  render(
    <Provider store={store}>
      <Router routes={routes} history={createHistory()} />
    </Provider>,
    document.getElementById('app')
  );
});

DashCart

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import DashCartItem from './DashCartItem.jsx';
import * as DashCartActions from './../../actionCreators/Dashboard/DashShoppingCart.js';

function DashCart (props) {

  var DashCartItemsList = props.cartSneakers.map((sneaker, key ) => {
    return <DashCartItem sneaker={sneaker} key={key} remove={props.actions.removeSneakerFromCart}></DashCartItem>;
  });

  var price = () => {
    var prices = [];
    props.cartSneakers.map((sneaker) => prices.push(sneaker.get('price')));
    var result = prices.reduce((sneakerOne, sneakerTwo) => sneakerOne + sneakerTwo, 0);
    return (result !== 0) ? 'Estimated Total: $' + result : 'Cart is Empty!';
  }

  var totalList = props.cartSneakers.map((sneaker, key) => {
    return <div key={key}><h4 className="sneaker">{sneaker.get('sneakerName')}</h4> <h4 className="price"> ${sneaker.get('price')}</h4></div>
  })

  var checkOut = () => props.actions.checkout(props.cartSneakers);

  return (
    <div className="DashCart">
      <div className="col-sm-9 segment nopadding">
        {DashCartItemsList}
      </div>
      <div className="col-sm-3 nopadding checkOut">
        <div className="panel panel-default">
          <div className="panel-heading">
            <h4 className="panel-title">Cart Estimated Subtotal</h4>
          </div>
            {totalList}
            <hr></hr>
            <h4 className="total">{price()}</h4>
            <h4 className="total"></h4>
          </div>
      </div>
      <button onClick={checkOut} className="checkout btn btn-default">CheckOut</button>
    </div>
  );
}

function mapStateToProps(state) {
  return {
    cartSneakers: state.dashboard.shoppingCart.get('cartSneakers')
  }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(DashCartActions, dispatch)
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(DashCart);

仪表板减速器

    export const dashboardReducer = combineReducers({
      userSneakers: dashSharedReducer,
      shoppingCart: dashShoppingCartReducer,
      trades: dashTradesReducer,
      orderHistory: dashOrderHistoryReducer,
      events: dashEventsReducer,
      sneakerTracking: dashSneakerTrackingReducer,
      shippingInfo: dashShippingReducer,
      billingInfo: dashBillingReducer,
      accountInfo: dashAccountSettingsReducer
    });
    //ShoppingCart Reducer
    export function dashShoppingCartReducer (state = sample, action ) {
      switch (action.type) {

        case CHECKOUT:
          return handleCheckout(state, action.cartPayload);

        case REMOVE_SNEAKER_FROM_CART:
          return handleRemoveSneakerFromCart(state, action.sneakerToRemove);

        case RECEIVE_SNEAKERS_IN_CART:
          return handleReceiveSneakersInCart(state, action.cartSneakers);

        default:
          return state;
      }
    }

我找到了解决问题的办法。我的应用程序的服务器实现自动强制 immutable 数据结构,这导致 get() 无法识别并抛出一个错误,导致应用程序中的所有 Javascript 停止。

要解决这个问题,您必须确保在服务器和客户端上调用 getState() 之后将状态转换为不可变数据结构,然后再将其传递给 configureStore()。为此,只需将以下行添加到服务器和客户端文件中:

var state = I.fromJS(initialState) 并将其传递到商店 configureStore(state)