1. 首先引入create-react-app
yarn add create-react-app -g
2.创建react项目
yarn create react-app my-app --template typescript
创建完成之后,进入项目,yarn start启动
3.引入antd和按需加载
- 引入 react-app-rewired 并修改 package.json 里的启动配置。由于新的 react-app-rewired@2.x 版本的关系,你还需要安装 customize-cra。
yarn add antd
yarn add react-app-rewired customize-cra
- 按需加载
修改package.json为
{
//...
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
//...
// 修改完成之后,在根路径中加入config-overrides.js
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
这一步完成后就可以直接在页面中 import { Button } from 'antd' 按需加载了
4.自定义主题
# 有三种方案
1:使用antd官网所说craco-less的配置 https://ant.design/docs/react/use-with-create-react-app-cn
2.yarn eject暴露webpack配置
3.使用刚才引入的react-app-rewired customize-cra
三种方案都需要 yarn add less less-loader,
此文章采用的第三种方案
在文件最外层新增config-overrides.js
// 详细配置
const { override, fixBabelImports, addLessLoader, adjustStyleLoaders } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
lessOptions: {
javascriptEnabled: true,
modifyVars: {
'@primary-color': '#1DA57A'
},
}
}),
adjustStyleLoaders(({ use: [, , postcss] }) => {
const postcssOptions = postcss.options;
postcss.options = { postcssOptions };
})
);
此步骤 遇到的问题汇总
按官方文档写的配置
addLessLoader({
lessOptions: {
javascriptEnabled: true,
modifyVars: {
'@primary-color': '#1DA57A'
},
}
}),
yarn start 之后
有报错,说是我们配置的对象是无效的,且在配置中
modifyVars
是未知的,
点开addLessLoader可以看到
export function addLessLoader(loaderOptions?: any, customCssModules?: any): OverrideFunc;
查看customize-cra
的issues找到,我们所添加到addLessLoader
里面的配置由于less-loader的更新,新版本的写法发生了变化,原来addLessLoader
的配置选项现在需要嵌套到一个lessOptions
对象中去。
即为
addLessLoader({
lessOptions: {
javascriptEnabled: true,
modifyVars: {
'@primary-color': '#1DA57A'
},
}
})
重新 yarn start
之后,出现了新的错误
这个问题是由于使用了create-react-app
的问题,因为版本升级到了5.0.1,所以webpack的版本也变成了5.0.1,所以我们需要
adjustStyleLoaders(({ use: [, , postcss] }) => {
const postcssOptions = postcss.options;
postcss.options = { postcssOptions };
})
重新 yarn start
之后可以看到运行成功
如果想采用暗黑模式,更改config-overrides.js
为
const path = require('path')
const {
override,
addLessLoader,
addWebpackAlias,
overrideDevServer,
fixBabelImports,
adjustStyleLoaders
} = require('customize-cra')
const { getThemeVariables } = require('antd/dist/theme');
const devServerConfig = () => config => {
return {
...config,
}
}
// 修改端口号
process.env.PORT = 3030
module.exports = {
webpack: override(
addWebpackAlias({
// 路径别名,还需要配置tsconfig.json、tsconfig-base.json,在后面,可配置多个路径别名
'@': path.resolve(__dirname, 'src/')
}),
// antd按需加载
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true // 支持less
}),
// 配置less
addLessLoader({
lessOptions: {
javascriptEnabled: true, // 启用支持内联javascript
modifyVars:{
...getThemeVariables({
dark: true, // 开启暗黑模式
compact: false, // 开启紧凑模式
}),
'@font-size-base': '14px',
'@primary-color': '#FF550',
'@text-color':'rgba(255, 255, 255, 1)',
}
}
}),
adjustStyleLoaders(({ use: [, , postcss] }) => {
const postcssOptions = postcss.options;
postcss.options = { postcssOptions };
})
),
devServer: overrideDevServer(devServerConfig())
}
下一步为创建路由和Redux或umi,放到下一篇文章中
5. react路由的引入和使用
路由引入
yarn add react-router-dom
yarn add @types/react-router-dom
路由使用
src文件下新增router/index.ts文件 ``` import React, { lazy, Suspense } from 'react';
import { BrowserRouter,Route,Routes } from 'react-router-dom';
import BaseLayout from '@/layout/BaseLayout';
const Home = lazy(() => import("@/views/Home"));
const Play = lazy(() => import("@/views/Play"))
const Drink = lazy(() => import("@/views/Drink"))
const Milk = lazy(() => import("@/views/Drink/components/Milk"))
const Beer = lazy(() => import("@/views/Drink/components/Beer"))
const Eat = lazy(() => import("@/views/Eat"))
const RouterContainer: React.FC = (props: any) => {
return (
<>
<BrowserRouter>
<BaseLayout>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home/>} />
<Route path="/Play" element={<Play/>} />
<Route path="/Drink" element={<Drink/>} > </Route>
<Route path="/Milk" element={<Milk/>} />
<Route path="/Beer" element={<Beer/>} />
<Route path= '/Eat' element={<Eat/>} />
</Routes>
</Suspense>
</BaseLayout>
</BrowserRouter>
</>
)
}
export default RouterContainer
```
新增src/config/menu.ts
```
export const allMenu = [
{
id:'0',
key:'/',
label:'Home',
},
{
id: '1',
key: '/Eat',
label:'Eat',
},
{
id: '2',
key: '/Drink',
label:'Drink',
children:[
{
id:'2-1',
key:'/Milk',
label:'Milk',
},
{
id:'2-2',
key:'/Beer',
label:'Beer',
}
]
},
{
id: '3',
key: '/Play',
label:'Play',
}
]
```
新增src/layout/BaseLayout.tsx
```
import {allMenu} from '../config/menu'
import { Breadcrumb, Layout, Menu } from 'antd';
import React, { useEffect, useState } from 'react';
import { NavLink, useLocation,useNavigate } from 'react-router-dom';
import SubMenu from 'antd/lib/menu/SubMenu';
const { Header, Content, Sider } = Layout;
const rootSubmenuKeys: any = new Set(['/', '/Eat', '/Drink', '/Play'])
console.log(allMenu);
const BaseLayout= (props:any) => {
const { children } = props
const [openKeys, setOpenKeys] = useState<any>([''])
const location = useLocation()
const navigate = useNavigate()
// const getMenuNodes = (menuList: any) =>
// menuList.length > 0 &&menuList.map((item: any) => {
// return (
// <>
// {item.children&&item.children.length>0?(
// <Menu.SubMenu title={item.label} key={item.key}>
// { getMenuNodes(item.children) }
// </Menu.SubMenu >
// ):
// <Menu.Item key={item.key} ><NavLink to={item.key}> { item.label}</NavLink> </Menu.Item>
// }
// </>
// )
// })
const onOpenChange = (keys: React.ReactText[]) => {
const latestOpenKey = keys.find((key) => !openKeys.includes(key))
console.log(latestOpenKey);
if (!rootSubmenuKeys.has(latestOpenKey)) {
setOpenKeys(keys)
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : [])
}
}
const gotoPath = ({ item, key, keyPath, selectedKeys, domEvent }:any)=>{
console.log(key,keyPath);
navigate(key)
}
return(
<Layout style={{width:'100%',height:'100%'}}>
<Header style={{color:'#fff'}} >
HeaderHeaderHeader
</Header>
<Layout>
<Sider width={200} className="site-layout-background" theme='light'>
<Menu
mode="inline"
openKeys={openKeys}
onOpenChange={onOpenChange}
items={allMenu}
onSelect={gotoPath}
>
{/* {getMenuNodes(allMenu)} */}
</Menu>
</Sider>
<Layout style={{ padding: '0 24px 24px' }}>
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item href="./">Home</Breadcrumb.Item>
<Breadcrumb.Item href={location.pathname}>{location.pathname.split('/')[1]}</Breadcrumb.Item>
</Breadcrumb>
<Content
className="site-layout-background"
style={{
padding: 24,
margin: 0,
minHeight: 280,
}}>
{children}
</Content>
</Layout>
</Layout>
</Layout>
)
}
export default BaseLayout;
```
6. redux的引入及使用
引入redux模块
yarn add @reduxjs/toolkit
yarn add react-redux
reduxjs/toolkit的使用
src文件下新建store文件夹,在store中新建index.ts和自己各个模块所存的文件夹 eg: index.ts新增feature文件夹,其中分别有counterSlice.ts模块和MoviSlice模块
counterSlice.ts
import { createSlice } from '@reduxjs/toolkit'
// initial state interface
export interface InitialStateTypes {
count:number,
loading: boolean;
visible: boolean;
isEditMode: boolean;
}
// initial state
const initialState: InitialStateTypes = {
count:0,
loading: false,
visible: false,
isEditMode: false,
};
export const counterSlice = createSlice({
name:'counter', // 命名空间,在调用action的时候会默认的设置为action的前缀
//初始值
initialState:initialState,
// 这里的属性会自动的导出为actions,在组件中可以直接通过dispatch进行触发
reducers:{
// console.log(action);
increment(state,{payload}){
state.count = state.count + payload.step // 内置了immutable
},
decrement(state) {
state.count -= 1;
},
}
})
// 导出actions
export const { increment, decrement } = counterSlice.actions;
// 内置了thunk插件,可以直接处理异步请求
export const asyncIncrement = (payload:any) => (dispatch:any) => {
setTimeout(() => {
dispatch(increment(payload));
}, 2000);
};
// 导出reducer,在创建store时使用到
export default counterSlice.reducer;
movieSlice.ts
import {createSlice} from '@reduxjs/toolkit'
export const movieSlice = createSlice({
name:'movie',
initialState:{
nowMovie:'time is god',
title:'redux toolkit pre'
},
reducers:{
changeMovieName(state,{payload}){
state.nowMovie = payload.name // 内置了immutable
},
}
})
export const {changeMovieName} = movieSlice.actions
// 内置了thunk插件,可以直接处理异步请求
export const asyncIncrement = (payload:any) => (dispatch:any) => {
setTimeout(() => {
dispatch(changeMovieName(payload));
}, 2000);
};
export default movieSlice.reducer; // 导出reducer,在创建store时使用到
index.ts
import { configureStore } from '@reduxjs/toolkit';
import counterSlice from './features/counterSlice';
import movieSlice from './features/movieSlice';
// configureStore创建一个redux数据
export default configureStore({
reducer: {
counter: counterSlice,
movie: movieSlice,
},
});
写好这些文件之后,在任意页面引入
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment,decrement, asyncIncrement } from '../../store/features/counterSlice'; // 引入actions
import { useEffect, useState } from 'react';
const Home = () =>{
const { count } = useSelector((state:any) => state.counter);
const dispatch = useDispatch();
// const [data,setData] = useState<any>([])
const [debounceData,setDebounceData] = useState<number>(1)
useEffect(() =>{
console.log(count);
setDebounceData(count)
},[count])
return(
<div>
<h2>{count}</h2>
<button onClick={() =>{dispatch(increment({step:3}))}}>添加</button>
<button onClick={() =>{dispatch(decrement()) }}> 异步添加 </button>
<input style={{width:'200px'}} type="text" />
<div>debounce</div>
<div>
<input type="text" onChange={() =>{}} value={debounceData} />
</div>
</div>
)
}
export default React.memo(Home)
请求拦截及处理,格式统一,严格模式等后续有空补充