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;
}
}
複製代碼