03-React组件通讯

学习目标

  • 组件通讯-介绍以及为什么要有组件通讯
  • 组件的props
  • 组件通讯的三种方式 父传子 子传父 兄弟组件
  • Context组件通讯
  • props深入

组件通讯

组件独立作用域 ,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能 拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件 之间不可避免的要共享某些数据 。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯

props

  • props的作用:接收传递给组件的数据
  • props的值:是标签属性组成对象。
  • props名字 :属性-property单词的缩写prop , + s

使用步骤:

  • 传递数据:给组件标签添加属性。 -- 与vue类似
  • 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据

函数组件通讯

父组件

js 复制代码
<Hello name="jack" age={19} />

子组件

js 复制代码
function Hello(props) {
    console.log(props)
    return (
    	<div>接收到数据:{props.name}</div>
    )
}

类组件通讯

子组件

js 复制代码
class Hello extends React.Component {
    render() {
        console.log(this, 'this')
        return (
        	<div>接收到的数据:{this.props.age}</div>
        )
    }
}

父组件

js 复制代码
<Hello name="jack" age={19} />

注意:

js 复制代码
1. 👍推荐解构`props` 
2. props是对象,标签属性组成的对象。
3. 不必先定义后使用,可以只传不接收。 (与Vue不同)

props的特点

  • 可以给组件传递任意类型的数据,包括虚拟 DOM 结构

    • 字符串、数字、
    • 数组、对象、
    • 💥函数、💥JSX
  • 💥props是只读的,不允许修改props的数据 - 单向数据流

  • 💥React中props绕不过引用地址。

总结:

  1. props是对象
  2. props可以是任意值,注意,function是对象,不能作为标签的内容

组件通讯三种方式

  • 父传子
  • 子传父
  • 兄弟组件

父传子

  1. 父组件提供要传递的state数据
  2. 给子组件标签添加属性,值为 state 中的数据
  3. 子组件中通过 props 接收父组件中传递的数据

静态结构准备

js 复制代码
class Parent extends React.Component {
  state = {
    money: 1000,
  };

  handleMakeMoney = () => {
    this.setState({
      money: 1000 + this.state.money,
    });
  };

  render() {
    return (
      <div>
        <button onClick={this.handleMakeMoney}>爸爸开始赚钱了</button>        
        <Child ></Child>
      </div>
    );
  }
}

class Child extends React.Component {
  render() { 
    return (
      <div>
        <h1>爸爸给我钱了: </h1>        
      </div>
    );
  }
}

父组件提供数据并且传递给子组件

js 复制代码
class Parent extends React.Component {
  // ...省略其它的代码
  state = { money: 1000}
  render() {
    return (
      <div>
        <button onClick={this.handleMakeMoney}>爸爸开始赚钱了</button>        
        <Child money={this.state.money}></Child>
      </div>
    );
  }
}

子组件接收数据

js 复制代码
class Child extends React.Component {
  render() {
    const { money } = this.props;

    return (
      <div>
        <h1>爸爸给我钱了: {money}</h1>
        
      </div>
    );
  }
}

子传父

思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。

  1. 父组件提供一个回调函数(用于接收数据)
  2. 将该函数作为标签属性的值,传递给子组件
  3. 子组件通过 props.语法找到函数,再 调用回调函数
  4. 将子组件的数据作为参数传递给回调函数

使用步骤:

父组件提供函数,并且通过标签属性,传递给子组件

js 复制代码
class Parent extends React.Component {
  state = {
    money: 0,
  };

  handleMakeMoney = () => {
    this.setState({
      money: 1000 + this.state.money,
    });
  };

  // 1. 在父组件中准备一个函数
  handleCost = (num) => {
    this.setState({
      money: this.state.money - num,
    });
  };

  render() {
    return (
      <div>
        <button onClick={this.handleMakeMoney}>爸爸开始赚钱了</button>
        {/* 2. 将函数通过props,传给子组件 */}
        <Child money={this.state.money} handleCost={this.handleCost}></Child>
      </div>
    );
  }
}

子组件接收函数并且调用

js 复制代码
class Child extends React.Component {
  handleChildCost = () => {
    //  3. 在子组件内,通过props.语法找到对应的函数
    //  4. 在组件内,通过()的方式,直接调用函数, 可以直接传参
    this.props.handleCost(1000);
  };

  render() {
    const { money } = this.props;

    return (
      <div>
        <h1>爸爸给我钱了: {money}</h1>
        <button onClick={this.handleChildCost}>点击我,儿子要花爸爸的钱了</button>
      </div>
    );
  }
}

