npx create-react-app react-template --template typescript
在vscode中打开项目,可以看到顺利生成了react项目且组件的后缀为tsx,此时说明成功创建了react+typescript项目的雏形
在项目根目录下,运行npm run start,成功启动项目
npm run start
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文件
///
///
///
///
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
接下来我们继续引入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;
安装cross-env
npm i cross-env -D
"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)
安装react-router-dom
和@types/react-router-dom
npm i react-router-dom @types/react-router-dom -S
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;
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();
暴露配置
npm run eject
安装less
和[email protected]
(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$/;
{
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'
),
},
通过create-react-app创建的react项目,其实是默认已经配置好sass的,所以我们先尝试在项目中引入sass文件
安装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;
如有配置自适应的需求,可参考这篇文章移动端自适应解决方案vw(以react为例)
至此,react项目创建和配置完成
https://www.cnblogs.com/gqx-html/p/13219422.html