本文最后更新于:2023年8月24日 晚上
搭建react项目
使用webpack
yarn
1 2 3 4 5
| yarn create react-app 项目名
npm init react-app 项目名
npx create-react-app 项目名
|
使用webpack作为底层服务器,构建比较慢,需要一分钟左右,启动在3000端口
使用vite构建
选择react即可,启动在5173端口
webpack构建的项目
public中的结构
| 文件名 |
说明 |
| index.html |
主要的渲染文件 |
| manifest.json |
应用加壳需要的配置文件 |
| robots.txt |
爬虫协议文件 |
分析index.html
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <title>React App</title> </head> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> </html>
|
src中的结构
| 文件名 |
说明 |
| App.js |
用函数式定义了一个叫做App的组件 |
| App.test.js |
做测试用的(基本用不到) |
| index.js |
webpack程序入口文件 |
| reportWebVitals.js |
用于记录页面性能的配置文件 |
| setupTests.js |
做组件测试的 |
分析index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <App /> </React.StrictMode> );
reportWebVitals();
|
简化结构
public
只剩下index.html即可
1 2 3 4 5 6 7 8 9 10
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>React App</title> </head> <body> <div id="root"></div> </body> </html>
|
src
只剩下App.js和index.js即可
App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React from "react";
class App extends React.Component { render() { return ( <div> Hello, React </div> ) }
}
export default App;
|
index.js
1 2 3 4 5 6 7 8 9 10
| import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <App /> </React.StrictMode> );
|
样式混乱
定义多个组件时,使用的样式文件重复名字的话会产生样式混乱
解决:可以在取名时加上module
子组件向父组件传参
子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React, { Component } from 'react'
interface IProps { setName: Function }
class Index extends Component<IProps, null> { render () { return ( <div> <button onClick={this.props.setName('FSAN')}>点击向父组件传参</button> </div> ) } }
export default Index as any
|
父组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React, { Component } from 'react' import Children from '../Children'
class Index extends Component { setName = (name: string) => { console.log('这里是父组件:', name) }
render () { return ( <div> <Children setName={this.setName} /> </div> ) } }
export default Index as any
|
vite代理跨域
请求地址:/api/XXX,接口地址:http://localhost:5000/api/XXX
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import eslintPlugin from 'vite-plugin-eslint'
export default defineConfig({ plugins: [ react(), eslintPlugin({ include: ['src/**/*.tsx', 'src/**/*.ts', 'src/*.ts', 'src/*.tsx'] }) ], server: { proxy: { '/api': { target: 'http://localhost:5000' } } } })
|
看server即可,除了target还可以配置:
changeOrigin: true 是否改写 origin,设置为 true 之后,就会把请求 API header 中的 origin,改成跟 target 里边的域名一样了。
rewrite: (path) => path.replace(/^\/api/, "") 可以把请求的 URL 进行重写,这里因为假设后端的 API 路径不带 /api 段,所以我们使用 rewrite去掉 /api。
注意一点:请求本地的时候,先是去获取到项目中的静态资源,没找到才会通过代理
消息订阅与发布(PubSub)
用于任意组件的沟通
下载
1 2 3 4 5
| npm i pubsub-js
yarn add pubsub-js
yarn add @types/pubsub-js
|
订阅消息
1 2 3 4 5 6 7 8 9 10 11 12 13
| token: string = ''
componentDidMount () { this.token = pubsub.subscribe('demoData', (_, data) => { console.log(_, data) this.setState({ data }) }) }
componentWillUnmount () { pubsub.unsubscribe(this.token) }
|
_ 下划线占位元素用的,类似c#中的弃元
发布消息
1
| pubsub.publish('demoData', '这是一条消息')
|
但是发送后莫名其妙的会有两次接收
TodoList案例
ToDoList.tsx
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
import { Component, KeyboardEvent, MouseEvent, createRef } from 'react' import './ToDoList.scss'
interface IListData { [key: string]: string | number | boolean
id: string name: string, isChecked: boolean }
interface IState { listData: IListData[], delBtnState: boolean }
class ToDoList extends Component<null, IState> { state = { listData: [], delBtnState: false }
selectAllBox = createRef<HTMLInputElement>()
add = (e: KeyboardEvent<HTMLInputElement>): void => { const { key, target }: { key: string, target: HTMLInputElement } = e if (key === 'Enter' && target.value) { target.value = target.value.trim() if (this.state.listData.findIndex((v: IListData) => v.name === target.value) === -1) { const id = (Math.random() * 100).toFixed(0) + Date.now().toString().slice(0, 10) this.setState({ listData: [ ...this.state.listData, { id, name: target.value, isChecked: false } as IListData ] }) target.value = '' } } }
selectAll = (e: MouseEvent) => { const target = e.target as HTMLInputElement this.setState({ listData: this.state.listData.filter((v: IListData) => { v.isChecked = target.checked return true }) }) }
changeSelect = (id: string): any => ({ target }: { target: HTMLInputElement }): void => { const { listData } = this.state listData.map((v: IListData) => v.id === id ? { ...v, isChecked: target.checked } : v) this.setState({ listData }) const current = this.selectAllBox.current as HTMLInputElement current.checked = this.state.listData.findIndex((v: IListData) => !v.isChecked) === -1 }
delList = (id: string): any => (): void => { this.setState({ listData: this.state.listData.filter((v: IListData) => v.id !== id) }) }
delAll = () => { this.setState({ listData: this.state.listData.filter((v: IListData) => !v.isChecked) }) }
handleMouse = (flag: boolean) => (e: MouseEvent<HTMLDivElement>): void => { const target = e.target as HTMLInputElement const btnDom = target.children[2] as HTMLButtonElement btnDom.style.display = flag ? 'block' : 'none' target.style.backgroundColor = flag ? '#ddd' : '#fff' this.setState({ delBtnState: flag }) }
render () { return ( <div id="todoList"> <input className="add-input" type="text" onKeyDown={this.add} placeholder="请输入你的任务名称,按回车键确认" /> <div className="list-box"> {this.state.listData.map(({ id, name, value, isChecked }) => { return ( <div className="list-item" key={id} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)} > <input type="checkbox" checked={isChecked} onClick={this.changeSelect(id)} name={name} value={value} id={id} /> <label htmlFor={id}>{name}</label> <button style={{ display: 'none' }} className="delBtn" onClick={this.delList(id)}>删除 </button> </div> ) })} </div> <div className="foo"> <input type="checkbox" value="" ref={this.selectAllBox} onClick={this.selectAll} /> <span>已完成{this.state.listData.reduce((result: number, v: IListData) => v.isChecked ? result + 1 : result, 0)} / 全部{this.state.listData.length}</span> <button className="delBtn" onClick={this.delAll}>删除全部已完成</button> </div> </div> ) } }
export default ToDoList as any
|
ToDoList.scss
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| #todoList { margin: 20px auto; width: 500px; border: 1px solid #ccc; padding: 10px; border-radius: 5px;
.add-input { width: 100%; height: 40px; line-height: 40px; padding-left: 10px; outline: none; border: 1px solid #ccc; margin-bottom: 10px; box-sizing: border-box;
&:focus { box-shadow: 0 0 5px #0097ff; } }
.list-box { display: flex; flex-flow: column; border: 1px solid #ccc; border-bottom: none;
.list-item { border-bottom: 1px solid #ccc; height: 40px; line-height: 40px; padding-left: 5px; position: relative;
label { margin-left: 5px; cursor: pointer; } input{ cursor: pointer; } } }
.foo { margin: 20px 5px; position: relative;
span { margin-left: 30px; } } .delBtn{ background: #cc0019; color: #fff; border: none; border-radius: 6px; padding: 5px 15px; position: absolute; right: 5px; top: 50%; cursor: pointer; transform: translate(0, -50%);
&:active{ transform: scale(0.95) translate(0, -51%); } } }
|
路由实现
底层
历史模式(history)
路由是怎么判断刷新和记录的?
利用windom中的history属性可以记录,回到上个页面等操作
存储的结构类似栈结构,后进先出
学习实现基础可以使用history.js(原生的api不好用)
哈希模式(Hash)
这个模式就没有用到dom中的history记录,主要就是类似锚点跳转,所以路由后都会有#
优点:兼容性极佳
下载
1 2 3
| yarn add react-router-dom
npm i react-router-dom
|
引入使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <BrowserRouter> <div className="aside"> {/* 路由跳转 */} <Link to="/about" className="menu-item"> About </Link> <Link to="/home" className="menu-item"> Home </Link> </div> <div className="container"> {/* 注册路由 */} <Routes> <Route path="/about" element={<About />} /> <Route path="/home" element={<Home />} /> </Routes> </div> </BrowserRouter>
|
Link和Route标签都需要在一个Router中,引入的依赖如下:
1 2 3
| import { Link, BrowserRouter, Route } from 'React-Router-Dom' import About from '../About' import Home from '../Home'
|
About和Home是路由组件,存放在 src / pages 下
这里的Route标签中的element属性是react-router-dom 6.0以上新版本写法,并且需要Routes标签,旧版如下:
1 2 3
| {} <Route path="/about" component={About} /> <Route path="/home" component={Home} />
|
又由于所有的页面都需要套一个Router控制,所以这个BrowserRouter标签就可以写在main.tsx中,直接给App组件套上
1 2 3 4 5 6 7 8 9 10 11 12 13
| import React from 'react' import { createRoot } from 'react-dom/client' import App from './App' import { BrowserRouter } from 'react-router-dom'
const dom = document.getElementById('root') as HTMLElement createRoot(dom).render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> )
|
Link 和 NavLink
共同点:都是用来跳转的
区别:NavLink 在style和className上支持函数形式
使用NavLink做访问后的样式
新版本:
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
| <div className="aside"> {} <NavLink to="/about" className={({ isActive }) => (isActive ? 'menu-item link-active' : 'menu-item')} > About </NavLink> <NavLink to="/home" className={({ isActive }) => (isActive ? 'menu-item link-active' : 'menu-item')} > Home </NavLink> </div>
{} <div className="aside"> <NavLink to="/home" className="menu-item" style={({ isActive }) => (isActive ? { background: '#787878', color: '#fff' } : {})} > Home </NavLink> <NavLink to="/about" className="menu-item" style={({ isActive }) => (isActive ? { background: '#787878', color: '#fff' } : {})} > About </NavLink> </div>
|
在NavLink标签中style和className都可以写成一个函数,回调参数是一个包含isActive的对象,解构出来就可以根据是否正在访问返回对应的样式`
旧版本写法如下:
1 2 3
| {} <NavLink to="/about" activeClassName="link-active" className="menu-item">About</NavLink> <NavLink to="/home" activeClassName="link-active" className="menu-item">Home</NavLink>
|
在旧版本中,默认的activeClassName为active
二次封装NavLink
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
| import React, { Component } from 'react' import { NavLink } from 'react-router-dom'
interface IProps { to: string, children?: any }
class Index extends Component<IProps, any> { render () { const { to, children } = this.props const paths = to.split('/') const title = paths[paths.length - 1].slice(0, 1).toUpperCase() + paths[paths.length - 1].slice(1).toLowerCase() const copyProps = { ...this.props, children: children || title } return ( <> <NavLink className={({ isActive }) => (isActive ? 'menu-item link-active' : 'menu-item')} {...copyProps} > </NavLink> </> ) } }
export default Index
|
对于NavLink标签,显示的文字不一定要放在标签体,放在children也可以,所以从props中传的children可以直接解构使用
使用:
1 2 3 4
| <div className="aside"> <MyNavLink to="/about">againAbout</MyNavLink> <MyNavLink to="/home" /> </div>
|
对于标签体内容,子组件接收的方式是放在props的children属性中
Switch使用(旧版本)
在旧版本的时候,使用多个route标签注册路由的时候,在同path属性时,会将匹配的页面全部显示
解决办法:使用switch标签包裹即可
1 2 3 4
| <Switch> <Route path='/about' component={About} /> <Route path='/home' component={Home} /> </Switch>
|
path 和 component 是一对一的关系
使用Switch标签可以提高路由效率(单一匹配)
在新版本中已经弃用,直接正常使用路由即可
样式丢失bug(旧版本)
对于多级路由的情况下,刷新时如果使用的相对路径的css就会出现样式丢失的问题,因为多级路径下,相对路径的请求会也会带上上一级路由,然后找不到请求路径,自动跳转到public的index.html下
解决办法:换为绝对路径,如:'./css/bootstrap.css' 改为 ‘/css/bootstrap.css’或%PUBLIC_URL%/css/bootstrap.css%
路由模糊与精准匹配
旧版本中路由默认匹配就是模糊匹配,如下:
1 2
| <MyNavLink to="/FSAN/home" /> <Route path='/FSAN' component={Home} />
|
这样是可以正常显示Home组件的
但是在新版本中默认就是校准匹配,如上的path是匹配不到的
旧版本使用exact属性开启精准匹配
1
| <Route exact path='/home' component={Home} />
|
默认为exact={true},可以省略
路由重定向
1 2 3 4 5
| <Routes> <Route path="/about" element={<About />} /> <Route path="/home" element={<Home />} /> <Route path="*" element={<Navigate to="/about" />} /> </Routes>
|
使用Navigate标签是新版本的写法, Routes标签下面只能使用Route标签,将path=“*”匹配全部,进行兜底跳转
旧版本写法如下:
1 2
| <Route exact={true} path='/home' component={Home} /> <Redirect to="/about" />
|
子组件
先在Routes中使用Route标签注册路由,然后再包含子组件的页面中需要展示的地方使用Outlet标签
1 2 3 4 5 6 7 8 9
| <Routes> <Route path="/about" element={<About />} /> <Route path="/home" element={<Home />} > <Route path="news" element={<News />} /> <Route path="message" element={<Message />} /> <Route path="" element={<Navigate to="news" />} /> </Route> <Route path="*" element={<Navigate to="/about" />} /> </Routes>
|
这里在home组件中注册了两个子路由,并且在点击home时重定向到 /home/news
Home页面:
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, { Component } from 'react' import './index.scss' import { NavLink, Outlet } from 'react-router-dom'
class Index extends Component { render () { return ( <> 我是Home的内容 <div className="home-header"> <NavLink className={({ isActive }) => 'tab-title ' + (isActive ? 'tab-active' : '')} to="news" >News</NavLink> <NavLink className={({ isActive }) => 'tab-title ' + (isActive ? 'tab-active' : '')} to="message" >Message</NavLink> </div> <div className="home-content"> <Outlet /> </div> </> ) } }
export default Index
|
- 先使用
Route嵌套标签注册路由
- 在父组件要显示子组件的位置使用
Outlet标签
路由传参
params参数(路径传递)
父组件(跳转的时候直接拼接到路径上就行了):
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import React, { Component } from 'react' import { Outlet, Link } from 'react-router-dom'
interface IDetail { id: number, title: string }
interface IState { detailData: IDetail[] }
class Index extends Component<any, IState> { state = { detailData: [ { id: 1, title: 'msg001' }, { id: 2, title: 'msg002' }, { id: 3, title: 'msg003' } ]
}
render () { const { detailData } = this.state return ( <div> 这是Message组件 <ul> { detailData.map(v => ( <li key={v.id}> <Link to={`${v.id}/${v.title}`}>{v.title}</Link> </li> )) } </ul> <div style={{ margin: '30px' }}> <Outlet /> </div> </div> ) } }
export default Index
|
这里Link中的to属性如: 1/msg001
如果路由需要在路径上携带参数,那么注册时就需要这样写
1 2 3
| <Route path="message" element={<Message />} > <Route path=":id/:title" element={<Detail />} /> </Route>
|
使用冒号后面跟着参数表示接收一个参数
在Detail页面就需要使用hook钩子获取到传递过来的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { useParams } from 'react-router-dom'
const Index = () => { const params = useParams() console.log(params) return ( <div> <p>ID: {params.id}</p> <p>TITLE: {params.title}</p> </div> ) }
export default Index
|
在v6中,获取路由中传递的参数只能通过使用hook获取
旧版本获取是放在props下的match当中的,只能自己从urlencoded格式获取数据
search参数(问号拼接传递)
同样将参数拼接到路径上即可
1 2 3 4 5 6 7 8 9
| <ul> { detailData.map(v => ( <li key={v.id}> <Link to={`detail?id=${v.id}&title=${v.title}`}>{v.title}</Link> </li> )) } </ul>
|
注册路由的时候就不需要额外的操作,正常注册即可
1 2 3 4 5
| <Route path="message" element={<Message />}> {} {} <Route path="detail" element={<Detail />} /> </Route>
|
接收参数使用hook中的useSearchParams
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
| import { useSearchParams } from 'react-router-dom'
const Index = () => { const [searchParams] = useSearchParams() const detailContent = [ { id: 1, content: 'FSAN1' }, { id: 2, content: 'FSAN2' }, { id: 3, content: 'FSAN3' } ] const { content } = detailContent.find(v => v.id === Number(searchParams.get('id'))) as any
return ( <div> <p>ID: {searchParams.get('id')}</p> <p>TITLE: {searchParams.get('title')}</p> <p>CONTENT: {content}</p> </div> ) }
export default Index
|
需要结构出第一个函数,使用第一个函数的get方法获取对于数据
旧版本数据是放在props下的location中的,并且需要自己从 urlencoded 格式中获取
1 2 3 4 5 6 7 8 9 10 11
| const swapUrlEncoded = (obj: object | string) => { if (typeof obj === 'object') { return Object.entries(obj).map(v => `${v[0]}=${v[1]}`).join('&') } const result: {[k: string]: any} = {} obj.split('&').forEach(v => { result[v.split('=')[0]] = v.split('=')[1] }) return result } swapUrlEncoded('id=1&title=主题&content=传递内容')
|
state参数(对象传递)
特点:传递的参数不会在路径中显示出来
注册路由的时候也不用额外操作,正常注册即可
1 2 3 4 5 6
| <Route path="message" element={<Message />}> {} {} {} <Route path="detail" element={<Detail />} /> </Route>
|
传递参数时使用state属性传递一个对象
1
| <Link to={'detail'} state={{ id: v.id, title: v.title }}>{v.title}</Link>
|
在旧版本中是这样写的:
1
| <Link to={{ pathname: 'detail', state: { id: v.id, title: v.title } }}>{v.title}</Link>
|
使用useLocation接收参数:
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
| import { useLocation } from 'react-router-dom'
const Index = () => { const stateParams = useLocation() const { id, title }: any = stateParams.state console.log(stateParams)
const detailContent = [ { id: 1, content: 'FSAN1' }, { id: 2, content: 'FSAN2' }, { id: 3, content: 'FSAN3' } ] const { content } = detailContent.find(v => v.id === Number(id)) as any
return ( <div> <p>ID: {id}</p> <p>TITLE: {title}</p> <p>CONTENT: {content}</p> </div> ) }
export default Index
|
旧版本中参数在props下location中的state中
页面刷新的时候,会发现数据还在,原理:我们用的是BrowserRouter路由模式,state属性放在浏览器的history中,history中的数据是一直被记录的,但是当清空了浏览器缓存的时候,数据就没了
路由覆盖
当我们点击一个路由后,会在栈结构中添加一个路由记录,使用浏览器回退的时候,回退顺序就是根据这个栈结构回退的,要是想让当前路由记录被新增的路由记录覆盖,也就是回退不到上一个页面,使用replace属性即可
1
| <Link replace to={'detail'} state={{ id: v.id, title: v.title }}>{v.title}</Link>
|
编程式导航
1 2 3 4 5 6 7 8 9
| import { useNavigate } from 'react-router-dom' const navigate = useNavigate()
navigate('1/FSAN1')
navigate('?id=1&title=FSAN1')
navigate('', { state: { id: 1, title: 'FSAN1' } })
|
匿名跳转(路由覆盖自己去看useNavigate,有个配置对象),回退就直接写数字即可,如navigate(-1)
在旧版本中,需要使用props中的location对象实现,对于非路由组件,一般组件想要使用路由跳转时,需要使用withRouter包裹组件,如下:
1 2
| import { withRouter } from 'react-router-dom' export default withRouter(Index)
|
使用UI组件库
material-ui是一个国外的react ui库,使用较为繁琐
ant-design 是由国内蚂蚁金服出的,下列记录基于这个UI库(简称antd)
下载
1 2 3
| yarn add antd # or npm i antd -D
|
导入并使用
在main.tsx中先引入css文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React from 'react' import { createRoot } from 'react-dom/client' import App from './App' import { BrowserRouter } from 'react-router-dom' import 'antd/dist/antd.css'
const dom = document.getElementById('root') as HTMLElement createRoot(dom).render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> )
|
页面中直接使用
1 2 3 4 5 6 7 8 9 10 11 12 13
| import React, { Component } from 'react' import { Button } from 'antd' class App extends Component { render () { return ( <div> <Button type="primary">这是一个ant的组件</Button> </div> ) } }
export default App
|
CRA项目css按需引入
对于CRA(create-react-app)项目来说,可以直接使用官网提供的carco插件进行配置,但我是使用vite构建的,目前暂没找到配置步骤
下载craco
1 2 3
| yarn add @craco/craco
npm i @craco/craco -D
|
修改配置项
1 2 3 4 5 6 7 8 9
| "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", + "start": "craco start", + "build": "craco build", + "test": "craco test", }
|
创建配置文件
在根目录下创建craco.config.js
1 2 3 4
| module.exports = { };
|
下载babel-plugin-import
babel-plugin-import是一个用于按需加载组件代码和样式的 babel 插件
1
| yarn add babel-plugin-import
|
在craco.config.js中配置如下:
1 2 3 4 5 6 7 8 9
| module.exports = { babel: { plugins: [['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css', }]], }, };
|
Vite项目css按需引入
删除在main.tsx中的全局引入样式
下载vite-plugin-imp
1
| yarn add vite-plugin-imp
|
下载less
因为antd的默认样式文件是less文件,编译需要使用less
修改vite.config.ts
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 { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import eslintPlugin from 'vite-plugin-eslint' import vitePluginImp from 'vite-plugin-imp'
export default defineConfig({ plugins: [ react(), eslintPlugin({ include: ['src/**/*.tsx', 'src/**/*.ts', 'src/*.ts', 'src/*.tsx'] }), vitePluginImp({ libList: [ { libName: 'antd', style: (name) => `antd/es/${name}/style` } ] }) ], css: { preprocessorOptions: { less: { javascriptEnabled: true, modifyVars: { '@primary-color': '#4377FE' } } } }, server: { proxy: { '/api': { target: 'http://localhost:5000' } } } })
|
添加上css,vitePluginImp
打包后使用serve服务器启动
下载
尝试了使用yarn,结果不行,可能要自己配置环境变量
启动打包后的文件