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>
  }
}
相关推荐
恋猫de小郭1 小时前
Flutter 3.35 发布,快来看看有什么更新吧
android·前端·flutter
chinahcp20082 小时前
CSS保持元素宽高比,固定元素宽高比
前端·css·html·css3·html5
gnip3 小时前
浏览器跨标签页通信方案详解
前端·javascript
gnip3 小时前
运行时模块批量导入
前端·javascript
hyy27952276844 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
逆风优雅4 小时前
vue实现模拟 ai 对话功能
前端·javascript·html
若梦plus4 小时前
http基于websocket协议通信分析
前端·网络协议
不羁。。4 小时前
【web站点安全开发】任务3:网页开发的骨架HTML与美容术CSS
前端·css·html
这是个栗子4 小时前
【问题解决】Vue调试工具Vue Devtools插件安装后不显示
前端·javascript·vue.js
姑苏洛言5 小时前
待办事项小程序开发
前端·javascript