搭建一个 react spa 客户端渲染项目,通过不同分支区分项目技术方案的演进
分支
master
原始模板rematch
使用 rematch 代替 redux,并重新组织目录结构useContext
使用 hook context 代替 reduxnode
引入 node 代理层graphql
引入 graphql 代替 restful 接口,包含客户端和服务端的配置node-awilix
使用 awilix ioc 框架node-inversify
使用 inversify 框架
- 将 npm 的源设置成淘宝镜像,使用 npm 安装项目固定版本的依赖(因为有 package-lock.json )
- 执行
npm run dll
生成 dll 缓存文件 - 执行
npm run dev
启动项目 - 执行
npm run type-check
开启 TS 类型检测
React Hook 函数式编程 + React Lazy 懒加载
- 项目中引入了
Normalize.css
- 项目默认支持 CSS 模块化
方案 1:(xxx.module.css/xxx.module.less/.xxx.module.scss 这些文件会默认 CSS 模块化 处理)
方案 2:简化 CSS 模块化配置
import 'swiper/dist/css/swiper.css';
import style from './index.less?css-modules';
- 构建项目时会自动兼容 CSS3 样式,所以不需要自己去写浏览器兼容样式,引入 postcss
- react-router 项目支持配置式路由
- connected-react-router 使用 redux 管理路由
- React Lazy 懒加载
项目使用按照路由模块划分组织 reducer,公共部分使用
- 项目中针对
axios
做了二次封装
接口请求、跨域问题的处理
开发:webpack-dev-server 使用 proxy 代理 生产:node 做 API 网关:你可以使用中间件代理请求或者在 node 层使用发起新的请求
rematch
分支集成了rematch
,简化redux
的使用,并且使用 react-redux 的 hook 版本 useDispacth 和 useSelectoruseContext
分支使用 context+useContext+useReducer 代替 reduxgraphql
利用 GraphQL 代替 Restful 风格的 API 查询:不同端、准确返回、一次请求
react hook 代替 redux 的示例
import React, { createContext, useContext, useReducer } from 'react';
import ReactDOM from 'react-dom';
// 这里我都写在一个文件里面了,实际项目中,Context肯定单独抽象出来
// 所有用到的地方再import进来
const Context = createContext();
const A = () => {
const { dispatch, state } = useContext(Context);
return (
<>
<button
disabled={state.loading}
onClick={() => {
dispatch({
type: 'click_async',
//触发异步action,注意payload里面是一个异步获取方法
payload: asyncFetch(new Date().getTime()),
});
}}
>
click async
</button>
<button
disabled={state.loading}
onClick={() => {
dispatch({
type: 'click_sync',
payload: new Date().getTime(),
});
}}
>
click sync
</button>
<pre>{JSON.stringify(state, null, 2)}</pre>
</>
);
};
function reducer(state, action) {
switch (action.type) {
case 'click_async':
case 'click_sync':
return { ...state, value: action.payload };
case 'loading_start':
return { ...state, loading: true };
case 'loading_end':
return { ...state, loading: false };
default:
throw new Error();
}
}
function isPromise(obj) {
return (
!!obj &&
(typeof obj === 'object' || typeof obj === 'function') &&
typeof obj.then === 'function'
);
}
// 这里是mock了一个异步方法,1秒后才会返回结果,模拟请求数据
async function asyncFetch(p) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(p);
}, 1000);
});
}
/*这里对dispatch函数进行一个封装,使其支持处理异步action
简而言之就是判断传进来的action是不是Promise对象,如果是的话
先执行loading_start,将loading置为true
然后执行完成Promise后,将获得的结果执行一次action
再执行loading_end(实际项目中请求失败也应该执行loading_end,因项目而异,不展开了)
注意:这个loading是我项目中喜欢用的一个标志位,用来记录当前是不是处于请求中
因为经常需要有如果在请求中,按钮需要禁用,防止用户再点击这种需求
另外实际项目中,loading可以扩展成对象,记录各种异步请求的状态
这个灵感来源于dva-loading,感谢*/
function wrapperDispatch(dispatch) {
return function (action) {
if (isPromise(action.payload)) {
dispatch({ type: 'loading_start' });
action.payload.then((v) => {
dispatch({ type: action.type, payload: v });
dispatch({ type: 'loading_end' });
});
} else {
dispatch(action);
}
};
}
function App() {
const [state, dispatch] = useReducer(reducer, { value: 0, loading: false });
return (
<>
{/* 这里把state和包装过的dispatch绑定到Context上去,便于全局获取使用 */}
<Context.Provider value={{ state, dispatch: wrapperDispatch(dispatch) }}>
<A />
</Context.Provider>
</>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
- 项目使用
Eslint + Prettier
统一代码风格 - 项目使用
husky
在git commit
提交代码前,进行代码风格校验并修复
- 项目中默认配置了一些常用工具函数
- webpack 优化
- docker 容器一键部署
- 公司框架平台
- webpack.dll.config.js
只用在开发环境,缓存模块提高打包速度,只存放一些不会经常变动的第三方库,每次引入新的第三方库,都要先运行 npm run dll
脚本构建缓存包。对于新版本的 Webpack 来说,dll 缓存依赖提升的速度不大明显。
-
什么时候需要代码分割
- 单页面的话:如果不做异步加载,那么是没有必要拆分 chunk 的
- 多页面或者要做异步加载时,那就需要拆分 chunk