React入门到入土系列(二)

88 阅读13分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

React应用篇

基于React脚手架

使用create-react-app: 创建react应用

1. react脚手架

  • xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
  1. 包含了所有需要的配置(语法检查、jsx编译、devServer…)
  2. 下载好了所有相关的依赖
  3. 可以直接运行一个简单效果
  • react提供了一个用于创建react项目的脚手架库: create-react-app
  • 项目的整体技术架构为: react + webpack + es6 + eslint
  • 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化

2. 创建项目并启动

第一步,全局安装:npm i -g create-react-app

第二步,切换到想创项目的目录,使用命令:create-react-app hello-react

第三步,进入项目文件夹:cd hello-react

第四步,启动项目:npm start

3. react脚手架项目结构


public ---- 静态资源文件夹

favicon.icon ------ 网站页签图标

index.html -------- 主页面

logo192.png ------- logo图

logo512.png ------- logo图

manifest.json ----- 应用加壳的配置文件

robots.txt -------- 爬虫协议文件

src ---- 源码文件夹

App.css -------- App组件的样式

App.js --------- App组件

App.test.js ---- 用于给App做测试

index.css ------ 样式

index.js ------- 入口文件

logo.svg ------- logo图

reportWebVitals.js

--- 页面性能分析文件(需要web-vitals库的支持)

setupTests.js

---- 组件单元测试的文件(需要jest-dom库的支持)

  • 导包规范:第三方的写在上面,自己的写在下面,样式写作最后

了解:

<React.StrictMode>包裹作用:检测App组件和App子组件的样式是否合理,用于检测组件中的错误

<React.StrictMode>
  <App />
</React.StrictMode>
  • {Component}:这里不是解构赋值

image-20220511165005051.png

注意: 这里{Component}不是解构赋值出来的,是分别暴露出来的 image-20220511164941338.png

css样式冲突

  • 为了避免这样的css样式冲突,需要做样式模块化

image-20220511182558849.png

css文件改为:Hello.module.css

// import './Hell.css' 改为:
import hello from './Hello.module.css'
​
{/* <h2 className="title">Hello React!</h2> */} 改为:
<h2 className={hello.title}>Hello React!<
    /h2>

:golbal一般用于定义的类名不允许修改的情况,需要写上父类的类名,例 .aa

image-20220523013750285.png

css module 配合sass (最常用

  1. 创建xxx.module.scss
  2. 将父元素使用被module转换了的类名
import style from './index.moudule.scss'
​
<div className="style.father"></div>
  1. 子元素用被 global 包裹的全局属性
.layout{
    // 写layout的样式
    :global{
        // 子元素的样式
    }
}

image-20220523014533925.png

4. 功能界面的组件化编码流程(通用)


  • 拆分组件: 拆分界面,抽取组件
  • 实现静态组件: 使用组件实现静态页面效果
  • 实现动态组件
  1. 动态显示初始化数据

    1. 数据类型
    2. 数据名称
    3. 保存在哪个组件?
  • 交互(从绑定事件监听开始)

5. TodoList 案例(需要掌握):


  • 安装插件:快速编码

image-20220511184142747.png

  • 获取全球唯一的id:npm i nanoid
  • 效果图:

image-20220512124220227.png

  • 案例结构图:

image-20220512124559027.png

  • 案例总结:

image-20220512112437406.png

主要步骤:

1.将状态存入父亲App的state中,然后通过props传递给List子节点

2.在list中,拿到todos通过map遍历,动态生成Item组件,再将数据传给每一个IItem

3.在Item中,通过拿到的数据中的name,done来渲染页面中的

4.将父亲App的state,通过props传递给Header子节点,通过输入框输入name,并通过addTodo函数返回给父节点App

5.点击Item,并返回该Item的id,和done,通过updateTodo函数传递给App,进行判断并跟新done

6.点击Item,并返回该Item的id,通过deleteTodo函数筛选出来与Item传过来的id不相同的newTodos,并存入状态重新渲染

7.在Item中给state中定义一个mouse=false,通过鼠标移入,移出事件,切换mouse状态,来改变背景

8.在Footer中,拿到App中的state,通过reduce或map来遍历里面的done,来确定已完成的个数,通过state的长度来判断总数

9.在Footer中,通过handleCheckAll函数,拿到全选按钮的checked布尔型,传给App跟新todos的done

10.在Footer中,通过已完成和全部的个数,来判断全选按钮的checked属性,为true或者false

11.在Footer中,点击清除完成任务按钮,调用App传来的claerAllDone函数,筛选出done=false的newObj,再重新存入状态重新

12.每个子节点对接收的props进行:类型,必要的限制

注意点:

  • defaultChecked={done}:表示能够动态勾选

但是存在bug,它以一第一次勾选为准

<input type="checkbox" defaultChecked={done} />
  • 兄弟传递数据的时候:这里是this.props不是this.state
const { todos, updateTodo, deleteTodo } = this.props

第4章:React ajax

1. 常用的ajax请求库

  • jQuery: 比较重, 如果需要另外引入不建议使用

  • axios: 轻量级, 建议使用

    1. 封装XmlHttpRequest对象的ajax

    2. promise风格

    3. 可以用在浏览器端和node服务器端

2. axios

  1. GET请求
axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response.data);
  })
  .catch(function (error) {
    console.log(error);
  });
