demo地址:github.com/xwb007/Koa-…css
本文爲前端小白自學總結,有錯誤的地方懇請各位大佬指出!html
一、SEO不友好前端
二、首次請求時間較長,體驗很差,等待白屏node
webpack 本地環境和生產環境配置我就很少闡述了,直接開始個人思路mysql
首先要用koa來啓動服務,webpack來打包咱們的react代碼react
sever/app.js-koa一些中間件webpack
import Koa from 'koa';
import json from 'koa-json';
import bodyParser from 'koa-bodyparser';
import logger from 'koa-logger';
import session from 'koa-session';
import compress from 'koa-compress';
import convert from 'koa-convert';
import cors from 'koa2-cors';
const app = new Koa();
app.use(convert(session(app)));
app.use(compress());
app.use(bodyParser());
app.use(cors());
app.use(json());
app.use(logger());
export default app;
複製代碼
而後就是server/server.dev.js-啓動本地開發server文件ios
const app = require('./app.js'),
convert = require('koa-convert'),
webpack = require('webpack'),
fs = require('fs'),
path = require('path'),
devMiddleware = require('koa-webpack-dev-middleware'),
hotMiddleware = require('koa-webpack-hot-middleware'),
views = require('koa-views'),
router = require('./routes'),
clientRoute = require('./middlewares/clientRoute'),
config = require('../build/webpack.dev.config'),
port = process.env.port || 3000,
compiler = webpack(config);
compiler.plugin('emit', (compilation, callback) => {
const assets = compilation.assets;
let file, data;
Object.keys(assets).forEach(key => {
if (key.match(/\.html$/)) {
file = path.resolve(__dirname, key);
data = assets[key].source();
fs.writeFileSync(file, data);
}
});
callback();
});
app.use(views(path.resolve(__dirname, '../views'), { map: { html: 'ejs' } }));
app.use(router.routes());
app.use(router.allowedMethods());
app.use(clientRoute);
app.use(
convert(
devMiddleware(compiler, {
noInfo: true,
publicPath: config.output.publicPath
})
)
);
app.use(convert(hotMiddleware(compiler)));
複製代碼
//ejs模版渲染
app.use(views(path.resolve(__dirname, '../views'), { map: { html: 'ejs' } }));
複製代碼
在項目中咱們會使用到rudux 、router路由徹底有react前端來控制,在用戶請求路由時傳到前端路由來匹配進行渲染相對應的路由模塊,react-router-dom 的 StaticRouter很好的針對服務端,ctx.url爲請求的路由,data爲請求的參數,須要在你渲染前傳遞過去。git
.clientRoute.jses6
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from '../../client/redux/store';
import { RoutesIndex, routes } from '../../client/router/index';
import getData from '../../client/common/getData';
async function clientRoute(ctx, next) {
for (let item of routes) {
if (item.path == ctx.url) {
const data = await getData(ctx.url);
await ctx.render('index', {
root: renderToStaticMarkup(
<Provider store={store}> <StaticRouter location={ctx.url} context={data}> <RoutesIndex /> </StaticRouter> </Provider>
)
});
break;
}
}
await next();
}
export default clientRoute;
複製代碼
咱們再來看看getData
client/common/getData.js
import request from './request';
async function getData(path) {
switch (path) {
case '/':
let data = {};
await request.config({ url: '/api/user/getUserInfo' }).then(res => {
data = res;
});
return data;
default:
break;
}
}
export default getData;
複製代碼
就是一個根據路由來匹配你當前路由須要的參數,而後用axios進行異步請求 request是我封裝的axios因此 es6 的async await超級好用呢,emmmmmm...這樣就能在咱們渲染返回html以前請求所須要的異步數據在服務端渲染,再來看看前端的配置,首先和react的cli基本同樣
client/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import { RoutesIndex } from './router';
import { Provider } from 'react-redux';
import store from './redux/store';
ReactDOM.render(
<Provider store={store}> <Router> <RoutesIndex /> </Router> </Provider>,
document.getElementById('root')
);
複製代碼
這兒要注意的問題就是Router必須放在這個地方,不能放在router/index.js裏面,由於在node中引入BrowserRouter是要報錯的
clinet/router/index.js
/** * 返回一個基本的RoutesIndex */
import React from 'react';
import { Route } from 'react-router-dom';
import Home from '../page/Home';
import About from '../page/About';
const routes = [{ path: '/', component: Home }, { path: '/about', component: About }];
class RoutesIndex extends React.Component {
render() {
return (
<div className="app-container"> {routes.map((item, index) => ( <Route key={index} path={item.path} exact component={item.component} /> ))} </div> ); } } export default { RoutesIndex, routes }; 複製代碼
我把routes放在一個數組中,新增的路由都在routes裏面配,這樣作的主要目的就是爲了在服務端渲染的時候方便匹配路由,再不是請求的路由時直接await next();
for (let item of routes) {
if (item.path == ctx.url) {
...js
}
}
await next();
複製代碼
redux我就不闡述了,我這裏沒有加入中間件redux-thunk,就是一個很簡單的redux狀態管理器
還有一點就是在咱們頁面中怎麼處理服務端傳遞過來的參數和前端本身請求的參數?
<StaticRouter location={ctx.url} context={data}>
<RoutesIndex /> </StaticRouter>
複製代碼
StaticRouter 傳遞的data在前端頁面會傳入props中的staticContext,在瀏覽器是看不見的,須要的服務端控制檯打印查看。
./Home/index.jsx
import React, { Component } from 'react';
import getData from '../../common/getData';
import styles from './index.scss';
class Home extends Component {
constructor(props) {
super(props);
this.state = {
user: props.staticContext
};
}
async componentDidMount() {
this.setState({ user: await getData(this.props.match.path) });
}
render() {
const { user } = this.state;
return (
<div className={styles.box}> <h1>hello koa-react-template</h1> <p>{user && user.userId}</p> <p>{user && user.name}</p> <p>{user && user.gender}</p> <p>{user && user.age}</p> </div>
);
}
}
export default Home;
複製代碼
我在項目中使用了mysql,若是你沒有安裝mysql,沒事,你能夠直在server\controllers\user.js 進行註釋
// import query from './config';
// const findUserInfo = () => {
// const _sql = 'select * from user';
// return query(_sql, []);
// };
const getUserInfo = async ctx => {
let data = {};
// await findUserInfo().then(result => {
// data = result[0];
// });
data = {
userId: 1001,
name: 'xwb007',
gender: '男',
age: 24
};
ctx.body = data;
};
export default { getUserInfo };
複製代碼