React 服务器端渲染 - 如何使用传入的 :p roductId 参数从服务器渲染

React Server Side Rendering - how to render from the server with :productId params passed in?

本文关键字:roductId 服务器 参数 服务器端 何使用 React      更新时间:2023-09-26

我有一个在客户端和服务器(节点和快速(上呈现的React应用程序。如果有人输入 url http://webaddress.com/products/1,我正在尝试让渲染工作。如果我输入并按回车键(或刷新页面(,应用程序崩溃,因为它不知道如何获取 url 中的 1 来解析和获取正确的产品。

如果我单击导航中链接到产品/1的链接,则应用程序运行正常并显示正确的产品。

如何让 React-Router 从访问者 http://webaddress.com/products/1 输入的 url 中获取:productsId(/products/1 中的 1(参数?

这是我的服务器.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 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'));
});

以下是路由.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')
);

以下是产品商店.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;

下面是呈现特定产品的产品组件:

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
};

更新:控制台.log来自服务器.js当我手动输入 url 或在页面上点击刷新一次时:

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路由,服务器控制台.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,第二次分配了由webpack构建的"build.js"?跆拳道。

显示道具从 productId 重写为"build.js"的要点: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;
事实证明,

这根本不是路由问题。不同领域存在许多问题:

我不得不将bundle.js重命名为另一个名称,例如 client-bundle.js .

然后,我必须修复产品商店中的一个问题,以过滤产品数组以匹配产品 Id:

function setProduct(productId) {
  var filteredProductsArr = products.filter(function(product) {
    return product.id == productId;
  });
  product = filteredProductsArr[0];
  return product;
};

然后我必须在产品组件的组件DidMount((中放置一个动作创建器: ProductsActionCreators.getProduct(this.props.params.productId);

所有这一切,它就像一个魅力:)

您应该能够使用

this.props.params.productId

一个很好的例子:https://github.com/rackt/react-router/blob/master/examples/query-params/app.js