​
axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
​
  1. POST请求
axios.post('/user', {
  firstName: 'Fred',
  lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
​

直接使用axios发起GET请求

axios({
    method:'get',
    url:'https://api.github.com/search/users?q=xxxxxx',
}).then(
    response => {console.log('成功了',response.date);},
    error => {console.log("失败了",error);}
)

3. 配置中间代理


方法一

在package.json中追加如下配置

"proxy":"http://localhost:5000"

说明:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

方法二


  1. 第一步:创建代理配置文件

    src下创建配置文件:src/setupProxy.js
    
  2. 编写setupProxy.js配置具体代理规则:

    //低版本
    //const proxy = require('http-proxy-middleware')
    
    //高版本
    const { createProxyMiddleware: proxy } = require('http-proxy-middleware');
    
    module.exports = function(app) {
      app.use(
        proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
          target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
          changeOrigin: true, //控制服务器接收到的请求头中host字段的值
          /*
          	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
          	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
          	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
          */
          pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
        }),
        proxy('/api2', { 
          target: 'http://localhost:5001',
          changeOrigin: true,
          pathRewrite: {'^/api2': ''}
        })
      )
    }
    

说明:

  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

4. github搜索案例:


效果图:

image-20220514130548265.png

总结:

	1.设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
	2.ES6小知识点:解构赋值+重命名
				let obj = {a:{b:1}}
				const {a} = obj; //传统解构赋值
				const {a:{b}} = obj; //连续解构赋值
				const {a:{b:value}} = obj; //连续解构赋值+重命名
	3.消息订阅与发布机制
				1.先订阅,再发布(理解:有一种隔空对话的感觉)
				2.适用于任意组件间通信
				3.要在组件的componentWillUnmount中取消订阅
	4.fetch发送请求(关注分离的设计思想)
				try {
					const response= await fetch(`/api1/search/users2?q=${keyWord}`)
					const data = await response.json()
					console.log(data);
				} catch (error) {
					console.log('请求出错',error);
				}

步骤:

// ------不带订阅功能-------
// 1.通过Header中get获取到了用户信息,将其通过函数参数发送到App的state中
// 2.将App的state,发送给List中,进行数据渲染

// 3.完善
// 4.定义isfirst,islanding,error存入App状态,用于显示初始页面,加载完成,出错页面
// 4.1首先再Header中,在请求网络前,后,来定义这些属性的布尔型,再通过updateAppState函数传递给App
// 4.2然后将App所有状态传给List,用于显示初始页面,加载完成,出错页面

// ------带订阅功能(兄弟节点可以直接通信)-------
// 1.安装PubSubJS工具库
// 2.直接将state定义在List中,再订阅一下消息
// 3.在Header中,把通过网络请求接受到的消息,以发布消息的形式,发送给List

小技巧:连续结构赋值+重命名:

// 获取用户的输入(连续解构赋值+重命名)
const { keyWordElement: { value: keyWord } } = this
console.log(keyWord); // wh

image-20220513210030336.png 注意:判断传过来到数据是数组,还是对象

