React18 - 组件化开发(一)

666 阅读8分钟

如果我们将一个⻚面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展

但如果,我们讲一个⻚面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个⻚面的管理和维护就变得非常容易了

  • 我们将一个完整的⻚面分成很多个组件
  • 每个组件都用于实现⻚面的一个功能块
  • 而每一个组件又可以进行细分
  • 而组件本身又可以在多个地方进行复用

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a47f93bbe32746d9b0344f9237e709af~tplv-k3u1fbpfcp-zoom-1.image

所以组件化是React的核心

  • 组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
  • 任何的应用都会被抽象成一颗组件树

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/22a88dcd3e8041c4952b8efef6ba0a20~tplv-k3u1fbpfcp-zoom-1.image

有了组件化的思想,我们在之后的开发中就要充分的利用它

尽可能的将⻚面拆分成一个个小的、可复用的组件

这样让我们的代码更加方便组织和管理,并且扩展性也更强

组件化

分类说明
根据组件的定义方式函数组件(Functional Component )和类组件(Class Component)
根据组件内部是否有状态需要维护无状态组件(Stateless Component )和有状态组件(Stateful Component)
根据组件的不同职责展示型组件(Presentational Component 只展示,业务逻辑较少)和容器型组件(Container Component 有着复杂的业务逻辑)

所以组件间划分的根本关注点是数据逻辑和UI展示的分离

  • 函数组件、无状态组件、展示型组件主要关注UI的展示
  • 类组件、有状态组件、容器型组件主要关注数据逻辑

组件化还有一些其余分类,例如异步组件、高阶组件等

类组件

定义要求

  1. 组件的名称是大写字符开头(无论类组件还是函数组件)
  2. 类组件需要继承自 React.Component 或 React.PureComponent
  3. 类组件必须实现render函数, render函数需要返回实际所需要渲染的内容
import React, { Component } from 'react'

export class App extends Component {
	// 用于初始化对应的状态
	// 如果没有对应的状态需要存储,构造器可以省略
	constructor(props) {
		super(props)

		// state中维护这界面中需要使用且改变时需要界面刷新的数据
		this.state = {

		}
	}

	// render函数是类组件唯一必须需要实现的函数
	// 该函数需要返回实际需要被渲染的内容
	render() {
		return (
			<div>index</div>
		)
	}
}

// 组件即被默认导出,又被单独导出,这就意味着使用
// import App from '....' 或 import { App } from '...'
// 都是被允许的
export default App

render

调用时机

  1. 初次被渲染
  2. this.props发生改变
  3. 通过setState函数修改this.state

返回值

类别说明
React元素所谓React元素 其实就是通过JSX编写的元素,也就是React.createElement方法的返回值
数组或fragments使得 render 方法可以返回多个元素
如果返回值是数组,会自动调用Array.prototype.join('')方法后进行渲染
Portals可以渲染子节点到不同的 DOM 子树中
字符串或数值类型它们在 DOM 中会被渲染为文本节点
布尔类型或null或undefined什么都不渲染

函数组件

函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容

在不使用hooks的前提条件下,函数组件具有如下特点:

  1. 没有生命周期,也会被更新并挂载,但是没有生命周期函数
  2. this关键字不能指向组件实例(因为没有组件实例)
  3. 没有内部状态 state
// 函数组件的名称也需要首字母大写
export default function App() {
	// 函数式组件返回需要展示的内容
	// 其可以返回的类型和render方法可以返回的类型一致
	// 可以认为函数组件其实就是只有render函数的类组件
	return (
		<div>Hello React</div>
	)
}

生命周期

很多的事物都有从创建到销毁的整个过程,这个过程称之为是生命周期

React组件也有自己的生命周期,了解组件的生命周期可以让我们在最合适的地方完成自己想要的功能

生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段:

  1. 装载阶段(Mount),组件第一次在DOM树中被渲染的过程
  2. 更新过程(Update),组件状态发生变化,重新更新渲染的过程
  3. 卸载过程(Unmount),组件从DOM树中被移除的过程

React内部会在执行到对应的特定的时间点的时候对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数

  • 比如实现componentDidMount函数:组件已经挂载到DOM上时,就会回调
  • 比如实现componentDidUpdate函数:组件已经发生了更新时,就会回调
  • 比如实现componentWillUnmount函数:组件即将被移除时,就会回调

我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能

常用的生命周期函数 image.png

不常用的生命周期函数

image.png

方法说明
getDerivedStateFromPropsstate 的值在任何时候都 依赖于 props时使用;该方法返回一个对象来更新 state
getSnapshotBeforeUpdate在React更新DOM之前回 调的一个函数,可以获取DOM更新前的一些信息(比如 说滚动位置)
shouldComponentUpdate返回一个boolean值,用于决定组件是否需要重新渲染,可以用于组件的性能优化
import React, { Component } from 'react'

export class App extends Component {
	constructor() {
		super()

		this.state = {
			msg: 'Hello World'
		}
	}

	render() {
		const { msg } = this.state

		return (
			<div>
				<div>{msg}</div>
				<button onClick={() => this.handleClick()}>click me</button>
			</div>
		)
	}

	handleClick() {
		this.setState({
			msg: 'Hello React'
		})
	}

