React + Redux Server使用商店和历史记录进行初始化

时间:2017-02-23 00:42:00

标签: reactjs express redux react-router isomorphic-javascript

我有以下应用:

客户端

  

index.js

import React from "react";
import ReactDOM from "react-dom";
import Root from "./containers/Root";
import configureStore from "./store/configureStore";
import { browserHistory } from "react-router";
import { loginUserSuccess } from "./actions/auth";
import { syncHistoryWithStore } from "react-router-redux";

const target = document.getElementById("root");
const store = configureStore(browserHistory, window.__INITIAL_STATE__);
// Create an enhanced history that syncs navigation events with the store
const history = syncHistoryWithStore(browserHistory, store)

const node = (
    <Root store={store} history={history} />
);

let token = localStorage.getItem("token");
if (token !== null) {
    store.dispatch(loginUserSuccess(token));
}

ReactDOM.render(node, target);
  

Root.js

import React from "react";
import {Provider} from "react-redux";
import AppRouter from "../routes/appRouter";

export default class Root extends React.Component {

    static propTypes = {
        store: React.PropTypes.object.isRequired,
        history: React.PropTypes.object.isRequired
    };

    render () {
        return (
            <Provider store={this.props.store}>
                <AppRouter history={this.props.history}>
                </AppRouter>
            </Provider>
        );
    }       
}
  

