#node.js #reactjs #express #server-side-rendering
#node.js #reactjs #экспресс #рендеринг на стороне сервера
Вопрос:
Вся цель использования SSR — скрыть данные о местоположении от клиента. Я хочу использовать объект location, который я получаю из функции findLocation, и передать его в приложение react. Я пытаюсь передать его как реквизит, но это плохо работает. Я получаю сообщение об ошибке в консоли браузера: «Ошибка типа: не удается прочитать свойство ‘lon’ неопределенного значения в (index.js:18)». Пример вывода из местоположения печати в server.js и в app.js на клиенте :
[nodemon] starting `node server/index.js`
SSR running on port 8080
{
xid: 'W89710856',
name: 'RBS',
dist: 62.66979048,
rate: 5,
osm: 'way/89710856',
kinds: 'historic_architecture,architecture,interesting_places,bank,banks,tourist_facilities,other_buildings_and_structures',
point: { lon: -1.900748, lat: 52.481361 },
cityDetails: {
name: 'Birmingham',
country: 'GB',
lat: 52.48142,
lon: -1.89983,
population: 984333,
timezone: 'Europe/London',
status: 'OK'
}
}
{
xid: 'W89710856',
name: 'RBS',
dist: 62.66979048,
rate: 5,
osm: 'way/89710856',
kinds: 'historic_architecture,architecture,interesting_places,bank,banks,tourist_facilities,other_buildings_and_structures',
point: { lon: -1.900748, lat: 52.481361 },
cityDetails: {
name: 'Birmingham',
country: 'GB',
lat: 52.48142,
lon: -1.89983,
population: 984333,
timezone: 'Europe/London',
status: 'OK'
}
} from the client
index.js для сервера:
const path = require("path");
const fs = require("fs");
const express = require("express");
const React = require("react");
const ReactDOMServer = require("react-dom/server");
import App from "../src/App";
import { getCities } from "./external/cities.js";
import { useOpenTripMap } from "./external/openTripMap.js";
const PORT = 8080;
const app = express();
const router = express.Router();
const citiesArray = getCities();
const radius = 1000;
// locations results limit
const limit = 20;
const findLocation = async () => {
try {
const cities = await citiesArray;
let random = Math.floor(Math.random() * cities.length);
const city = await useOpenTripMap(
"geoname",
encodeURI("name=" cities[random].name)
);
const locations = await useOpenTripMap(
"radius",
encodeURI(
`radius=${radius}amp;limit=${limit}amp;lon=${city.lon}amp;lat=${city.lat}amp;format=json`
)
);
if (!Array.isArray(locations) || !locations.length) {
return findLocation();
}
random = Math.floor(Math.random() * locations.length);
return locations[random].name
? {
...locations[random],
cityDetails: { ...city },
}
: findLocation();
} catch (error) {
console.error(error);
}
};
const serverRenderer = (req, res, next) => {
fs.readFile(path.resolve("./build/index.html"), "utf8", (err, data) => {
if (err) {
console.error(err);
return res.status(500).send("An error occurred");
}
findLocation()
.then((location) => {
console.log(location); / a valid object with a point property
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(
<App location={location} />
)}</div>`
)
);
})
.catch((error) => console.log(error));
});
};
router.use("^/$", serverRenderer);
router.use(express.static(path.resolve(__dirname, "..", "build")));
app.use(router);
app.listen(PORT, () => {
console.log(`SSR running on port ${PORT}`);
});
index.js для приложения react:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
const mountElement = document.getElementById("root");
const reactMountFn =
mountElement?.childElementCount === 0 ? ReactDOM.render : ReactDOM.hydrate;
reactMountFn(<App />, mountElement);
app.js в папке src в приложении react:
import React, { useState, useEffect } from "react";
import Map from "./components/map";
function App({ location }) {
console.log(location, "from the client");
return (
<div className="app">
<Map point={location?.point} />
</div>
);
}
export default App;
Комментарии:
1. Если вы выходите из
cities
системы иcity
после их инициализации, определены ли они оба?2. @ZacAnger Да. Я получаю массив объектов cities из «cities» и объект city из «city».
Ответ №1:
Кажется Map
, что компонент выдает вам ошибку, поскольку вы не предоставляете App
компоненту объект location (и point
, следовательно, реквизит Map не определен) на стороне клиента:
// index.js
...
reactMountFn(<App />, mountElement); // <-- App's location prop is undefined
В качестве быстрого решения я бы сделал следующее:
function App({ location }) {
return (
<div className="app">
<Map point={location ? location.point : {}} />
</div>
);
}
export default App;
React, вероятно, выдаст вам предупреждение о несоответствиях между разметками клиента и сервера. В этом случае вам нужно будет ввести переменные на стороне сервера (например, location
object) в результирующий HTML и использовать их на стороне клиента. Пожалуйста, обратитесь к этому руководству для получения более подробной информации.
Например, на стороне сервера index.js:
...
return res.send(
data.replace(
'<div id="root"></div>',
`<script>window.__LOCATION__=${JSON.stringify(location)}</script>
<div id="root">
${ReactDOMServer.renderToString(
<App location={location} />
)}
</div>`
)
);
...
На стороне клиента index.js:
// Get the location from a global variable injected into the server generated HTML
const location = JSON.parse(window.__LOCATION__);
reactMountFn(<App location={location} />, mountElement);
Комментарии:
1. Сейчас я не получаю никаких ошибок, но я не могу использовать точку местоположения с сервера. Я не использую redux, поэтому я не понимаю, как использовать эту ссылку для решения проблемы.
2. Не имеет значения, используете ли вы redux или нет. Вы можете ввести любую переменную в свой шаблон, используя тот же принцип. Добавлен короткий пример к моему ответу, пожалуйста, ознакомьтесь с ним.
3. Хорошо, я понял, но теперь это не имеет смысла, потому что целью было скрыть данные о местоположении от клиента, и теперь они видны.
4. Тогда вы не должны отображать компоненты, которые используют данные о местоположении. В противном случае вам придется передавать местоположение со стороны сервера.
5. Как это сделать? В этом весь смысл того, что я пытаюсь сделать с самого начала, получить карту с местоположением, рассчитанным на сервере, не предоставляя ее клиенту.