react-scripts
,这种作法保证了底层依赖版本升级和迁移的时候,能够平滑迁移,不会影响到业务项目。react-app-rewired
使用或yarn eject
暴露 相关配置后进行修改)。npm
命令全局安装脚手架:npm install -g create-react-app
复制代码
create-react-app my-app
复制代码
create-react-app my-app --typescript
复制代码
本节主要是介绍react-scripts一些相关内容,若是只对配置感兴趣的同窗能够跳过本节。css
前面介绍到,create-react-app 在 webpack 上封装了一层 react-scripts
,一方面是可使得不习惯 eslint,babel 和 webpack 的新手只需关注于组件的编写,另外一方面是能够不断的更新和改进默认选项,而不会影响到业务代码。html
可见,react-scripts
的做用就是经过将一些底层配置封装起来,从而向上屏蔽了众多细节,使得业务开发者只需关注业务代码的开发。node
去到项目 node_modules 目录下,能够看到 create-react-app + typescript 里的react-scripts
的目录结构以下:react
若是要修改这些配置有三种办法:webpack
(1)经过 react-app-rewired
覆盖默认的 webpack 配置。git
(2)fork 对应的 react-scripts
包, 本身维护这个依赖包。github
(3)直接 eject 出整个配置在业务项目里维护。该操做的缺点是不可逆,一旦配置文件暴露后就不可再隐藏。web
因为本人技术尚浅,本人采用第三种方案。typescript
首先,进入项目目录:npm
cd my-app
复制代码
react-scripts
包:yarn eject
yarn run v1.17.3
$ react-scripts eject
NOTE: Create React App 2+ supports TypeScript, Sass, CSS Modules and more without ejecting: https://reactjs.org/blog/2018/10/01/create-react-app-v2.html
? Are you sure you want to eject? This action is permanent. (y/N)
# 输入 y 便可
复制代码
react-scripts
包。而若是先安装了其余依赖或改动项目其余内容以后,再使用 yarn eject
命令时就会报错:This git repository has untracked files or uncommitted changes:
Remove untracked files, stash or commit any changes, and try again.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! XXX@0.1.0 eject: `react-scripts eject`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the XXX@0.1.0 eject script.
npm ERR! This is probably not a problem with npm. There is likely additional log
ging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\Administrator\AppData\Roaming\npm-cache\_logs\2019-8-1T0
3_18_15_677Z-debug.log
复制代码
git add .
git commit -am "init"
yarn eject
复制代码
成功 eject 出配置后,能够发现项目目录的变化以下:
若是须要定制化项目,通常就是在config目录下对默认的 webpack 进行修改。
less
、添加 tslint
和 stylelint
、引入 react-router
、封装 fetch
请求、引入 react-loadable
和按需加载 antd
。less
和 less-loader
:yarn add less less-loader –dev
复制代码
less
配置变量:const lessRegex = /\.less$/; // 新增less配置
const lessModuleRegex = /\.module\.less$/; // 新增less配置
复制代码
module: {
strictExportPresence: true,
rules: [
/* 省略代码 */
{
oneOf: [
/* 省略代码 */
/* 下面是原有代码块 */
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
}),
},
/* 上面是原有代码块 */
/* 下面是添加代码块 */
{
test: lessRegex,
exclude: lessModuleRegex,
use: getStyleLoaders({
importLoaders: 1,// 值是1
sourceMap: isEnvProduction && shouldUseSourceMap
},
"less-loader"
),
sideEffects: true
},
{
test: lessModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true, // 增长这个能够经过模块方式来访问less
getLocalIdent: getCSSModuleLocalIdent
},
"less-loader"
)
},
/* 上面是添加代码块 */
/* 下面是原有代码块 */
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
sideEffects: true,
},
/* 上面是原有代码块 */
],
},
],
},
复制代码
less
(以xx.module.less命名的文件) 而且和全局 less
(以xx.less命名的文件)区分开。import * as styles from ./index.module.less
复制代码
less
,还须要在 src/react-app-env.d.ts 文件中进行配置,不然ts会发生报 错 Cannot find module './index.module.less'
,配置内容以下:declare module '*.less' {
const styles: any;
export = styles;
}
复制代码
less
在 react + typescript 项目中的引入。# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
复制代码
"extends": ["tslint-react"]
, 若是有特殊规则也能够本身加。内容示例:{
"extends": ["tslint-react"],
"rules": {
/* 本身添加的特殊规则 */
}
}
复制代码
"extends": ["stylelint-config-standard"]
, 若是有特殊规则也能够本身加。内容示例:{
"extends": "stylelint-config-standard",
"rules": {
/* 本身添加的特殊规则 */
}
}
复制代码
tslint
和 stylelint
的添加。yarn add react-router
吗,还须要教?其实否则,看👇面:react-router
,react-router-dom
,react-router-native
。react-router
,这个包为 React Router 应用提供了核心的路由组件和函数,另外两个包提供了特定环境的组件(浏览器和 react-native
对应的平台),不过他们也是将 react-router
导出的模块再次导出。react-router-dom
。同时,因为项目中我还使用了typescript,因此还要安装@types/react-router-dom
。安装命令:yarn add react-router-dom
yarn add @types/react-router-dom --dev
复制代码
react-router
的引入。若是只是简单的请求,不必引入 aixos
,经过将fetch请求的相关代码封装在request.js/request.ts文件中,在使用的时候引入相关请求方法便可,好处有几点:
请求的地方代码更少。
公共的错误统一在一个地方添加便可。
请求定制的错误仍是请求本身也能够处理。
扩展性好,添加功能只须要改一个地方。
下面给出我在项目中封装的 request.ts 文件具体内容:
// path:src/utils/request.ts
const request = (url: string, config: any) => {
return fetch(url, config)
.then((res: any) => {
if (!res.ok) {
// 服务器异常返回
throw Error('接口请求异常');
}
return res.json();
})
.catch((error: any) => {
return Promise.reject(error);
});
};
// GET请求
export const get = (url: string) => {
return request(url, { method: 'GET' });
};
// POST请求
export const post = (url: string, data: any) => {
return request(url, {
body: JSON.stringify(data),
headers: {
'content-type': 'application/json',
},
method: 'POST',
});
};
复制代码
// path:src/services/api/list.ts
import * as Fetch from '../../utils/request';
export async function getListData () {
return Fetch.get('URL1');
}
export async function getListItemDetail (id: number) {
return Fetch.get(
`URL2/${id}`,
);
}
复制代码
// path:src/services/api.ts
export * from './api/list';
复制代码
// path:src/components/xxx.tsx
import React from 'react';
import * as api from '../../services/api';
class HomePage extends React.Component<any> {
/* 省略代码 */
async loadListData () {
try {
const res = await api.getListData();
this.setState({
listData: res.data.list,
});
} catch (error) {
// do something
}
}
/* 省略代码 */
}
export default HomePage;
复制代码
fetch
请求的封装。yarn build
打包项目时, create-react-app 将生成一个大文件,它包含咱们的应用程序所需的全部JavaScript。可是,若是用户只是加载登陆页面进行登陆;咱们用它加载应用程序的其他部分是没有意义的。react-loadable
,同时,因为项目中我还使用了typescript,因此还要安装@types/react-loadable
。安装命令:yarn add react-loadable
yarn add @types/react-loadable --dev
复制代码
routeData
引入使用便可:// path:src/App.tsx
import { createHashHistory } from 'history';
import React from 'react';
import { Router } from 'react-router';
import routeData from './common/route';
const history = createHashHistory();
const App: React.FC = () => {
return (
<Router history={history}>
<Switch>
{routeData.map(({ path, component, exact }: IRouterItem) => (
<Route key={path} path={path} component={component} exact={exact} />
))}
<Route component={NotFound} />
</Switch>
</Router>
);
};
export default App;
复制代码
// path:src/common/route.tsx
import * as React from 'react';
import Loadable from 'react-loadable';
import Loading from '../components/Loading';
const routeConfig: any = [
{
path: '/',
component: asyncLoad(() => import('../views/HomePage')),
},
{
path: '/detail/:id',
component: asyncLoad(() => import('../views/DetailPage')),
},
/**
* Exception 页面
*/
{
path: '/exception/404',
component: asyncLoad(() => import('../views/Exception')),
},
];
function generateRouteConfig (route: IRouteConfig[]) {
return route.map(item => {
return {
key: item.path,
exact: typeof item.exact === 'undefined' ? true : item.exact,
...item,
component: item.component,
};
});
}
function asyncLoad (loader: () => Promise<any>) {
return Loadable({
loader,
loading: props => {
if (props.pastDelay) {
return <Loading />;
} else {
return null;
}
},
delay: 500,
});
}
export default generateRouteConfig(routeConfig);
复制代码
babel-plugin-import
来进行按需加载。下面介绍具体步骤。antd
:yarn add antd
复制代码
babel-plugin-import
:yarn add babel-plugin-import --dev
复制代码
module: {
strictExportPresence: true,
rules: [
/* 省略代码 */
{
oneOf: [
/* 省略代码 */
/* 下面是原有代码块 */
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent: '@svgr/webpack?-svgo,+ref![path]',
},
},
},
],
/* 上面是原有代码块 */
/* 下面是添加代码块 */
[
'import',{ // 导入一个插件
libraryName: 'antd', // 暴露的库名
style: 'css' // 直接将antd样式文件动态编译成行内样式插入,就不须要每次都导入
}
],
],
/* 上面是添加代码块 */
/* 下面是原有代码块 */
cacheDirectory: true,
cacheCompression: isEnvProduction,
compact: isEnvProduction,
/* 上面是原有代码块 */
},
},
],
},
],
},
复制代码
import { Button } from 'antd';
复制代码
less
,若是要在模块内修改 antd 组件的样式,须要使用:global
,如::global {
.ant-divider {
margin: 0 0;
}
}
复制代码