React
此文章类写法,函数写法参考其他文章
React框架有知识或者概念有很多和Vue类似,
此文章没有写详细知识点的描述,主要以语法配合demo的实际应用,
更多细节和知识点请参考官方手册。
概述
用于构建用户界面的JavaScript库(只关注View)
由 Facebook 开源
(1) 英文官网: https://reactjs.org/
(2)中文官网: https://doc.react-china.org/
特点
1、Declarative(声明式编码)
以声明式编写 UI,可以让你的代码更加可靠,且方便调试。
2、Component-Based(组件化编码)
组件逻辑使用 JavaScript 编写而非模版,因此你可以轻松地在应用中传递数据,并使得状态与 DOM 分离
3、Learn Once,Write Anywhere(支持客户端与服务器渲染)
React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用。
4、高效
5、单项数据流
依赖库
1、react.js (React的核心库)
2、react-dom.js (提供操作 DOM 的react扩展库)
3、babel.min.js(解析JSX语法代码转为纯JS语法代码的库)
测试
<body>
<div id="test"></div>
<script src="react.development.js"></script>
<script src="react-dom.development.js"></script>
<script src="babel.min.js"></script>
<script type="text/babel">
// 创建虚拟Dom元素对象
var vDom = <h1>Hello</h1>
// 将虚拟Dom渲染到页面真实Dom容器中
ReactDOM.render(vDom,document.getElementById('test'))
</script>
</body>
JSX
JSX是一种JavaScript的语法扩展,运用于React架构中,其格式比较像是模版语言,但事实上完全是在JavaScript内部实现的。
元素是构成React应用的最小单位,JSX就是用来声明React当中的元素,React使用JSX来描述用户界面。
作用: 用来创建react虚拟DOM(元素)对象
a. var ele = <h1>Hello JSX!</h1>
b. 注意1: 它不是字符串, 也不是HTML/XML标签
c. 注意2: 它最终产生的就是一个JS对象
基本语法规则
a. 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
b. 遇到以 { 开头的代码,以JS语法解析: 标签中的js代码必须用{ }包含
例如,以下代码:
<body>
<div id="container"></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script type="text/babel">
const myId = 'test'
const content = '我是内容'
// 创建虚拟dom
let element = <h1 id={myId}>{content}</h1>
// 渲染dom
ReactDOM.render(element,document.getElementById('container'))
</script>
</body>
babel.js的作用
a. 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
b. 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理
渲染虚拟DOM
1) 语法: ReactDOM.render(virtualDOM, containerDOM)
2) 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示
3) 参数说明
a. 参数一: 纯js或jsx创建的虚拟dom对象
b. 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)
建虚拟DOM的2种方式
(1) 纯JS(一般不用)
React.createElement('h1', {id:'myTitle'}, title)
(2) JSX:
<h1 id='myTitle'>{title}</h1>
创建列表(demo)
<body>
<div id="container"></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script type='text/babel'>
const data = ['first','second','third','fourth']
// 创建虚拟DOM
let element = (
<ul>
{
data.map((name,index) => <li key={index}>{name}</li>)
}
</ul>
)
// 渲染 DOM
ReactDOM.render(element,document.getElementById('container'))
</script>
</body>
组件
三大属性
1、state
state是组件对象最重要的属性, 值是对象(可以包含多个数据)
组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
2: props
每个组件对象都会有props(properties的简写)属性
组件标签的所有属性都保存在props中
作用:
1) 通过标签属性从组件外向组件内传递变化的数据
2) 注意: 组件内部不要修改props数据
3: refs
1) 组件内的标签都可以定义ref属性来标识自己
a. <input type="text" ref={input => this.msgInput = input}/>
b. 回调函数在组件初始化渲染完或卸载时自动调用
2) 在组件中可以通过this.msgInput来得到对应的真实DOM元素
3) 作用: 通过ref获取组件内容特定标签对象, 进行读取其相关数据
事件处理:
1) 通过onXxx属性指定组件的事件处理函数(注意大小写)
a. React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
b. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
语法
定义组件(2种方式)
(1) 创建组件
/*方式1: 工厂函数组件(简单组件)*/
function MyComponent () {
return <h2>工厂函数组件(简单组件)</h2>
}
/*方式2: ES6类组件(复杂组件)*/
class MyComponent2 extends React.Component {
render () {
console.log(this) // MyComponent2的实例对象
return <h2>ES6类组件(复杂组件)</h2>
}
}
(2) 渲染组件标签
ReactDOM.render(<MyComponent />, document.getElementById('example1'))
例如以下代码:
<body>
<div id="container"></div>
<div id="box"></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script type='text/babel'>
// 第一种方式(定义简单组件)
function MyComponent() {
return <h1>我是简单的组件</h1>
}
// ES6 类组件(复杂组件)
class MyClassComponent extends React.Component{
render(){
return <h1>我是类定义的复杂组件</h1>
}
}
ReactDOM.render(<MyClassComponent /> , document.getElementById('container'))
ReactDOM.render(<MyComponent />, document.getElementById('box'))
</script>
</body>
点击按钮(state属性的应用)
<body>
<div id="container"></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script type='text/babel'>
// 点击like切换组件
class LikeComponent extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = {
isLike: false
}
// 将新增方法的this强制绑定为组件对象,这样不会出现this丢失
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
console.log(this)
const isLike = !this.state.isLike
//更新状态
this.setState({ isLike })
}
// 重写组件类的方法
render() {
// 读取状态
const { isLike } = this.state
return <h2 onClick={this.handleClick}>{isLike ? '你喜欢我' : '我不喜欢'}</h2>
}
}
ReactDOM.render(<LikeComponent />, document.getElementById('container'))
</script>
</body>
组件传参(props属性的应用)
以下代码,设置了组件的默认参数,还有参数的类型和是否是必要传参
新增了prop-types.js文件,这个文件作用是更方便的去设置参数的类型和必要性
代码如下:
<body>
<div id="container"></div>
<div id="box"></div>
<div id="box2"></div>
<div id="box3"></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script src="./prop-types.js"></script>
<script type='text/babel'>
// 创建Person组件,第一种方法
function Person(props) {
return (
<ul>
<li>姓名:{props.name}</li>
<li>性别:{props.sex}</li>
<li>年龄:{props.age}</li>
</ul>
)
}
// 默认数据(默认性别为男,年龄18)-----指定默认值
Person.defaultProps = {
sex: '男',
age: 18
}
// 指定属性值类型和必要性(isRequired代表必须传参,否则报错)
Person.propTypes = {
name : PropTypes.string.isRequired,
age : PropTypes.number
}
// 参数数据
const jine = {
name: 'jine',
age: 22,
sex: '男'
}
// 参数数据
const ren = {
name: 'ren',
age: 18,
sex: '男'
}
// 参数数据,只传姓名参数,其它参数由组件默认提供
const onlyName = {
name: 'go'
}
// 创建Person组件,第二种方法
class PersonClass extends React.Component {
render() {
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{this.props.sex}</li>
<li>年龄:{this.props.age}</li>
</ul>
)
}
}
// 渲染DOM,原始传参
ReactDOM.render(<Person name={jine.name} age={jine.age} sex={jine.sex} />, document.getElementById('box'))
// 渲染DOM,ES6扩展运算符传参
ReactDOM.render(<Person {...jine} />, document.getElementById('container'))
// 渲染DOM,ES6扩展运算符传参(只传姓名参数,其他参数由组件默认提供)
ReactDOM.render(<Person {...onlyName} />, document.getElementById('box3'))
// 渲染DOM,ES6扩展运算符传参
ReactDOM.render(<PersonClass {...ren} />, document.getElementById('box2'))
</script>
</body>
输入框(refs属性和事件处理)
refs可以当做定位元素用(旧版本)
input => this.input = input (这是以下代码中新版的支持,也是官方建议写法)
<body>
<div id="container"></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script type='text/babel'>
// 定义组件
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.showInput = this.showInput.bind(this)
}
showInput() {
const input = this.refs.content
alert(input.value)
alert(this.input.value)
}
handleBlur(event) {
alert(event.target.value)
}
render() {
return (
<div>
<input type="text" ref='content' />
<input type="text" ref={input => this.input = input} />
<button onClick={this.showInput} >输入</button>
<button onClick={this.showInput} >输入</button>
<input type="text" onBlur={this.handleBlur} placeholder='失去焦点提示内容' />
</div>
)
}
}
// 渲染组件
ReactDOM.render(<MyComponent />, document.getElementById('container'))
</script>
</body>
Todo(组件应用综合demo)
<body>
<div id='container'></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script src="./prop-types.js"></script>
<script type='text/babel'>
// 定义父组件
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
todos: ['吃饭', '睡觉', '打代码']
}
this.addTodo = this.addTodo.bind(this)
}
addTodo(todo) {
const { todos } = this.state
todos.unshift(todo)
this.setState({ todos })
}
render() {
const { todos } = this.state
return (
<div>
<h1>ToDo List</h1>
<Add count={todos.length} addTodo={this.addTodo} />
<List todos={todos} />
</div>
)
}
}
// 定义子组件,Add
class Add extends React.Component {
constructor(props) {
super(props)
this.add = this.add.bind(this)
}
add() {
// 读取数据
const todo = this.input.value.trim()
// 检查合法性
if (!todo) return
this.props.addTodo(todo)
// 清楚输入
this.input.value = ''
}
render() {
return (
<div>
<input type="text" name="" id="" ref={input => this.input = input} />
<button onClick={this.add}>add #{this.props.count + 1}</button>
</div>
)
}
}
// 定义子组件,list
class List extends React.Component {
render() {
return (
<ul>
{
this.props.todos.map((todo, index) => {
return <li key={index}>{todo}</li>
})
}
</ul>
)
}
}
Add.propTypes = {
count: PropTypes.number.isRequired,
addTodo: PropTypes.func.isRequired
}
List.propTypes = {
todos: PropTypes.array.isRequired
}
// 渲染组件
ReactDOM.render(<App />, document.getElementById('container'))
</script>
</body>
表单提交(受控组件与非受控组件应用)
受控组件: 表单项输入数据能自动收集成状态(读的是state)
非受控组件: 需要时才手动读取表单输入框中的数据
<body>
<div id="container"></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script type='text/babel'>
// 定义表单组件
class MyForm extends React.Component {
constructor(props) {
super(props)
this.state = {
pwd: ''
}
this.handleSub = this.handleSub.bind(this)
this.handleChange = this.handleChange.bind(this)
}
// 提交处理
handleSub(event) {
// 获取用户姓名(非受控组件)
const username = this.username.value
// 获取密码(受控组件)
const { pwd } = this.state
alert(`提交的用户为:${username},密码为:${pwd}`)
// 阻止默认提交行为
event.preventDefault();
}
handleChange(event) {
const pwd = event.target.value
// 将用户输入的密码添加到state中
this.setState({ pwd })
}
render() {
return (
<form action="#">
用户名:
<input type="text" ref={name => this.username = name} />
密码:
<input type="password" onChange={this.handleChange} />
<input type="submit" value='提交' onClick={this.handleSub} />
</form>
)
}
}
// 渲染 DOM
ReactDOM.render(<MyForm />, document.getElementById('container'))
</script>
</body>
组件生命周期
React 16 之后有三个生命周期被废弃(但并未删除)
componentWillMount
componentWillReceiveProps
componentWillUpdate
目前React 16.8 +的生命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段。
1、挂载阶段:
constructor: 构造函数,最先被执行,我们通常在构造函数里初始化state对象或者给自定义方法绑定this
getDerivedStateFromProps: static getDerivedStateFromProps(nextProps, prevState),这是个静态方法,当我们接收到新的属性想去修改我们state,可以使用getDerivedStateFromProps
render: render函数是纯函数,只返回需要渲染的东西,不应该包含其它的业务逻辑,可以返回原生的DOM、React组件、Fragment、Portals、字符串和数字、Boolean和null等内容
componentDidMount: 组件装载之后调用,此时我们可以获取到DOM节点并操作,比如对canvas,svg的操作,服务器请求,订阅都可以写在这个里面,但是记得在componentWillUnmount中取消订阅
2、更新阶段:
getDerivedStateFromProps: 此方法在更新个挂载阶段都可能会调用
shouldComponentUpdate: shouldComponentUpdate(nextProps, nextState),有两个参数nextProps和nextState,表示新的属性和变化之后的state,返回一个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回true,我们通常利用此生命周期来优化React程序性能
render: 更新阶段也会触发此生命周期
getSnapshotBeforeUpdate: getSnapshotBeforeUpdate(prevProps, prevState),这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用
componentDidUpdate: componentDidUpdate(prevProps, prevState, snapshot),该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统一触发回调或更新状态。
3、卸载阶段:
componentWillUnmount: 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除定时器,取消网络请求或清除在 componentDidMount() 中创建的订阅,清理无效的DOM元素等垃圾清理工作。
异常处理:
static getDerivedStateFromError: 此生命周期会在渲染阶段后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state。
componentDidCatch:此生命周期在后代组件抛出错误后被调用。 它接收两个参数:1. error —— 抛出的错误。2. info —— 带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息。componentDidCatch 会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况。
上张图的生命周期细节坑比较多,建议先会使用这张图的生命周期:
计时器(生命周期应用)
<body>
<div id="container"></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script type="text/babel">
class Life extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = {
opacity: 1
}
this.componentWillUnmount = this.componentWillUnmount.bind(this)
}
componentDidMount() {
this.inter = setInterval(() => {
console.log('计时器...')
console.log(this, 'aa')
let { opacity } = this.state
opacity -= 0.1
if (opacity < 0) {
opacity = 1
}
this.setState({ opacity })
}, 100)
}
componentWillUnmount() {
clearInterval(this.inter)
}
render() {
const { opacity } = this.state
return (
<div>
<h2 style={{ opacity: opacity }}>{this.props.msg}</h2>
<button onClick={this.componentWillUnmount}>暂停</button>
</div>
)
}
}
ReactDOM.render(<Life msg='我是React' />, document.getElementById('container'))
</script>
</body>
虚拟DOM与DOM Diff算法
基本原理图,如下:
具体diff算法实现,可以参考我其他文章
React 脚手架
概述
(1) xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
a. 包含了所有需要的配置
b. 指定好了所有的依赖
c. 可以直接安装/编译/运行一个简单效果
(2) react提供了一个用于创建react项目的脚手架库: create-react-app(这的项目采用了官方脚手架帮助学习-因可以兼容低版本。企业应用项目建议用新的脚手架 next.js来创建-兼容版本为16以上,性能相对更好)
(3) 项目的整体技术架构为: react + webpack + es6 + eslint
(4) 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
安装并使用
npm install -g create-react-app
create-react-app hello-react
cd hello-react
npm start
评论(demo)
访问链接:
异步请求
axios(结合react)
<body>
<div id="container"></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.0/axios.js"></script>
<script type='text/babel'>
class AsyncRepo extends React.Component {
state = {
repoName: '',
repoUrl: ''
}
componentDidMount() {
const url = 'https://api.github.com/search/repositories?q=re&sort=stars'
axios.get(url).then(res => {
// 得到数据
console.log(res)
const { name, html_url } = res.data.items[0]
// 更新状态
this.setState({ repoName: name, repoUrl: html_url })
})
}
render() {
const { repoName, repoUrl } = this.state
if (!repoName) {
return <h2>LOADING...</h2>
} else {
return <h2>Most star repo is <a href={repoUrl}>{repoName}</a></h2>
}
}
}
ReactDOM.render(<AsyncRepo />, document.getElementById('container'))
</script>
</body>
fetch(结合react)
fetch(url).then(res=>{
return res.json()
}).then(data =>{
const { name, html_url } = data.items[0]
// 更新状态
this.setState({ repoName: name, repoUrl: html_url })
})
}
组件通信
props 传递
1) 共同的数据放在父组件上, 特有的数据放在自己组件内部(state)
2) 通过props可以传递一般数据和函数数据, 只能一层一层传递
3) 一般数据-->父组件传递数据给子组件-->子组件读取数据
4) 函数数据-->子组件传递数据给父组件-->子组件调用函数
缺点:
若兄弟组件或祖孙组件,就不能直接传,还要借用第三个中间组件来一层层传递
1) 共同的数据放在父组件上, 特有的数据放在自己组件内部(state)
2) 通过props可以传递一般数据和函数数据, 只能一层一层传递
3) 一般数据-->父组件传递数据给子组件-->子组件读取数据
4) 函数数据-->子组件传递数据给父组件-->子组件调用函数
缺点:
若兄弟组件或祖孙组件,就不能直接传,还要借用第三个中间组件来一层层传递
使用消息订阅(subscribe)-发布(publish)机制
1) 工具库: PubSubJS
2) 下载: npm install pubsub-js --save
3) 使用:
import PubSub from 'pubsub-js' //引入
PubSub.subscribe('delete', function(data){ }); //订阅
PubSub.publish('delete', data) //发布消息
这样可以实现任意组件通信
redux
redux也可以实现任意组件通信,后面单独写redux内容
路由(react-router4)
概述
1) react的一个插件库
2) 专门用来实现一个SPA应用
3) 基于react的项目基本都会用到此库
SPA
1) 单页Web应用(single page web application,SPA)
2) 整个应用只有一个完整的页面
3) 点击页面中的链接不会刷新页面, 本身也不会向服务器发请求
4) 当点击路由链接时(相当跳转到了子组件), 只会做页面的局部更新
5) 组件内容数据都需要通过ajax请求获取, 并在前端异步展现
路由的理解
1) 什么是路由?
a. 一个路由就是一个映射关系(key:value)
b. key为路由路径, value可能是function/component
2) 路由分类
a. 后台路由: node服务器端路由, value是function, 用来处理客户端提交的请求并返回一个响应数据
b. 前台路由: 浏览器端路由, value是component, 当请求的是路由path时, 浏览器端前没有发送http请求, 但界面会更新显示对应的组件
3) 后台路由
a. 注册路由: router.get(path, function(req, res))
b. 当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
4) 前端路由
a. 注册路由: <Route path="/about" component={About}>
b. 当浏览器的hash变为#about时, 当前路由组件就会变为About组件
路由实现
1) history库
a. 网址: https://github.com/ReactTraining/history
b. 管理浏览器会话历史(history)的工具库
c. 包装的是原生BOM中window.history和window.location.hash
2) history API
a. History.createBrowserHistory(): 得到封装window.history的管理对象
b. History.createHashHistory(): 得到封装window.location.hash的管理对象
c. history.push(): 添加一个新的历史记录
d. history.replace(): 用一个新的历史记录替换当前的记录
e. history.goBack(): 回退到上一个历史记录
f. history.goForword(): 前进到下一个历史记录
g. history.listen(function(location){}): 监视历史记录的变化
react-router相关API
1. 组件
1) <BrowserRouter> (使用HTML5的history API(pushState, replaceState和popState),让页面的UI与URL同步。)
2) <HashRouter> (使用的是URL的hash部分(即window.location.hash))
3) <Route>
4) <Redirect>
5) <Link>
6) <NavLink>
7) <Switch>
2. 其它
1) history对象
2) match对象
3) withRouter函数
安装
1) 下载react-router: npm install --save react-router-dom
2) 引入bootstrap.css: <link rel="stylesheet" href="/css/bootstrap.css">
使用
关键代码如下:
// 导入路由库
import {Route, Switch, Redirect} from 'react-router-dom'
{/*导航路由链接*/}
<MyNavLink className="list-group-item" to='/about' >About</MyNavLink>
<MyNavLink className="list-group-item" to='/home'>Home</MyNavLink>
{/*可切换的路由组件,渲染跳转*/}
<Switch>
<Route path='/about' component={About} />
<Route path='/home' component={Home} />
// 重定向
<Redirect to='/about' />
</Switch>
index.js 文件下的配置
import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter, HashRouter} from 'react-router-dom'
import App from './components/app'
import './index.css'
ReactDOM.render(
(
<BrowserRouter>
<App />
</BrowserRouter>
/*<HashRouter>
<App />
</HashRouter>*/
),
document.getElementById('root')
)
React UI 组件库
常用库
material-ui (国外)
官网:http://www.material-ui.com/#/
gitHub: https://github.com/callemall/material-ui
2、ant-design(国内蚂蚁金服)
PC 官网:https://ant.design/index-cn
移动官网:https://mobile.ant.design/index-cn
Github:https://github.com/ant-design/ant-design/
Github:https://github.com/ant-design/ant-design-mobile/
安装:
npm install antd --save
组件按需打包(ant-design)
有的组件有自己的css样式和js特效,那么原来的做法是按照一键导入,需要import 一个总的css包或js包,但这样有点浪费资源。所以可以进行如下配置,实现自动用哪个组件按需要导入相应的css或js包
1、下载依赖包
yarn add antd
yarn add @craco/craco
yarn add craco-less
'请注意':
下面这个依赖包在2.0版本后已经废弃
yarn add react-app-rewired customize-cra babel-plugin-import
若非想要安装这个依赖包,只能是降低自己的 react-app-rewired 版本(不建议这样做)
// 从 2.0版本开始 react-app-rewired 很多东西被废弃了,这里采用官方推荐的craco
2、修改默认配置(package.json文件)
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test"
}
3、创建文件(config-overrides.js文件)
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
},
},
],
};
4、在src文件夹下创建App.less文件
@import '~antd/dist/antd.less';
5、使用相应组件(如按钮)
import React, { Component } from 'react'
import { Button } from 'antd';
// 导入配置好的less文件
import './App.less';
// 这样就不用导入这个CSS总文件
//import 'antd/dist/antd.css';
export default class App extends Component {
state = {
loadings: [],
};
enterLoading = index => {
this.setState(({ loadings }) => {
const newLoadings = [...loadings];
newLoadings[index] = true;
return {
loadings: newLoadings,
};
});
setTimeout(() => {
this.setState(({ loadings }) => {
const newLoadings = [...loadings];
newLoadings[index] = false;
return {
loadings: newLoadings,
};
});
}, 6000);
};
render() {
const { loadings } = this.state;
return (
<>
<Button type="primary" loading={loadings[0]} onClick={() => this.enterLoading(0)}>
Click me!
</Button>
</>
);
}
}