前言
记录一次通过 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
| const { override, addDecoratorsLegacy, addWebpackAlias, addPostcssPlugins, } = require('customize-cra')
const path = require('path')
function resolve(dir) { return path.join(__dirname, '.', dir) }
module.exports = override( addDecoratorsLegacy(), addWebpackAlias({ '@': resolve('src'), '@comps': resolve('src/components'), '@style': resolve('src/assets/style'), '@images': resolve('src/assets/images'), '@pages': resolve('src/pages'), }), addPostcssPlugins([ require('postcss-px2rem')({ remUnit: 37.5 }), require('autoprefixer'), 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) 全局路由
注: 此处有一个小知识点
- Route 渲染组件有三个属性:
component
、render
、children
, 渲染优先级为children
> component
> render
, - 只要有
children
就会优先渲染子组件,component
组件是传入一个组件,底层会通过React.createElement
的方法去创建组件,如果写成函数形式,则每次渲染都会重新渲染组件经历新组件的创建和废弃组件的卸载,而不是对组件的更新; 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)在页面中使用
在页面上使用useHistory
、useLocation
、useRouteMatch
等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 => { 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 => { 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 => {
const match:IMatchProps | null = useRouteMatch('/user/:id')
useEffect((): void => {
console.log(match?.params?.id) }, [])
return ( <div> <h2 className={style.home}>User</h2> </div> ) }
export default User
|
6. 配置 sass
- 安装
node-sass
;这里可能会存在版本问题,如遇报错回退一下node-sass
版本就 ok; - 把 css 文件后缀改为 scss 就可以开始开发了;
7. 配置 axios
- 安装
axios
包; - 配置拦截器;
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) => { return config }, (err) => {} )
axios.interceptors.response.use( (response) => { if (response.status === REQUEST.SUCCESS_CODE) { } else { } return response.data }, (err) => {} )
export default axios
|
- 新建
api.js
统一管理请求;
1 2 3 4 5
| import axios from './index'
export const apiTest = function (data) { return axios.post('/test', data) }
|
- 页面上使用;
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
| yarn add redux react-redux redux-thunk redux-promise redux-logger
|
- 新建
src/store/index.js
、src/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' import promise from 'redux-promise' import logger from 'redux-logger'
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 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()
...
const asyncAction = ():void => { dispatch((dispatch: any, getState: any) => { apiTest({appointNo: 'XXXX'}).then((res: IObjectProps) => { if (res.success == 't') { 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 拦截处理、项目性能优化等… 需要源代码请留言。