React 基础

1,228 阅读19分钟

React 介绍

React 是用于构建用户界面的 JavaScript 库

  • 构建用户界面. User Interface,对咱们前端来说,简单理解为:HTML 页面

  • javscrtipt库。不是框架,是库。

    • vue: 是渐进式的javascript框架

    • react 全家桶是框架

      • react: 核心库
      • react-dom: dom操作
      • react-router:路由,
      • redux:集中状态管理

背景

  1. 框架背景

    1. react是Facebook(meta) 内部项目
    2. vue是尤雨溪个人作品
    3. angular是goole公司产品
  2. 趋势

    1. react全球第一
    2. vue在国内较多,react也慢慢多了
    3. angular在跨国公司使用较多

React 特点

1.声明式

只需要描述UI看起来时什么样的,就跟写html一样。

用类似于html的语法来定义页面。react中通过数据驱动视图的变化,当数据发生改变react能够高效地更新并渲染DOM。

<div className="app">
    <h1>Hello React! 动态数据变化:{count}</h1>
</div>

2.组件化

组件是react中最重要的内容

组件用于表示页面中的部分内容

组合、复用多个组件,就可以实现完整的页面功能

3.学习一次,随处使用

使用react/rect-dom可以开发Web应用

使用react/react-native可以开发移动端原生应用(react-native)RN

使用react可以开发VR(虚拟现实)应用(react/react360)

React脚手架-从零开始创建项目

脚手架create-react-app

官方工具: create-react-app

创建方式1

  1. 全局安装脚手架工具包

    命令:npm i -g create-react-app

  2. 用脚手架工具来创建项目

    命令:create-react-app your-project-name

创建方式2

直接使用npx来创建项目

命令:

npx create-react-app your-project-name

解释:

  • npx create-react-app 是固定命令,create-react-app 是 React 脚手架的名称
  • your-project-name 表示项目名称,可以修改

React脚手架-了解项目的工作方式

启动项目

npm start

目录src

说明:

  • src 目录是我们写代码进行项目开发的目录
  • index.js是入口文件
  • 查看 package.json 两个核心库:reactreact-dom(脚手架已经帮我们安装好,我们直接用即可)

理解react-dom

rect包 提供必要功能来定义react组件。

react-dom包用来将react组件渲染到dom中。

react-native包 用来将react组件渲染到IOS和Android程序中。

JSX是什么

JSX:是 JavaScript XML的缩写。

  • 在 JS 代码中书写 XML 结构

  • React用它来创建 UI(HTML)结构

理解:我们之前用html写页面,现在是用jsx来写页面

jsx示例

// 导入react和react-dom
import React from 'react'
import ReactDOM from 'react-dom'

// jsx创建元素
const list = <ul><li>html</li><li>js</li><li>css</li><ul>

// 渲染react元素
ReactDOM.render(list, document.getElementById('root'))

React 组件介绍

特点

  • 独立
  • 可复用
  • 可组合

分类

  • 基础组件:指inputbutton这种基础标签,以及antd封装过的通用UI组件
  • 业务组件:由基础组件组合成的业务抽象化UI。例如: 包含了A公司所有部门信息的下拉框
  • 区块组件:由基础组件组件和业务组件组合成的UI块
  • 页面组件:展示给用户的最终页面,一般就是对应一个路由规则

React 组件的两种创建方式

使用函数创建组件

定义组件

使用 JS 的函数(或箭头函数)创建的组件,叫做函数组件

  • 约定1:函数名首字符大写

    必须以大写字母开头**,React 据此区分组件普通的 HTML

  • 约定2:必须有返回值

    表示该组件的 UI 结构;如果不需要渲染任何内容,则返回 null

// 1. 使用普通函数创建组件:
function Hello() {
  return <div>这是我的第一个函数组件!</div>
}

function Button() {
  return <button>按钮</button>
}

// 2.  使用箭头函数创建组件:
const Hello = () => <div>这是我的第一个函数组件!</div>

类组件-用class创建组件

定义格式

使用 ES6 的 class 创建的组件,叫做类(class)组件

// import { Component } from 'react'
// class 类名 extends Component {
import React form 'react'
class 类名 extends React.Component {
  // ... 
  render () {
    return 本组件的UI结构
  }
}

