基于脚手架定制React项目

暴露 react-scripts 配置

create-react-app react-app --typescript

我们需要进行二次配置,所以我们需要暴露配置文件:

yarn eject

运行后的目录如下:

项目目录

配置 css 预处理语言

个人比较喜欢使用 scss,react 脚手架默认配置了 scss 环境,scss 提供的功能比较强大。但是有时候需要使用轻便的 less,所以下面演示配置 less 环境。

  • 安装 less 与 less-loader

    yarn add less less-loader –dev

  • 添加 less 文件匹配规则:

    1
    2
    const lessRegex = /\.less$/;
    const lessModuleRegex = /\.module\.less$/;
  • 添加处理规则:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    rules: [
    /* 省略代码 */
    {
    oneOf: [
    //......
    {
    test: lessRegex,
    exclude: lessModuleRegex,
    use: getStyleLoaders(
    {
    importLoaders: 1, // 只需要引入一个loader
    sourceMap: isEnvProduction && shouldUseSourceMap,
    },
    "less-loader"
    ),
    sideEffects: true,
    },
    {
    test: lessModuleRegex,
    use: getStyleLoaders(
    {
    importLoaders: 1,
    sourceMap: isEnvProduction && shouldUseSourceMap,
    modules: {
    getLocalIdent: getCSSModuleLocalIdent,
    },
    },
    "less-loader"
    ),
    },
    ]

    src/react-app-env.d.ts声明一下模块,防止Cannot find module './xxx.module.less'错误

    1
    2
    3
    4
    declare module '*.less' {
    const styles: any;
    export = styles;
    }

    这样在项目中可以直接使用 less 模块了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import React, { useEffect } from "react";
    import styles from "./less/index.module.less";

    function App() {
    useEffect(() => {
    console.log(styles);
    }, []);

    return (
    <div className={styles.father}>
    <div className={styles.son}></div>
    </div>
    );
    }

    export default App;

    定义别名

定义常用的目录别名:

1
2
3
4
5
6
alias: {
'@': path.join(__dirname, '../src/'),
'@components': path.join(__dirname, '../src/components'),
'@images': path.join(__dirname, '../src/images'),
// ......
}

使用 tslint 与 stylelint 统一风格

  • tslint:用来约束项目的逻辑代码风格,可以通过在根目录添加.tslint.json进行配置,可以直接引入tslint-react,其他规则自己添加:

    1
    2
    3
    4
    5
    6
    {
    "extends": ["tslint-react"],
    "rules": {
    /* 自定义规则 */
    }
    }
  • stylelint: 约束项目样式的代码风格:

    1
    2
    3
    4
    5
    6
    {
    "extends": "stylelint-config-standard",
    "rules": {
    /* 自定义规则 */
    }
    }

    封装 axios 请求

如果需要跨域调试接口,可以在 package.json 设置代理:

"proxy": "http://127.0.0.1:3000"

引如 axios 包:

yarn add axios

配置封装 axios,此处 po 上我的常用配置与封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import axios from "axios";
/** 设置axios的默认配置 */
// axios.defaults.baseURL = "http://127.0.0.1:3000/api/"; //如果使用了proxy代理,这里不需要配置
axios.defaults.timeout = 6000;
// 超时重连的解决方案
axios.interceptors.response.use(undefined, function (error) {
const config = error.config;
config._count = config._count || 0;
if (++config._count > 4) {
return Promise.reject(error);
}
const handlePromise = new Promise(function (resolve) {
setTimeout(() => {
resolve();
}, config._count * 1000 || 1000); //超时重连间隔+1s
});

return handlePromise.then(function () {
return axios(config);
});
});

type HttpMethods = "GET" | "POST";
interface RequestParams {
method?: HttpMethods;
data?: object;
query?: object;
}

/**
* 发送GET与POST的异步请求,
* 使用柯里化分离请求地址和发送数据,
* 根据query与data确认请求方法,query是get请求,data是post请求
* 请求示例:require(url)({})/require(url)({query:{id:1}})/require(url)({data:{id:1}})
* @param {string} url 请求url
* @return {function} 请求配置函数
*/
let request = (url: string) => {
url = url && url.length !== 0 ? url : "/";
return (params: RequestParams) => {
const method = params.data ? "POST" : "GET";
let query = null;
let data = null;
switch (method) {
case "GET":
query = method === "GET" ? (params.query ? params.query : {}) : null;
break;
case "POST":
data = method === "POST" ? (params.data ? params.data : {}) : null;
break;
}
const config: any = {};
config["url"] = url;
config["method"] = method;
data && (config["data"] = data);
query && (config["params"] = query);
return new Promise((resolve, reject) => {
axios(config)
.then((res) => {
resolve(res.data);
})
.catch((err) => {
reject(err);
});
});
};
};

export { request };

也可以使用 fetch 请求,可以自己做一下封装。

按需引入 react-router

react-router 总共有react-routerreact-router-domreact-router-native,三个包。若是 web 开发则只需要使用 react-router-dom 包。

1
2
yarn add react-router-dom
yarn add @types/react-router-dom -D

react-loadable

为什么引入 react-loadable?

SPA 因为需要引入大量 js 脚本进行渲染,所以首屏渲染极慢,为了解决这个问题,我们需要对代码进行分割(使用懒加载动态 import()进行代码拆分),但是我们需要对这个加载状态进行控制,所以使用了 react-loadable。

下载:

yarn add react-loadable

如果使用 ts 则下载:

yarn add @types/react-loadable --dev

路由配置

react-router 是离散式路由,显然不如集中式路由方便管理配置,所以我们封装一下集中式管理 route。

touch src/routes/index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React from "react";
import Loadable from "react-loadable";
import Loading from "../components/Loading"; // 自定义Loading显示组件
import Error from "./components/Error"; // 自定义错误显示组件

const asyncLoad = (loader: () => Promise<any>) => {
return Loadable({
loader,
loading: (props) => {
if (props.pastDelay) {
return <Loading />;
} else if (props.error) {
return <Error />;
} else {
return null;
}
},
delay: 400,
});
};

const generateRouteConfig = (routes: IRouteConfig[]) => {
return routes.map((_item) => {
return {
key: _item.path,
exact: typeof _item.exact === "undefined" ? true : _item.exact,
component: _item.component,
..._item,
};
});
};

const routeConfig: any = [
{
path: "/",
component: asyncLoad(() => import(/*你的组件*/)),
},
{
path: "/exception",
component: asyncLoad(() => import(/*你的组件*/)),
},
/**
* // TODO
*/
];

export default generateRouteConfig(routeConfig);

使用路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from ‘react';
import {Router} from 'react-router';
import routes from './routes';

const App = () => {
return (
<Router>
<Switch>
{routes.map(({path, component, exact} => (
<Route key={path} path={path} component={component} exact={exact} />
)))}
</Switch>
</Router>
)
}

// 其他的随着使用再加…….

查看评论