AppRouter(routes / appRouter.js

import React from "react";
import routes from "./routes";
import { Router } from "react-router";

export default (
    <Router routes={routes} history={this.props.history}></Router>
)
  

路线(routes / routes.js)

import React from "react";
import {Route, IndexRoute} from "react-router";
import { App } from "../containers";
import {HomeView, LoginView, ProtectedView, NotFoundView} from "../views";
import {requireAuthentication} from "../components/core/AuthenticatedComponent";

export default (
    <Route path='/' component={App} name="app" >
        <IndexRoute component={requireAuthentication(HomeView)}/>
        <Route path="login" component={LoginView}/>
        <Route path="protected" component={requireAuthentication(ProtectedView)}/>
        <Route path="*" component={NotFoundView} />
    </Route>
)
  

requireAuthentication(/component/core/AuthenticatedComponent.js)

import React from "react";
import {connect} from "react-redux";
import { push } from "react-router-redux";

export function requireAuthentication(Component) {

    class AuthenticatedComponent extends React.Component {

        componentWillMount () {
            this.checkAuth(this.props.isAuthenticated);
        }

        componentWillReceiveProps (nextProps) {
            this.checkAuth(nextProps.isAuthenticated);
        }

        checkAuth (isAuthenticated) {
            if (!isAuthenticated) {
                let redirectAfterLogin = this.props.location.pathname;
                this.props
                    .dispatch(push(`/login?next=${redirectAfterLogin}`));
            }
        }

        render () {
            return (
                <div>
                    {this.props.isAuthenticated === true
                        ? <Component {...this.props}/>
                        : null
                    }
                </div>
            )

        }
    }

    const mapStateToProps = (state) => ({
        token: state.auth.token,
        userName: state.auth.userName,
        isAuthenticated: state.auth.isAuthenticated
    });

    return connect(mapStateToProps)(AuthenticatedComponent);

}
  

configureStore.js

import rootReducer from "../reducers";
import thunkMiddleware from "redux-thunk";
import {createStore, applyMiddleware, compose} from "redux";
import {routerMiddleware} from "react-router-redux";
import {persistState} from "redux-devtools";
import createLogger from "redux-logger";
import DevTools from "../dev/DevTools";

const loggerMiddleware = createLogger();

const enhancer = (history) => 
    compose(
        // Middleware you want to use in development:
        applyMiddleware(thunkMiddleware, loggerMiddleware, routerMiddleware(history)),
        // Required! Enable Redux DevTools with the monitors you chose
        DevTools.instrument(), 
        persistState(getDebugSessionKey())
    );


function getDebugSessionKey() {
    if(typeof window == "object") {
        // You can write custom logic here!
        // By default we try to read the key from ?debug_session=<key> in the address bar
        const matches = window.location.href.match(/[?&]debug_session=([^&#]+)\b/);
        return (matches && matches.length > 0)? matches[1] : null;
    }

    return;
}

export default function configureStore(history, initialState) {
    // Add the reducer to your store on the `routing` key
    const store = createStore(rootReducer, initialState, enhancer(history))

    if (module.hot) {
        module
            .hot
            .accept("../reducers", () => {
                const nextRootReducer = require("../reducers/index");
                store.replaceReducer(nextRootReducer);
            });
    }

    return store;
}

服务器

  

server.js

import path from "path";
import { Server } from "http";
import Express from "express";
import React from "react";
import {Provider} from "react-redux";
import { renderToString } from "react-dom/server";
import { match, RouterContext } from "react-router";
import routes from "./src/routes/routes";
import NotFoundView from "./src/views/NotFoundView";
import configureStore from "./src/store/configureStore";
import { browserHistory } from "react-router";
// import { syncHistoryWithStore } from "react-router-redux";

// initialize the server and configure support for ejs templates
const app = new Express();
const server = new Server(app);
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "./"));

// define the folder that will be used for static assets
app.use("/build", Express.static(path.join(__dirname, "build")));

// // universal routing and rendering
app.get("*", (req, res) => {
    const store = configureStore(browserHistory);

    match(
        { routes, location: req.url },
        (err, redirectLocation, renderProps) => {
            // in case of error display the error message
            if (err) {
                return res.status(500).send(err.message);
            }

            // in case of redirect propagate the redirect to the browser
            if (redirectLocation) {
                return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
            }

            // generate the React markup for the current route
            let markup;
            if (renderProps) {
                // if the current route matched we have renderProps
                // markup = renderToString(<Provider store={preloadedState} {...renderProps}/>);
                markup = renderToString(
                    <Provider store={store} >
                        <RouterContext {...renderProps} />
                    </Provider>
                );
            } else {
                // otherwise we can render a 404 page
                markup = renderToString(<NotFoundView />);
                res.status(404);
            }

            // render the index template with the embedded React markup
            const preloadedState = store.getState()
            return res.render("index", { markup, preloadedState });
        }
    );
});

// start the server
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || "production";
server.listen(port, err => {
    if (err) {
        return console.error(err);
    }
    console.info(`Server running on http://localhost:${port} [${env}]`);
});

我不知道我是否在服务器端正确初始化了我的商店。 我怎么知道这个?因为renderProps始终为null,因此返回NotFoundView

但是我做了一些修改,因为我知道我的路线被错误地初始化了。

这使我在服务器端使用我的configureStore(...)(我在客户端使用它并且它正在工作)。

现在我收到以下错误:

  

TypeError:无法读取属性&#39; push&#39;未定义的

此错误发生在以下行中的AuthenticatedComponent

this.props
    .dispatch(push(`/login?next=${redirectAfterLogin}`));

这很奇怪,因为它在客户端和服务器端工作,它只会抛出此错误。

有什么想法吗?

PS

我是否通过在客户端和服务器中使用相同的Routes.js来正确执行此操作?

我的configureStore(...)也一样吗?

我看到的所有示例中,他们使用不同的方法创建服务器端存储,createStore来自redux而不是客户端的存储配置。

PS2

我现在明白push可能无法在服务器上运行,只能在客户端运行(对吗?)。有没有解决方法呢?

PS3

我遇到的问题正在发生,因为我正在将<Router />呈现到服务器中,而我的routes.js应该只包含<Route />个节点。

但是,过了一段时间后,我发现不应该在服务器中配置历史记录,我只是配置了我的商店并将其传递给正在呈现的<Provider />

但现在我需要编译我的JSX并抛出:

Error: Module parse failed: D:\VS\Projects\Tests\OldDonkey\OldDonkey.UI\server.js Unexpected token (46:20)
You may need an appropriate loader to handle this file type.
|                 // markup = renderToString(<Provider store={preloadedState} {...renderProps}/>);
|                 markup = renderToString(
|                     <Provider store={store} >
|                         <RouterContext {...renderProps} />
|                     </Provider>

0 个答案:

没有答案