#javascript #reactjs #react-router #server-side-rendering #react-loadable
#javascript #reactjs #react-маршрутизатор #Рендеринг на стороне сервера #react-загружаемый
Вопрос:
Как я могу иметь Next.js Например, выборка данных (getInitialProps) с помощью маршрутизатора React и возможность загрузки React с помощью Razzle. У меня была выборка данных без react-loadable, но без разделения кода пакет приложений слишком большой, и загрузка страницы для клиентов занимает слишком много времени.
этот код работает, но я просто не понимаю, что я делал около года назад (он немного отличается от предыдущего примера)
const promises = routes
.map((route) => {
const match = matchPath(req.url, route)
if (match amp;amp; route.component) {
let promise
if (typeof route.component.preload === "function") {
promise = route.component.preload()
} else {
promise = Promise.resolve(route.component)
}
return promise
.then((res) => {
return res amp;amp; res.__esModule ? res.default : res
})
.then((component) => {
let promises = []
// STATIC INTI ACTION
promises.push(store.dispatch(getToken()))
if (component.initialAction) {
let results = component.initialAction({ match })
results = Array.isArray(results)
? results
: [results]
results.forEach((result) => {
promises.push(store.dispatch(result))
})
}
return promises
})
}
return null
})
.filter((el) => el !== null)
// page not found
if (promises.length === 0) {
renderTree(req, res, store)
} else {
Promise.all(promises.map((data) => data.then((moreData) => moreData)))
.then((data) => {
Promise.all(data[0]).then(() => renderTree(req, res, store))
})
Server.js
const promises = []
routes.some((route) => {
const match = matchPath(req.url, route);
if (match) {
// route.component is React Loadable Component so getInitialData is undefined
if (route.component.getInitialData) {
promises.push(route.component.getInitialData({ match, req, res }))
}
return true;
}
return false;
});
Promise.all(promises)
.then(renderReact)
.catch(handleError)
// and at the end i will call
Loadable.preloadAll()
.then(sendResponseToUser)
маршруты:
[
{
path: "/",
exact: true,
component: Loadable({
loader: () => import("@views/Home"),
loading: Loading,
}),
},
{
path: "/shop",
exact: true,
component: Loadable({
loader: () => import("@views/Shop"),
loading: Loading,
}),
},
]
Мои компоненты такие:
class Home extends React.Component {
// This works similarly to Next.js's `getInitialProps`
static getInitialData({ match, req, res }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
text: `
This text is server rendered if and only if it's the initial render.
Go to another route.
`,
currentRoute: match.pathname,
});
}, 500);
});
}
render() {
return <p>this is just a test</p>
}
}
Загружаемый компонент React имеет метод preload (), который может загружать компонент, поэтому я попробовал:
route.component.preload()
но это не работает.
Я попробовал loadable-components, и с этим тоже та же проблема, но я могу заменить react-loadable на loadable-components (моя предпочтительная библиотека — loadable-components, потому что с StrictMode все в порядке).
на самом деле, after.js решил эту проблему (он использует Razzle), если бы я мог извлечь логику разделения кода и использовать ее в своем приложении или иметь какой-нибудь рабочий пример совместной выборки данных и загрузки реакции, это было бы потрясающе.
Ответ №1:
Вот как я это сделал.
Он работает с обоими. загружаемые компоненты и обычные компоненты.
Внутри matchRoutes(). проверьте наличие component.preload.
Если это true, то разрешите ему загрузить component.preload() .
Это вернет обещание, и после разрешения обещания мы получим наш компонент. Затем проверьте наличие loadData / getInitialProps / fetchData, какое бы имя статического метода мы ни использовали.
return res.default.loadData ? res.default.loadData(store) : null;
вы можете вызвать запрос на выборку на стороне сервера .. я называю это loadData ()
Всегда помните о возврате обещания, потому что это цепочка обещаний.
Как и в ваших действиях redux, всегда не забывайте возвращать dispatch() .
app.get('*', (req, res) => {
// store instance for every incoming request
const store = createStore(req);
// loadData from api... before rendering
const promises = matchRoutes(Routes, req.path)
.map((comp) => {
console.log(comp, comp.route.component.loadData)
if (comp.route.component.preload){
console.log("LoadableComponent")
comp.route.component.preload().then(res => {
return res.default.loadData ? res.default.loadData(store) : null;
})
}
return comp.route.component.loadData ? comp.route.component.loadData(store) : null;
})
.map(promise => {
if (promise) {
return new Promise((resolve, reject) => {
promise.then(resolve).catch(resolve);
});
}
});
Promise.all(promises).then(() => {
console.log(store.getState())
const context = {};
const content = renderer(req, store, context);
if (context.url) {
return res.redirect(301, context.url);
}
if (context.notFound) {
res.status(404);
}
Loadable.preloadAll().then(() => {
res.send(content);
});
});
});
Комментарии:
1. В настоящее время я пытаюсь использовать этот подход, но вызов метода предварительной загрузки возвращает undefined в цепочку обещаний. Можете ли вы просветить меня о том, что может быть не так? в операторе if
comp.route.component.preload
определено и действительно является функцией, но затем я получаю ошибку ‘TypeError: не удается прочитать свойство’, а затем ‘неопределенной’