Skip to content

Commit

Permalink
feat: react spa asyncData support
Browse files Browse the repository at this point in the history
  • Loading branch information
hubcarl committed Jan 30, 2020
1 parent 57fcc9c commit daca939
Show file tree
Hide file tree
Showing 43 changed files with 264 additions and 13,068 deletions.
21 changes: 4 additions & 17 deletions app/controller/home/index.js → app/controller/blog/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const egg = require('egg');
module.exports = class IndexController extends egg.Controller {

async ssr(ctx) {
await ctx.render('home.js', {
await ctx.render('blog.js', {
url: ctx.url,
title: 'Egg React ',
keywords: 'Egg,React,Egg React,Egg React SSR, Egg React CSR, Server Side Render, Client Side Render',
Expand All @@ -15,13 +15,13 @@ module.exports = class IndexController extends egg.Controller {
async node(ctx) {
const result = this.service.article.getArtilceList();
console.time('render');
await ctx.render('blog.js', result);
await ctx.render('list.js', result);
console.timeEnd('render');
}

async csr(ctx) {
const result = this.service.article.getArtilceList();
await ctx.renderClient('home.js', result);
await ctx.renderClient('blog.js', result);
}

async list() {
Expand All @@ -31,19 +31,6 @@ module.exports = class IndexController extends egg.Controller {
async detail(ctx) {
const id = ctx.params.id;
const article = this.service.article.getArticle(Number(id));
await ctx.render('detail.js', {
title: `${article.title}|Egg React `,
keywords: 'Egg,React,Egg React,Egg React SSR, Egg React CSR, Server Side Render, Client Side Render',
description: `${article.summary} | Egg + React + Webpack 服务端渲染 SSR (Server Side Render) 和 前端渲染 CSR (Client Side Render) 工程骨架项目`,
article
});
}

async stateless() {
const { ctx } = this;
await ctx.render('stateless.js', {
text: 'React Stateless Component Server Render'
});
ctx.body = { article };
}

};
21 changes: 21 additions & 0 deletions app/controller/example/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';
module.exports = app => {
return class DataController extends app.Controller {

async nodeDataRender(ctx) {
const title = 'Node 直接获取渲染数据';
const article = await ctx.service.article.getArticle(1);
await ctx.render('example/node.js', { title, article });
}

async asyncDataRender(ctx) {
const title = '前端 React 代码 asyncData 获取渲染数据';
await ctx.render('example/data.js', { title });
}

async article(ctx) {
const article = await ctx.service.article.getArticle(1);
ctx.body = { article };
}
};
};
17 changes: 17 additions & 0 deletions app/controller/example/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';
module.exports = app => {
return class ExampleController extends app.Controller {

async asyncComponentRender(ctx) {
await ctx.render('async.js', { message: 'Egg React Server Side Async Component Render' });
}

async statelessRender() {
const { ctx } = this;
await ctx.render('example/stateless.js', {
text: 'React Stateless Component Server Render'
});
}

};
};
17 changes: 0 additions & 17 deletions app/controller/test/test.js

This file was deleted.

19 changes: 10 additions & 9 deletions app/router.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@

module.exports = app => {
const { router, controller } = app;
router.get('/api/list', controller.home.index.list);
router.get('/api/blog/list', controller.blog.index.list);
router.get('/api/blog/:id', controller.blog.index.detail);
router.get('/intro', controller.intro.intro.index);
router.get('/detail/:id', controller.home.index.detail);
router.get('/csr', controller.home.index.csr);
router.get('/node', controller.home.index.node);
router.get('/stateless', controller.home.index.stateless);
router.get('/test/async', controller.test.test.asyncComponentRender);
router.get('/test/data', controller.test.test.asyncDataRender);
router.get('/test/api/article', controller.test.test.article);
router.get('/*', controller.home.index.ssr);
router.get('/csr', controller.blog.index.csr);
router.get('/node', controller.blog.index.node);
router.get('/example/stateless', controller.example.index.statelessRender);
router.get('/example/async', controller.example.index.asyncComponentRender);
router.get('/example/data/node', controller.example.data.nodeDataRender);
router.get('/example/data/async', controller.example.data.asyncDataRender);
router.get('/example/data/api/article', controller.example.data.article);
router.get('/(.*?)', controller.blog.index.ssr);
};
7 changes: 0 additions & 7 deletions app/web/component/layout/blog.jsx

This file was deleted.

File renamed without changes.
15 changes: 8 additions & 7 deletions app/web/framework/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { matchRoutes } from 'react-router-config';

async function asyncData(context, router) {
const url = context.url;
const matchRoute = matchRoutes(router, url);
const promises = matchRoute.map(({ route }) => {
const componentAsyncData = route.component.asyncData;
return componentAsyncData instanceof Function ? componentAsyncData(context, route) : null;

async function asyncData(locals, router) {
const url = locals.url;
const matchRouteList = matchRoutes(router, url);
const promises = matchRouteList.map(matchRoute=> {
const componentAsyncData = matchRoute.route.component.asyncData;
return componentAsyncData instanceof Function ? componentAsyncData(locals, matchRoute) : null;
});
const list = await Promise.all(promises);
return list.reduce((item, result) => {
Expand All @@ -24,7 +25,7 @@ function bootstrap(Entry) {
ReactDOM[renderMethod](<Entry />, root);
if (EASY_ENV_IS_DEV) {
module.hot.accept(() => {
ReactDOM[renderMethod](<RootEntry />, root);
ReactDOM[renderMethod](<Entry />, root);
});
}
return;
Expand Down
14 changes: 8 additions & 6 deletions app/web/framework/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ axios.defaults.timeout = 15000;
axios.defaults.xsrfHeaderName = 'x-csrf-token';
axios.defaults.xsrfCookieName = 'csrfToken';
export default {
post(url, json, state = {}) {
async post(url, json, locals = {}) {
const headers = {};
if (EASY_ENV_IS_NODE) {
headers['x-csrf-token'] = state.csrf;
headers.Cookie = `csrfToken=${state.csrf}`;
headers['x-csrf-token'] = locals.csrf;
headers.Cookie = `csrfToken=${locals.csrf}`;
}
return axios.post(`${state.origin}${url}`, json, { headers });
const res = await axios.post(`${locals.origin}${url}`, json, { headers });
return res.data;
},
get(url, state = {}) {
return axios.get(`${state.origin}${url}`);
async get(url, locals = {}) {
const res = await axios.get(`${locals.origin}${url}`);
return res.data;
}
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
33 changes: 33 additions & 0 deletions app/web/page/blog/router/detail.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader/root'

import request from '../../../framework/request';

class Detail extends Component {
static async asyncData(locals, route) {
const id = route.match.params.id;
return request.get(`/api/blog/${id}`, locals);
}

render() {
const { article } = this.props;
return article ? <div>
<h2 className="easy-article-detail-title">{article.title}</h2>
<div className="easy-article-info">
<iframe src={article.url} frameBorder="0" width="100%" style={{minHeight: '800px'}}></iframe>
</div>
</div> : null;
}
}


const mapStateToProps = state => {
return {
article: state.article
};
};

export default connect(mapStateToProps)(EASY_ENV_IS_DEV ? hot(Detail) : Detail)
21 changes: 21 additions & 0 deletions app/web/page/blog/router/example.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// eslint-disable-next-line no-unused-vars
import React, { Component } from 'react';

export default class Example extends Component {

render() {
const styleTitle = {
marginTop: '40px', marginBottom: '40px', textAlign: 'center'
};
const styleSub = {
marginBottom: '40px', textAlign: 'center', color: '444444'
};
return <div>
<h2 style={{...styleTitle}}>Egg React Example</h2>
<h4 style={{...styleSub}}><a target="_blank" href="/example/async">React Async Component Render</a></h4>
<h4 style={{...styleSub}}><a target="_blank" href="/example/stateless">React Stateless Component Render</a></h4>
<h4 style={{...styleSub}}><a target="_blank" href="/example/data/node">React Server Render for Node Data Mode</a></h4>
<h4 style={{...styleSub}}><a target="_blank" href="/example/data/async">React Server Render for asyncData Mode</a></h4>
</div>;
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';

import { hot } from 'react-hot-loader/root'
import request from 'framework/request';
import { add, del } from '../store/actions';
import './home.css'
class Home extends Component {
static asyncData(context, route) {
return request.get('/api/list', context).then(res => {
return res.data;
});
static async asyncData(context, route) {
return request.get('/api/blog/list', context);
}

render() {
const { list } = this.props;
const { list = [] } = this.props;
return <div className="easy-article-list">
<ul>
{list.map(function (item) {
return <li key={item.id} className="easy-article-item">
<h2 className="easy-article-title"><a href={'/detail/' + item.id} target="_blank">{item.title}</a></h2>
<h2 className="easy-article-title"><Link to={'/detail/' + item.id}>{item.title}</Link></h2>
<div className="easy-article-summary">{item.summary}</div>
<div className="easy-article-meta">
<span>11Word Count:{item.wordCount} </span>
Expand All @@ -36,4 +36,9 @@ const mapStateToProps = state => {
};
};

export default connect(mapStateToProps, { add, del })(Home);

const mapDispatchToProps = dispatch => {
return {};
};

export default connect(mapStateToProps, mapDispatchToProps)(EASY_ENV_IS_DEV ? hot(Home) : Home);
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Route } from 'react-router-dom';
import Home from './home';
import Async from './async';
import Example from './example';
import Detail from './detail';
import About from './about';
const NotFound = () => {
return (
Expand All @@ -20,16 +23,24 @@ const NotFound = () => {
export default function createRouter() {
return [
{
path: '/',
component: Home
path: '/detail/:id',
component: Detail
},
{
path: '/async',
component: Async
},
{
path: '/example',
component: Example
},
{
path: '/about',
component: Async
component: About
},
{
path: '/',
component: Home
},
{
path: '*',
Expand Down
40 changes: 40 additions & 0 deletions app/web/page/blog/router/route.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

import React, { Component } from 'react';
import { Route } from 'react-router-dom';
import { connect } from 'react-redux';
import { update } from '../store/actions';

class WrapperRoute extends Component {

async componentWillReceiveProps(nextProps) {
const { type, locals, component, computedMatch, updateState } = nextProps;
console.log('>>route', computedMatch.url, this.props.computedMatch.url);
if (computedMatch.url !== this.props.computedMatch.url) {
const { asyncData } = component;
if (asyncData) {
const data = await asyncData(locals, { match: computedMatch });
console.log('>>>update', type, data);
updateState(type, data);
}
}
}

render() {
return <Route {...this.props} />
}
}

const mapStateToProps = state => {
return {
locals: state
}
};

const mapDispatchToProps = dispatch => {
return {
updateState: (type, data) => dispatch(update(type, data))
};
};

export default connect(mapStateToProps, mapDispatchToProps)(WrapperRoute);
6 changes: 6 additions & 0 deletions app/web/page/blog/store/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const update = (type, data) => {
return {
type,
data
};
}
4 changes: 4 additions & 0 deletions app/web/page/blog/store/constant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const ARTICLE_LIST = 'ARTICLE_LIST';
export const ARTICLE_DETAIL = 'ARTICLE_DETAIL';
export const ADD = 'ADD';
export const DEL = 'DEL';
File renamed without changes.
Loading

0 comments on commit daca939

Please sign in to comment.