webpack react loadable redux ssr
中文文档
入门
欢迎来到 react-ssr-lazy-loading 文档!
说明:
- 0成本学习,api几乎和react api 一致
- 页面按访问加载和按需加载,与代码切割
- 实现了spa同构。
- 利用 webpack-dev-middleware 和 webpack-hot-server-middleware 实现 热启动编译内存访问
- 拓展性好,react-ssr-lazy-loading 是 react + webpack 构建的项目。所需的 plugins 和 loader可以根据自己需求搭配 6.SSR + SPA 体验升级 。 7.路由,渲染,数据,ajax 同构。
带有测验的交互式课程将指导您了解使用 react-ssr-lazy-loading 所需的一切。
系统要求
-
Node.js 12.22.0或更高版本
-
支持 MacOS、Windows(包括 WSL)和 Linux
我们建议使用 创建一个新的 应用程序,它会自动为您设置所有内容。要创建项目,请运行:
git clone https://github.com/qq281113270/react-ssr-lazy-loading.git
cd react-ssr-lazy-loading
npm i
# or
yarn
安装完成后 ,
安装所需的依赖
npm install
客户端命令
如果不需要ssr渲染,可以选择 start:client:dev
启动 不需要 ssr 服务器选择的开发命令
npm run start:client:dev
打包 不需要 ssr 服务器的渲染的 线上打包命令
npm run build:client:prod
ssr 服务器渲染命令
ssr服务器渲染开发打包命令 : start:ssr:dev
npm run start:ssr:dev
ssr服务器渲染线上打包命令 build:ssr:prod
npm run build:ssr:prod
启动 ssr 服务器命令start:server
npm run start:server
项目目录结构:
├── README.md # 文档说明
├── bin # 脚本执行
│ ├── cmd.js # 脚本执行
│ └── index.js
├── client # 客户端目录,react code
│ ├── App
│ │ ├── App.js
│ │ ├── App.less
│ │ ├── CreateApp.js
│ │ └── index.js
│ ├── assets #静态资源目录
│ │ ├── css
│ │ ├── img
│ │ └── js
│ ├── component # 公共组件目录
│ │ ├── Head
│ │ ├── InitState
│ │ ├── LazyLoadingImg
│ │ ├── Loadable
│ │ ├── Loading
│ │ ├── Nav
│ │ └── Table
│ ├── index.js # 客户端入口js
│ ├── pages # 路由页面 page 目录
│ │ ├── Home
│ │ ├── User
│ │ └── marketing
│ ├── public # webpack 引入 html 模板目录
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ └── logo512.png
│ ├── redux # react-redux 目录
│ │ ├── index.js
│ │ ├── initComponentState.js
│ │ └── models
│ ├── router # react 路由 目录
│ │ ├── Routers.js
│ │ ├── addRouterApi.js
│ │ ├── history.js
│ │ ├── historyPush.js
│ │ ├── index.js
│ │ ├── react-router-dom
│ │ ├── routePaths.js
│ │ ├── routesComponent.js
│ │ └── routesConfig.js
│ ├── static # webpack 不打包静态资源目录
│ └── utils # 一些工具和方法
│ ├── CheckDataType.js
│ ├── FloatingBall.js
│ ├── SubscribePublished.js
│ ├── createStore.js
│ ├── ergodic.js
│ ├── getBaseInitState.js
│ ├── getCssAttr.js
│ ├── index.js
│ ├── regular.js
│ ├── resolvePath.js
│ ├── setInitData.js
│ ├── stringToObject.js
│ ├── throttlingStabilization.js
│ └── transformRoutePaths.js
├── dist # build code 打包目录
│ ├── client # 客户端code打包目录
│ └── server # 服务端code 打包目录
├── node_modules #node_modules
├── nodemon.json # nodemon.json 重启node配置
├── package.json # npm 依赖包
├── server # 服务端 code 目录
│ ├── app.js # 服务器 koa app
│ ├── controller # 控制器
│ ├── index.js # server 入口执行文件
│ ├── middleware # 中间件添加 目录
│ │ ├── clientRouter
│ │ ├── index.js
│ │ └── webpackHot
│ ├── router # server 路由目录
│ │ ├── api.js
│ │ └── index.js
│ ├── service # service目录
│ │ └── user.js
│ └── utils # 服务端 一些工具方法
│ ├── copyFile.js
│ ├── index.js
│ ├── readFile.js
│ └── watchFile.js
├── webpack # webpack 配置目录
│ ├── config
│ │ ├── client # client webpack 配置
│ │ └── server # server webpack 配置
│ ├── defineLoader # 自定义Loader
│ │ └── MyExampleWebpackLoader.js
│ ├── definePlugin # 自定义Plugin
│ │ ├── HelloWorldCheckerPlugin
│ │ ├── MyExampleWebpackPlugin.js
│ │ ├── react-loadable
│ │ ├── react-loadable-ssr-addon
│ │ ├── webpack-plugin-copy-file
│ │ ├── webpack-plugin-no-require-css
│ │ ├── webpack-plugin-resolve-alias
│ │ └── webpack-plugin-router
│ ├── index.js # webpack 导出文件
│ └── utils # webpack 工具方法
│ ├── alias.js
│ ├── copyFile.js
│ ├── index.js
│ ├── readFile.js
│ ├── readWriteFiles.js
│ ├── stringToObject.js
│ └── watchFile.js
页面配置与路由
页面
├── client # 客户端目录,react code
│ ├── pages
├──Home # Home 页面
├── marketing # marketing 目录 二级路由
├── pages
├── DiscountCoupon # DiscountCoupon页面
├── router
├── routesConfig.js # 二级路由配置
│ ├── router # 路由配置
├── routesConfig.js # 一级路由配置
如果是设置一级路由那么 配置文件在 client --> router --> routesConfig.js 中
如果是二级路由那么是在 client --> pages --> marketing --> router --> routesConfig.js 中
routesConfig.js 内容
import { getHaoKanVideo } from "../assets/js/request/requestApi";
// 路由配置
export default [
{
path: "/", # 路由路径
exact: true, # react 路由参数exact
name: "home", # react 路由 name
entry: "/pages/Home/index.js", # 路由路径
initState: async (parameter = {}) => { # 每个页面初始化数据用于 node 后台静态请求数据,或者是 客户端 刚初始化完成 dom 请求数据
const { page = 1, size = 10 } = parameter;
return await getHaoKanVideo({
page,
size
})
.then((res) => {
const { result: { list = [], total } = {} } = res;
return {
list: list.map((item) => ({
...item,
url: item.userPic
})),
total
};
})
.catch(() => {
// console.log("Error: ", err.message);
});
},
level: 1 # 路由级别 1级路由,因为我们在渲染的时候会把路由点平 ,所以级别是靠这个数字识别
},
{
path: "/user",
name: "user",
entry: "/pages/User/index.js",
level: 1
}
];
如果我们需要新加页面,只需要在 client pages中添加页面组件之后,配置 增加 routesConfig.js 参数即可。
webpack 打包编译的时候会自动生成routesComponent.js文件。
react会去取路由routesComponent.js文件。形成路由地址。支持ssr和单纯client开发模式。
路由
在react prop中我注入了两个 路由属性 一个是pushRoute 路由跳转函数。一个是routePaths 路由组件地址参数信息。
当路由跳转的时候我们不需要使用 react 中的 this.props.history.push()
我们可以这样 name 是路由的name ,比如当路由跳转的时候我们不需要使用 react 中的 this.props.history.push()
我们可以这样 name 是路由的name ,比如 home页面 的 name 是 home
{
path: "/", # 路由路径
exact: true, # react 路由参数exact
name: "home", # react 路由 name
entry: "/pages/Home/index.js", # 路由路径
}
使用路由跳转可以这样做
this.pushRoute('home')
如果需要传参 get 传参
this.pushRoute({
name:'home',
query:{
age:18,
name:'yao guan shou'
}
})
跳转则为 http://localhost:3002?age=18&name=yaoGuanShou
我们也可以通过地址传参
路由path配置
path: "/:id/:name",
this.pushRoute({
name:'home',
params:{
age:18,
name:'yao guan shou'
}
})
跳转则为 http://localhost:3002/18/yaoGuanShou
数据获取
数据获取与redux
node ssr获取每个页面数据
数据获取可以在node请求获取数据之后然后渲染成html直接发送给客户端,可以在客户端做请求。
如果希望在node服务端做请求那么这样配置。 在 routesConfig.js 中配置
{
path: "/",
exact: true,
name: "home",
entry: "/pages/Home/index.js",
initState: async (parameter = {}) => { # ajax 请求
const { page = 1, size = 10 } = parameter;
# 发送 ajax 请求
return await getHaoKanVideo({
page,
size
})
.then((res) => {
const { result: { list = [], total } = {} } = res;
return {
list: list.map((item) => ({
...item,
url: item.userPic
})),
total
};
})
.catch(() => {
// console.log("Error: ", err.message);
});
},
level: 1
},
在redux --> models --> home.js 新建一个home.js 用于存储ajax请求数据。
import { setInitData } from "client/utils";
export default (global) => ({
state: {
initState: setInitData(global, "home"),
count: 0
},
reducers: {
setCount(state, newState) {
return {
...state,
count: newState
};
},
setInitState(state, newState) {
return {
...state,
...newState
};
}
},
effects: {
}
});
在用户请求页面的时候 node 服务器会 匹配到 home路由 会调用routesConfig.js 请求 initState 方法 发送ajax 请求,然后会把数据存放于 redux initState中。
读取 initState 数据. 引入 import { mapRedux } from "client/redux"; mapRedux 装饰器,把 redux 注入 组件中。
import { mapRedux } from "client/redux";
const Index = (props) => {
let [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const {
dispatch: { home: { setInitState = () => {} } = {} } = {},
state: { home: { initState: { list = [] } = {} } = {} } = {}
} = props;
console.log("props=======", props);
return (
<div className="home"> home </div>
);
};
打印 props 可以看到props.state.home.initState 数据。
客户端 更新 initState 。在 props.dispatch.home 有setCount和setInitState。这两个方法就是刚才在redux --> models --> home.js定义的方法。
更新initState
props.dispatch.home.setInitState(newData)
node ssr 获取公共数据
比如客户端头部有个数据每个页面都需要的,这个我们可以这样做在 redux --> models --> baseInitState.js定义的方法。
import { getWeather } from "../../assets/js/request/requestApi";
const setInitData = (global, name) => {
let initState = {};
if (global && global.__INITIAL_STATE__ && global.__INITIAL_STATE__[name]) {
initState = global.__INITIAL_STATE__[name];
}
return initState;
};
export default (global) => ({
state: {
...setInitData(global, "baseInitState"),
menuActive: "/"
},
reducers: {
setInitState(state, newState) {
return {
...state,
...newState
};
},
setMenuActive(state, newState) {
return {
...state,
...newState
};
}
},
effects: (dispatch) => ({
async getWeatherAsync() {
return await getWeather({
key: "2d935fc56c5f9ab2ef2165822cedff56",
city: "440300",
extensions: "all"
})
.then((data) => {
dispatch.baseInitState.setInitState({
weather: data.forecasts[0]
});
return data;
})
.catch((err) => {
console.log("Error: ", err.message);
});
}
})
});
在 effects 中定义 getWeatherAsync 获取 天气数据的ajax请求,这里定义一定要 getNameAsync
开头是get+name+Async 结尾是Async函数因为在框架中会匹配这样规则的函数,然后调用该方法发送请求,然后存到redux中。
client 获取数据
获取客户端数据,和react api一样使用 在useEffect回调即可
function Profile() {
const [data, setData] = useState(null)
const [isLoading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
fetch('/api/profile-data')
.then((res) => res.json())
.then((data) => {
setData(data)
setLoading(false)
})
}, [])
if (isLoading) return <p>Loading...</p>
if (!data) return <p>No profile data</p>
return (
<div>
<h1>{data.name}</h1>
<p>{data.bio}</p>
</div>
)
}
css支持
本框架已经配置了 css 与 less,如果需要sass可以在webpack --> config 的配置文件中添加 sass loader即可。
环境变量
.env 文件 公共环境变量配置
.env.development 文件 开发环境变量配置
.env.production 文件 生产环境变量配置
如果需要其他环境 可自行添加 .env.xxxx 文件 然后在运行shell脚本的时候 dotenv_config_path=.env.xxxx 即可。比如:
"cross-env target='ssr' npx babel-node -r @babel/register ./dist/server/index.js -r dotenv/config dotenv_config_path=.env.xxxx ",
如果你对你有用,请移动你的手指,为我点击starred。谢谢你! git 地址 :github.com/qq281113270…