注意:

  1. 类名必须以大写字母开头
  2. extends是一个关键字,用来实现类之间的继承。类组件应该继承 React.Component 父类,从而使用父类中提供的方法或属性。
  3. 类组件必须提供 render 方法,render 方法必须有返回值,表示该组件的 UI 结构。render会在组件创建时执行一次

使用组件

// 导入 React
import React from 'react'
import ReactDom from 'react-dom'
// 1. 定义组件
class Hello extends React.Component {
  render() {
    return <div>Hello Class Component!</div> 
  }
}

const content = (<div><Hello/></div>)

ReactDOM.render(content, document.getElementById('root'))

有状态组件和无状态组件

什么是状态(state)

定义:用来描述事物在某一时刻的形态数据。一般称为state

特点:

  • 状态能被改变,改变了之后视图会有对应的变化

作用:

  • 保存数据。例如:要循环生成一份歌曲列表,那要提前准备好歌曲数据吧
  • 为后续更新视图打下基础。如果用户点击了操作,让歌单的内容+1了,视图会自动更新(这是由react库决定的)

小结:

有状态组件:能定义state的组件。类组件就是有状态组件。

无状态组件:不能定义state的组件。函数组件又叫做无状态组件

注意:2019年02月06日,rect 16.8中引入了 React Hooks,从而函数式组件也能定义自己的状态了

定义状态

固定格式,使用

  1. state = 对象
  2. 在构造函数中用this.state= 对象来做初始化
import React from "react";
export default class Hello extends React.Component {
  // 1. state就是状态
  state = {
    list: [{ id: 1, name: "明天会更好" },{ id: 2, name: "难忘今宵" }],
    isLoading: true
  };
  // 2. 构造函数
  constructor() {
    this.state = {
      list: [{ id: 1, name: "明天会更好" },{ id: 2, name: "难忘今宵" }],
      isLoading: true
    }
  }
  render() {
    return (
      <>
        <h1>歌单-{this.state.count}</h1>
        <ul>
          {this.state.list.map((item) => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
        <div>{this.state.isLoading ? "正在加载" : "加载完成"}</div>
      </>
    );
  }
}

事件绑定

格式

<元素 事件名1={ 事件处理函数1 } 事件名2={ 事件处理函数2 }  ></元素>

注意:React 事件名采用驼峰命名法,比如:onMouseEnter、onFocus、 onClick ......

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

const title = <h1>react中的事件</h1>


export default class Hello extends React.Component {
  fn() {
    console.log('mouseEnter事件')
  }
  render() {
    return (
      <div
        onClick={() => console.log('click事件')}
        onMouseEnter={this.fn}
        能处理鼠标进入或者点击事件
      </div>
    )
  }
}

const content = (
  <div>
    {title}
    {<Hello />}
  </div>
)

ReactDOM.render(content, document.getElementById('root'))

注意:

  1. 事件名是小驼峰命名格式

  2. 在类中补充方法

  3. this.fn不要加括号:

    • onClick={ this.fn() } 先调用fn(),然后将fn的执行结果当做click事件的处理函数

别忘记了写this

获取事件对象

react中,通过事件处理函数的形参来获取。

落地代码


  handleClick(e)=> {
    e.preventDefault()
    console.log('单击事件触发了', e)
  }
	render() {
  	return (
        <div>
            <button onClick={(e)=>{console.log('按钮点击了', e)}}>按钮</button>
    	    <a href="http://itcast.cn/" onClick={this.handleClick}>嘻嘻</a>
  	</div>)  
  }
}

事件处理-this指向问题

不信你快看

class App extends React.Component {
  state = {
    msg: 'hello react'
  }
  handleClick() {
    console.log(this) // 这里的this是?undefined
  }
  render() {
    console.log(this) // 这里的this是?
    return (
      <div>
        <button onClick={this.handleClick}>点我</button>
      </div>
    )
  }
}
  • render方法中的this指向的而是当前react组件。
  • 事件处理程序中的this指向的是undefined

事出有因

