前言

记录一次通过 create-react-app 搭建一个 react 项目的过程,坑一个接着一个~

1. 创建项目

1
npx create-react-app my-app --typescript

2. 扩展 webpack 配置

需要用到 react-app-rewired customize-cra

1
yarn add react-app-rewired customize-cra -D

配置支持装饰器、别名、postcss

根目录下新建一个config-overrides.js文件,

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
/* config-overrides.js */
const {
override,
addDecoratorsLegacy,
addWebpackAlias,
addPostcssPlugins,
} = require('customize-cra')

const path = require('path')

function resolve(dir) {
return path.join(__dirname, '.', dir)
}

module.exports = override(
// 装饰器
addDecoratorsLegacy(),
// Alias
addWebpackAlias({
'@': resolve('src'),
'@comps': resolve('src/components'),
'@style': resolve('src/assets/style'),
'@images': resolve('src/assets/images'),
'@pages': resolve('src/pages'),
}),
// postcss
addPostcssPlugins([
require('postcss-px2rem')({ remUnit: 37.5 }),
require('autoprefixer'), // css 自动加前缀
require('postcss-autoreset')({
// 重置浏览器默认样式
reset: {
margin: 0,
padding: 0,
borderRadius: 0,
},
}),
])
)

3. 配置 api 代理

使用http-proxy-middleware

1
yarn add http-proxy-middleware -D

src 目录下新建一个setupProxy.js文件,名字只能叫这个,http-proxy-middleware 读配置只会从src/setupProxy.js这个地址去读

此处有坑,按照官网的配置会报错,心累;下面代码,亲测有效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { createProxyMiddleware } = require('http-proxy-middleware')

module.exports = function (app) {
app.use(
createProxyMiddleware('/api', {
target: 'http://xxx.xxx.xxx.xxx:xxxx/api',
ws: true,
changeOrigin: true,
pathRewrite: {
'^/api': '',
},
})
)
}

4. 配置 typescript

此处也有坑,每次项目重启tsconfig.json会被重置,配置的东西就会丢失!坑啊~
解决办法:新建一个tsconfig.path.json文件,把多余的配置写在这里,然后在tsconfig.json里面通过extends的方法导入,这样就不会被清除掉;

1
2
3
4
5
6
{
...
"extends": "./tsconfig.path.json",
...
}

1
2
3
4
5
6
7
8
9
10
11
12
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@pages/*": ["src/pages/*"],
"@style/*": ["src/assets/style/*"],
"@comps/*": ["src/components/*"],
"@asstes/*": ["src/asstes/*"]
}
}
}

5. 配置路由

1) 全局路由

注: 此处有一个小知识点

  1. Route 渲染组件有三个属性: componentrenderchildren, 渲染优先级为children > component > render,
  2. 只要有children就会优先渲染子组件,component组件是传入一个组件,底层会通过React.createElement的方法去创建组件,如果写成函数形式,则每次渲染都会重新渲染组件经历新组件的创建和废弃组件的卸载,而不是对组件的更新;
  3. render传入一个函数,可以更灵活的控制组件的渲染。

使用 react-router-dom包;直接上代码吧~

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
import React from 'react'
import {
BrowserRouter as Router,
Route,
Switch,
Redirect,
} from 'react-router-dom'

import NotFound from '@pages/noFound/noFound'
import Home from '@pages/home/home'
import User from '@pages/user/user'
import Loading from '@comps/loading'

const { Suspense } = React

const RenderRouter = () => {
return (
<Router>
<Suspense fallback={<Loading />}>
<Switch>
<Redirect path="/" exact to="/home"></Redirect>
<Route
path="/home"
exact
component={Home}
// render={(props)=>(<div>这是内联组件</div>)}
// children={(props)=><NotFound/>}
></Route>
<Route path="/user" exact component={User}></Route>
<Route component={NotFound}></Route>
</Switch>
</Suspense>
</Router>
)
}

export default RenderRouter

2)在页面中使用

在页面上使用useHistoryuseLocationuseRouteMatch 等hook可以进行路由跳转、拿到当前路由信息、传参等操作

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
const history = useHistory()
const location = useLocation()

useEffect((): void => {
// history信息
console.log(history)
// 当前路由信息
console.log(location)
}, [])

const handleToUser = (): void => {
// 路由跳转
history.push('user')
}

const handleBack = (): void => {
// 返回上一页
history.goBack()
}

const handleSetState = (): void => {
// 路由传参 方式一
history.push({ pathname: '/user', state: { test: 'aaa' } })
}

const handleSetMatch = (): void => {
// 路由传参 方式二 这种需要配合 Route组件的配置
// <Route path="/user/:id" render={(props:IRouterProps):ReactElement => <User {...props} />} ></Route>
history.push('/user/666')
}

user页面 拿到传参

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
import React, { useEffect, ReactElement } from 'react'
import { useRouteMatch, useLocation} from 'react-router-dom'
import style from '@style/modules/user.module.scss'

const User: React.FC<IRouterProps> = (props: IRouterProps): ReactElement => {

// 传参方式一 从location身上取参数
// const location:ILocationProps = useLocation()

// 传参方式二 利用useRouteMatch 取餐
const match:IMatchProps | null = useRouteMatch('/user/:id')


useEffect((): void => {
// console.log(location?.state?.id)

console.log(match?.params?.id)
}, [])

return (
<div>
<h2 className={style.home}>User</h2>
</div>
)
}

export default User

6. 配置 sass

  1. 安装node-sass;这里可能会存在版本问题,如遇报错回退一下node-sass版本就 ok;
  2. 把 css 文件后缀改为 scss 就可以开始开发了;

7. 配置 axios

  1. 安装axios包;
  2. 配置拦截器;
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
import axios from 'axios'
import { REQUEST } from '@/assets/constant'

axios.defaults.timeout = 500000
axios.defaults.baseURL = 'http://localhost:3000/api'

/**
* 请求拦截
*/
axios.interceptors.request.use(
(config) => {
// TODO 根据项目 请求前设置token等
return config
},
(err) => {}
)