// 拿到用户信息,传入saveUser函数
// 注意:这里传的是items用户信息,不是response.data
this.props.saveUser(response.data.items)

可以这样将App状态中的数据,批量传给List的props中

 <List  {...this.state} />

优化


  • 消息订阅-发布机制

通过PubSubJS工具,来实现兄弟之间的通信

List中:

// 挂载完成,订阅消息,使用
componentDidMount() {
    this.token = PubSub.subscribe('wh', (_, userObj) => {
        this.setState(userObj)
        console.log(this);
    })
}
// 将要卸载,用完取消订阅
componentWillUnmount() {
    PubSub.unsubscribe(this.token)
}

Header中:

PubSub.publish('wh', { islanding: false, users: response.data.items })
  • 通过fetch发送网络请求:
// 发送网络请求,使用fetch发送(优化)
try {
    const response = await fetch(`/api1/search/users?q=${keyWord}`)
    const data = await response.json()
    console.log(data);
    PubSub.publish('wh', { islanding: false, users: data.items })
} catch (error) {
    console.log('请求出错了', error);
    PubSub.publish('wh', { islanding: false, error: error.message })
}
// 注:这段代码如果想运行,外面需要包一个 async function

5.扩展:Fetch(了解)


文档:

特点:

  • fetch: 原生函数,不再使用XmlHttpRequest(xhr)对象提交ajax请求
  • 老版本浏览器可能不支持

相关API:

  1. GET请求
fetch(url).then(function(response) {
    return response.json()
  }).then(function(data) {
    console.log(data)
  }).catch(function(e) {
    console.log(e)
  });
  1. POST请求
  fetch(url, {
    method: "POST",
    body: JSON.stringify(data),
  }).then(function(data) {
    console.log(data)
  }).catch(function(e) {
    console.log(e)
  })

第5章:React路由

1. SPA的理解

  • 单页Web应用(single page web application,SPA)。
  • 整个应用只有一个完整的页面
  • 点击页面中的链接不会刷新页面,只会做页面的局部更新。
  • 数据都需要通过ajax请求获取, 并在前端异步展现。

2. 路由的理解

1. 什么是路由?

一个路由就是一个映射关系(key:value)

key为路径, value可能是function或component

2. 路由分类

后端路由:

  1. 理解: value是function, 用来处理客户端提交的请求。

  2. 注册路由: router.get(path, function(req, res))

  3. 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

前端路由:

  1. 浏览器端路由,value是component,用于展示页面内容。

  2. 注册路由: <Route path="/test" component={Test}>

  3. 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

3. react-router-dom的理解

react的一个插件库。

专门用来实现一个SPA应用。

基于react的项目基本都会用到此库。

4.前端路由基石:

效果图:

image-20220514134224188.png

通过浏览器中的 history 来记录路径,replace 替换掉上一条路径(点击返回:到上上条路径),push(栈的方式)来最追加路径

	<a href="http://www.atguigu.com" onclick="return push('/test1') ">push test1</a><br><br>
	<button onClick="push('/test2')">push test2</button><br><br>
	<button onClick="replace('/test3')">replace test3</button><br><br>
	<button onClick="back()">&lt;= 回退</button>
	<button onClick="forword()">前进 =&gt;</button>

	<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
	<script type="text/javascript">
		// let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API
		let history = History.createHashHistory() //方法二,hash值(锚点)(性能最好)带#号

		function push (path) {
			history.push(path)
			return false
		}
		//replace替换掉上一条路径
		function replace (path) {
			history.replace(path)
		}

		function back() {
			history.goBack()
		}

		function forword() {
			history.goForward()
		}

		history.listen((location) => {
			console.log('请求路由路径变化了', location)
		})
	</script>

5. react-router-dom相关API

1.	<BrowserRouter>
    <HashRouter>
    <Route>
    <Redirect>
    <Link>
    <NavLink>
    <Switch>
2.其它
    history对象
    match对象
    withRouter函数

路由的基本使用


1.明确好界面中的导航区、展示区
2.导航区的a标签改为Link标签
	<Link to="/xxxxx">Demo</Link>
3.展示区写Route标签进行路径的匹配
	<Route path='/xxxx' component={Demo}/>