  • 事件处理程序的函数式函数调用模式,在严格模式下,this指向undefined
  • render函数是被组件实例调用的,因此render函数中的this指向当前组件
class Person(name) {
  constructor(){
    this.name = name
  }
  say() {
    console.log(this)
  }
}
let p1 = new Person('小花')
p1.say()
const t = p1.say
t()

总结

  1. class的内部,开启了局部严格模式use strict,所以this不会指向window undefined
  2. onClick={this.fn}中,this.fn的调用并不是通过类的实例调用的,所以值是undefined

事件处理-this指向三种解决方案

方式1:在外层补充箭头函数

class App extends React.Component {
  state = {
    msg: 'hello react'
  }
  handleClick() {
    console.log(this.state.msg)
  }
  render() {
    return (
      <div>
        <button onClick={() => {this.handleClick()}}>点我</button>
      </div>
    )
  }
}

原理:箭头函数中的this指向外层作用域中的this

缺点:需要额外包裹一个箭头函数,结构不美观

方式2:使用bind

class App extends React.Component {
  state = {
    msg: 'hello react'
  }
  handleClick() {
    console.log(this.state.msg)
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick.bind(this)}>点我</button>
      </div>
    )
  }
}

原理:bind能绑定this

方式3:class 的实例方法

class App extends React.Component {
  state = {
    msg: 'hello react'
  }

  handleClick = () => {
    console.log(this.state.msg)
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>点我</button>
      </div>
    )
  }
}

小结

  1. 方式3是最方便的,也是以后用的最多的方式

组件的状态-修改状态

setState

语法:this.setState({ 要修改的部分数据 })

作用:

  1. 修改 state
  2. 更新 UI

落地代码

state = {
  count: 0 
};

this.setState({
  count: this.state.count++
})

理解状态不可变

react核心理念之状态不可变

不要直接修改当前状态值,而是创建新的状态值去覆盖老的值。

this.state.count = 100 // 无效

setState的典型用法

落地代码

import { Component } from 'react'
import ReactDOM from 'react-dom'

class HelloReact extends Component {
  state = {
    name: 'jack',
    assets: [{ id: 1, name: '手机' }, { id: 2, name: '耳机' }],
    skill: ['vue', 'react'], // angular
    info: {
      age: 18,
      city: '武汉'
    }
  }

  hClick5 = () => {
    console.log(this.state.skill)
    const newAssets = this.state.assets.filter((item) => item.id !== 2)
    this.setState({
      assets: newAssets
    })
  }

  hClick4 = () => {
    console.log(this.state.skill)
    const newSkill = [...this.state.skill, 'angular']
    this.setState({
      skill: newSkill
    })
  }

  hClick3 = () => {
    console.log(this.state.assets)
    const newAssets = [...this.state.assets]
    newAssets[0].name = '电脑'
    this.setState({
      assets: newAssets
    })
  }

  hClick2 = () => {
    console.log(this.state.info)
    this.setState({
      info: {
        ...this.state.info,
        age: 20
      }
    })
  }

  hClick1 = () => {
    console.log(this.state.name)
    this.setState({
      name: '小花'
    })
  }

  render () {
    const { name, assets, skill, info } = this.state
    return (
      <div>
        <p>姓名:{name}</p>
        <p>
          age:{info.age}, city:{info.city}
        </p>
        <div>
          资产:<ul>{assets.map((item) => <li key={item.id}>{item.name}</li>)}</ul>
        </div>
        <p>skill:{skill.join(', ')}</p>
        <hr />
        {/*
        1. 把name改成'小花'
        2. 把age改成20
        3. 把'手机'改成'电脑'
        4. 向skill中添加'angular'
        5. 删除id为2的assets */}
        <button onClick={this.hClick1}>1. 把name改成小花</button>
        <button onClick={this.hClick2}>2. 把age改成20</button>
        <button onClick={this.hClick3}>3. 把手机改成电脑</button>
        <button onClick={this.hClick4}>4. 向skill中添加angular</button>
        <button onClick={this.hClick5}>5. 删除id为2的assets</button>
      </div>
    )
  }
}

const app = (
  <div>
    app
    {/* 2.使用组件 */}
    <HelloReact />
  </div>
)

ReactDOM.render(app, document.getElementById('root'))

非受控组件-ref

借助于ref,使用原生DOM的方式来获取表单元素的值

