React专题-chapter2

817 阅读8分钟

前提概要

接着上一章 React专题-chapter1,这里介绍使用 React 开发时需要知道的基础知识

setState方法

概念:setState方法由父类Component所提供,当我们调用这个函数的时候,React.js会更新组件的状态state,并且重新调用render方法,然后再把render方法所渲染的最新的内容显示到页面上

Note1:当我们要改变组件数据的时候,不能直接用this.state = xxx这种方式来修改,如果这样做react.js就没法知道你修改了组件的状态,他也就没法更新页面。所以,一定要使用React.js提供的setState方法,它接受一个对象或者函数作为参数。

Note2: 对于setState方法,当对多个状态的其中一个状态更改时,只需传入需要修改的状态而无需传入所有状态,因为setState不会覆盖掉其它未传入的状态

Note3:当调用setState时,React.js并不会马上修改state,而是把这个对象放到一个更新队列中,在合适的时机从队列中提取状态并合并到state中,然后再触发组件更新。

handleClick(){
    console.log(this.state.isLiked);
    this.setState({
        isLiked: !this.state.isLiked
    })
    console.log(this.state.isLiked);
}

// false
// false

这里,两次打印的值都为false,说明中间使用setState对isLiked取反操作没有生效。如果想根据setState的操作结果来执行某种操作,就需要将setState方法的参数改为函数,并在函数中接收setState上一次的操作结果,如下:

handleClick(){
    this.setState( preState => { count: 0 } )
    this.setState( preState => { count: preState.count + 1 } ) // 上一个setState操作返回的count是0,当前返回1
    this.setState( preState => { count: preState.cunt + 2 } ) // 上一个setState操作返回的count是1,当前返回3
    
    // 最后的结果是this.state.count === 3
    // PS:这让我想起了 Vue 中的 nextTick
}

比较:这和vue2.x利用Object.defineProperty方法监听属性值的改变并在属性改变的时候重新渲染页面内容的想法是相同的,虽然实现原理不同。

setState的合并

上面的示例代码中,总共调用了三次setState方法,但是实际上组件只会重新渲染一次。

试想:如果setState方法调用一次,页面就需要重新渲染一次,这也太不合理了。

调用setState方法时,React内部会把这些修改state的操作都放到消息队列中缓存起来,并在这一次event loop结束时合并这些操作,从而保证针对state的修改在一个事件循环内只发生一次,减少页面重新渲染次数的同时而又不影响页面内容的更新。

函数式组件(Functional Components)

在React中,函数式组件是一种更简单的方法来编写只包含render方法且没有自己state(状态)的组件。而不是定义一个React.Component的扩展类,我们可以编写一个函数,它将Props作为输入,并返回应该渲染的内容。函数式组件写入比类更乏味,并且可用这种方式表达许多组件。

// 这里创建了一个Square的无状态组件,它没有自己的state,只依靠来自父组件的onClick和value输入值来渲染内容和完成交互
function Square(props) {
    return (
        <button className="square" onClick={props.onClick}>
            {props.value}
        </button>
    )
}

JSX语法的一些注意事项

  • 元素属性 class 需要使用 className 代替、元素属性 for 需要使用 htmlFor 代替
  • jsx 语法中,标签必须闭合,单标签需要自闭和
  • 使用 jsx 创建 Dom 时,所有的节点必须被一个唯一的根节点包裹
  • 组件的首字母必须大写
  • React 中获取元素的引用时和 Vue 基本一致,只不过 Vue 是 $refs,而 React 是 refs
  • 创建行内样式时,必须使用双大括号的形式,类似下面:
<div style={{color: 'red'}}>JSX中的行内样式</div>
// 遇到font-sizez-index等连字符属性时,可以采用如下方式,除数字以外的属性值必须用引号包裹
<div style={{fontSize: '16px',zIndex: 12,textAlign: 'center',fontWeight: 200,border:'1px solid #eee'}}>JSX中的行内样式</div>

<div style={{boxShadow: '0 0 10px #ccc'}}></div>

创建组件的两种方式

无状态组件:如果你想写的组件只包含一个 render 方法,并且不包含 state,那么使用函数组件就会更简单,注意:组件必须返回一个合法的 JSX Dom 元素或者返回 null

function Hello(props) {
    return <div>{props.name}</div>
}

// 使用Hello组件
render() {
    const foo = 'hah'
    return <div><Hello name={foo}></Hello></div>
}

通用组件:继承自 React.Component 类的普通类,必须实现 render 方法,render方法内部返回的是组件要渲染的元素,这种组件可以拥有自己的状态

