1、组件的分类

原理:

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

抽象的思想:
image.pngReact的组件相对于Vue更加灵活和多样,按照不同的方式可以分成很多类组件:

  • 根据组件的定义分类
    • 函数式组件(function component)
    • 类组件 (class component)
  • 根据组件内部是否有状态需要维护分类
    • 有状态组件(stateful component) 一般使用类class组件
    • 无状态组件(stateless component)一般使用函数式组件
  • 根据组件的不同指责分类
    • 展示型组件(presentational component)不负责数据的展示,作为UI框架来使用
    • 容器类组件 (container component) 有相应的数据,需要进行数据的操作

2、类组件的书写

类组件的定义有如下要求:

  • 组件的名称必须是大写字符开头【无论是类组件还是函数式组件】
  • 类组件需要继承自React.Component
  • 类组件必须实现render函数

在ES6之前,可以通过create-react-class模块来定义类组件,但是目前官网建议我们使用ES6的class类定义:
使用class定义一个组件:

  • constructor是可选的,我们通常在constructor中初始化一些数据;
  • this.state中维护的就是我们组件内部的数据;
  • render()方法是class组件唯一必须实现的方法;

    1. export default class App extends Component {
    2. render() {
    3. return [
    4. <div>嘻嘻嘻</div>
    5. <div>哈哈哈</div>
    6. <div>呵呵呵</div>
    7. ]
    8. }
    9. }

    3、函数式组件的书写

    函数式组件就是使用function来定义的函数,只是这个函数会返回与类class组件render函数返回一样的内容。
    函数组件也有自己的特点:(hooks也会有不同)

  • 没有生命周期、也会被更新并挂载、但是没有生命周期函数(函数式组件可以模拟生命收起函数)

  • 没有this(组件实例)
  • 没有自己的内部状态

定义一个函数式组件:

  1. // 定义函数式组件
  2. export default function App() {
  3. // 返回jsx表达式即可
  4. return (
  5. <div>
  6. <h2>我是函数式组件</h2>
  7. </div>
  8. )
  9. }

4、组件的生命周期

最基础、最常用的生命周期函数:

image.png

4.1 组件构造阶段(constructor)

constructor构造方法:如果不初始化state或者进行方法的绑定,则不需要为react组件实现构造函数。
constructor通常只用做两件事:

  • 通过给this.state赋值对象来初始化组件内部的状态。
  • 为事件绑定实例(this),后面可使用箭头函数来改变this的指向。

    1. // 构造方法
    2. constructor(props) {
    3. super(props);
    4. // 不要在这里调用 this.setState()
    5. this.state = { counter: 0 };
    6. this.handleClick = this.handleClick.bind(this);
    7. }

    image.png4.2 componentDidMount组件完成挂载

  • componentDidMount()会在组件挂载后(插入DOM树中)立即调用

  • componentDisMount生命周期中可以做的事情:
    • 依赖dom的操作可以在这里进行。
    • 在此处发送网路请求是最好的地方(官方建议)。
    • 可以在此处添加一些订阅(在生命周期componentWillUnmount取消订阅)

4.3 componentDidUpdate组件数据更新

  • componentDidUpdate会在页面更新后进行调用,首次页面渲染的时候不会调用此方法
  • 当组件更新后,可以在此处对dom进行操作
  • 如果你对更新前后的props 进行了比较,也可以选择在此处进行网络请求;(例如,当props 未发生变化时,则不会执行网络请求)。

4.4 componentWillUnmount组件将要卸载

  • 此方法会在组件卸载和销毁之前被调用
  • 在此方法中执行必要的清理操作
    • 如清除定时器,取消网络请求或者取消在componentDidMount生命周期中创建的订阅。

整个组件的构建过程:
image.png