ref的使用格式

步骤

  1. 导入方法。import { createRef } from 'react'
  2. 调用createRef方法创建引用,假设名为refDom。 const refDom = createRef()
  3. refDom设置给表单元素的ref属性。<input ref={refDom}/>
  4. 通过refDom.current.value来获取值。console.log(this.refDom.current.value)

内容

  • 受控组件是通过 React 组件的状态来控制表单元素的值
  • 非受控组件是通过手动操作 DOM 的方式来控制
  • 此时,需要用到一个新的概念:ref
  • ref:用来在 React 中获取 DOM 元素

落地代码

// 1. 导入方法
import { createRef } from 'react'

class Hello extends Component {
  // 2. 调用createRef方法创建引用
  txtRef = createRef()

  handleClick = () => {
    // 4. 通过.current.value来获取值
    console.log(this.txtRef.current.value)
  }

  render() {
    return (
      <div>
      	<h1>如何获取input中的值-非受控组件-ref</h1>
         {/* 3. 设置给表单元素的ref属性 */}
        <p><input type="text" ref={this.txtRef}/></p>
        <button onClick={handleClick}>获取文本框的值</button>
      <div>
    )
  }
}

受控组件

如何理解受控

正常情况下,表单元素input是可任意输入内容的,可以理解为input自己维护它的状态(value)

受控组件的思路:

  1. 在state中定义状态
  2. 将state中的状态与表单元素的value值绑定到一起,进而通过state中的状态来控制表单元素的值

受控组件:value值受到了react控制的表单元素

基本步骤

有两个步骤:

  1. 在state中定义状态

  2. 对表单元素做两件事

    1. 设置value为上面定义的状态
    2. 绑定onChange事件,并在回调中通过setState来修改状态值

落地代码

class App extends React.Component {
  state = {
    // 1. 在state中定义状态
    msg: 'hello react'
  }

  handleChange = (e) => {
    this.setState({
      msg: e.target.value
    })
  }
  
  handleClick = ()=> {
    console.log(this.state.msg)
  }

  render() {
    return (
      <div>
      	<h1>如何获取input中的值-受控组件</h1>
        <p>
        {/* 2. 对表单元素做两件事 */}
          	<input type="text"
		 value={this.state.msg}
		 onChange={this.handleChange}
		/>
        </p>
        <button onClick={handleClick}>获取文本框的值</button>
      <div>
    )
  }
}

注意

使用受控组件的方式处理表单元素后,状态的值就是表单元素的值。即:想要操作表单元素的值,只需要操作对应的状态即可

组件通讯

内容

  • 组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。
  • 在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。
  • 而在这个过程中,多个组件之间不可避免的要共享某些数据
  • 为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯

三种方式

  • 父子组件之间
  • 兄弟组件之间
  • 跨组件层级

总结

组件中的状态是私有的,也就是说,组件的状态只能在组件内部使用,无法直接在组件外使用

props基本使用

image.png

格式

父组件-传入数据

<子组件 自定义属性1={值1} 自定义属性2={值2} .... />

子组件-函数式组件-接收数据

// 接收数据: 函数组件需要通过补充形参来获取
function 子组件(props) {
  console.log('从父组件中传入的自定义属性被收集在对象:', props)
  return (<div>子组件的内容</div>)
}

子组件-类组件-接收数据

// 接收数据: class 组件需要通过 this.props 来获取
class 子组件 extends Component {
  console.log('从父组件中传入的自定义属性被收集在对象:', this.props)
  render() { return (<div>子组件的内容</div>) }
}

函数组件获取props

// 接收数据:
// props 的值就是:{ age: 19 }
function HelloFunc(props) {
  return (
    <div>接收到数据:{props.name}</div>
  )
}

// 传递数据:
// 可以把传递数据理解为调用函数 Hello,即:HelloFunc({ age: 19 })
<HelloFunc age={19} />

类组件获取props

// 接收数据:
// class 组件需要通过 this.props 来获取
class HelloClass extends Component {
  render() {
    return (
      <div>接收到的数据:{this.props.age}</div>
    ) 
  }
}

// 传递数据:
<HelloClass age={19} />

props的三个注意事项