/**
* 响应拦截
*/
axios.interceptors.response.use(
(response) => {
// TODO 根据项目 数据处理,全局接口错误处理
// 成功
if (response.status === REQUEST.SUCCESS_CODE) {
} else {
// 失败
}
return response.data
},
(err) => {}
)

export default axios
  1. 新建api.js 统一管理请求;
1
2
3
4
5
import axios from './index'

export const apiTest = function (data) {
return axios.post('/test', data)
}
  1. 页面上使用;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { useEffect, useState, useCallback, ReactElement } from 'react'
import style from '@style/modules/home.module.scss'
import { apiTest } from '@/assets/http/api'

const Home: React.FC<IRouterProps> = (props: IRouterProps): ReactElement => {
const [user, setUser] = useState(null)

useEffect((): void => {
// 请求测试
apiTest({ appointNo: 'XXX' }).then((res: any) => {
console.log(res)
setUser(res.result)
})
}, [])

return (
<div>
<h1 className={style.home}>Home</h1>
<p>{JSON.stringify(user)}</p>
</div>
)
}

export default Home

8. 项目数据流管理库

当前项目是 redux,小项目的话此项是可选的;

  1. 安装
1
yarn add redux react-redux redux-thunk redux-promise redux-logger
  1. 新建src/store/index.jssrc/store/home.store.ts

src/store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createStore, applyMiddleware, combineReducers } from 'redux'
import thunk from 'redux-thunk' // 处理action异步
import promise from 'redux-promise' // 处理action promise风格
import logger from 'redux-logger' // 打印log

import { countReducer, userReducer } from './home.store'

const store = createStore(
combineReducers({ count: countReducer, user: userReducer }),
applyMiddleware(thunk, promise, logger)
)

export default store

src/store/home.store.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { actionType, objType } from '@/@types/global'
import { apiTest } from '@/assets/http/api'

export const countReducer = (state = 0, action: IActionProps) => {
switch (action.type) {
case 'ADD':
return state + (action.payload || 1)
case 'MINUS':
return state - (action.payload || 1)
default:
return state
}
}

export const userReducer = (state = null, action: IActionProps) => {
switch (action.type) {
case 'SET':
return Object.assign({}, state, action.payload)
default:
return state
}
}
  1. 在页面中使用
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
import { useSelector, useDispatch } from 'react-redux'

...

const countRedux = useSelector((state: IObjectProps) => state.count)
const userRedux = useSelector((state: IObjectProps) => state.user)
const dispatch = useDispatch()

...

/**
* 异步action
*/
const asyncAction = ():void => {
dispatch((dispatch: any, getState: any) => {
// 测试api
apiTest({appointNo: 'XXXX'}).then((res: IObjectProps) => {
// console.log(res)
if (res.success == 't') {
// 修改store中的数据
dispatch({ type: 'SET', payload: res.result })
}
})
})
}

...

return (
<div>
<h1 className={style.home}>Home</h1>
<p>{countRedux}</p>
<button onClick={() => { dispatch({ type: 'ADD', payload: 100 }) }}> ADD </button>
<button onClick={() => { dispatch({ type: 'MINUS', payload: 5 }) }}> MINUS </button>
<button onClick={asyncAction}>USER</button>
<p>{JSON.stringify(userRedux)}</p>
</div>
)
...

总结

以上就是本次项目搭建的基本流程,文章里只记录项目搭建的最简实现确保项目运行正常,更多需求请根据项目实际需要进行另行配置,如修改 router 模式、router 权限控制、封装全局样式、全局组件、全局 utils、全局 api 拦截处理、项目性能优化等… 需要源代码请留言。