React学习笔记 --- Ant Design的基本使用

1,057 阅读7分钟

一、什么是Antd

AntDesign ,简称 antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。

  • 中后台的产品 属于工具性产品,很多优秀的设计团队通过自身的探索和积累,形成了自己的设计体系。

AntDesign的特点:

  • 提炼自企业级中后台产品的交互语言和视觉风格。
  • 开箱即用的高质量 React 组件。
  • 使用 TypeScript 开发,提供完整的类型定义文件。
  • 全链路开发(从业务战略—用户场景—设计目标—交互体验—用户流程—预期效率全方面进行分析和考)和设计工具体系。
  • 数十个国际化语言支持。
  • 深入每个细节的主题定制能力

安装

npm install antd –save

# 或者使用 (注意: 库名叫做antd 不是ant-design)
yarn add antd

tips:默认情况下引入的antd的组件是没有样式的,如果需要引入的组件存在样式,需要将antd的样式在全局进行引入

import "antd/dist/antd.css";

tips: 在默认情况下,antd将其所有的icons导出为了一个单独的包,如果需要使用antd中的icons,则需要单独下载并引入对应包中的icon

yarn add '@ant-design/icons'

import { PoweroffOutlined } from '@ant-design/icons';

Antd是否会将一些没有用的代码(组件或者逻辑代码)引入,造成包很大呢?

antd 官网有提到:antd 的 JS 代码默认支持基于 ES modulestree shaking,对于 js 部分,直接引入 import { Button } from 'antd' 就会有按需加载的效果

什么是tree shaking

所谓的tree shaking就是在打包的过程中,可能存在一些引入的代码中存在一些重来就没有被使用过的代码(dead-code)

于是在编译的时候摇一摇这颗组件树,将没有使用的函数,组件,和变量进行移除,这个操作就是tree shaking

这个过程被称为DCE( dead code elimination(消除死代码)), tree shakingDCE概念的一种技术实现

这个概念最早是由rollup完成的(rollup是基于ES2015实现的,tree shaking是ES6 moudle中特性, ES6引入自己的模块格式的一个原因是启用静态结构静态结构意味着您可以在编译时确定导入和导出(静态) --- 您只需要查看源代码,而不必执行它)

参考 Tree-Shaking 和ES6 module

webpack和rollup的区别?

webpack的优势在于静态资源分析和代码切割

rollup轻量,高效,打包后体积更小

所以webpack适合于有大量静态资源的项目(有大量图片,html,css,js)的项目

rollup适合于那些不是特别注重界面的项目(如vue,react)

总结: 在开发应用时使用 Webpack,开发库时使用 Rollup

参考 Webpack vs Rollup

二、 高级配置

对Antd中主题等相关的高级特性进行配置,需要修改create-react-app 的默认配置。

如何修改create-react-app 的默认配置呢?

  • 前面我们讲过,可以通过yarn run eject(CRA)来暴露出来对应的配置信息进行修改;
  • 但是对于webpack并不熟悉的人来说,直接修改 CRA 的配置是否会给你的项目带来负担,甚至会增加项目的隐患和不稳定 性呢?
  • 所以,在项目开发中是不建议大家直接去修改 CRA 的配置信息的;

那么如何来进行修改默认配置呢?社区目前有两个比较常见的方案:

  1. react-app-rewired + customize-cra;(这个是antd早期推荐的方案)
  2. craco;(目前antd推荐的方案)

craco是第三方的社区解决方案,不是react自带的解决方案

2.1 craco的基本使用

1. 安装

# @scope/project-name 这是npm提供的范围包的命名规范
# 之前的@xxx 可以是一个用户也可以是一个机构
# 这样可以在多个人或机构之间发布同名的包,而不会产生冲突
yarn add @craco/craco

2. 修改配置 将原来使用react-scripts启动的脚本修改为使用craco来启动

"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
+ "start": "craco start",
+ "build": "craco build",
+ "test": "craco test",
}

3.在根目录下创建craco.config.js文件用于修改默认配置

// 这个文件一般被设置在根目录下,以便于在设置类似于别名等配置的时候,查找路径方便
module.exports = {
	// 配置文件
}

2.2 修改antd默认主题

主题 === 全局样式

具体使用步骤参考:在 create-react-app 中使用中的高级配置

  1. 因为antd的样式使用的是less进行编写,所以如果需要我们自己去定义配置修改默认配置的话,必须使用的是less格式的样式,所以我们需要引入的是less格式的antd全局样式,而不是css格式的antd全局样式

    - import 'antd/dist/antd.css'
    + import 'antd/dist/antd.less'
    
  2. 在CRA中默认对于webpack的配置是隐藏的,所以我们需要按craco来帮助我们修改webpack的样式,(其会将craco.config.js中的样式和webpack.config.js中的样式进行合并,且craco.config.js中的样式会覆盖webpack.config.js中的样式)

craco.config.js

// 在这里需要引入craco-less来帮助我们在解析的时候,将modifyVars中设置的值覆盖原本antd中的原本值
const CracoLessPlugin = require('craco-less');

module.exports = { // => 导出配置
  plugins: [ // => 配置的插件列表 因为可能存在多个plugin所以使用数组的方式
    {
      plugin: CracoLessPlugin, //=> 插件名称
      options: { // => 配置选项,具体查看文档
        lessLoaderOptions: {
          lessOptions: {
            modifyVars: { '@primary-color': '#1DA57A' },
            javascriptEnabled: true,
          },
        },
      },
    },
  ],
};

因为修改了配置文件,所以需要重启项目,才可以使配置文件生效

配置别名

const CracoLessPlugin = require('craco-less')
const path = require('path')