注意:回调函数中 this 指向问题!

案例-评论列表改造

兄弟组件

  • 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态

  • 思想:状态提升

  • 公共父组件职责:

    • 提供共享状态
    • 提供操作共享状态的方法
  • 要通讯的子组件只需通过 props 接收状态或操作状态的方法

状态提升前

状态提升之后

状态提升练习:

src/components/Husband.js

js 复制代码
import React, { Component } from 'react';

export default class Husband extends Component {
  state = {
    money: 0,
  };

  /* 赚钱方法 */
  handleMakeMoney = () => {
    this.setState({ money: this.state.money + 1000 });
  };

  render() {
    return (
      <div style={{ padding: '10px', border: '10px solid #ccc' }}>
        <h1>老公的钱: {this.state.money}</h1>
        <button onClick={this.handleMakeMoney}>老公赚钱</button>
      </div>
    );
  }
}

src/components/Wife.js

js 复制代码
import React, { Component } from 'react';

export default class Wife extends Component {
  /* 花钱方法 */
  handleCostMoney = () => {
    // 想要花点老公赚的钱
  };

  render() {
    return (
      <div style={{ padding: '10px', border: '10px solid #ccc' }}>
        <button onClick={this.handleCostMoney}>老婆开始花钱</button>
      </div>
    );
  }
}

App.js

js 复制代码
import React, { Component } from 'react';
import Husband from './components/Husband';
import Wife from './components/Wife';


export default class App extends Component {
  render() {
    return (
      <div>
        <h1 style={{ textAlign: 'center' }}>家庭存款:</h1>
        <Husband></Husband>
        <hr />
        <Wife></Wife>
      </div>
    );
  }
}

组件通讯-context - 跨组价通信

基本概念

思考:App 组件要传递数据给 Child 组件,该如何处理?

处理方式:使用 props 一层层组件往下传递(繁琐)

更好的姿势:使用 Context

作用:跨组件传递数据(比如:主题、语言等)

App.js 直接复制如下静态结构

js 复制代码
/* 
  目标: 使用Context来跨组价通信,让App组件-直接传数据给SonSon组件
*/
export default class App extends React.Component {
  render() {
    return (
      <div>
        <h1>父组件</h1>
        <Son></Son>
      </div>
    );
  }
}

class Son extends Component {
  render() {
    return (
      <div>
        <h2> 儿子</h2>
        <SonSon></SonSon>
      </div>
    );
  }
}

class SonSon extends Component {
  render() {
    return (
      <div>
        <h2> 孙子</h2>
      </div>
    );
  }
}

实现步骤

  • 调用 React. createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件
js 复制代码
const { Provider, Consumer } = React.createContext()
  • 使用 Provider 组件作为父节点。
js 复制代码
<Provider>
    <div className="App">
    	<Child1 />
    </div>
</Provider>
  • 设置 value 属性,表示要传递的数据。
js 复制代码
<Provider value="pink">
  • 调用 Consumer 组件接收数据。
js 复制代码
<Consumer>
	{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>

总结:

  1. 如果两个组件是远方亲戚(比如,嵌套多层)可以使用Context实现组件通讯
  2. Context提供了两个组件:Provider 和 Consumer。
  3. createContext()可以调用多次,每次解构出来的Provider和Consumer要成对使用。
  4. Provider组件:用来存入数据
  5. Consumer组件:用来取出数据

Context的优缺点及常见使用场景

优点:React自带,不需要借助其它包,即可跨组件通信

缺点:Provider 和Consumer增加嵌套结构,代码理解成本加大。

UI库中:

  1. 多语言切换
  2. 一键换色

redux

后续学习

props深入

children属性

children属性:表示该组件的子节点,只要组件有子节点,props就有该属性

children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)

js 复制代码
function Hello(props) {
  return (
    <div>
      该组件的子节点:{props.children}
    </div>
  )
}

<Hello>我是子节点</Hello>

props校验

目的:校验接收的props的数据类型,增加,

对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据

如果传入的数据格式不对,可能会导致组件内部报错。组件的使用者不能很明确的知道错误的原因。

props校验允许在创建组件的时候,就约定props的格式、类型等

作用:规定接收的props的类型必须为数组,如果不是数组就会报错,增加组件的健壮性。

