组件从概念上来看就像JS中的一个函数,它可以接收任意的输入值(称之为props),并返回一个需要在页面上展示的React元素。我们可以将UI切分成几个不同的,独立的,可复用的部分,进行单个部分即单个组件的构建,后面进行整合展示就可。

  一、函数组件和类组件

  组件的名称必须是大写开头,这样可以在使用时和html标签区分开来。函数组件的创建是定义一个首字母大写的函数,这个函数返回jsx,jsx它是依赖React,所以组件内部必须要引入React。在使用组件时,里面写的行内属性都是自定义属性,我们在函数的内部通过props进行接收。组件需要返回一个并且只能返回一个React根元素,所以有多个组件时要用空标签或者是div标签进行包裹。

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

let str = '我是天空里的一片云'
//普通函数
function Div(props) {
  // 在组件上使用的行内属性都是自定义属性
  return <h3>我的名字是:{props.name},年龄是:{props.age}</h3>
}
//箭头函数
let H3 = (props) => {
  // 在html标签上使用的行内属性都是react规定的
  return <h3 style={{color:props.style}}>{str}</h3>
}

ReactDOM.render(<>
  <Div name="davina" age={20} />
  <H3  style="ligthblue"></H3>
</>, 
document.querySelector('#root'))

  同函数组件一样,类组件首字母也需大写,它继承自React.Component。类组件有自己的this和生命周期。如下所示,我们创建了一个Welcome类,它里面必须要有render函数,会默认调用render方法,并且返回一个能被渲染的结果,这个返回结果就是虚拟DOM,即React元素。在return的React元素有多个时,要有标签进行包裹,不然会报错。

  类组件也是先进行属性对象的收集,像下面的<Welcome {...data} />中{ name: 'davina', age: 20 }就会作为Welcome类组件的props属性对象进行传入,传入后会把属性对象传递给构造函数,并得到类的实例。在props初始化完成后,this.props变量就保存了props属性对象的地址,后面我们在调用render函数时,可以通过this.props访问数据。组件的props一般来源于默认属性或者是由父组件的state内部数据传递而来,基于react是单向数据流,所以在组件内部props是只读的不能修改。虽然可以通过修改父组件的state方式进行修改,但是不建议它仍旧遵循的是props属性对象不可更改这一规则。

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

//Wlecome类继承于React.Component这个类
class Welcome extends React.Component {
  //在class声明的类中有一个规定:写了constructor就必须要写super()
  // constructor(props) {
  // super()它相当于是call继承,其实就是继承的那个类的函数体本身,在这里指的就是React.Component
  // super(props) //将props挂载在this上
  // }
  render() {
    //可以通过this.props调用到属性
    console.log( this.props);
    return <h3>我的名字是:{this.props.name},年龄是:{this.props.age}</h3>
  }
}
let data = { name: 'davina', age: 20 }
ReactDOM.render(<Welcome {...data} />, document.querySelector('#root'))

  二、state

  react中的组件只有两大数据源,一个属性props,二是状态state。上文中我们说了props,下面我们来看一下state。每个组件都有自己独立的内部数据,在类组件中state内部数据是放在构造函数中作为私有属性来定义的。

  当我们要修改state时,因为react不像vue那样对数据进行监听并且在数据变化时刷新视图,它的数据变化是通过重新调用render方法来更新视图。所以我们需要调用render函数。babel提供了一个可以自动调用render函数的API即setState()。我们利用setState可以触发视图的更新,也就是让render函数执行。换句话说,当我们调用了setState这个函数时,react会更新组件的state,并且重新调用render方法,再把render方法渲染的最新内容显示到页面上。react在更新组件的state时它并不会马上修改state,而是把它放到一个事件队列里面,当数据更数完后才会将新的state提取出来合并到旧的state中,所以只需要传入新的state需要修改的部分就可。随后再进行组件的更新操作。

import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
  constructor(props) {
    super(props)
    //在构造函数中这是唯一可以给this.state赋初始值的地方
    // 当前组件的私有属性
    this.state = {
      name: 'davina',
      date: new Date().toLocaleTimeString()
    }
  }
  //当组件渲染完成后会触发componentDidMount钩子函数
  componentDidMount() {
    //改变状态的方法:setState => 修改状态 重新render
    this.$timer = setInterval(() => {
      this.setState({ date: new Date().toLocaleTimeString() })
    }, 1000);
  }
  render() {
    //解构赋值
    let { name, date } = this.state;
    return <>
      <h3>我的名字是:{name}</h3>
      <h4> 现在的时间为:{date}</h4>
    </>
  }
}
ReactDOM.render(<App />, document.getElementById('root'))

  setState函数它是异步的,因为如果每次调用setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率会很低。再者如果同步的更新state,但是还没有执行render函数,那state和props不能保持同步和一致性,这样会在开发中导致很多问题,所以最好的办法就是获取到多个更新后批量的进行操作异步处理。

  但setState一定是异步的吗?不一定。更新数据时绝大多数情况下是异步操作。但在原生事件或者是计时器中setTimeout/setInterval这种react无法掌握的API会直接去更新state,可以立即得到最新的state它是一个同步的操作。 

  一般来说在组件生命周期或react合成事件中,setState是异步的,在setTimeout或者原生事件中setState是同步的。 

  我们可以使用以下方法强制让react不用异步操作。setState函数式用法即将一个回调函数传入setState方法中或者可以在setState更新后进行的逻辑封装到一个函数中作为第二个参数传给setState即setState(updater,[callback])还可以把需要在setState更新后进行逻辑放在生命周期的hook函数中通过以上的三种方法我们可以实现同步的操作。