	getSnapshotBeforeUpdate() {
		// 返回一个对象
		// 该方法会在组件DOM更新前执行
		return {
			name: 'Klaus',
			age: 23
		}
	}

	// componentDidUpdate方法可以接收三个参数
	// prevProps - 更新前的props
	// prevState - 更新前的state
	// shapshot - 更新前getSnapshotBeforeUpdate方法返回的对象
	componentDidUpdate(prevProps, prevState, shapshot) {
		console.log(prevProps, prevState, shapshot)
	}
}

export default App
class EmailInput extends Component {
  constructor(props) {
    super(props)

    this.state = {
      email: this.props.defaultEmail,
      prevUserID: this.props.userID
    }
  }

  // getDerivedStateFromProps 会在调用 render 方法之前调用
  // getDerivedStateFromProps 的存在只有一个目的:让组件在 props 变化时更新 state
  // 该方法返回一个对象用于更新 state,如果返回 null 则不更新任何内容
  // getDerivedStateFromProps并不被推荐使用,只有在极少数情况下,可能会需要使用该方法
  static getDerivedStateFromProps(nextProps, prevState) {
    // 当新传入的uerID 和 原始的userID不一致时,重置user信息
    if (nextProps.userID !== prevState.prevUserID) {
      return {
        prevPropsUserID: nextProps.userID,
        email: nextProps.defaultEmail
      };
    }
    return null;
  }
}

组件间通信

父传子

父组件在展示子组件,可能会传递一些数据给子组件

  • 父组件通过 属性 = 值 的形式来传递给子组件数据
  • 子组件通过 props 参数获取父组件传递过来的数据

类组件

父组件

import React, { PureComponent } from 'react'
import Child from './components/Child'

export class App extends PureComponent {
	constructor(props) {
		super(props)

		this.state = {
			msg : 'msg in App'
		}
	}


	render() {
		return (
			<div>
      	<!-- 父组件以属性键值对的形式向子组件传递props -->
      	<Child msg={this.state.msg} />
      </div>
		)
	}
}

export default App

子组件

import React, { PureComponent } from 'react'

export class Child extends PureComponent {
	// 创建子组件实例的时候,会将子组件上的props封装成一个对象后,作为构造函数的参数被传入
	// 如果构造函数只是接收外部传入的props,但是不需要定义自身的state的时候
	// 对应的构造函数可以省略,因为这是默认形式

	// constructor(props) {
	// 	// 将props传递给父类的构造函数
	// 	// 父类构造函数内部会执行 this.props = props
	// 	// 又因为子类继承使用的是借用构造函数继承
	// 	// 所以可以在子组件中通过this.props来访问外部传入的props数据
	// 	super(props)
	// }

	render() {
		return (
			<div>{this.props.msg}</div>
		)
	}
}

export default Child

函数组件

import React from 'react'

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

export default Child

类型验证

对于传递给子组件的数据,有时候我们可能希望进行验证,特别是对于大型项目来说

当然,如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证

但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证

从 React v15.5 开始,React.PropTypes 已移入另一个包中: prop-types 库

import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'

export class Child extends PureComponent {
	// props类型校验
	// 如果校验失败 会报警告
	static propTypes = {
		// 如果是boolean类型 - PropTypes.bool
		// 如果是function类型 - PropTypes.func
    // 如果传入的是一个ReactElement - PropTypes.element
    //  --- JSX中所有的标签都是ReactElement,包括JSX中的div,h2, ....
    //  --- 在使用Children模拟插槽的时候,如果只希望使用者只能传入一个插槽内容
    //  --- 就可以使用PropTypes.element来进行限制,因为如果传入多个插槽内容,props.children属性将会变为数组类型
		name: PropTypes.string.isRequired,
		age: PropTypes.number
	}

	// 定义默认值
	static defaultProps = {
		age: 18
	}

	render() {
		return (
			<div>{this.props.name} - { this.props.age }</div>
		)
	}
}

export default Child
import React from 'react'
import PropTypes from 'prop-types'

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

Child.propTypes = {
	name: PropTypes.string.isRequired,
	age: PropTypes.number
}

Child.defaultProps = {
	age: 18
}

export default Child

spread props

{/* userInfo = { name: 'Klaus', age: 24 } */}

<Cpn {...userInfo} />

<!-- 等价于 -->
<Cpn name={userInfo.name} age={ userInfo.age } />

子传父

某些情况,我们也需要子组件向父组件传递消息

在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可

父组件

import React, { PureComponent } from 'react'
import Increment from './components/Increment'

export class App extends PureComponent {
	constructor(props) {
		super(props)

		this.state = {
			count: 0
		}
	}

	render() {
		return (
			<div>
				<div>{ this.state.count }</div>
				{/*
					 子组件在调用父组件传递过来的props的时候,使用的是this.props
					 所以为了可以在父组件中,可以获取到正确的this
					 父组件传递给子组件的方法props一般都是箭头函数
        */}
				<Increment changeCount={n => this.changeCount(n)} />
			</div>
		)
	}

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

export default App

子组件

import React, { PureComponent } from 'react'

export class Increment extends PureComponent {
	render() {
		return (
			<button onClick={ () => this.addFive() }>+5</button>
		)
	}

	addFive() {
		this.props.changeCount(5)
	}
}

export default Increment