内容

  • 可以传递任意数据
  • 只读的
  • 单向数据流

可以传递任意数据

props可以传递:数字 字符串 布尔类型 数组 对象 函数 jsx

props 是只读对象

只能读取对象中的属性,无法修改

this.props.age =20 // 错误

单向数据流

也叫做:自上而下的数据流

  1. 父组件中的数据可以通过 props 传递给子组件,并且,当父组件中的数据更新时,子组件就会自动接收到最新的数据
  2. 父组件的数据更新会流动到子组件,不能反过来,子组件直接去修改父组件的数据
  3. 类比:就像瀑布的水一样只能从上往下流动,并且,当上游的水变浑浊,下游的水也会受到影响

props的children属性

children属性

  • children属性:表示该组件的子节点,只要组件有子节点,props就有该属性
  • children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)

image.png

测试代码

function Hello(props) {
  return (
    <div>
      该组件的子节点:{props.children}
    </div>
  )
}

<Hello>我是子节点</Hello>

组件通讯-父传子

内容

  • 父组件提供要传递的state数据
  • 给子组件标签添加属性,值为 state 中的数据
  • 子组件中通过 props 接收父组件中传递的数据

核心代码

父组件提供数据并且传递给子组件

class Parent extends React.Component {
    state = { lastName: '王' }
    render() {
        return (
            <div>
                传递数据给子组件:<Child name={this.state.lastName} />
            </div>
        )
    }
}

子组件接收数据

function Child(props) {
	return <div>子组件接收到数据:{props.name}</div>
}

组件通讯-子传父

步骤

  1. 父组件

    1. 定义一个回调函数f(将会用于接收数据)
    2. 将该函数f作为属性的值,传递给子组件
  2. 子组件

    1. 通过 props 获取f
    2. 调用f,并传入将子组件的数据

落地代码

父组件提供函数并且传递给子组件

class Parent extends React.Component {
    state: {
      num: 100
    }
    f = (num) => {
        console.log('接收到子组件数据', num)
    }
    render() {
        return (
            <div>
            	子组件:<Child f={this.f} />
            </div>
        )
    }
}

子组件接收函数并且调用

class Child extends React.Component {
    handleClick = () => {
      // 调用父组件传入的props,并传入参数
    	this.props.f(100)
    }
    return (
    	<button onClick={this.handleClick}>点我,给父组件传递数据</button>
    )
}

小结

子传父:在子组件中调用从父组件中定义的方法,并根据需要传入参数

props校验-基本使用

对于子组件来说,props是外来的,无法保证组件使用者传入什么格式的数据,有了类型校验,我们的程序就更加健壮了。

步骤

  1. 导入 prop-types 包 。这个包在脚手架创建项目时就自带了,无须额外安装

    import PropTypes from 'prop-types' 这里的PropTypes可以改成其他的名字

  2. 使用组件名.propTypes = {属性名1: 类型1, ...} 来给组件的props添加校验规则

    这里的propTypes是固定写法

例如

import PropTypes from 'prop-types'
class App extends React.component {
  render(){
    return (
    	<h1>Hi, {props.colors}</h1>
    )
  }
    
}
App.propTypes = {
    // 约定colors属性为array类型
    // 如果类型不对,则报出明确错误,便于分析错误原因
    colors: PropTypes.array
}

作用:规定接收的props的类型必须为数组,如果不是数组就会报错,报错的格式如下

props校验-常见规则

内容:

  1. 常见类型:array、bool、func、number、object、string
  2. React元素类型:element
  3. 必填项:isRequired
  4. 特定结构的对象:shape({ })

示例

Com.propTypes = {
  fn: PropTypes.func, // fn 是函数类型
  isEdit: PropTypes:bool.isRequired // isEdit的类型是bool,且必须要传入
  
  // 特定结构的对象
  goodInfo: PropTypes.shape({ // goodInfo的格式一个对象,有 数值price属性 和 字符串name属性
    price: PropTypes.number,
    name: PropTypes.string
  })
}

props默认值

默认值

定义: 没有赋值的情况下,给属性赋一个值

好处:简化调用组件时要传入的属性,更方便用户使用

两种方式

两种方式:

  1. defaultProps
  2. 解构赋值的默认值

