学习笔记(同构应用之router, redux)

学习链接

從零開始實作 SSR — Redux 篇
從零開始實作 SSR — React-router 篇

需求:在 webpack实现ssr打包 的基础上,实现路由功能,状态管理

Router

功能

  1. 根据不同的 请求URL,后端生成不同的页面字符串返回
  2. 首次渲染后,以 SPA 应用的方式管理路由

实现

使用 react-router-dom ,服务端使用 StaticRouter 渲染 对应路由 的页面。前端在首次渲染后,再次渲染时使用 BrowserRouter

  • 同构应用的路由组件
// src/client/Routes.js
import React from "react";
import { Route } from "react-router-dom";
import Home from "./components/Home";
import UserList from "./components/UserList";

const Routes = () => {
  return (
    <div>
      <Route exact path="/" component={Home} />
      <Route path="/users" component={UserList} />
    </div>
  );
};

export default Routes;
  • 页面组件
// src/client/components/Home.js
import React from "react";
import { Link } from "react-router-dom";

const Home = () => {
  return (
    <div>
      <div>Homepage</div>
      <Link to="/users">To user page</Link>
      <button onClick={() => console.log("click me")}>click me</button>
    </div>
  );
};

export default Home;


// src/client/components/UserList.js
import React from "react";
import { Link } from "react-router-dom";

const UserList = () => {
  return (
    <div>
      <div>User List</div>
      <Link to="/">To home page</Link>
    </div>
  );
};

export default UserList;
  • express 根据 url 的 path,返回对应组件的 字符串
//...
app.get("*", (req, res) => {
  const content = renderToString(
    <StaticRouter location={req.path}>
      <Routes />
    </StaticRouter>
  );

  const html = `
    <html>
      <head></head>
      <body>
        <div id="root">${content}</div>
        <script src="bundle.js"></script>
      </body>
    </html>
  `;

  res.send(html);
});
  • 客户端,使用 BrowserRouter 路由的方式(SPA),接手后端渲染
// ...
ReactDOM.render(
  <BrowserRouter>
    <Routes />
  </BrowserRouter>,
  document.getElementById("root")
);

Redux

核心:类似路由的实现,在前后端分别添加 reducer, action

核心库:redux react-redux redux-thunk axios

配置及实现

babel

  • 解析 jsx
  • 解析 async 语法(需添加 @babel/polyfill 依赖)
{
  "presets": [
      "@babel/preset-react",
      ["@babel/preset-env", {
        "useBuiltIns":"usage"
      }]
  ]
}
  • 初始化 store
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import userReducer from "./users/users.reducer";

const store = createStore(userReducer, applyMiddleware(thunk));

export default store;
  • action
import axios from "axios";

export const FETCH_USERS = "FETCH_USERS";

export const fetchUsers = () => async (dispatch) => {
  const res = await axios.get("https://reqres.in/api/users?page=2");

  dispatch({
    type: FETCH_USERS,
    payload: res.data.data,
  });
};
  • reducer
import { FETCH_USERS } from "./users.actions";

const reducer = (state = { users: [] }, action) => {
  switch (action.type) {
    case FETCH_USERS:
      return {
        ...state,
        users: action.payload,
      };
    default:
      return state;
  }
};

export default reducer;
  • client 端注入 store
//...
ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <Routes />
    </BrowserRouter>
  </Provider>,
  document.getElementById("root")
);
  • 服务器端 注入 store
// ...
app.get("*", (req, res) => {
  const content = renderToString(
    <Provider store={store}>
      <StaticRouter location={req.path}>
        <Routes />
      </StaticRouter>
    </Provider>
  );
  // ...
});
  • UserList 页面使用 store
    问题:该组件 mount 后,才请求 列表数据,导致服务端渲染没列表数据
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { Link } from "react-router-dom";
import { fetchUsers } from "../redux/users/users.actions";

const UserList = () => {
  const dispatch = useDispatch();
  const users = useSelector((state) => state.users);
  useEffect(() => {
    dispatch(fetchUsers());
  }, []);

  return (
    <div>
      <div>User List</div>
      <Link to="/">To home page</Link>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.first_name} </li>
        ))}
      </ul>
    </div>
  );
};

export default UserList;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值