4.<App>的最外侧包裹了一个<BrowserRouter><HashRouter>

下载:npm i react-router-dom@5.2.0老版本(学习用)

App.jsx下:

import { Link, Route } from 'react-router-dom'
<div className="list-group">
    {/* 原生html中,靠a标签跳转不同的页面 */}
    {/* <a className="list-group-item" href="./about.html">About</a>
    <a className="list-group-item active" href="./home.html">Home</a> */}

    {/* 在React中靠路由链接实现切换组件 --编写路由*/}
    {/* 一般用小写:/about */}
    <Link className="list-group-item" to="/about">About</Link>
    <Link className="list-group-item" to="/home">Home</Link>
</div>
<div className="panel-body">
    {/* 注册路由 */}
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
</div>

index.js下:

root.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>
);

路由组件与一般组件


1.写法不同:
    一般组件:<Demo/>
    路由组件:<Route path="/demo" component={Demo}/>
2.存放位置不同:
    一般组件:components
    路由组件:pages
3.接收到的props不同:
    一般组件:写组件标签时传递了什么,就能收到什么
    路由组件:接收到三个固定的属性
        history:
                    go: ƒ go(n)
                    goBack: ƒ goBack()
                    goForward: ƒ goForward()
                    push: ƒ push(path, state)
                    replace: ƒ replace(path, state)
        location:
                    pathname: "/about"
                    search: ""
                    state: undefined
        match:
                    params: {}
                    path: "/about"

image-20220514150433522.png

NavLink与封装NavLink


  • 给选中的标签加样式:用NavLink
import { Route, NavLink } from 'react-router-dom'

<NavLink activeClassName="bgc" className="list-group-item" to="/home">Home</NavLink>
<NavLink activeClassName="bgc" className="list-group-item" to="/about">About</NavLink>
  • 封装自己的NavLink:MyNavLink组件

components-MyNavLink-index.jsx下:

import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'

export default class MyNavLink extends Component {
    render() {
        // console.log(this.props); 
        // {to: './about', children: 'About'}
        return (
            <NavLink activeClassName="bgc" className="list-group-item" {...this.props} />
        )
    }
}

使用:

<MyNavLink to="/home">Home</MyNavLink>
<MyNavLink to="/about">About</MyNavLink>

Switch的使用:


匹配到了/about后,就不会继续向下匹配

import { Route, NavLink, Switch } from 'react-router-dom'

<Switch>
    <Route path="/home" component={Home} />
    <Route path="/about" component={About} />
    <Route path="/about" component={Test} />
</Switch>

总结:

NavLink可以实现路由链接的高亮,通过activeClassName指定样式名

通常情况下,path和component是一一对应的关系

Switch可以提高路由匹配效率(单一匹配)。

解决多级路径刷新页面样式丢失的问题:


1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
3.使用HashRouter

路由的严格匹配与模糊匹配

1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2.开启严格匹配:<Route exact={true} path="/about" component={About}/>
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

Redirect的使用

1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2.具体编码:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>

嵌套路由


1.注册子路由时要写上父路由的path2.路由的匹配是按照注册路由的顺序进行的
<Switch>
    <Route path="/home/news" component={News} />
    <Route path="/home/message" component={Message} />
    <Redirect to="/home/news" />
</Switch>

image-20220514191238417.png

向路由组件传递参数(重点)


总结:

1.params参数(常用)
    路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
    注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
    接收参数:this.props.match.params
    优势 : 刷新地址栏,参数依然存在 缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋
2.search参数(其次)
    路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
    注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    接收参数:this.props.location.search
    备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
3.state参数(一般)
    路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
    注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    接收参数:this.props.location.state
    优势:刷新也可以保留住参数

image-20220514202615698.png

Message下:编写路由

messageArr.map((msgObj) => {
    return (
        <li key={msgObj.id}>
            {/* 1.params方式 */}
            <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>

            {/* 2.search方式 */}
            <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>

            {/* 3.state方式 */}
            <Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
        </li>
    )
})

声明路由:

