비동기 적으로 초기화 된 React.js 컴포넌트의 서버 측 렌더링 전략
의 가장 큰 장점 중 하나 React.js이 있어야하는데 서버 측 렌더링 . 문제는 핵심 기능 React.renderComponentToString()
이 동기 적이므로 구성 요소 계층이 서버에 렌더링 될 때 비동기 데이터를로드 할 수 없다는 것입니다.
페이지의 어느 곳에 나 놓을 수있는 주석 처리를위한 범용 구성 요소가 있다고 가정 해 보겠습니다. 속성은 일종의 식별자 (예 : 주석이 배치 된 기사의 ID)와 다른 모든 속성은 구성 요소 자체 (로드, 추가, 주석 관리)에 의해 처리됩니다.
정말 같은 플럭스의 아키텍처는 많은 것들을 훨씬 쉽게 만들기 때문에, 그 상점은 서버와 클라이언트 사이에 상태를 공유하기위한 이상적입니다. 주석이 포함 된 내 상점이 초기화되면 간단히 직렬화하여 서버에서 클라이언트로 전송하여 쉽게 복원 할 수 있습니다.
문제는 내 상점을 채우는 가장 좋은 방법입니다. 지난 일 동안 나는 인터넷 검색을 많이 해왔고 몇 가지 전략을 겪어 왔으며, React 의이 기능이 얼마나 "촉진"되는지를 고려할 때 실제로 좋은 것으로 보이지는 않았습니다.
제 생각에는 가장 간단한 방법은 실제 렌더링이 시작되기 전에 모든 상점을 채우는 것입니다. 즉, 구성 요소 계층 외부의 어딘가에 있습니다 (예 : 라우터에 연결). 이 접근법의 문제점은 페이지 구조를 두 번 정의해야한다는 것입니다. 더 복잡한 페이지 (예 : 실제 블로그 게시물, 댓글, 관련 게시물, 최신 게시물, 트위터 스트림 등)가 많은 여러 구성 요소가있는 블로그 페이지를 고려하십시오. React 구성 요소를 사용하여 페이지 구조를 디자인 한 다음 다른 곳 에서이 현재 페이지에 필요한 각 저장소를 채우는 프로세스를 정의해야합니다. 그것은 나에게 좋은 해결책처럼 보이지 않습니다. 불행히도 대부분의 동형 튜토리얼은 이러한 방식으로 설계됩니다 (예 :이 큰 자속 튜토리얼 ).
반응 비동기 . 이 접근법은 완벽합니다. 각 구성 요소의 특수 함수에서 상태를 초기화하는 방법을 간단하게 정의 할 수 있습니다 (동기식 또는 비동기식 여부는 중요하지 않음). 계층 구조가 HTML로 렌더링 될 때 이러한 함수가 호출됩니다. 상태가 완전히 초기화 될 때까지 구성 요소가 렌더링되지 않는 방식으로 작동합니다. 문제는 섬유 가 필요하다는 것입니다내가 이해하는 한 표준 JavaScript 동작을 변경하는 Node.js 확장입니다. 결과가 정말 마음에 들지만 해결책을 찾는 대신 게임 규칙을 변경 한 것으로 보입니다. 그리고 React.js의 핵심 기능을 사용하도록 강요해서는 안된다고 생각합니다. 또한이 솔루션의 일반적인 지원에 대해 잘 모르겠습니다. 표준 Node.js 웹 호스팅에서 파이버를 사용할 수 있습니까?
나는 내 자신에 대해 조금 생각하고 있었다. 구현 세부 사항을 실제로 생각하지는 않았지만 일반적인 아이디어는 React-async와 비슷한 방식으로 구성 요소를 확장 한 다음 루트 구성 요소에서 React.renderComponentToString ()을 반복적으로 호출한다는 것입니다. 각 패스 중에 확장 콜백을 수집 한 다음 패스와 패스에서 콜백하여 상점을 채 웁니다. 현재 구성 요소 계층에 필요한 모든 상점이 채워질 때까지이 단계를 반복합니다. 해결해야 할 것이 많으며 특히 성능에 대해 확신이 없습니다.
내가 뭐 놓친 거 없니? 다른 접근 방식 / 솔루션이 있습니까? 지금은 반응 비동기 / 섬유 방식에 대해 생각하고 있지만 두 번째 요점에서 설명한대로 그것에 대해 완전히 확신하지는 못합니다.
GitHub 관련 토론 . 분명히 공식적인 접근법이나 해결책은 없습니다. 실제 질문은 React 컴포넌트가 어떻게 사용되는지에 대한 것일 수 있습니다. 간단한 뷰 레이어 (내 제안 번호 1과 거의 같음) 또는 실제 독립적이며 독립형 구성 요소와 같은가?
react-router 를 사용하는 경우 willTransitionTo
컴포넌트에서 메소드를 정의하면 Transition
호출 할 수 있는 객체 가 전달 .wait
됩니다.
Router.run
모든 .wait
ed promise가 해결 될 때까지에 대한 콜백이 호출되지 않으므로 renderToString이 동기식인지 여부는 중요하지 않으므로 renderToString
미들웨어에서 시간 이 지나면 상점을 채울 수 있습니다. 상점이 싱글 톤 인 경우에도 동기 렌더링 호출 전에 데이터를 일시적으로 적시에 임시로 설정하면 구성 요소가이를 볼 수 있습니다.
미들웨어의 예 :
var Router = require('react-router');
var React = require("react");
var url = require("fast-url-parser");
module.exports = function(routes) {
return function(req, res, next) {
var path = url.parse(req.url).pathname;
if (/^\/?api/i.test(path)) {
return next();
}
Router.run(routes, path, function(Handler, state) {
var markup = React.renderToString(<Handler routerState={state} />);
var locals = {markup: markup};
res.render("layouts/main", locals);
});
};
};
routes
(경로 계층 구조를 설명합니다) 개체는 클라이언트와 서버에 그대로 공유
나는 이것이 정확히 당신이 원하는 것이 아니라는 것을 알고 있으며, 이해가되지 않을 수도 있지만, 두 가지 모두를 처리하기 위해 구성 요소를 약간 수정하는 것을 기억합니다.
- 필요한 경우 모든 초기 상태를 이미 검색하여 비동기 적으로 서버 측에서 렌더링)
- 필요한 경우 ajax를 사용하여 클라이언트 측에서 렌더링
그래서 같은 :
/** @jsx React.DOM */
var UserGist = React.createClass({
getInitialState: function() {
if (this.props.serverSide) {
return this.props.initialState;
} else {
return {
username: '',
lastGistUrl: ''
};
}
},
componentDidMount: function() {
if (!this.props.serverSide) {
$.get(this.props.source, function(result) {
var lastGist = result[0];
if (this.isMounted()) {
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}
}.bind(this));
}
},
render: function() {
return (
<div>
{this.state.username}'s last gist is
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
// On the client side
React.renderComponent(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);
// On the server side
getTheInitialState().then(function (initialState) {
var renderingOptions = {
initialState : initialState;
serverSide : true;
};
var str = Xxx.renderComponentAsString( ... renderingOptions ...)
});
정확한 코드를 가지고 있지 않아서 죄송합니다.이 방법으로 문제가 해결되지 않을 수도 있지만 토론을 위해 게시하고 있습니다.
다시, 아이디어는 벙어리보기로 구성 요소의 대부분을 처리하고, 가능한 한 데이터를 가져 오는 처리하는 것입니다 밖으로 구성 요소의.
나는 오늘 이것에 정말로 혼란 스러웠으며 이것이 당신의 문제에 대한 답은 아니지만이 접근법을 사용했습니다. React Router 대신 라우팅에 Express를 사용하고 싶었고 노드에서 스레딩 지원이 필요하지 않기 때문에 Fiber를 사용하고 싶지 않았습니다.
따라서로드시 플럭스 저장소로 렌더링해야하는 초기 데이터의 경우 AJAX 요청을 수행하고 초기 데이터를 저장소로 전달하기로 결정했습니다.
이 예제에서는 Fluxxor를 사용하고있었습니다.
따라서 내 특급 노선,이 경우에는 /products
노선 :
var request = require('superagent');
var url = 'http://myendpoint/api/product?category=FI';
request
.get(url)
.end(function(err, response){
if (response.ok) {
render(res, response.body);
} else {
render(res, 'error getting initial product data');
}
}.bind(this));
그런 다음 데이터를 저장소로 전달하는 초기화 렌더 메소드.
var render = function (res, products) {
var stores = {
productStore: new productStore({category: category, products: products }),
categoryStore: new categoryStore()
};
var actions = {
productActions: productActions,
categoryActions: categoryActions
};
var flux = new Fluxxor.Flux(stores, actions);
var App = React.createClass({
render: function() {
return (
<Product flux={flux} />
);
}
});
var ProductApp = React.createFactory(App);
var html = React.renderToString(ProductApp());
// using ejs for templating here, could use something else
res.render('product-view.ejs', { app: html });
나는이 질문이 1 년 전에 요청되었다는 것을 알고 있지만 동일한 문제가 있었고 렌더링 할 구성 요소에서 파생 된 중첩 된 약속 으로이 문제를 해결합니다. 결국 우리는 앱에 대한 모든 데이터를 가지고 방금 전송했습니다.
예를 들면 다음과 같습니다.
var App = React.createClass({
/**
*
*/
statics: {
/**
*
* @returns {*}
*/
getData: function (t, user) {
return Q.all([
Feed.getData(t),
Header.getData(user),
Footer.getData()
]).spread(
/**
*
* @param feedData
* @param headerData
* @param footerData
*/
function (feedData, headerData, footerData) {
return {
header: headerData,
feed: feedData,
footer: footerData
}
});
}
},
/**
*
* @returns {XML}
*/
render: function () {
return (
<label>
<Header data={this.props.header} />
<Feed data={this.props.feed}/>
<Footer data={this.props.footer} />
</label>
);
}
});
라우터에서
var AppFactory = React.createFactory(App);
App.getData(t, user).then(
/**
*
* @param data
*/
function (data) {
var app = React.renderToString(
AppFactory(data)
);
res.render(
'layout',
{
body: app,
someData: JSON.stringify(data)
}
);
}
).fail(
/**
*
* @param error
*/
function (error) {
next(error);
}
);
를 사용하여 서버 측 렌더링에 대한 접근 방식을 공유하고 싶습니다 Flux
.
component
상점의 초기 데이터 가 있다고 가정 해 봅시다 .class MyComponent extends Component { constructor(props) { super(props); this.state = { data: myStore.getData() }; } }
클래스에 초기 상태를 위해 미리로드 된 데이터가 필요한 경우 다음에 대한 로더를 만들어 보겠습니다
MyComponent
.class MyComponentLoader { constructor() { myStore.addChangeListener(this.onFetch); } load() { return new Promise((resolve, reject) => { this.resolve = resolve; myActions.getInitialData(); }); } onFetch = () => this.resolve(data); }
저장:
class MyStore extends StoreBase { constructor() { switch(action => { case 'GET_INITIAL_DATA': this.yourFetchFunction() .then(response => { this.data = response; this.emitChange(); }); break; } getData = () => this.data; }
이제 라우터에 데이터를로드하십시오.
on('/my-route', async () => { await new MyComponentLoader().load(); return <MyComponent/>; });
just as a short rollup -> GraphQL will solve this entierly for your stack...
- add GraphQL
- use apollo and react-apollo
- use "getDataFromTree" before you start rendering
-> getDataFromTree will automatically find all the involved queries in your app and execute them, pouplating your apollo cache on the server and thus, enabling fully working SSR.. BÄM
'program story' 카테고리의 다른 글
피벗 컨트롤이 사라집니다 (0) | 2020.07.29 |
---|---|
Emacs에서 knitr Markdown 하이라이트? (0) | 2020.07.29 |
SystemWindows에 정확히 맞는 것은 무엇입니까? (0) | 2020.07.29 |
안드로이드 파이썬 프로그래밍 (0) | 2020.07.29 |
FFmpeg C API 문서 / 튜토리얼 (0) | 2020.07.29 |