方法1:defaultProps

通过defaultProps可以给组件的props设置默认值,在未传入props的时候生效

定义组件

class App extends React.Component{
    return (
        <div>
            此处展示props的默认值:{ this.props.pageSize }
        </div>
    )
}
// 设置默认值
App.defaultProps = {
	pageSize: 10
}
// 类型限制
App.propTypes = {
  pageSize: PropTypes.number
}

使用组件

// 不传入pageSize属性
<App />

方法2:解构赋值的默认值

class App extends React.Component{
    return (
     // 解构赋值的默认值
     const { pageSize = 10} = this.props
        <div>
            此处展示props的默认值:{ this.props.pageSize }
        </div>
    )
}
// 类型限制
App.propTypes = {
  pageSize: PropTypes.number
}

props校验和默认值简化-类的静态成员-static

什么是静态成员

  • 静态成员:通过类或者构造函数本身才能访问的属性或者方法

  • 实例成员: 通过实例调用的属性或者方法

    示例

class Person {
  constructor(name){
    this.name = name // 实例成员
  }
  static n = 1 // 静态成员 有static修饰
}

静态成员的定义和访问

定义

class Person {
  constructor(name){
    this.name = name // 实例成员
  }
  // 方法1:在class内部 通过static来定义
  static n = 1 // 静态成员 有static修饰
}
// 方法2 在class外部 通过类型.属性名来定义
Person.m = function() { console.log()}

访问

Person.n
Person.m()

组件生命周期-概述

生命周期

生命周期:一个事物从创建到最后消亡经历的整个过程

组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程

意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等

钩子函数

在生命周期的不同阶段,会自动被调用执行的函数,为开发人员在不同阶段操作组件提供了时机。

注意:只有类组件 才有生命周期钩子函数

react类组件的生命周期钩子函数-整体说明

projects.wojtekmaj.pl/react-lifec…

image.png

组件生命周期-挂载阶段

内容

  • 执行时机:组件创建时(页面加载时)
  • 执行顺序:constructor() -> render() -> componentDidMount()
钩子 函数触发时机作用
constructor创建组件时,最先执行1. 初始化state 2. 创建Ref等
render每次组件渲染都会触发渲染UI
componentDidMount组件挂载(完成DOM渲染)后1. 发送网络请求 2.DOM操作

组件生命周期-更新阶段

更新阶段的钩子

更新阶段会执行两个钩子: render() -> componentDidUpdate()

三种操作可触发组件更新

  1. 调用setState。它能改数据&& 更新页面
  2. 调用forceUpdate()
  3. 组件接收到新的props

说明:以上三者任意一种发生,组件就会进入更新阶段

更新阶段能做的事情

钩子函数触发时机作用
render每次组件渲染都会触发渲染UI(与 挂载阶段 是同一个render)
componentDidUpdate组件更新(完成DOM渲染)后DOM操作,可以获取到更新后的DOM内容

如果有些事情是需要每次更新去做的,就可以写在componentDidUpdate中。例如:数据本地存储

组件生命周期-卸载阶段

内容

执行时机:组件销毁

钩子函数触发时机作用
componentWillUnmount组件卸载(从页面中消失)执行清理工作(比如:清理定时器等)

示例:清理定时器&&移除事件

/* eslint-disable react/prop-types */
import React, { Component, createRef } from 'react'
import ReactDOM from 'react-dom'

class Son extends Component {
  constructor () {
    super()

    this.timer = setInterval(() => {
      console.log('子组件:', Date.now())
    }, 1000)
    this.fn = () => {
      console.log('鼠标移动...')
    }
    window.addEventListener('mousemove', this.fn)
  }

  render () {
    return <div>子组件:{this.props.content}</div>
  }

  componentDidUpdate () {
    console.log('子组件: componentDidUpdate')
  }

  componentWillUnmount () {
    console.log('子组件 卸载: componentWillUnmount')
    // 删除事件
    window.removeEventListener('mousemove', this.fn)
    // 删除定时器
    clearInterval(this.timer)
  }
}

export default class App extends Component {
  constructor () {
    super()
    console.log('1. constructor')
    this.state = {
      content: '',
      isShow: true
    }
    this.refTxt = createRef()
  }