{/* 1.声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail} /> 

{/* 2.search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail} /> 

{/* 3.state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail} />

Detail下:接收参数

// 1.接收params参数
const { id, title } = this.props.match.params

// 2.接收saerch参数
const { search } = this.props.location
// search = '?id=01&title=消息1'
// console.log(search.slice(1)); //id=01&title=消息1
const { id, title } = qs.parse(search.slice(1))

// 3.接收state参数
const { id, title } = this.props.location.state || {}

//4.筛选结果
const findResult = DetailData.find((obj) => {
    return obj.id === id
}) || {}

编程式路由导航


说明:点击按钮,实现push,replace,go.....跳转,主要操作的是history下的这些方法

image-20220515093935809.png

Message下:

<li key={msgObj.id}>
    {/* 1.params方式 */}
    <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>

	{/* 4.编程式路由导航 */}
    &nbsp;<button onClick={() => { this.pushShow(msgObj.id, msgObj.title) }>push查看</button>
    &nbsp;<button onClick={() => { this.replaceShow(msgObj.id, msgObj.title) }>replace查看</button>
</li>
{/* 1.声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail} />


{/* 4.编程式路由导航 */}
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
replaceShow = (id, title) => {
    // 编写一段代码,让其实现跳转到Detail组件,且为replace跳转
    this.props.history.replace(`/home/message/detail/${id}/${title}`)

    // 编写一段代码,让其实现跳转到Detail组件,且为search跳转
    // this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`)

    // 编写一段代码,让其实现跳转到Detail组件,且为state跳转
    // this.props.history.replace('/home/message/detail', { id, title })
}
pushShow = (id, title) => {
    // 编写一段代码,让其实现跳转到Detail组件,且为replace跳转
    this.props.history.push(`/home/message/detail/${id}/${title}`)

    // 编写一段代码,让其实现跳转到Detail组件,且为search跳转
    // this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`)

    // 编写一段代码,让其实现跳转到Detail组件,且为state跳转
    // this.props.history.push('/home/message/detail', { id, title })
}

back = () => {
    this.props.history.goBack()
}

forward = () => {
    this.props.history.goForward()
}

go = () => {
    this.props.history.go(2)
}

或设置自动跳转:

在News下:

componentDidMount() {
    setInterval(() => {
        this.props.history.push(`/home/message/detail/`)
    }, 2000)
}

withRouter


withRouter可以加工一般组件让一般组件具备路由组件所特有的API

withRouter的返回值是一个新组件

import { withRouter } from 'react-router-dom'

export default withRouter(Home)

BrowserRouter与HashRouter的区别

1.底层原理不一样:
    BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
    HashRouter使用的是URL的哈希值。
2.path表现形式不一样
    BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
    HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
    (1).BrowserRouter没有任何影响,因为state保存在history对象中。
    (2).HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题。

第6章:React UI组件库

1. material-ui(国外)

官网: www.material-ui.com/#/

github: github.com/callemall/m…

2. ant-design(国内蚂蚁金服)

官网: ant.design/index-cn

Github: github.com/ant-design/…

3. antd的按需引入+自定主题

	1.安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import less less-loader
	2.修改package.json
			....
				"scripts": {
					"start": "react-app-rewired start",
					"build": "react-app-rewired build",
					"test": "react-app-rewired test",
					"eject": "react-scripts eject"
				},
			....
	3.根目录下创建config-overrides.js
			//配置具体的修改规则
			const { override, fixBabelImports,addLessLoader} = require('customize-cra');
			module.exports = override(
				fixBabelImports('import', {
					libraryName: 'antd',
					libraryDirectory: 'es',
					style: true,
				}),
				addLessLoader({
					lessOptions:{
						javascriptEnabled: true,
						modifyVars: { '@primary-color': 'green' },
					}
				}),
			);
		4.备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css'应该删掉

error:

WARNING in ./node_modules/antd/dist/antd.css (./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[5].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[5].use[2]!./node_modules/source-map-loader/dist/cjs.js!./node_modules/antd/dist/antd.css)
Compiled successfully!

出现这个问题是因为 react-script 升级到5.0.0,通过npx create-react-app 创建的项目,引入antd.css 之后会看到这个警告。 解决方法就是 将 import 'antd/dist/antd.css'; 替换为 import 'antd/dist/antd.min.css';