步骤1:通过create-react-app脚手架创建项目
npx create-react-app react-template --template typescript
在vscode中打开项目,可以看到顺利生成了react项目且组件的后缀为tsx,此时说明成功创建了react+typescript项目的雏形
在项目根目录下,运行npm run start,成功启动项目
npm run start
步骤2:引入antd
npm install antd -S
安装完成之后,再次运行npm run start启动项目,发现报错了,提示Could not find a declaration file for module 'react'.
这实际上是create-react-app 4.x版本的bug
解决方案如下:
- 在项目根目录创建react-app-env.d.ts文件
/// <reference types="react-scripts" /> /// <reference types="node" /> /// <reference types="react" /> /// <reference types="react-dom" /> declare namespace NodeJS { interface ProcessEnv { readonly NODE_ENV: 'development' | 'production' | 'test'; readonly PUBLIC_URL: string; } } declare module '*.bmp' { const src: string; export default src; } declare module '*.gif' { const src: string; export default src; } declare module '*.jpg' { const src: string; export default src; } declare module '*.jpeg' { const src: string; export default src; } declare module '*.png' { const src: string; export default src; } declare module '*.webp' { const src: string; export default src; } declare module '*.svg' { import * as React from 'react'; export const ReactComponent: React.FunctionComponent<React.SVGProps< SVGSVGElement > & { title?: string }>; const src: string; export default src; } declare module '*.module.css' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.module.scss' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.module.sass' { const classes: { readonly [key: string]: string }; export default classes; } // declare module 'react-imageview';
- 删除node_modules文件夹,并重新执行
npm install
- 重新执行
npm run start
,项目成功运行
接下来我们继续引入antd
在App.tsx中添加import 'antd/dist/antd.css';
,同时引入所需要的antd组件,如Button
import React from 'react'; import './App.css'; import 'antd/dist/antd.css'; import { Button } from 'antd'; function App() { return ( <div className="App"> <Button type="primary">Button</Button> </div> ); } export default App;
可以看到效果:
此时antd就引入完成了
步骤3:配置不同环境的打包命令,如测试环境、生产环境
- 安装cross-env
npm i cross-env -D
- 配置package.json命令
"scripts": { "serve": "cross-env REACT_APP_ENV=development node scripts/start.js", "build": "cross-env REACT_APP_ENV=production node scripts/build.js", "uat": "cross-env REACT_APP_ENV=uat node scripts/build.js", "sit": "cross-env REACT_APP_ENV=sit node scripts/build.js" },
- 创建.env ,.env.development ,.env.production ,.env.uat ,.env.sit,
.env
REACT_APP_ENV=development
- .env.development
REACT_APP_ENV=development
- .env.production
REACT_APP_ENV=production
- .env.uat
REACT_APP_ENV=uat
- .env.sit
REACT_APP_ENV=sit
- 项目中获取当前环境变量
console.log(process.env.REACT_APP_ENV)
步骤4:配置路由
- 安装
react-router-dom
和@types/react-router-dom
npm i react-router-dom @types/react-router-dom -S
- src目录下创建views文件夹,views内创建页面组件,如Index
- src目录下创建router文件夹,文件夹下配置各个功能模块的路由,同时创建index.tsx,对各个功能模块的路由做统一的引入和暴露
功能模块CommonRouter.tsx:
import { lazy } from "react"; const Index = lazy(() => import("../views/Index/Index")); const CommonRouter: any = [ { path: "/", component: Index, exact: true, title: "首页", }, ]; export default CommonRouter;
-
index.tsx引入所有的功能模块路由配置,做统一暴露
import CommonRouter from "./CommonRouter"; const routerConfig: any = [...CommonRouter] export default routerConfig;
- App.tsx中引入Route和自定义的路由配置
import React, { Suspense, Component, Fragment } from "react"; import { Route, Switch, Redirect, withRouter as realWithRouter } from "react-router-dom"; // 这里的是路由配置文件 import routes from "./router/index"; import 'antd/dist/antd.css'; type StateType = { [propName: string]: any; }; type PropType = { [propName: string]: any; }; interface App { state: StateType; props: PropType; } // 获取路由信息 const withRouter: any = realWithRouter; @withRouter class App extends Component { render() { return ( <Fragment> <Suspense fallback={<div></div>}> <Switch> {routes.length > 0 && routes.map((route: any) => { //遍历路由数组 const { path, component: C, exact } = route; return ( <Route exact={exact} key={path} path={path} render={(props: any) => { return <C {...props} />; }} /> ); })} {/* 默认进入/时自动匹配到/ */} <Redirect exact from="/" to={"/"} /> {/* 默认无效路径时自动匹配到首页 */} <Redirect to="/" /> </Switch> </Suspense> </Fragment> ); } } export default App;
- 根目录index.tsx中这样定义
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { BrowserRouter as Router } from "react-router-dom"; ReactDOM.render( <React.StrictMode> <Router> <App /> </Router> </React.StrictMode>, document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
-
至此,路由配置就完成了
步骤5:配置less
- 暴露配置
npm run eject
- 此时项目多出了config文件夹
- 安装
less
和less-loader@5.0.0
(less-loader必须安装指定版本5.0.0)
npm install less less-loader@5.0.0 -S
- 仿照sass修改config目录下的webpack.config.js
①搜索 cssRegex ,找到后添加两行代码,添加less相关正则
// style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/; const lessRegex = /\.less$/; const lessModuleRegex = /\.module\.less$/;
- ②修改 getStyleLoaders 函数,添加代码
{ loader: require.resolve('less-loader'), options: lessOptions, },
- ③搜索 cssRegex ,在 css 配置下添加 less 配置
// Opt-in support for LESS (using .less extensions). // By default we support LESS Modules with the // extensions .module.less { test: lessRegex, exclude: lessModuleRegex, use: getStyleLoaders( { importLoaders: 1, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, }, 'less-loader' ), // Don't consider CSS imports dead code even if the // containing package claims to have no side effects. // Remove this when webpack adds a warning or an error for this. // See https://github.com/webpack/webpack/issues/6571 sideEffects: true, }, // Adds support for CSS Modules, but using LESS // using the extension .module.less { test: lessModuleRegex, use: getStyleLoaders( { importLoaders: 1, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, modules: { getLocalIdent: getCSSModuleLocalIdent, }, }, 'less-loader' ), },
- 重新启动项目,创建less文件并引入
样式生效,说明less配置成功
步骤6:配置sass
通过create-react-app创建的react项目,其实是默认已经配置好sass的,所以我们先尝试在项目中引入sass文件
样式生效,说明sass配置成功
步骤7:配置react-redux
- 安装
react-redux
、@types/react-redux
、redux-thunk
、@types/redux-thunk
npm install react-redux @types/react-redux redux-thunk @types/redux-thunk -S
- 创建redux核心模块
如以下示例:
├── src │ └── redux │ ├── action │ │ ├── TestAction.tsx │ └── asyncAction │ │ ├── AsyncTestAction.tsx │ └── reducer │ │ ├── reducer.tsx │ │ └── TestReducer.tsx │ └── store.tsx
-
reducer~TestReducer.tsx // 创建TestReducer
interface StateType { name: string; age: number; } const commonState: StateType = { name: '', age: 0, }; const TestReducer = (state = commonState, action: any): any => { switch (action.type) { case "CHANGE_NAME": return { ...state, name: action.payload.name, }; case "CHANGE_AGE": return { ...state, age: action.payload.age, }; case "ASYNC_CHANGE_NAME": return { ...state, addressObj: action.payload.name, }; default: return state; } }; export default TestReducer;
- reducer~reducer.tsx // 创建管理所有reducer的配置
import { combineReducers } from 'redux'; // 引入其他reducer import TestReducer from './TestReducer'; // 将所有引入的reducer合并成一个reducers,暴露出去 export default combineReducers({ TestReducer, });
- action~TestAction.tsx // 创建同步更改state的方法
export const changeName = (name: string) => { return { type: 'CHANGE_NAME', payload: { name } } } export const changeAge = (age: number) => { return { type: 'CHANGE_AGE', payload: { age } } }
- asyncAction~AsyncTestAction.tsx // 创建异步更改state的方法
export const asyncChangeName = () => { return async (dispatch: any) => { const countDown: Promise<any> = new Promise((resolve: any, reject: any) => { setTimeout(() => { resolve({ name: '模拟网络请求获取到的name' }) }, 1000) }) const data: any = await countDown; const params: any = { type: 'ASYNC_CHANGE_NAME', payload: { name: data.name } }; dispatch(params); } }
- store.tsx // 配置store
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import reducer from './reducer/reducer'; export default createStore(reducer, applyMiddleware(thunk));
- 入口文件index.tsx配置redux
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { BrowserRouter as Router } from "react-router-dom"; import { Provider } from 'react-redux'; import store from './redux/store'; ReactDOM.render( <Provider store={store}> <React.StrictMode> <Router> <App /> </Router> </React.StrictMode> </Provider>, document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
- 组件中使用redux,如在App.tsx中使用redux:
import React, { Suspense, Component, Fragment } from "react"; // 这里的是路由配置文件 import { Route, Switch, Redirect, withRouter as realWithRouter } from "react-router-dom"; import routes from "./router/index"; // 公共样式 import 'antd/dist/antd.css'; import './assets/style/public.less'; // redux import { changeName, changeAge } from "./redux/action/TestAction"; import { asyncChangeName } from "./redux/asyncAction/AsyncTestAction"; import { connect as realConnect } from "react-redux"; type StateType = { [propName: string]: any; }; type PropType = { [propName: string]: any; }; interface App { state: StateType; props: PropType; } // 获取redux const mapStateToProps = (state: any) => { return { state, }; }; const connect: any = realConnect; // 获取路由信息 const withRouter: any = realWithRouter; @withRouter @connect(mapStateToProps, { changeName, changeAge, asyncChangeName }) class App extends Component { componentDidMount() { // 调用redux中指定reducer的同步action和异步action方法 this.props.changeName('张三'); this.props.changeAge(25); this.props.asyncChangeName(); // 获取redux中指定reducer的state console.log(this.props.state.TestReducer); } render() { return ( <Fragment> <Suspense fallback={<div></div>}> <Switch> {routes.length > 0 && routes.map((route: any) => { //遍历路由数组 const { path, component: C, exact } = route; return ( <Route exact={exact} key={path} path={path} render={(props: any) => { return <C {...props} />; }} /> ); })} {/* 默认进入/时自动匹配到/ */} <Redirect exact from="/" to={"/"} /> {/* 默认无效路径时自动匹配到首页 */} <Redirect to="/" /> </Switch> </Suspense> </Fragment> ); } } export default App;
步骤8:自适应
如有配置自适应的需求,可参考这篇文章移动端自适应解决方案vw(以react为例)
至此,react项目创建和配置完成