很多的事物都有从创建到销毁的整个过程,这个过程称之为是生命周期;
React组件也有自己的生命周期,了解组件的生命周期可以让我们在最合适的地方完成自己想要的功能;
生命周期和生命周期函数的关系:
生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段;
比如装载阶段(Mount),组件第一次在DOM树中被渲染的过程;
比如更新过程(Update),组件状态发生变化,重新更新渲染的过程;
比如卸载过程(Unmount),组件从DOM树中被移除的过程;
React内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数:
比如实现componentDidMount函数:组件已经挂载到DOM上时,就会回调;
比如实现componentDidUpdate函数:组件已经发生了更新时,就会回调;
比如实现componentWillUnmount函数:组件即将被移除时,就会回调;
我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能;
我们谈React生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的
我们先来了解一下最基础、最常用的生命周期函数:
class HelloWorld extends React.Component {
// 1.构造方法: constructor
constructor() {
console.log("HelloWorld constructor")
super()
this.state = {
message: "Hello World"
}
}
changeText() {
this.setState({ message: "你好啊, 李银河" })
}
// 2.执行render函数
render() {
console.log("HelloWorld render")
const { message } = this.state
return (
<div>
<h2>{message}</h2>
<p>{message}是程序员的第一个代码!</p>
<button onClick={e => this.changeText()}>修改文本</button>
</div>
)
}
// 3.组件被渲染到DOM: 被挂载到DOM
componentDidMount() {
console.log("HelloWorld componentDidMount")
}
// 4.组件的DOM被更新完成: DOM发生更新
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("HelloWorld componentDidUpdate:", prevProps, prevState, snapshot)
}
// 5.组件从DOM中卸载掉: 从DOM移除掉
componentWillUnmount() {
console.log("HelloWorld componentWillUnmount")
}
// 不常用的生命周期补充
shouldComponentUpdate() {
return true
}
getSnapshotBeforeUpdate() {
console.log("getSnapshotBeforeUpdate")
return {
scrollPosition: 1000
}
}
}
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
constructor中通常只做两件事情:
通过给 this.state 赋值对象来初始化内部的state;
为事件绑定实例(this);
componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。
componentDidMount中通常进行哪里操作呢?
依赖于DOM的操作可以在这里进行;
在此处发送网络请求就最好的地方;(官方建议)
可以在此处添加一些订阅(会在componentWillUnmount取消订阅);
componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。
当组件更新后,可以在此处对 DOM 进行操作;
如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。
componentWillUnmount() 会在组件卸载及销毁之前直接调用。
在此方法中执行必要的清理操作;
例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等;
除了上面介绍的生命周期函数之外,还有一些不常用的生命周期函数:
getDerivedStateFromProps:state 的值在任何时候都依赖于 props时使用;该方法返回一个对象来更新state;
getSnapshotBeforeUpdate:在React更新DOM之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位置);
shouldComponentUpdate:该生命周期函数很常用,但是我们等待讲性能优化时再来详细讲解;
另外,React中还提供了一些过期的生命周期函数,这些函数已经不推荐使用。
在开发过程中,我们会经常遇到需要组件之间相互进行通信:
比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示;
又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给他们来进行展示;
也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;
总之,在一个React项目中,组件之间的通信是非常重要的环节;
父组件在展示子组件,可能会传递一些数据给子组件:
父组件通过 属性=值 的形式来传递给子组件数据;
子组件通过 props 参数获取父组件传递过来的数据;
对于传递给子组件的数据,有时候我们可能希望进行验证,特别是对于大型项目来说:
当然,如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证;
但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证;
从 React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 库
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
// 一个 React 元素。
optionalElement: PropTypes.element,
// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,
// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),
// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
// 任意类型的必需数据
requiredAny: PropTypes.any.isRequired,
// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
你可以通过 PropTypes.element 来确保传递给组件的 children 中只包含一个元素。
import PropTypes from 'prop-types';
class MyComponent extends React.Component {
render() {
// 这必须只有一个元素,否则控制台会打印警告。
const children = this.props.children;
return (
<div>
{children}
</div>
);
}
}
MyComponent.propTypes = {
children: PropTypes.element.isRequired
};
您可以通过配置特定的 defaultProps 属性来定义 props 的默认值:
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
// 指定 props 的默认值:
Greeting.defaultProps = {
name: 'Stranger'
};
// 渲染出 "Hello, Stranger":
const root = ReactDOM.createRoot(document.getElementById('example'));
root.render(<Greeting />);
从 ES2022 开始,你也可以在 React 类组件中将 defaultProps 声明为静态属性。这种现代语法需要添加额外的编译步骤才能在老版浏览器中工作。
class Greeting extends React.Component {
static defaultProps = {
name: 'stranger'
}
render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}
defaultProps 用于确保 this.props.name 在父组件没有指定其值时,有一个默认值。propTypes 类型检查发生在 defaultProps 赋值后,所以类型检查也适用于 defaultProps
如果你在常规开发中使用函数组件,那你可能需要做一些适当的改动,以保证 PropsTypes 应用正常。
假设你有如下组件:
export default function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
如果要添加 PropTypes,你可能需要在导出之前以单独声明的一个函数的形式,声明该组件,具体代码如下:
function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
export default HelloWorldComponent
接着,可以直接在 HelloWorldComponent 上添加 PropTypes:
import PropTypes from 'prop-types'
function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
HelloWorldComponent.propTypes = {
name: PropTypes.string
}
export default HelloWorldComponent
某些情况,我们也需要子组件向父组件传递消息:
在vue中是通过自定义事件来完成的;
在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;
我们这里来完成一个案例:
将计数器案例进行拆解;
将按钮封装到子组件中:CounterButton;
CounterButton发生点击事件,将内容传递到父组件中,修改counter的值;
app.jsx
import React, { Component } from 'react'
import AddCounter from './AddCounter'
import SubCounter from './SubCounter'
export class App extends Component {
constructor() {
super()
this.state = {
counter: 100
}
}
changeCounter(count) {
this.setState({ counter: this.state.counter + count })
}
render() {
const { counter } = this.state
return (
<div>
<h2>当前计数: {counter}</h2>
<AddCounter addClick={(count) => this.changeCounter(count)}/>
<SubCounter subClick={(count) => this.changeCounter(count)}/>
</div>
)
}
}
export default App
subCounter.jsx
import React, { Component } from 'react'
export class SubCounter extends Component {
subCount(count) {
this.props.subClick(count)
}
render() {
return (
<div>
<button onClick={e => this.subCount(-1)}>-1</button>
<button onClick={e => this.subCount(-5)}>-5</button>
<button onClick={e => this.subCount(-10)}>-10</button>
</div>
)
}
}
export default SubCounter
addCounter.jsx
import React, { Component } from 'react'
// import PropTypes from "prop-types"
export class AddCounter extends Component {
addCount(count) {
this.props.addClick(count)
}
render() {
return (
<div>
<button onClick={e => this.addCount(1)}>+1</button>
<button onClick={e => this.addCount(5)}>+5</button>
<button onClick={e => this.addCount(10)}>+10</button>
</div>
)
}
}
// AddCounter.propTypes = {
// addClick: PropTypes.func
// }
export default AddCounter