我有一个在客户端和服务器上呈现的React应用程序(node和express)。如果有人在网址http://webaddress.com/products/1中输入内容,我会尝试让渲染工作正常。如果我输入并输入(或刷新页面),应用程序崩溃,因为它不知道如何获取URL中的1来解析并获得正确的产品。
如果我点击导航链接到products / 1的链接,应用程序就可以正常运行并显示正确的产品。
如何让React-Router从访问者在http://webaddress.com/products/1中输入的网址中获取:productsId
(1 in / products / 1)参数?
这是我的server.js:
import express from 'express';
import http from 'http';
var PageNotFound = require('./js/components/PageNotFound.react');
import React from 'react';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import { routes } from './routes';
const app = express();
app.use(express.static('public'));
app.set('view engine', 'ejs');
/* We call match(), giving it the routes object defined above and the req.url, which contains the the url of the request. */
app.get('*', (req, res) => {
// routes is our object of React routes defined above
match({ routes, location: req.url }, (err, redirectLocation, props) => {
if (err) {
// something went badly wrong, so 500 with a message
res.status(500).send(err.message);
} else if (redirectLocation) {
// we matched a ReactRouter redirect, so redirect from the server
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (props) {
// console.log("props on server.js: ", props);
// if we got props, that means we found a valid component to render
// for the given route. renderToString() from ReactDOM takes that RoutingContext
// component and renders it with the properties required.
const markup = renderToString(<RouterContext {...props} />);
// render `index.ejs`, but pass in the markup we want it to display
res.render('index', { markup })
} else {
// no route match, so 404. In a real app you might render a custom
// 404 view here
console.log("not found page");
res.sendStatus(404);
// respond with html page
if (req.accepts('html')) {
res.render('404', { url: req.url });
return;
}
// respond with json
if (req.accepts('json')) {
res.send({ error: 'Not found' });
return;
}
// default to plain-text. send()
res.type('txt').send('Not found');
}
});
});
const server = http.createServer(app);
app.set('port', (process.env.PORT || 3000))
app.get('/', (req, res) => {
var result = 'App is Running'
res.send(result);
}).listen(app.get('port'), () => {
console.log('App is running, server is listening on port', app.get('port'));
});
这里是routes.js文件:
import TopLevelContainerApp from './js/components/TopLevelContainerApp.react'
import Home from './js/components/Home.react';
import Product from './js/components/Product.react';
const routes = {
path: '',
component: SolidBroadheadApp,
childRoutes: [
{
path: '/',
component: Home
},
{
path: '/products/:productId',
component: Product
}
}
export { routes };
这是客户端渲染js:
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, browserHistory } from 'react-router';
import { routes } from './../routes';
ReactDOM.render(
<Router routes={routes} history={browserHistory} />, document.getElementById('website-app')
);
这是ProductStore.js:
var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var articles = null;
var links = null;
var product = null;
function setArticles(receivedArticles) {
articles = receivedArticles;
return articles;
}
function setLinks(receivedLinks) {
links = receivedLinks;
return links;
}
function setProduct(productId) {
console.log("products store productId: ", productId);
function filterById(obj) {
return obj.id === productId;
}
var filteredArticlesArr = articles.filter(filterById);
product = filteredArticlesArr[0];
return product;
};
function emitChange() {
ProductsStore.emit('change');
}
var ProductsStore = assign({}, EventEmitter.prototype, {
addChangeListener: function(callback) {
this.on('change', callback);
},
removeChangeListener: function(callback) {
this.removeListener('change', callback);
},
getArticles: function() {
return articles;
},
getLinks: function() {
return links;
},
getProduct: function() {
return product;
}
});
function handleAction(action) {
switch (action.type) {
case 'received_products_articles':
setArticles(action.articles);
emitChange();
break;
case 'get_links':
setLinks(action.articles);
emitChange();
break;
case 'get_product':
setProduct(action.productId);
emitChange();
break;
}
}
ProductsStore.dispatchToken = AppDispatcher.register(handleAction);
module.exports = ProductsStore;
以下是呈现特定产品的Product组件:
var React = require('react');
var ProductArticle = require('./products-solid-components/ProductArticle.react');
var ProductsStore = require('./../stores/ProductsStore');
// gets all the products
var ProductsArticlesWebUtilsAPI = require('./../../utils/ProductsArticlesWebUtilsAPI');
ProductsArticlesWebUtilsAPI.initializeArticles();
var Product = React.createClass({
getInitialState: function() {
return {
articles: ProductsStore.getArticles(),
product: ProductsStore.getProduct()
};
},
componentDidMount: function() {
ProductsStore.addChangeListener(this.onProductChange);
},
componentWillUnmount: function() {
ProductsStore.removeChangeListener(this.onProductChange);
},
onProductChange: function() {
this.setState({
articles: ProductsStore.getArticles(),
product: ProductsStore.getProduct()
});
},
render: function() {
if (this.state.articles)
if (this.state.product) {
return (
<div className="product">
<section className="container-fluid">
<ProductArticle name={this.state.product.name} largeImage={this.state.product.largeImage} description={this.state.product.description} />
</section>
);
} else {
return (
<div>Product is on the way</div>
);
}
}
});
module.exports = Product;
这是获取信息的文件:
var ProductsActionCreators = require('../js/actions/ProductsActionCreators');
var productArticles = [
{
"id": 1,
"name": "product 1",
"largeImage": "1.png",
"largeVideo": "1.mp4",
"description": "1 description",
},
{
"id": 2,
"name": "product 2",
"largeImage": "2.png",
"largeVideo": "2.mp4",
"description": "2 description",
},
{
"id": 3,
"name": "product 3",
"largeImage": "3.png",
"largeVideo": "3.mp4",
"description": "3 description",
},
];
var products = [];
function separateProductIdsAndNamesOut(productArticles) {
console.log("productArticles: " + productArticles);
products = productArticles.map(function(product) {
return { id: product.id, name: product.name };
});
return products;
}
function initializeArticles() {
return ProductsActionCreators.receiveArticles(productArticles);
}
// to build links in a nav component without payload of video and large img etc
function initializeProductsForNav() {
return ProductsActionCreators.receiveArticlesIdsAndNames(separateProductIdsAndNamesOut(productArticles));
}
module.exports = {
initializeArticles: initializeArticles,
initializeProductsForNav: initializeProductsForNav
};
更新: 当我手动输入网址或在页面上点击刷新一次时,来自server.js的console.log:
props on server.js: { routes:
[ { path: '', component: [Object], childRoutes: [Object] },
{ path: '/products/:productId', component: [Object] } ],
params: { productId: '4' },
location:
{ pathname: '/products/4',
search: '',
hash: '',
state: null,
action: 'POP',
key: '8b8lil',
query: {},
'$searchBase': { search: '', searchBase: '' } },
components:
[ { [Function] displayName: 'SolidBroadheadApp' },
{ [Function] displayName: 'Product' } ],
history:
{ listenBefore: [Function],
listen: [Function],
transitionTo: [Function],
push: [Function],
replace: [Function],
go: [Function],
goBack: [Function],
goForward: [Function],
createKey: [Function],
createPath: [Function],
createHref: [Function],
createLocation: [Function],
setState: [Function],
registerTransitionHook: [Function],
unregisterTransitionHook: [Function],
pushState: [Function],
replaceState: [Function],
isActive: [Function],
match: [Function],
listenBeforeLeavingRoute: [Function] },
router:
{ listenBefore: [Function: listenBefore],
listen: [Function: listen],
transitionTo: [Function: transitionTo],
push: [Function: push],
replace: [Function: replace],
go: [Function: go],
goBack: [Function: goBack],
goForward: [Function: goForward],
createKey: [Function: createKey],
createPath: [Function: createPath],
createHref: [Function: createHref],
createLocation: [Function: createLocation],
setState: [Function],
registerTransitionHook: [Function],
unregisterTransitionHook: [Function],
pushState: [Function],
replaceState: [Function],
__v2_compatible__: true,
setRouteLeaveHook: [Function: listenBeforeLeavingRoute],
isActive: [Function: isActive] } }
this.state.searchResult: null
this.state.productLinks in component: null
更新2:我删除了404和splat路由,服务器console.log显示:
App is running, server is listening on port 3000
props.params on server.js: { productId: '4' } // looks good; proper productId is passed.
this.state.productLinks in component: null
this.props in product component: { productId: '4' } // looks good; should render properly
props.params on server.js: { productId: 'build.js' } // why is this assigned 'build.js' causing the problem???
this.state.productLinks in component: null
this.props in product component: { productId: 'build.js' } // why is this assigned 'build.js'? This is probably the cause of the problem.
该过程似乎运行了两次,第一次分配了正确的ID,第二次,它分配了#build; js&#39;那是由webpack构建的? WTF。
显示道具从productId重写为&#39; build.js&#39;的要点: https://gist.github.com/gcardella/1367198efffddbc9b78e
Webpack配置:
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: path.join(process.cwd(), 'client/client-render.js'),
output: {
path: './public/',
filename: 'build.js'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel'
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production'),
APP_ENV: JSON.stringify('browser')
}
})
]
}
更新3:我通过删除状态检查来修复双周期,但是在渲染服务器端时失败,因为状态尚未设置。所以我添加一个检查以查看产品组件中是否存在state.product:
var React = require('react');
var ProductArticle = require('./products-solid-components/ProductArticle.react');
var ProductPresentation = require('./products-solid-components/ProductPresentation.react');
var ProductLargeImage = require('./products-solid-components/ProductLargeImage.react');
var LargeLeftImageArticle = require('./reusable-tool-components/LargeLeftImageArticle.react');
var LargeRightImageArticle = require('./reusable-tool-components/LargeRightImageArticle.react');
var ProductsStore = require('./../stores/ProductsStore');
// if (process.env.APP_ENV === 'browser') {
var ProductsArticlesWebUtilsAPI = require('./../../utils/ProductsArticlesWebUtilsAPI');
ProductsArticlesWebUtilsAPI.initializeArticles();
// }
var Product = React.createClass({
getInitialState: function() {
return {
articles: ProductsStore.getArticles(),
product: ProductsStore.getProduct()
};
},
componentDidMount: function() {
ProductsStore.addChangeListener(this.onProductChange);
},
componentWillUnmount: function() {
ProductsStore.removeChangeListener(this.onProductChange);
},
onProductChange: function() {
this.setState({
articles: ProductsStore.getArticles(),
product: ProductsStore.getProduct()
});
},
render: function() {
console.log("this.props in product component: ", this.props);
// if (this.state.articles)
console.log("after check for this.state.product on component this.props.params.productId: ", this.props.params.productId);
if (this.state.product) {
return (
<div className="product">
<section className="container-fluid">
<ProductArticle name={this.state.product.name} largeImage={this.state.product.largeImage} description={this.state.product.description} />
</section>
</div>
);
} else {
console.log("no state");
return (
// if there is no state, how do I render something on the server side?
);
}
}
});
module.exports = Product;
答案 0 :(得分:1)
事实证明它根本不是路由问题。不同领域存在许多问题:
我必须将bundle.js
重命名为其他名称,例如client-bundle.js
。
然后我不得不修复我的ProductsStore中的问题来过滤产品数组以匹配productId:
function setProduct(productId) {
var filteredProductsArr = products.filter(function(product) {
return product.id == productId;
});
product = filteredProductsArr[0];
return product;
};
然后我必须在我的产品组件的componentDidMount()中添加一个动作创建器:
ProductsActionCreators.getProduct(this.props.params.productId);
这一切都像魅力一样:)
答案 1 :(得分:0)
您应该可以使用
检索此内容this.props.params.productId
一个很好的例子:https://github.com/rackt/react-router/blob/master/examples/query-params/app.js