使用步骤

  1. 导入 prop-types 包 (不用下载,脚手架自带)
  2. 使用:组件名.propTypes = {} 来给组件的props添加校验规则对象
  3. 规则的数据类型,通过 PropTypes 对象来指定
js 复制代码
import PropTypes from 'prop-types'
function App(props) {
    return (
    	<h1>Hi, {props.colors}</h1>
    )
}
App.propTypes = {
    // 约定colors属性为array类型
    // 如果类型不对,则报出明确错误,便于分析错误原因
    colors: PropTypes.array
}

约束规则

  1. 常见类型:array、bool、func、number、object、string
  2. React元素类型:element
  3. 必填项:isRequired
  4. 特定结构的对象:shape
js 复制代码
// 常见类型
optionalFunc: PropTypes.func,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
	color: PropTypes.string,
	fontSize: PropTypes.number
})

注意:函数式组件的校验,语法上没有什么不同。

props默认值

目标: 给组件的props提供默认值

内容:

  • 通过defaultProps可以给组件的props设置默认值,在未传入props的时候生效

核心代码:

为函数组件添加 props 默认值:

js 复制代码
function App(props) {
  return (
    <div>
      此处展示props的默认值:{props.pageSize}
    </div>
  )
}
// 设置默认值
App.defaultProps = {
	pageSize: 10
}
// 不传入pageSize属性
<App />

为类组件添加 props 默认值:

js 复制代码
class App extends Component {
  render() {
    return (
      <div>
        此处展示props的默认值:{this.props.pageSize}
      </div>
    )
  }
}
设置默认值
App.defaultProps = {
	pageSize: 10
}

// 不传入pageSize属性
<App />

注意:

  1. 函数组件,新版 React 已经不再推荐使用 defaultProps 来添加默认值,
  2. 而是推荐使用函数参数的默认值来实现
js 复制代码
// 通过函数参数默认值,来提供 props 默认值
const App = ({ pageSize = 10 }) {
  return (
    <div>
      此处展示props的默认值:{pageSize}
    </div>
  )
}

// 不传入pageSize属性
<App />

类的静态属性-static

目标: 能够通过类的static语法简化props校验和默认值

内容:

  • 实例成员:通过实例对象调用的属性或者方法,叫做实例成员(属性或者方法)
  • 静态成员:通过类或者构造函数本身才能访问的属性或者方法

核心代码

js 复制代码
class Person {
  // 实例属性
  name = 'zs'
	// 实例方法
  sayHi() {
    console.log('哈哈')
  }

  // 静态属性
  static age = 18
	// 静态方法
  static goodBye() {
    console.log('byebye')
  }
}
const p = new Person()

console.log(p.name) 		// 访问实例属性
p.sayHi()								// 调用实例方法

console.log(Person.age)	// 访问静态属性
Person.goodBye()				// 调用静态方法

示例:

js 复制代码
class List extends Component {
  static propTypes = {
    colors: PropTypes.array,
    gender: PropTypes.oneOf(['male', 'female']).isRequired
  }

  static defaultProps = {
		gender: ''
  }
  
  render() {
    const arr = this.props.colors
    const lis = arr.map((item, index) => <li key={index}>{item.name}</li>)
    return <ul>{lis}</ul>
  }
}
相关推荐
明辉光焱11 分钟前
[Electron]总结:如何创建Electron+Element Plus的项目
前端·javascript·electron
牧码岛31 分钟前
Web前端之汉字排序、sort与localeCompare的介绍、编码顺序与字典顺序的区别
前端·javascript·web·web前端
开心工作室_kaic1 小时前
ssm111基于MVC的舞蹈网站的设计与实现+vue(论文+源码)_kaic
前端·vue.js·mvc
晨曦_子画1 小时前
用于在 .NET 中构建 Web API 的 FastEndpoints 入门
前端·.net
慧都小妮子1 小时前
Spire.PDF for .NET【页面设置】演示:在 PDF 文件中添加图像作为页面背景
前端·pdf·.net·spire.pdf
咔咔库奇1 小时前
ES6基础
前端·javascript·es6
Jiaberrr1 小时前
开启鸿蒙开发之旅:交互——点击事件
前端·华为·交互·harmonyos·鸿蒙
徐小夕2 小时前
Flowmix/Docx 多模态文档编辑器:V1.3.5版本,全面升级
前端·javascript·架构
Json____2 小时前
学法减分交管12123模拟练习小程序源码前端和后端和搭建教程
前端·后端·学习·小程序·uni-app·学法减分·驾考题库