类组件的生命周期执行顺序: 1、类组件先执行constructor构造器方法; 2、类组件执行componentWillMount生命周期;—>不推荐使用 3、类组件执行render渲染方法; 4、类组件执行componentDidMount生命周期;—>发送网络请求、事件订阅 5、类组件执行componentDisUpdate生命周期; 6、类组件执行componentWillUnmount生命周期;—>清除定时器、取消事件的订阅

5、组件的通信

5.1 父组件向子组件进行通信

通信原理:在父组件对子组件进行引用的时候,通过子组件的标签添加属性的方式来完成数据的传递。 在子组件中我们只需要在组件渲染数据的同时使用this.props(类组件)来获取传递的数据即可。函数式组件只需要在函数声明的时候声明一个接受的参数即可,在渲染的时候,直接通过参数来获取传递的数据即可。

5.1.1 class类组件的通信

  1. // 父子组件之间的通信 通过组件的标签属性进行数据的传递 数据从父组件单向传递给子组件
  2. import React, { Component } from "react";
  3. // 定义子组件
  4. class ChildCpn extends React {
  5. // 构造方法 这个构造方法是不需要写 在实例的构造过程中会自动执行此函数
  6. // constructor(props) {
  7. // super(props)
  8. // }
  9. // 渲染方法
  10. render() {
  11. // 直接从父组件中解构出传递的属性 父组件向子组件传递数据的精华就是这一部分
  12. const { name, age, sex } = this.props;
  13. return (
  14. <div>
  15. <h2>姓名: { name }</h2>
  16. <h2>年龄: { age }</h2>
  17. <h2>性别: { sex }</h2>
  18. </div>
  19. )
  20. }
  21. }
  22. // 定义父组件
  23. class App extends React {
  24. // 渲染方法
  25. render() {
  26. return (
  27. // 直接使用子组件 通过标签的属性类传递数据
  28. <ChildCpn name="coderweiwei" age=18 sex="男" />
  29. <hr />
  30. )
  31. }
  32. }

5.1.2 函数式组件之间的通信

  1. import React, { Component } from "react";
  2. // 定义函数类型的组件
  3. function ChildCpn(props) {
  4. // props就是接收的父组件传递过来的数据
  5. // 对父组件传递过来的数据 进行解构赋值
  6. const { name, age, sex } = props;
  7. return (
  8. <div>
  9. <h2>姓名:{ name }</h2>
  10. <h2>年龄:{ age }</h2>
  11. <h2>性别:{ sex }</h2>
  12. </div>
  13. )
  14. }
  15. // 定义父组件
  16. class App extends Component {
  17. render() {
  18. return (
  19. <h2>我是父组件</h2>
  20. // 直接引入子组件 通过组件的标签属性进行数据传递 这里传递的数据都是字符串的形式
  21. <ChildCpn name="coderweiwei" age="18" sex="男" />
  22. )
  23. }
  24. }
  25. // 将组件默认导入 在其他的页面可直接引入该组件
  26. export default App;

5.2 参数验证propTypes

对于传递给子组件的数据,我们希望对数据的类型进行验证,以免发生不可避免的错误

注意:从V15.5开始,React.PropTypes已移入另外一个包中”prop-types”库
函数式组件参数的验证

  1. // 先安装 prop-types 库
  2. // 在组件中引入这个库
  3. import PropTypes from "prop-types"
  4. // 对子组件接收的参数进行校验
  5. function ChildCpn(props) {
  6. // 对传递的参数进行解构赋值
  7. const { name, age, sex } = props;
  8. return (
  9. <div>
  10. <h2>姓名:{ name }</h2>
  11. <h2>年龄:{ age }</h2>
  12. <h2>性别:{ sex }</h2>
  13. </div>
  14. )
  15. }
  16. // 对子组件进行数据校验 组件的方法
  17. ChildCpn.propTypes = {
  18. name: PropTypes.string, // 字符串类型
  19. age: PropTypes.number, // 数字类型
  20. sex: PropTypes.string, // 字符串类型
  21. skill: PropTypes.array // 数组类型
  22. // 传递的数据 数据是否为必传的字段 在后面加上is_required进行限制
  23. skill: PropTypes.array.isRequired
  24. }
  25. // 当父组件不传递参数的时候 我们可以给参数加上默认值
  26. ChildCpn.protoTypes = {
  27. name: "coderweiwei",
  28. age: 14,
  29. sex: "男",
  30. skill: ["html", "css", "javascript"]
  31. }
  32. // 父组件利用组件的标签传递属性
  33. <ChildCpn name="coderweiwei" age={123} sex="男" skill={ ["vue", "react", "javascript"] } />

