数据通信
添加 store Action
js
export const actionTypes = {
setDatas: "movies/setDatas",
fetchMovies: "movies/fetchMovies",
};
export function setDatas(datas) {
return { type: actionTypes.setDatas, payload: datas };
}
export function fetchMovies(page = 1, limit = 10) {
return async function (dispatch) {
const resp = await fetch("/api/getArticleList").then((res) => res.json());
dispatch(setDatas(resp.data));
};
}添加 store Reducer
js
import { actionTypes } from "../actions/movies";
export default function (state = [], { type, payload }) {
switch (type) {
case actionTypes.setDatas:
return payload;
default:
return state;
}
}添加 store
js
import { createStore, compose, applyMiddleware } from "redux";
import reducer from "./reducers";
import thunk from "redux-thunk";
let store;
store = createStore(reducer, compose(applyMiddleware(thunk)));
export default store;页面组件
jsx
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { fetchMovies } from "../../store/actions/movies";
//类组件:componentWillMount 服务端运行
//类组件:componentDidMount 服务端不运行
function Page({ movies = [], loadMovies }) {
useEffect(() => {
loadMovies && loadMovies();
}, []);
return (
<div>
<h1>电影列表</h1>
<ul>
{movies.map((m) => (
<li key={m._id}>{m.name}</li>
))}
</ul>
</div>
);
}
function mapStateToProps(state) {
return {
movies: state.movies,
};
}
function mapDispatchToProps(dispatch) {
return {
loadMovies() {
dispatch(fetchMovies());
},
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Page);服务器端加载数据
render.js 渲染前加载数据
js
// src/server/render.js
import loadData from "./loadData";
export default async (req, res) => {
const context = {};
//加载数据到仓库
//调用对应组件(根据路由匹配到的组件)的loadData
await loadData(req.path);
//渲染组件
const componentHTML = ReactDom.renderToString(
<App location={req.path} context={context} />
);
const html = getHTML(componentHTML, req.path);
res.send(html);
};loadData.js 加载数据
js
// src/server/loadData.js
import { matchRoutes } from "react-router-config";
import routeConfig from "../routes/routeConfig";
import store from "../store";
/**
* 负责服务端渲染前的加载
*/
export default function (pathname) {
const matches = matchRoutes(routeConfig, pathname);
const proms = [];
for (const match of matches) {
if (match.route.component.loadData) {
proms.push(Promise.resolve(match.route.component.loadData(store)));
}
}
return Promise.all(proms);
}对应页面组件添加loadData方法
jsx
// src/pages/Movies/index.js
function Page({ movies = [], loadMovies }) {
useEffect(() => {
// 如果服务器处理了数据,则什么也不做
// 如果服务器没有处理数据,则需要加载数据
if (window.requestPath === "/movies") {
//不需要加载数据
console.log("不需要加载数据");
return;
} else {
console.log("加载数据");
loadMovies && loadMovies();
}
}, []);
return (
<div>
<h1>电影列表</h1>
<ul>
{movies.map((m) => (
<li key={m._id}>{m.name}</li>
))}
</ul>
</div>
);
}
// 在组件服务端渲染之前需要运行的函数
Page.loadData = async function (store) {
await store.dispatch(fetchMovies());
};store.js 注入window.pageDatas
js
import { createStore, compose, applyMiddleware } from "redux";
import reducer from "./reducers";
import thunk from "redux-thunk";
let store;
if (globalThis.document) {
store = createStore(
reducer,
window.pageDatas,
compose(applyMiddleware(thunk))
);
} else {
store = createStore(reducer, compose(applyMiddleware(thunk)));
}
export default store;页面模板文件添加
js
import store from "../store";
export default (componentHTML, path) => {
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>SSR</title>
${getLinks()}
</head>
<body>
<div id="root">${componentHTML}</div>
<script>
window.pageDatas = ${JSON.stringify(store.getState())}
window.requestPath = "${path}";
</script>
${getScripts()}
</body>
</html>
`;
return html;
};模拟请求接口
js
const express = require("express");
const app = express();
app.get("/getArticleList", (req, res) => {
console.log("getArticleList");
res.send({
code: 0,
data: [
{
_id: 1,
name: "react",
},
{
_id: 2,
name: "vue",
},
],
msg: "success",
});
});
app.listen(8081, () => {
console.log("service start on 8081");
});
``;服务端数据共享
服务端数据共享,即在服务端渲染时,将数据传递给客户端,客户端再进行渲染。
store.js 调整
js
import { createStore, compose, applyMiddleware } from "redux";
import reducer from "./reducers";
import thunk from "redux-thunk";
function makeStore() {
let store;
if (globalThis.document) {
store = createStore(
reducer,
window.pageDatas,
compose(applyMiddleware(thunk))
);
} else {
store = createStore(reducer, compose(applyMiddleware(thunk)));
}
return store;
}
export default makeStore;App.jsx 调整
jsx
// server/App.jsx
export default ({ location, context, store }) => {
return (
<Provider store={store}>
<StaticRouter location={location} context={context}>
<RouteApp />
</StaticRouter>
</Provider>
);
};
// client/App.jsx
import makeStore from "@/store";
const store = makeStore();
export default () => {
return (
<Provider store={store}>
<BrowserRouter>
<RouteApp />
</BrowserRouter>
</Provider>
);
};render.js
js
// server/render.js
import makeStore from "../store";
export default async (req, res) => {
const store = makeStore();
// 加载数据到仓库
const context = {};
//加载数据到仓库
//调用对应组件(根据路由匹配到的组件)的loadData
await loadData(req.path, store);
const componentHTML = ReactDom.renderToString(
<App location={req.path} context={context} store={store} />
);
res.send(getHtmlTemplate(componentHTML, req.path, store));
};