自制的React同构脚手架
Web前端世界日新月异变化太快,为了让自己跟上节奏不掉队,总结出了自己的一套React脚手架,方便日后新项目可以基于此快速上手开发。
源码: https://github.com/54sword/react-starter
特点
- 服务端渲染,完美解决SEO问题
- 按页面将代码分片,然后按需加载
- 支持 CSS Modules,避免CSS全局污染
- 支持流行UI框架 Bootstrap 4
- 开发环境支持热更新
- 内置登录、退出、页面权限控制、帖子列表获取、帖子详情获取等功能
- 内置用户访问页面时,301、404 状态相应的处理逻辑
需求配置
node ^8.6.0
npm ^5.7.1
没有在windows机器上测试过,可能会报错
开始
$ git clone git@github.com:54sword/react-starter.git
$ cd react-starter
$ npm install
$ npm run dev
浏览器打开 http://localhost:4000
相关命令说明
开发环境
注意:开发环境下,代码不分片,生产环境下才会分片
npm run dev
生产环境测试
npm run dist
npm run server
部署到服务器
1、修改 config/index.js 中的 public_path 配置
2、打包文件,除了index.ejs是服务端渲染的模版文件,其他都是客户端使用的文件
npm run dist
3、将项目上传至你的服务器
4、启动服务
Node 启动服务
NODE_ENV=production __NODE__=true BABEL_ENV=server node src/server
或使用 pm2 启动服务
NODE_ENV=production __NODE__=true BABEL_ENV=server pm2 start src/server --name "react-starter" --max-memory-restart 400M
目录结构
.
├── config # 项目配置文件
├── dist # 所有打包文件储存在这里
├── src # 程序源文件
│ ├── actions # redux actions
│ ├── client # 客户端入口
│ ├── common # 全局可复用的容器组件
│ ├── components # 全局可复用的容器组件
│ ├── pages # 页面组件
│ ├── reducers # redux reducers
│ ├── router # 路由配置
│ ├── server # 服务端入口
│ ├── store # redux store
│ └── view # html模版文件
├── .babelrc # 程序源文件
├── webpack.development.config.js # 开发环境的webpack配置项
└── webpack.profuction.config.js # 生产环境的wbepakc配置项
运行效果图



