组件从概念上来看就像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> <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() 它是卸载阶段唯一的方法,在组件即将被卸载或者是销毁时进行调用。 在这个生命周期中我们可以取消网络请求,清理定时器,移除事件监听等等。