如何避免使用 redux 在 react-apollo SSR 中重新获取客户端?
How to avoid client re fetching in react-apollo SSR with redux?
我是使用 react-appollo 的 graphql 新手我想将 react apollo 与 redux 以及服务器端渲染一起使用一切都很好我的应用程序正在运行但问题是当我的应用程序渲染时它实际上正在回忆 api 再次它没有使用我的渲染状态..
服务器.js
import express from 'express';
import bodyParser from 'body-parser';
import path from 'path';
import expressGraphQL from 'express-graphql';
import schema from './GraphQL/Schema';
import React from 'react';
import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router';
import { ApolloClient, createNetworkInterface, ApolloProvider } from 'react-apollo';
import { getDataFromTree } from "react-apollo"
import store from '../client/Redux/Store/store';
import {serverClient} from './lib/apollo'
require('es6-promise').polyfill();
require('isomorphic-fetch');
import WApp from '../client/App';
//Dev HMR
import HMR from './serverUtils/HMR';
const app = express();
app.use(bodyParser.json());
app.use('/api', expressGraphQL({
schema,
graphiql: true
}));
app.use('/static',express.static('build'));
HMR(app);
function Html({ content, state }) {
return (
<html>
<body>
<div id="app" dangerouslySetInnerHTML={{ __html: content }}/>
<script src="/static/app.js" />
<script dangerouslySetInnerHTML={{
__html: `window.__APOLLO_STATE__=${JSON.stringify(state).replace(/</g, '\u003c')};`,
}} />
</body>
</html>
);
}
function createReactHandler(req) {
return async function reactHandler(ctx) {
const routeContext = {};
const client = serverClient();
const components = (
<StaticRouter location={req.url} context={routeContext}>
<ApolloProvider store={store} client={client}>
<WApp />
</ApolloProvider>
</StaticRouter>
);
await getDataFromTree(components);
// const html = ReactDOMServer.renderToString(components);
// // Handle redirects
// if ([301, 302].includes(routeContext.status)) {
// // 301 = permanent redirect, 302 = temporary
// ctx.status = routeContext.status;
//
// // Issue the new `Location:` header
// ctx.redirect(routeContext.url);
//
// // Return early -- no need to set a response body
// return;
// }
//
// // Handle 404 Not Found
// if (routeContext.status === 404) {
// // By default, just set the status code to 404. You can add your
// // own custom logic here, if you want to redirect to a permanent
// // 404 route or set a different response on `ctx.body`
// ctx.status = routeContext.status;
// }
// return html;
// console.log(html)
}
}
const HTML = ({ html,state}) => (
<html lang="en" prefix="og: http://ogp.me/ns#">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta httpEquiv="Content-Language" content="en" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div
id="app"
dangerouslySetInnerHTML={{ __html: html }} />
<script dangerouslySetInnerHTML={{
__html: `window.__STATE__=${JSON.stringify(state)};`,
}} />
<script src="/static/app.js" />
</body>
</html>
);
app.get('/*',(req,res) => {
const routeContext = {};
const client = serverClient();
const components = (
<StaticRouter location={req.url} context={routeContext}>
<ApolloProvider store={store} client={client}>
<WApp />
</ApolloProvider>
</StaticRouter>
);
getDataFromTree(components).then(() => {
const html = ReactDOMServer.renderToString(components);
const initialState = {apollo: client.getInitialState()}
console.log(client);
res.send(`<!DOCTYPE html>\n${ReactDOMServer.renderToStaticMarkup(
<HTML
html={html}
state={initialState}
/>,
)}`)
})
})
app.listen(3000,() => {
console.log('Man I on')
})
store.js
import { createStore, compose, applyMiddleware } from 'redux';
import { syncHistoryWithStore } from 'react-router-redux';
import thunk from 'redux-thunk';
import {createLogger} from 'redux-logger';
import client from '../apolloClient';
import rootReducer from '../Reducers'
//All Reducer
import {initialState as allPosts} from '../Reducers/AllPosts_Reucer';
const isProduction = process.env.NODE_ENV !== 'development';
const isClient = typeof document !== 'undefined';
const initialState = {
allPosts
};
const middlewares = [thunk, client.middleware()];
const enhancers = [];
if (!isProduction && isClient) {
const loggerMiddleware = createLogger();
middlewares.push(loggerMiddleware);
if (typeof devToolsExtension === 'function') {
const devToolsExtension = window.devToolsExtension;
enhancers.push(devToolsExtension());
}
}
const composedEnhancers = compose(
applyMiddleware(...middlewares),
...enhancers
);
const store = createStore(
rootReducer,
{},
composedEnhancers,
);
export default store;
apolloClient.js
import ApolloClient, {
createNetworkInterface,
} from 'apollo-client';
const isProduction = process.env.NODE_ENV !== 'development';
const testUrl = 'http://localhost:3000/api';
// const url = isProduction ? productionUrl : testUrl;
const url = testUrl;
const client = new ApolloClient({
networkInterface: createNetworkInterface({uri:testUrl}),
dataIdFromObject:({id}) => id,
initialState: (typeof window !=='undefined')? window.__STATE__:{},
reduxRootSelector:state => state.custom
});
export default client;
Home.js
import React,{Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import * as postActions from '../../Redux/Actions/postActions';
class Home extends Component{
componentWillMount(){
// console.log('From Will Mount',this.props.posts)
}
renderAllPost(){
const {loading,posts} = this.props;
if(!loading){
return posts.map(data => {
return <li key={data.id}>{data.title}</li>
})
}else{
return <div>loading</div>
}
}
render(){
return(
<div>
{this.renderAllPost()}
</div>
)
}
}
//start from here
const GetallPosts = gql`
query getAllPosts{
posts{
id
title
body
}
}
`;
const mapDispatchToProps = (dispatch) => ({
actions:bindActionCreators(
postActions,
dispatch
)
});
const ContainerWithData = graphql(GetallPosts,{
props:({ data:{loading,posts} }) => ({
posts,
loading,
})
})(Home)
export default connect(
// mapStateToPros,
// mapDispatchToProps
)(ContainerWithData)
我可以确认我理解正确吗?
您正在呈现 HTML 服务器端。
- HTML(包括所有帖子)在 HTML returned 到浏览器中。
- React 然后将其更改为加载 window
- React 然后进行 API 调用,并呈现新帖子
注意:Apollo 将始终进行 AJAX 调用,因为这是作为 ContainerWithData 的一部分自动完成的。
解决方案
使用所有数据渲染 Redux Store。
例如,当调用 "createStore" 时,您当前正在传递一个空对象。如果您在此处进行 AJAX 调用,则可以使用所需的所有数据填充 browser/redux 存储。
此时可以去掉Container中对GraphQL的调用。您可以用 "componentWillMount".
的一些逻辑替换它
逻辑是:
- 使用 return 来自 API
的模型数据创建商店
- 调用主页组件
- 本垒打 "componentWillMount"
- componentWillMount 检查 store.posts 是否有数据
- 然后从 API (GraphQL)
加载数据
- else return true 并继续渲染
我是使用 react-appollo 的 graphql 新手我想将 react apollo 与 redux 以及服务器端渲染一起使用一切都很好我的应用程序正在运行但问题是当我的应用程序渲染时它实际上正在回忆 api 再次它没有使用我的渲染状态..
服务器.js
import express from 'express';
import bodyParser from 'body-parser';
import path from 'path';
import expressGraphQL from 'express-graphql';
import schema from './GraphQL/Schema';
import React from 'react';
import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router';
import { ApolloClient, createNetworkInterface, ApolloProvider } from 'react-apollo';
import { getDataFromTree } from "react-apollo"
import store from '../client/Redux/Store/store';
import {serverClient} from './lib/apollo'
require('es6-promise').polyfill();
require('isomorphic-fetch');
import WApp from '../client/App';
//Dev HMR
import HMR from './serverUtils/HMR';
const app = express();
app.use(bodyParser.json());
app.use('/api', expressGraphQL({
schema,
graphiql: true
}));
app.use('/static',express.static('build'));
HMR(app);
function Html({ content, state }) {
return (
<html>
<body>
<div id="app" dangerouslySetInnerHTML={{ __html: content }}/>
<script src="/static/app.js" />
<script dangerouslySetInnerHTML={{
__html: `window.__APOLLO_STATE__=${JSON.stringify(state).replace(/</g, '\u003c')};`,
}} />
</body>
</html>
);
}
function createReactHandler(req) {
return async function reactHandler(ctx) {
const routeContext = {};
const client = serverClient();
const components = (
<StaticRouter location={req.url} context={routeContext}>
<ApolloProvider store={store} client={client}>
<WApp />
</ApolloProvider>
</StaticRouter>
);
await getDataFromTree(components);
// const html = ReactDOMServer.renderToString(components);
// // Handle redirects
// if ([301, 302].includes(routeContext.status)) {
// // 301 = permanent redirect, 302 = temporary
// ctx.status = routeContext.status;
//
// // Issue the new `Location:` header
// ctx.redirect(routeContext.url);
//
// // Return early -- no need to set a response body
// return;
// }
//
// // Handle 404 Not Found
// if (routeContext.status === 404) {
// // By default, just set the status code to 404. You can add your
// // own custom logic here, if you want to redirect to a permanent
// // 404 route or set a different response on `ctx.body`
// ctx.status = routeContext.status;
// }
// return html;
// console.log(html)
}
}
const HTML = ({ html,state}) => (
<html lang="en" prefix="og: http://ogp.me/ns#">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta httpEquiv="Content-Language" content="en" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div
id="app"
dangerouslySetInnerHTML={{ __html: html }} />
<script dangerouslySetInnerHTML={{
__html: `window.__STATE__=${JSON.stringify(state)};`,
}} />
<script src="/static/app.js" />
</body>
</html>
);
app.get('/*',(req,res) => {
const routeContext = {};
const client = serverClient();
const components = (
<StaticRouter location={req.url} context={routeContext}>
<ApolloProvider store={store} client={client}>
<WApp />
</ApolloProvider>
</StaticRouter>
);
getDataFromTree(components).then(() => {
const html = ReactDOMServer.renderToString(components);
const initialState = {apollo: client.getInitialState()}
console.log(client);
res.send(`<!DOCTYPE html>\n${ReactDOMServer.renderToStaticMarkup(
<HTML
html={html}
state={initialState}
/>,
)}`)
})
})
app.listen(3000,() => {
console.log('Man I on')
})
store.js
import { createStore, compose, applyMiddleware } from 'redux';
import { syncHistoryWithStore } from 'react-router-redux';
import thunk from 'redux-thunk';
import {createLogger} from 'redux-logger';
import client from '../apolloClient';
import rootReducer from '../Reducers'
//All Reducer
import {initialState as allPosts} from '../Reducers/AllPosts_Reucer';
const isProduction = process.env.NODE_ENV !== 'development';
const isClient = typeof document !== 'undefined';
const initialState = {
allPosts
};
const middlewares = [thunk, client.middleware()];
const enhancers = [];
if (!isProduction && isClient) {
const loggerMiddleware = createLogger();
middlewares.push(loggerMiddleware);
if (typeof devToolsExtension === 'function') {
const devToolsExtension = window.devToolsExtension;
enhancers.push(devToolsExtension());
}
}
const composedEnhancers = compose(
applyMiddleware(...middlewares),
...enhancers
);
const store = createStore(
rootReducer,
{},
composedEnhancers,
);
export default store;
apolloClient.js
import ApolloClient, {
createNetworkInterface,
} from 'apollo-client';
const isProduction = process.env.NODE_ENV !== 'development';
const testUrl = 'http://localhost:3000/api';
// const url = isProduction ? productionUrl : testUrl;
const url = testUrl;
const client = new ApolloClient({
networkInterface: createNetworkInterface({uri:testUrl}),
dataIdFromObject:({id}) => id,
initialState: (typeof window !=='undefined')? window.__STATE__:{},
reduxRootSelector:state => state.custom
});
export default client;
Home.js
import React,{Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import * as postActions from '../../Redux/Actions/postActions';
class Home extends Component{
componentWillMount(){
// console.log('From Will Mount',this.props.posts)
}
renderAllPost(){
const {loading,posts} = this.props;
if(!loading){
return posts.map(data => {
return <li key={data.id}>{data.title}</li>
})
}else{
return <div>loading</div>
}
}
render(){
return(
<div>
{this.renderAllPost()}
</div>
)
}
}
//start from here
const GetallPosts = gql`
query getAllPosts{
posts{
id
title
body
}
}
`;
const mapDispatchToProps = (dispatch) => ({
actions:bindActionCreators(
postActions,
dispatch
)
});
const ContainerWithData = graphql(GetallPosts,{
props:({ data:{loading,posts} }) => ({
posts,
loading,
})
})(Home)
export default connect(
// mapStateToPros,
// mapDispatchToProps
)(ContainerWithData)
我可以确认我理解正确吗?
您正在呈现 HTML 服务器端。
- HTML(包括所有帖子)在 HTML returned 到浏览器中。
- React 然后将其更改为加载 window
- React 然后进行 API 调用,并呈现新帖子
注意:Apollo 将始终进行 AJAX 调用,因为这是作为 ContainerWithData 的一部分自动完成的。
解决方案 使用所有数据渲染 Redux Store。 例如,当调用 "createStore" 时,您当前正在传递一个空对象。如果您在此处进行 AJAX 调用,则可以使用所需的所有数据填充 browser/redux 存储。
此时可以去掉Container中对GraphQL的调用。您可以用 "componentWillMount".
的一些逻辑替换它逻辑是:
- 使用 return 来自 API 的模型数据创建商店
- 调用主页组件
- 本垒打 "componentWillMount"
- componentWillMount 检查 store.posts 是否有数据
- 然后从 API (GraphQL) 加载数据
- else return true 并继续渲染