class Hi extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: 'ddd'
        }
    }
    render() {
        return <div>{this.state.name}<Hello name={this.state.name}></Hello></div>
    }
}

function Hello(props) {
    return <div>{props.name}</div>
}

// 组件的使用
<Hi></Hi>

// 给组件传递属性时,同时传递多个属性
const foo = {
    age: 19,
    name: 'hh',
    gender: 'd'
}
<Hi {...foo}></Hi>
// 这时Hi组件内部会接收到所有的foo属性,比较方便

无状态组件和有状态组件之间的区别:无状态组件没有自己的 state,并且无生命周期函数

propsstate之间的区别:

  • 数据来源:props 中的属性值都是由调用它的父组件传递而来,而 state 属于组件的私有状态
  • 数据读取性:props 属性只读,不可修改,state 属性属于组件的私有状态,在组件内部可以随意修改

注意:在一个组件中导入 css 样式文件,默认是在全局、整个项目中生效的,那么如何使组件的 css 样式私有化呢?

create-react-app 脚手架

概念:Create React App 是 FaceBook React 官方团队出的一个构建 React 应用的脚手架工具。它本身集成了 Webpack 构建工具,并配置了一系列内置的 loader 和默认的 npm 脚本,可以很轻松的实现零配置快速开发的 React 应用程序。

基本使用

// 安装
npm i -g create-react-app

// 创建应用程序
npx create-react-app my-app

// 启动应用程序(在my-app目录中)
npm start

使 create-react-app 脚手架项目启用 less

默认情况下,使用该脚手架创建的项目是隐藏 Webpack 配置的,如果需要手动修改配置,可通过内置提供的 reject 命令生成

另外截止2020-10-20日,默认情况下该项目是支持 sass 配置的,如果需要支持 less,需要手动安装 less-laoder 和 less,并在配置文件中做相应修改

配置文件提供了 getStyleLoaders 函数,配置 less 相对较为方便,详情看下面配置

暴露webpack配置

npm run eject 或者 yarn eject

支持less

{
  test: /\.less$/,
  exclude: sassModuleRegex,
  use: getStyleLoaders(
    {
      importLoaders: 3,
      sourceMap: isEnvProduction && shouldUseSourceMap,
    },
    'less-loader'
  ),
  // Don't consider CSS imports dead code even if the
  // containing package claims to have no side effects.
  // Remove this when webpack adds a warning or an error for this.
  // See https://github.com/webpack/webpack/issues/6571
  sideEffects: true,
},

为普通样式表文件启用模块化

默认情况下,在 React 项目中任意组件中导入一个 css 文件时,样式会在项目全局所有的组件中生效,这不符合组件化开发的初衷,解决方法其一就是通过给 css-loader 的 modules 参数给所有的 css 文件启用模块化

// 01.css
.title {
    color: red;
    font-weight: bold;
}
// 这里引入的cssobj也就是01.css只会在这个文件内生效
import cssobj from '@/css/01.css'

function Test() {
    return <div className={cssobj.title}></div>
}

// 对于启用了css模块化的css文件,如果希望某个类不被模块化,可以采用注解的方式
// 02.css
.title {
    color: red;
    font-weight: bold;
}
// title2这个类就会在全局生效,不会被模块化
:global(.title2) {
    color: blue;
    font-weight: 200;
}

为项目中自己书写的样式提供模块化,而像第三方样式如bootstrap依然全局生效

通常情况下,项目中自己的样式文件都是以scss或less书写,而第三方如bootstrap提供的样式文件都是css文件,那么只需针对这两种不同的样式文件类型做分别处理即可,只为.scss或.less启用模块化

module: {
    rules: [
        { test: /\.scss$/, use: ['style-loader','css-loader?modules','sass-loader'] },
        { test: /\.css$/, use: ['style-loader', 'css-loader'] }
    ]
}

// 使用时
import cssobj from '@/css/01.scss'
import 'bootstrap/dist/css/bootstrap.css'

function Test() {
    // 注意,这里使用bootstrap全局样式时使用的是字符串,而对自己的样式则使用{}方式
    return <div className={cssobj.box}><span class="btn btn-primary"></span></div>
}

React 类型校验工具 PropTypes 的使用

React 组件的静态属性 defaultProps 允许父组件在调用子组件时省略参数,直接使用子组件的默认值,例如 B组件需要一个 count 属性,而调用时没有传入,那么就需要在 defaultProps 对象中定义 count 属性作为未传入时的默认值,在B组件中获取 count 的方式是一样的,都是this.porps.count

如果需要对传入的 props做类型检验,则需要使用额外的包 react-types,使用方式如下:

