我有一个ExpressJS服务器,有时在初始渲染时呈现错误的用户数据.请参阅下面的(稍微简化)版本. 问题是index.ejs文件经常在reduxState中呈现错误的用户数据… 我的困惑是因为我希望从’rout
问题是index.ejs文件经常在reduxState中呈现错误的用户数据…
我的困惑是因为我希望从’routes.js’导入{store}的调用以每个用户的请求覆盖商店{}.问题似乎是商店正在成为网站上每个用户组合在一起的组合商店.
如何确保每个用户只能看到他们在网站上的数据?
routes.js
// src/routes.js import React from 'react'; import { createStore, applyMiddleware, compose } from "redux"; import routerConfig from "base/routes/routes"; import thunk from "redux-thunk"; import { rootReducer } from "base/reducers"; let initialState = {}; const store = createStore( rootReducer, initialState, compose(applyMiddleware(thunk)) ); const routes = routerConfig(store); export {store}; export default routes;
server.js
import { store } from 'routes'; let getReduxPromise = (renderProps, request) => { let store = require('./routes/index.jsx').store let { query, params } = renderProps let comp = renderProps.components[renderProps.components.length - 1]; let at = null; if (request && request.cookies && request.cookies.accessToken) { at = request.cookies.accessToken } if (comp.fetchData) { return comp.fetchData({ query, params, store, at }).then(response => { if (request) { if (request.cookies && request.cookies.accessToken && request.cookies.userInfo) { store.dispatch( actions.auth(request.cookies.userInfo), request.cookies.accessToken ) } else { store.dispatch(actions.logout()) } } return Promise.resolve({response, state: store.getState()}) }); } else { return Promise.resolve(); } } app.get('*', (request, response) => { let htmlFilePath = path.resolve('build/index.html' ); // let htmlFilePath = path.join(__dirname, '/build', 'index.html'); let error = () => response.status(404).send('404 - Page not found'); fs.readFile(htmlFilePath, 'utf8', (err, htmlData) => { if (err) { console.log('error 1') error(); } else { match({routes, location: request.url}, (err, redirect, renderProps) => { if (err) { console.log('error 2') error(); } else if (redirect) { return response.redirect(302, redirect.pathname + redirect.search) } else if (renderProps) { let parseUrl = request.url.split('/'); if (request.url.startsWith('/')) { parseUrl = request.url.replace('/', '').split('/'); } // User has a cookie, use this to help figure out where to send them. if (request.cookies.userInfo) { const userInfo = request.cookies.userInfo if (parseUrl[0] && parseUrl[0] === 'profile' && userInfo) { // Redirect the user to their proper profile. if (renderProps.params['id'].toString() !== userInfo.id.toString()) { parseUrl[1] = userInfo.id.toString(); const url = '/' + parseUrl.join('/'); return response.redirect(url); } } } getReduxPromise(renderProps, request).then((initialData) => { let generatedContent = initialData.response ? render(request, renderProps, initialData.response) : render(request, renderProps, {}); const title = initialData.response.seo.title || ''; const description = initialData.response.seo.description || ''; var draft = []; const currentState = initialData.state; if (currentState) { const reduxState = JSON.stringify(currentState, function(key, value) { if (typeof value === 'object' && value !== null) { if (draft.indexOf(value) !== -1) { // Circular reference found, discard key return; } // Store value in our collection draft.push(value); } return value; }); draft = null; ejs.renderFile( path.resolve('./src/index.ejs' ), { jsFile, cssFile, production, generatedContent, reduxState, title, description }, {}, function(err, str) { if (err) { console.log('error 3') console.log(err); } response.status(200).send(str); }); } else { console.log('error 4') console.log(err) error(); } }).catch(err => { console.log('error 5') console.log(err) error(); }); } else { console.log('error 6') console.log(err) error(); } }); } }) });
index.ejs
<!DOCTYPE html> <html lang="en"> <head> <title><%- title %></title> <meta name="description" content="<%- description %>"/> <link href="<%- cssFile %>" rel="stylesheet"/> <script type="text/javascript" charset="utf-8"> window.__REDUX_STATE__ = <%- reduxState %>; </script> </head> <body> <div id="root"><%- generatedContent %></div> <script type="text/javascript" src="<%- jsFile %>" defer></script> </body> </html>
React组件中的示例fetchData函数
ExamplePage.fetchData = function (options) { const { store, params, at } = options return Promise.all([ store.dispatch(exampleAction(params.id, ACTION_TYPE, userAccessToken)) ]).spread(() => { let data = { seo: { title: 'SEO Title', description: 'SEO Description' } } return Promise.resolve(data) }) }模块范围中定义的变量在整个运行时环境中只有一个副本.这意味着每个node.js进程都有自己的副本,每个浏览器选项卡/框架都有自己的副本.但是,在每个选项卡或每个进程中,只有一个副本.这意味着您无法将商店定义为模块级const,并且仍然为每个用户创建一个新商店.你可以这样解决:
SRC / routes.js
import React from 'react'; import { createStore, applyMiddleware, compose } from "redux"; import routerConfig from "base/routes/routes"; import thunk from "redux-thunk"; import { rootReducer } from "base/reducers"; let initialState = {}; export function newUserEnv() { const store = createStore( rootReducer, initialState, compose(applyMiddleware(thunk)) ); const routes = routerConfig(store); return { store, routes }; }
server.js
import { newUserEnv } from 'routes'; let getReduxPromise = (renderProps, request) => { const { store } = newUserEnv(); let { query, params } = renderProps ...
这为每个请求创建了一个新商店,并允许每个用户拥有自己的数据.请注意,如果您需要来自不同模块的相同商店,则需要传递它.您不能只导入{newUserEnv},因为它会创建一个新的.