import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
  //state的另一种写法,可以不用写constructor
  state = { count: 100, val: 20 }
  add() {
    this.setState(
      { count: this.state.count + 1 }, function () {
        console.log(this.state.count);//这时数据更新完就立即触发,它是一个同步操作
      }
    )
    console.log(this.state.count);//它是一个异步操作,得到的不是立即加1的值,它是旧的state 
  }

  componentDidMount() {
    //同步执行
    this.timer = setInterval(() => {
      this.setState({ val: this.state.val - 1 })
      console.log(this.state.val);
    }, 1000);
  }

  render() {
    let { count, val } = this.state;
    return <>
      <button onClick={this.add.bind(this)}>add</button>
      <h3>count的当前值是:{count}</h3>

      <h4>val的当前值是:{val}</h4>
    </>
  }
}

ReactDOM.render(<App />, document.getElementById('root'))
import React, { Component } from 'react'
export default class App extends Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
    }
    render() {

        return (
            <div>
                <h3>当前计数是:{this.state.count}</h3>
                <button onClick={e => this.incrementOne()}>clickOne</button>&nbsp;&nbsp;
                <button onClick={e => this.incrementTwo()}>clickTwo</button>

            </div>
        )
    }
    incrementOne() {
        // 1.setState本身的合并 多次调用会产生合并
        this.setState({
            count: this.state.count + 1
        })
        this.setState({
            count: this.state.count + 1
        })
    }
    incrementTwo() {
        // 2.setState合并时进行累加,那么后面需要跟上一个函数
        //这个函数接收前一个状态值作为第一个参数,第二个参数是更新后的值
        this.setState((prevState, props) => {
            return {
                count: prevState.count + 1
            }
        });
        this.setState((prevState, props) => {
            return {
                count: prevState.count + 1
            }
        });
    }
}

  三、生命周期

 

 

 当组件实例被创建并插入到DOM中时,它要经历以下几个步骤:

  1、Mounting(挂载阶段):组件第一次在DOM树中被渲染的过程

    1.1 constructor构造函数

    就像下面的代码中显示的一样,App类继承了React Component这个基类,constructor()它是用来做一些组件的初始化操作通过分配对象到this.state来初始化state,或者是进行事件处理中this的绑定 。需要注意的是在第一行要加上super(props)将父组件的props传递给子组件进行读取。

    constructor(props) {
        super(props);
        console.log(' mount阶段- 执行了组件的constructor方法')
        this.state = {
            count: 0,
        }
    }

    1.2 getDerivedStateFromProps(nextProps,prevState)

    这个函数它在render方法之前执行,它有两个参数:第一个参数为即将更新的props值,第二个参数为之前的state值。当它返回nulll时,不做任何其它的处理,当它返回一个对象时,更新state状态值。一般用的很少。

    1.3 render()

    根据组件的state和props,return一个react元素。它是一个纯函数,在类组件中其它的生命周期可以没有,但它是唯一必须存在的。组件渲染时会走到这个生命周期,展示什么组件都是由render()生命周期的返回值来决定的,但是它不负责组件的实际渲染工作。当shouldComponentUpdate()方法返回false时,render()不会被调用。

    1.4 componentDidMount()

    它是在组件挂载后即插入到DOM树中立即调用,只会被调用一次。我们可以把依赖于DOM的操作、发放网络请求、添加订阅事件、进行事件监听等操作放入其中。在这个生命周期中可以调用setState方法。

  2、Updating阶段(更新阶段):组件状态发生变化,重新更新渲染的过程

    2.1 shouldComponentUpdate(nextProps,nextState)

    它是组件准备更新之前调用的,可以控制组件是否进行更新,当它返回true时,执行render方法,组件更新,当它返回false,组件不更新。它包含两个参数,第一个是即将更新的props值,第二个是即将更新后的state值。shouldComponentUpdate()它可以用于性能的优化上,根据更新前后的props或者state进行判断。强调一点的是setState()不能用于shouldComponentUpdate中,不然会导致无限循环调用更新。

    2.2 getSnapshotBeforeUpdate(prevProps,prevState)

    它是在render后,即将对组件进行挂载时调用。getSnapshotBeforeUpdate()的返回值会传递给componentDidUpdate()。这个方法一般用的也比较少。

    2.3 componentDidUpdate(prevProps,prevState,snappshot)

    componentDidUpdate在更新发生之后立即被调用。这个生命周期在组件第一次渲染时不会触发。在这个生命周期中可以调用setState方法,但是这是有条件的,即它要包含在条件语句中。

  3、Unmounting(卸载阶段):组件从DOM树中被移除的过程

    3.1 componentWillUnmount() 它是卸载阶段唯一的方法,在组件即将被卸载或者是销毁时进行调用。 在这个生命周期中我们可以取消网络请求,清理定时器,移除事件监听等等。

 

posted on 2020-09-06 11:28  人称小小贩  阅读(622)  评论(0编辑  收藏  举报