类组件参数的验证

  1. // 类组件参数的校验
  2. import React, { Component } from "React";
  3. import PropTypes from 'prop-types'
  4. // 定义子组件 使用es6类的形式进行定义
  5. class ChildCpn extends Component {
  6. // 只要不初始化组件的内部状态和为组件实例绑定this 就可以不需要构造函数
  7. //constructor(props) {
  8. // super(props)
  9. //}
  10. // 渲染方法
  11. render() {
  12. // 对父组件传入的数据 进行解构赋值
  13. const { name, age, sex } = this.props;
  14. return (
  15. <div>
  16. <h2>姓名:{ name }</h2>
  17. <h2>年龄:{ age }</h2>
  18. <h2>性别:{ sex }</h2>
  19. </div>
  20. )
  21. }
  22. }
  23. // 对父组件传递的数据进行类型判断、一级必要性说明
  24. // 方式1: 直接使用组件的属性 对其进行赋值的操作
  25. ChildCpn.propTypes = {
  26. name: PropTypes.string,
  27. age: PropTypes.number,
  28. sex: PropTypes.string
  29. }
  30. // 参数是否进行必传 在类型的后面添加isRequired属性
  31. ChildCpn.propTypes = {
  32. name: PropTypes.string.isRequired,
  33. age: PropTypes.number.isRequired,
  34. sex: PropTypes.string.isRequired
  35. }
  36. // 给传入的参数一定的默认值 这样的话 在我们从父组件传值的时候 如果没有传递的话,有相应的默认值,那么我们的浏览器也不会报错 有默认值的话 相当于我们已经传递了
  37. ChildCpn.defaultProps = {
  38. name: "默认的姓名值",
  39. age: 12345678, // 给相应的默认值
  40. sex: "默认性别的值-男"
  41. }
  42. // 方法2 将我们的验证规则 加在class组件的定义里面
  43. class ChildCpn extends Component {
  44. // 构造方法
  45. //constructor(props) {
  46. // super(props)
  47. //}
  48. // 将验证的规则放在类的静态属性里面 传递的数据类型 以及是否为必填
  49. static propTypes = {
  50. name: PropTypes.string.isRequired,
  51. age: PropTypes.number.isRequired,
  52. sex: PropTypes.string.isRequired
  53. }
  54. // 验证的规则2 传递的值 给相应的默认值
  55. static defaultProps = {
  56. name: "默认值-coderweiwei",
  57. age: 12345678910,
  58. sex: "默认的性别值-男"
  59. }
  60. // 渲染方法
  61. render() {
  62. return (
  63. // JSX support
  64. )
  65. }
  66. }
  67. // 定义的父组件
  68. export default class App extends Component {
  69. render() {
  70. return (
  71. <div>
  72. // 使用标签对组件进行映射 通过标签对子组件进行数据传递
  73. <ChildCpn name="coderweiwei" age={18} sex="男" />
  74. </div>
  75. )
  76. }
  77. }

5.3 子组件向父组件进行通信

原理:子组件触发父组件传递的自定义方法完成数据的通信。 具体来说就是父组件可以通过自元素标签向子组件传递数据,其中这个数据包括传递的属性和方法,父组件向子组件传递的属性,子组件可以直接使用。父组件向子组件传递的方法,子组件也可以直接进行调用,并且可以携带相应的参数,这个方法是定义在父组件内部的,相当于子组件触发了父组件的方法。