// 定义公共方法
const resolve = dir => path.resolve(__dirname, dir)

module.exports = {
  plugins: [
    {
      plugin: CracoLessPlugin,
      options: {
        lessLoaderOptions: {
          lessOptions: {
            modifyVars: { '@primary-color': '#1DA57A' },
            javascriptEnabled: true,
          },
        },
      },
    },
  ],

  webpack: { // =>所有的webpack中的配置可以配置在这里,其会和webpack中的配置进行合并,如果冲突,这里的配置会覆盖webpack中的配置
    alias: { // => 起别名
      '@': resolve('src') //=> 此时@代码的就是项目根目录下的src文件夹
      // 其他还有一些别名components => src/components utils => src/utils .... (了解,不常用)
    }
  }
}

三、 阶段案例

0GCKDH.png

index.js

import React from 'react'
import ReactDOM from 'react-dom'

import App from './App'

// 默认引入的antd是没有样式的,如果需要使用其样式,需要单独将其样式文件进行引入
import 'antd/dist/antd.css';

ReactDOM.render(<App/>, document.getElementById('root'))

App.js

import React, { PureComponent } from 'react'

import CommentInput from "./CommentInput"
import CommentItem from './CommentItem'

export default class  extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      comments: []
    }
  }

  render() {
    return (
      <div style={{ width: '500px', padding: '20px' }}>

        {
          this.state.comments.map((comment, index) =>
            /*
              实际调用这个deleteComment函数的时候,
              因为使用了箭头函数,所以这个组件的this是App组件
              因此index可以传入App的deleteComment组件
            */
            <CommentItem
              key={comment.create_at}
              comment={comment}
              deleteComment={ e => this.deleteComment(index) }
            />
          )
        }

        {/* 这里需要给自组件传入的是函数对象,不是函数调用后的返回值,所以不要加括号 */}
        {/* 这里是传递给子组件,由子组件进行调用的,所以实际调用是子组件的props对象
            但是在这个函数中调用的this应该是App组件,所以需要重新绑定this为当前App对象
        */}
        <CommentInput submitComment={ this.addComment.bind(this) } />
        {/* <CommentInput submitComment={ info => this.addComment(info) } /> */}
      </div>
    )
  }

  addComment(info) {
    // 不要直接修改state中的对象类型数据,需要浅克隆一个新的对象
    const newComments = [...this.state.comments, info]

    // 调用setState修改界面
    this.setState({
      comments: newComments
    })
  }

  deleteComment(index) {
    const newComments = [...this.state.comments]
    newComments.splice(index, 1)

    this.setState({
      comments: newComments
    })
  }
}

CommentInput.js

import React, { PureComponent } from 'react'
import dayjs from 'dayjs'

import { Input, Button } from 'antd'

// 或者直接使用 Input.TextArea
const { TextArea } = Input


export default class CommentInput extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      content: ''
    }
  }

  render() {
    return (
      <>
        <TextArea
          rows={5}
          value={ this.state.content }
          onChange={ e => this.handleChange(e) }
        />

        {/* React中的样式名使用驼峰法 */}
        <Button
          type="primary"
          style={{ 'marginTop': '10px' }}
          onClick={ _e => this.addComment() }
        >
          添加评论
        </Button>
      </>
    )
  }

  addComment() {
    // 暂时写死一个数据对象
    const data = {
      create_at: Date.now(),
      avatar: 'https://upload.jianshu.io/users/upload_avatars/10598662/3dc22fee-5e4f-4fbe-b692-882a87025fd8.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240',
      author: 'Klaus',
      content: this.state.content,
      dateTime: dayjs()
    }

    // 调用父组件传入的方法
    this.props.submitComment(data)

    this.setState({
      content: ''
    })
  }

  // 定义受控组件的change事件
  handleChange(e) {
   this.setState({
     content:e.target.value
   })
  }
}

CommentItem.js

import React, { PureComponent } from 'react'

import { Comment, Avatar, Tooltip } from 'antd';
import { DeleteOutlined } from '@ant-design/icons'


// 和moment不一样,dayjs将fromNow方法单独抽离出来形成一个单独的plugin
// 具体使用查看 https://day.js.org/docs/zh-CN/plugin/relative-time
// 修改中文配置 https://day.js.org/docs/zh-CN/i18n/changing-locale
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' // 引入含有fromNow的插件
import 'dayjs/locale/zh-cn' // 引入中文模块
dayjs.locale('zh-cn') // 使用dayjs的中文模块(默认输出是英文 可以使用dayjs.locale()来查看当前语言配置)
dayjs.extend(relativeTime) // 使用fromNow插件


export default class CommentItem extends PureComponent {
  render() {
    const { avatar, author, content, dateTime } = this.props.comment
    return (
      /*
        如果一个超链接暂时没有设置跳转的地址的时候,应该设置为\#
        如果设置为的是#的话,其依旧是无法通过ESLint的编译的
        
        对于content和datatime这一类的属性既可以传入String,也可以传入ReactNode
        也就是既可以传入一个字符串类型的值,也可以传入一个子组件
      */
      <Comment
        author={<a href="/#">{author}</a>}
        avatar={<Avatar src={ avatar } alt={ author } />}
        content={ <p> {content} </p> }
        datetime={
          <Tooltip title={ dateTime.format('YYYY-MM-DD HH:mm:ss')}>
            { dateTime.fromNow() }
          </Tooltip>
         }
         actions={[
           <span  onClick={e => this.deleteComment()} >
             <DeleteOutlined />删除
           </span>
         ]}
      />
    )
  }

  deleteComment() {
    this.props.deleteComment()
  }
}