上一节引入了 redux 以及使用 redux-saga 来进行异步函数的处理, 而上一节的目录只是简单的引入 redux 而已, redux 可是相当庞大和复杂的, 并且也算是个人习惯了吧. action 分离, reducer 分离, 状态组件 container 等等. 我喜欢把这些东西划分的清清楚楚, 这样一个项目维护起来才会方便~这一节就从头到尾来进行目录的划分, 因为 Next.JS 和原本的 React SPA 项目有一定的区别, 主要体现在路由部分, 所以我也是按照自己的理解和舒服的方式进行目录重构!
重构完的目录
- // ================ 目录结构 ================== //
- ------
- | -- asserts // ant-design 全局 Less 变量设置文件夹
- | -- components // React 展示组件 (也就是 UI 组件) 文件夹
- | -- constants // 整个应用的常量文件夹
- | -- ActionsTypes.JS // 存放所有 action type 的常量文件
- | -- ApiUrlForBE.JS // 存放所有后端数据的 apiUrl
- | -- ...
- | -- containers // React 状态组件文件夹
- | -- pages // Next.JS 路由文件夹
- | -- redux
- | -- actions // 处理整个应用所有的 action
- | -- middlewares // 中间件, 处理各种特殊情况, 比如获取失败之后的 message 提醒
- | -- reducers // 处理整个应用所有的 reducer
- | -- sagas // 处理整个应用所有的 saga
- | -- store.JS
- | -- static // 存放整个应用所有的静态资源(如图片等)
- | -- .babelrc
- | -- .eslintrc
- | -- .gitignore
- | -- next.config.JS // Next.JS 配置文件
- | -- package.JSON
- | -- server.JS // 服务端 server 文件
- | ...
原谅我臭不要脸一下, 个人认为这个结构还是非常清晰的, 只不过可能新手写起来可能会觉得有些繁琐, 不过项目大的情况下, state 树很大, 这种结构非常的清晰~
重构 actions
其实 actions 完全可以放在一个文件里使用, 不过项目庞大了以后维护起来还是有些麻烦的, 所以按照组件化思想, 每一个组件对应一个 action, 或者每一个大功能块对应一个 action 还是比较合理的.
- -- redux
- | -- actions
- | -- home.JS // 处理首页 action
- | -- user.JS // 处理与用户有关 action
- | ... // 其他 action
重构 reducers
reducer 部分肯定是要分离的, 因为 redux 的官方为我们提供 combineReducer 这个 API 就是合并不同组件的 reducer 的, 所以可以理解为 redux 的 reducer 推荐就是根据组件进行划分的~就如同整个应用只有一个状态树一样, 每一个 reducer 负责处理树的不同枝叶派发出来的 action. 具体 reducer 内容还是去看 redux 官方文档吧.
重构 sagas
- -- redux
- | -- reducers
- | -- home // 首页部分 reducer
- | -- user // 用户相关 reducer
- | ... // 其他 reducer
- | index.JS // rootReducer, 由 combineReducer 生成
抽离 container
这里需要特别说明一下~~~由于 Next.JS 的特殊原因, 其实已经做到了 UI 组件的分离, 其实这一层 container 完全可以由 pages 文件夹代替, 也就是可以用路由组件通过 react-redux 的 connect 函数封装一下, 这样就变成了一个带状态的路由组件, 不知道大家明不明白我说的话... 下面是两种方法, 大家按需自己采取, 以 UserList 组件为例:
第一种, 抽离 container
- // /conatiners/user/UserList.JS
- import { connect } from 'react-redux';
- import { fetchUserListData } from '../../redux/actions/user';
- import UserList from '../../components/User/UserList';
- const mapStateToProps = state => ({
- list: state.user.list.list,
- });
- const mapDispatchToProps = dispatch => ({
- fetchUserListData() {
- dispatch(fetchUserListData());
- }
- });
- export default connect(mapStateToProps, mapDispatchToProps)(UserList);
- // pages/user/userList.JS
- import UserList from '../../containers/user/UserList';
- import { fetchUserListData } from '../../redux/actions/user';
- // 这部分内容下一章节讲~
- UserList.getInitialProps = async (props) => {
- const { store, isServer } = props.ctx;
- if (store.getState().user.list.list.length === 0) {
- store.dispatch(fetchUserListData());
- }
- return { isServer };
- };
- export default UserList;
简单来说其实就是路由组件导入的是状态组建 UserList.JS, 而状态组建是通过 react-redux 的 connect 方法封装 UI 组件 UserList.JS 而得来的.
第二种, 带状态的路由组件
- // /pages/user/userList.JS
- import { connect } from 'react-redux';
- import UserList from '../../containers/user/UserList';
- import { fetchUserListData } from '../../redux/actions/user';
- UserList.getInitialProps = async (props) => {
- const { store, isServer } = props.ctx;
- if (store.getState().user.list.list.length === 0) {
- store.dispatch(fetchUserListData());
- }
- return { isServer };
- };
- const mapStateToProps = state => ({
- list: state.user.list.list,
- });
- const mapDispatchToProps = dispatch => ({
- fetchUserListData() {
- dispatch(fetchUserListData());
- }
- });
- export default connect(mapStateToProps, mapDispatchToProps)(UserList);
简单来说, 就是在路由组件内把 UI 组件 UserList.JS 通过 connect 变成了状态组件.
个人推荐第一种方法, 虽然写起来稍微麻烦了一些, 但是第二种方法完全是因为 Next.JS 的特殊性才能实现的, 当然, 对于 Next.JS 来说, 第二种方式确实更简单一些~
结束语
经历了上面几个部分的重构, 整个基于 Next.JS 的服务端渲染脚手架基本结构也就成型了. 在搭建过程中还是遇到了很多坑的, 不过也都一点点的踩过去了. 希望对大家有些帮助, 个人认为这个结构还是值得参考一下的~原本到这里就可以结束系列文章了, 不过我在使用过程又发现了一些坑, 顺便的 Next.JS 还有一些内容我还没碰过, 就帮大家都踩一踩, 下一节来一个其他内容的大杂烩~
代码地址
来源: https://juejin.im/post/5bbb22325188255c451ec451