父组件向子组件传递属性和方法:

  1. import React, { Component } from 'react'
  2. class Son extends Component {
  3. render() {
  4. console.log('父组件传递的数据', this.props.num)
  5. return (
  6. <div>
  7. <p>Son子组件</p>
  8. <h2>子组件接收的数据: { this.props.num }</h2>
  9. <button onClick={ () => this.updateParentCounter() }>修改父组件的数据</button>
  10. </div>
  11. )
  12. }
  13. updateParentCounter() {
  14. console.log(this.props)
  15. // 触发父级组件传递的方法 传递的参数 可以通过这个函数进行传递
  16. this.props.sonClick(100, '参数2')
  17. }
  18. }
  19. export default class Parent extends Component {
  20. constructor() {
  21. super()
  22. this.state = {
  23. counter: 123
  24. }
  25. }
  26. render() {
  27. return (
  28. <div>
  29. <p>Psarent父组件</p>
  30. <h2>当前计数: { this.state.counter }</h2>
  31. <button onClick={ this.increment.bind(this) }>点击增加</button>
  32. <button onClick={ () => this.decrement() }>点击减少</button>
  33. <hr/>
  34. {/* 传递属性 传递方法 */}
  35. <Son num={ this.state.counter } sonClick={ (a, b) => this.clickItem(a, b) }/>
  36. <hr/>
  37. </div>
  38. )
  39. }
  40. // 自定义事件
  41. clickItem(num, params2) {
  42. // 修改数据
  43. this.setState({
  44. counter: this.state.counter + num
  45. })
  46. }
  47. increment() {
  48. this.setState({
  49. counter: this.state.counter + 1
  50. })
  51. }
  52. decrement() {
  53. this.setState({
  54. counter: this.state.counter - 1
  55. })
  56. }
  57. }

6、state和props的区别

state和props的总结:

  • state的主要作用是用于组件保存、控制、修改自己的可变状态。state在组件内部初始化,可以被组件自身修改,而瓦齐布不能访问也不能修改。你可以认为state是一个局部的、只能被组件自身控制的数据源。state中状态可以通过this.setState方法进行更新,setState会导致组件的重新渲染。
  • props的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的props,否则组件的props永远保持不变。
  • state和props有着千丝万缕的联系。他们都可以决定一个组件的行为和显示状态。一个组件的state中的数据可以通过props传给子组件,一个组件可以使用外部传入的props来初始化自己的state。但是他们的职责非常分明:state是让组件控制自己的状态,props是让外部对组件自己进行配置。
  • 如果搞不清state和props的使用场景,记住一个简单的原则:尽量少地使用state,尽量多地用props.
  • 没有state的组件叫无状态组件(stateless component),设置了state的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。前端应用状态管理是一个复杂的问题。

无状态组件

  1. // 有状态组件
  2. class HelloWord extends Component {
  3. constructor() {
  4. super()
  5. }
  6. // 定义的方法
  7. sayHi() {
  8. console.log('hello world')
  9. }
  10. // 渲染函数
  11. render() {
  12. return (
  13. <div onClick={this.sayHi.bind(this)}></div>
  14. )
  15. }
  16. }

有状态组件-函数式组件的编写:

  1. const helloWorld = (props) => {
  2. // 定义函数
  3. const sayHi = (event) => console.log('Hello, World')
  4. // 返回值
  5. return (
  6. <div onClick={sayHi}>Hello world</div>
  7. )
  8. }

以前一个组件是通过继承Component来构建,一个子类就是一个组件。而用函数式的组件编写方式是一个函数就是一个组件,你可以和以前通过使用该组件。不同的是,函数式组件只能接受props而无法像类组件一样可以在constructor里面初始化state。你可以理解函数式组件就是一种只能接受props和提供render方法的类组件。