部分功能实现思路详解
配置路由
src/router/index.js 为路由配置文件,如下代码是一个路由项的配置说明
{
// 路径
path: '/',
// 如果为true,则只有在路径完全匹配location.pathname时才匹配
exact: true,
// 页面头部组件
head: Head,
/**
* 内容组件(页面主要内容)
* generateAsyncRouteComponent 为生成一个异步加载组件,
* 客户端打包的时候 ../pages/home,会将该组件单独打包成一个js文件,用于在客户端按需加载。
*/
component: generateAsyncRouteComponent({
loader: () => import('../pages/home')
}),
/**
* 进入该页面的触发事件
* requireAuth 为需要登录才能访问
* requireTourists 只有游客可以访问
* triggerEnter 进入事件,可以用作任何人都可以访问
*/
enter: requireAuth
}
页面组件详细
src/pages/ 为页面组件,实现具体的页面内容,以首页为例的说明 ./src/pages/home/index.js
import React from 'react';
import PropTypes from 'prop-types';
// 加载帖子列表的方法
import { loadPostsList } from '../../actions/posts';
// http://blog.csdn.net/ISaiSai/article/details/78094556
import { withRouter } from 'react-router-dom';
// 壳组件,给页面组件套一个壳组件,方便给所有页面增加额外功能和属性
import Shell from '../../components/shell';
// 生成页面Meta,如标题、描述、关键词
import Meta from '../../components/meta';
// 帖子列表组件
import PostsList from '../../components/posts/list';
export class Home extends React.Component {
// 服务端渲染
// 加载需要在服务端渲染的数据
static loadData({ store, match }) {
return new Promise(async function (resolve, reject) {
/**
* 这里的 loadPostsList 方法,是在服务端加载 posts 数据,储存到 redux 中。
* 这里对应的组件是 PostsList,PostsList组件里面也有 loadPostsList 方法,但它是在客户端执行。
* 然后,服务端在渲染 PostsList 组件的时候,我们会先判断如果redux中,是否存在该条数据,如果存在,直接拿该数据渲染
*/
await loadPostsList({
id: 'home',
filter: {
sort_by: "create_at",
deleted: false,
weaken: false
}
})(store.dispatch, store.getState);
resolve({ code:200 });
})
}
constructor(props) {
super(props);
}
render() {
return(<div>
<Meta title="首页" />
<PostsList
id={'home'}
filter={{
sort_by: "create_at",
deleted: false,
weaken: false
}}
/>
</div>)
}
}
Home = withRouter(Home);
export default Shell(Home);
服务端渲染
import path from 'path';
import express from 'express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import compress from 'compression';
// 服务端渲染依赖
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter, matchPath } from 'react-router';
import { Provider } from 'react-redux';
import DocumentMeta from 'react-document-meta';
// 路由配置
import configureStore from '../store';
// 路由组件
import createRouter from '../router';
// 路由初始化的redux内容
import { initialStateJSON } from '../reducers';
import { saveAccessToken, saveUserInfo } from '../actions/user';
// 配置
import { port, auth_cookie_name } from '../../config';
import sign from './sign';
import webpackHotMiddleware from './webpack-hot-middleware';
const app = express();
// ***** 注意 *****
// 不要改变如下代码执行位置,否则热更新会失效
// 开发环境开启修改代码后热更新
if (process.env.NODE_ENV === 'development') webpackHotMiddleware(app);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(compress());
app.use(express.static(__dirname + '/../../dist'));
// 登录、退出
app.use('/sign', sign());
app.get('*', async (req, res) => {
// 创建 store
const store = configureStore(JSON.parse(initialStateJSON));
let user = null;
let accessToken = req.cookies[auth_cookie_name] || '';
// 验证 token 是否有效
if (accessToken) {
// 这里可以去查询 accessToken 是否有效
// your code
// 这里假设如果有 accessToken ,那么就是登录用户,将他保存到redux中
user = { id: '001', nickname: accessToken };
// 储存用户信息
store.dispatch(saveUserInfo({ userinfo: user }));
// 储存access token
store.dispatch(saveAccessToken({ accessToken }));
}
// 创建路由,返回 list 、dom
// list 是路由的配置列表,dom render的dom
const router = createRouter(user);
const _Router = router.dom;
let _route = null,
_match = null;
// 从路由配置列表中,找到对应的路由
router.list.some(route => {
let match = matchPath(req.url.split('?')[0], route);
if (match && match.path) {
_route = route;
_match = match;
return true;
}
})
/**
* 加载异步组件,并在异步组件中执行 loadData,loadData 加载的数据,储存到redux store中
*/
const context = await _route.component.load({ store, match: _match });
// 渲染页面
let html = ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<_Router />
</StaticRouter>
</Provider>
);
// 将redux state 转换成 json 储存到页面中
let reduxState = JSON.stringify(store.getState()).replace(/</g, '\\x3c');
// 获取页面的meta,嵌套到模版中
// 给客户端 initState
let meta = DocumentMeta.renderAsHTML();
if (context.code == 301) {
res.writeHead(301, {
Location: context.url
});
} else {
res.status(context.code);
res.render('../dist/index.ejs', { html, reduxState, meta });
}
res.end();
});
app.listen(port);
console.log('server started on port ' + port);
自制的React同构脚手架
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
自制的React同构脚手架的更多相关文章
- React同构直出原理浅析
通常,当客户端请求一个包含React组件页面的时候,服务端首先响应输出这个页面,客户端和服务端有了第一次交互.然后,如果加载组件的过程需要向服务端发出Ajax请求等,客户端和服务端又进行了一次交互,这 ...
- React 同构
React 同构 搬运 https://segmentfault.com/a/1190000004671209 究竟什么是同构呢? 同构就是希望前端 后端都使用同一套逻辑 同一套代码 Nodejs出现 ...
- React/VUE 脚手架2.0和3.0
react官方脚手架 npm install -g create-react-app create-react-app my-app cd my-app npm start 区别自己对比 vue2.x ...
- 打造高可靠与高性能的React同构解决方案
前言 随着React的兴起, 结合Node直出的性能优势和React的组件化,React同构已然成为趋势之一.享受技术福利的同时,直面技术挑战,在复杂场景下,挑战10倍以上极致的性能优化. 什么是同构 ...
- React同构直出优化总结
收录待用,修改转载已取得腾讯云授权 作者:郭林烁 joeyguo 原文地址 React 的实践从去年在 PC QQ家校群开始,由于 PC 上的网络及环境都相当好,所以在使用时可谓一帆风顺,偶尔遇到点小 ...
- React 同构开发(二)
React 同构 所谓同构,简单的说就是客户端的代码可以在服务端运行,好处就是能极大的提升首屏时间,避免白屏,另外同构也给SEO提供了很多便利. React 同构得益于 React 的虚拟 DOM.虚 ...
- React 同构开发(一)
为什么要做同构 要回答这个问题,首先要问什么是同构.所谓同构,顾名思义就是同一套代码,既可以运行在客户端(浏览器),又可以运行在服务器端(node). 我们知道,在前端的开发过程中,我们一般都会有一个 ...
- 腾讯新闻构建高性能的 react 同构直出方案
在腾讯新闻抢金达人活动 node 同构直出渲染方案的总结文章中我们整体了解了下同构直出渲染方案在我们项目中的使用.正如我在上篇文章结尾所说的: 应用型技术的难点不是在克服技术问题,而是在于能够不断的结 ...
- React同构起步
React同构从0到1 前言 如果你想快速做react同构的新项目建议你去了解next.js等成熟框架,本教程仅限于想了解如何从0开始实现一个同构环境过程的同学,对于想改造现有spa项目的同学也很有帮 ...
随机推荐
- POCO C++ SOCKET
// client program #include "Poco/Net/DatagramSocket.h" #include "Poco/Net/SocketAddre ...
- 【linux高级程序设计】(第十五章)UDP网络编程应用 4
socket信号驱动 为了使一个套接字能够使用信号驱动I/O,至少需要以下3步操作. 1.安装SIGIO信号 2.套接字的拥有者设定为当前进程.因为SIGIO信号只会送到socket拥有者进程. 通过 ...
- FTP-Filezilla首次配置
最新新弄了个服务器,先吐槽下,之前买镜像都是免费的,昨天试了,竟然收费.... 好吧,用户多了也正常. 代码发布之前都是很暴力的直接远程桌面然后粘贴,有个合作伙伴突然需要FTP,说之前用的就是,我就做 ...
- 51nod 1001 数组中和等于K的数对【二分查找/排序】
1001 数组中和等于K的数对 基准时间限制:1 秒 空间限制:131072 KB 分值: 5 难度:1级算法题 收藏 关注 给出一个整数K和一个无序数组A,A的元素为N个互不相同的整数,找出数组 ...
- 大数据技术之_16_Scala学习_08_数据结构(下)-集合操作+模式匹配
第十一章 数据结构(下)-集合操作11.1 集合元素的映射-map11.1.1 map 映射函数的操作11.1.2 高阶函数基本使用案例1+案例211.1.3 使用 map 映射函数来解决11.1.4 ...
- [LOJ6208]树上询问
题目大意: 有一棵n节点的树,根为1号节点.每个节点有两个权值ki,ti,初始值均为0. 给出三种操作: 1.Add(x,d)操作:将x到根的路径上所有点的ki←ki+d 2.Mul(x,d)操作:将 ...
- 最近公共祖先LCA Tarjan 离线算法
[简介] 解决LCA问题的Tarjan算法利用并查集在一次DFS(深度优先遍历)中完成所有询问.换句话说,要所有询问都读入后才开始计算,所以是一种离线的算法. [原理] 先来看这样一个性质:当两个节点 ...
- String中的方法
1.string s1 = "abcdefghij"; string s2 = "kuo"; Console.WriteLine(s1.Clone()) ...
- ttServer缓存的简单使用
ttserver是一款 DBM 数据库,该数据库读写非常快,哈希模式写入100万条数据只需0.643秒,读取100万条数据只需0.773秒,是 Berkeley DB 等 DBM 的几倍.利用Toky ...
- [给自己扫盲]名词解释——LAMP、MEAN、Web应用框架等
名词解释 LAMP The LAMP software bundle (here additionally with Squid). A high performance and high-avail ...