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>
  }
}
相关推荐
gongzemin3 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox16 分钟前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿43 分钟前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下1 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758102 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周2 小时前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端