  hChange = (e) => {
    this.setState({ content: e.target.value })
  }

  click = () => {
    this.forceUpdate()
  }

  render () {
    console.log('2. render')
    return (
      <div>
        <button onClick={this.click}>更新</button>
        组件<input value={this.state.content} onChange={this.hChange} />
        <br />
        {this.state.isShow && <Son content={this.state.content} />}
      </div>
    )
  }

  componentDidMount () {
    console.log('3. componentDidMount')
    console.log(this.refTxt.current)

    // axios.get()
  }

  componentDidUpdate () {
    console.log('更新完成: componentDidUpdate')
  }
}

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

setState进阶-掌握它的3个特点

setState的特点

  1. 可以表现为异步。

    setState调用之后,并不会立即去修改state的值,也不会立即去更新dom

  2. 多次调用会合并。setState({对象})会合并,再统一触发一次render()

  3. 使用不当会死循环。在componentDidUpdate, render 中调用setState()会导致死循环

setState进阶-第二个参数

setState的第二个参数

格式1

this.setState({} [,回调函数])

回调函数是可选的,它的作用是:当本轮setState生效(state更新,页面ui更新)之后,会调用回调函数

格式2

image.png

this.setState((上一状态) => {
  return 新状态
}[,回调函数])

回调函数是可选的,它的作用是:当本轮setState生效(state更新,页面ui更新)之后,会调用回调函数

示例1

 state = {
    n: 1
  }

  hClick = () => {
    this.setState( preState => ({ n: preState.n + 1 }))
    this.setState( preState => ({ n: preState.n + 2 }))
    this.setState( preState => ({ n: preState.n + 3 }))
  }
  // 页面上的n会是多少?

示例2

  state = {
    n: 1
  }

  hClick = () => {
    this.setState( preState => ({ n: preState.n + 1 }), ()=>{console.log(this.state.n)})
    this.setState( preState => ({ n: preState.n + 2 }), ()=>{console.log(this.state.n)})
    this.setState( preState => ({ n: preState.n + 3 }), ()=>{console.log(this.state.n)})
  }
  // 1. 页面上的n会是多少?
  // 2. 三个console.log会输出什么?

setState进阶-同步or异步

内容

setState本身并不是一个异步(setTime, setInterval, ajax,Promise.then.....)方法,其之所以会表现出一种异步的形式,是因为react框架本身的性能优化机制

在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用 setState 不会同步更新 this.state,除此之外的setState调用会同步执行this.state。

所谓“除此之外”,指的是绕过React通过 addEventListener 直接添加的事件处理函数,还有通过setTimeout || setInterval 产生的异步调用。

简单一点说:

  1. 经过React 处理(事件回调,钩子函数)中的setState是异步更新
  2. 没有经过React处理(通过 addEventListener || setTimeout/setInterval)中的setState是同步更新

image.png

示例代码

import reactDom from 'react-dom'

import React, { Component } from 'react'

class App extends Component {
  state = {
    n: 1
  }

  // setTimeout(() => {
  //   this.setState({ n: 2 })
  //   console.log(this.state.n)
  //   console.log(document.getElementById('btn').innerHTML)
  // }, 2000)

  componentDidMount () {
    // this.setState({ n: 2 })
    // console.log(this.state.n)
    // console.log(document.getElementById('btn').innerHTML)

    document.getElementById('btn').addEventListener('click', () => {
      this.setState({ n: 2 })
      console.log(this.state.n)
      console.log(document.getElementById('btn').innerHTML)
    })
  }

  click = () => {
    this.setState({ n: 2 })
    console.log(this.state.n)
    console.log(document.getElementById('btn').innerHTML)
  }

  render () {
    return (
      <div>
        {/* <button id="btn" onClick={this.click}> */}
        <button id="btn" onClick={this.click}>
          {this.state.n}
        </button>
      </div>
    )
  }
}

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

总结

setState是同步的方法,但是react为了性能优化,所以setState在react的事件中表现得像异步。

参考链接:

宝,你都看到这了不给我一个star嘛?

PS: 如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果有帮助,欢迎点赞和收藏,转载请著明出处,如果有问题也欢迎私信交流