import React from 'react'
// React V15.* 之前,react-types 集成在React包中,v15之后被抽离作为单独的包
import ReactTypes from 'react-types'

class Counter extends React.Component {
    constructor(props) {
        super(props)
        this.state = {}
    }
    static defaultProps = {
        initcount: 0
    }
    static propTypes = {
        // 限定 initcount 只能是数字类型,如果在使用该组件时,给 initcount 字段传入的是非数字类型,控制台就会报出类型错误的提示
        initcount: ReactTypes.number
    }
}

shouldComponentUpdate生命周期函数

这个函数通过返回 true 或 false 来决定页面是否应该被重新渲染,不过即使返回 false,组件内部的 state 值依然会被修改。

该函数在组件第一次渲染时不会被调用,后面在接受新的 state 或者 props 时,在 render 方法前被调用。该方法默认总是返回 true,如果你确定某些 state 或者 props 改变后不需要重新渲染组件,也可以在这个函数中指定返回 false,需要注意的是,这样的结果会导致后面的 render、componentWillUpdate、componentDidUpdate 都不会被调用

虽然 shouldComponentUpdate 是在 props 或者 state 值发生变化后触发,但需要注意的是,这个函数内部通过this.state.xxx获取 state 值是旧值,如果想获取最新的 state 值需要通过参数实现

class Test extends React.Component{
    shouldComponentUpdate(nextProps, nextState) {
        // 旧的state
        console.log(this.state)
        // 旧的props
        console.log(this.props)
        // 最新的props
        console.log(nextProps)
        // 最新的state
        console.log(nextState)
    }
}

componentWillReceiveProps生命周期函数

这个函数的触发时机是父子组件通信时,当父组件传递给子组件的属性值发生变化时,就会触发这个函数。

不过需要注意的是,当子组件第一次被渲染到页面上的时候,不会触发这个函数,只有通过某些事件重新修改了父组件传递给子组件的 props 数据之后,才会触发这个函数

这个函数内部通过this.props获取到的依然是旧数据,想要获取最新的 props 数据,依然和 shouldComponentUpdate 函数一样,是通过函数的参数列表实现

class Foo extends React.Component{
  constructor(props) {
    super(props)
    this.state = {
      msg: 'first value'
    }
  }
  render(){
    return (
      <div>
        <Bar msg={this.state.msg} onClick={() => this.onClick()}></Bar>
      </div>
    )
  }
  onClick() {
    this.setState({
      msg: 'last value'
    })
  }
}

class Bar extends React.Component{
  render() {
    return (
      <div onClick={this.props.onClick}>
        {this.props.msg}
      </div>
    )
  }
  // componentWillReceiveProps 生命周期函数将会在调用该组件的父组件传递过来的props属性发生变化时执行
  componentWillReceiveProps(nextProps) {
    console.log('componentWillReceiveProps')
  }
}
// 上面的实例代码中,在初次打开页面时componentWillReceiveProps不会被执行,只有在点击页面上的first value并修改其值后,componentWillReceiveProps在会被执行

componentWillUpdate函数

该函数在 props 或者 state 值发生变化后被调用,并且调用顺序在 render 之前

Note: 该函数的调用是因为 state 或者 props 的值发生变化,因此不能再在这个函数中修改 state 或者 props 的值,否则又会触发该函数,导致无限循环

componentDidUpdate 函数

该函数类似于 Vue 中的 mounted 生命周期函数,可以在这里访问并修改 DOM

React 组件生命周期函数的执行顺序

一、Mounting
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount()
二、Updating
1. componentWillReceiveProps(nextProps)
2. shouldComponentUpdate(nextProps, nextState)
3. render()
4. componentDidUpdate(prevProps, prevState)
三、Unmounting
1. componentWillUnmount()

Electron 是什么

简介:Electron 是一个使用 Javascript、HTML、CSS 技术去开发跨平台桌面应用的技术,这里的桌面应用指的是在 Windows、OSX 及 Linux 系统上运行的程序

应用:被前端开发人员熟知的 VSCode、Atom 编辑器都是使用 Electron 开发

Ant Design 是什么

简介:Ant Design 是一个 UI 设计语言,是一套主要用于企业级后台产品的交互语言和设计体系。Ant Design 源自蚂蚁金服体验技术部的后台产品开发,蚂蚁金服的设计师和前端工程师经过大量的项目实践和总结,希望能抽象出一套企业级的交互视觉规范,统一后台项目的前端 UI 设计,屏蔽各种不必要的设计差异和前端实现成本,解放设计和前端开发资源。