[React] 组件通讯
组件通讯
组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能 拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据 。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯。
大白话:一个组件使用另一个组件的状态
props
- 组件是封闭的,要接收外部数据应该通过props来实现
- props的作用:接收传递给组件的数据
- 传递数据:给组件标签添加属性
- 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
函数组件通讯
父组件
<Hello name="jack" age={19} />
子组件
function Hello({name, age}) {
// console.log(props)
return (
<div>接收到数据:{name}</div>
)
}
类组件通讯
父组件
<Hello name="jack" age={19} />
子组件
class Hello extends React.Component {
render() {
return (
<div>接收到的数据:{this.props.age}</div>
)
}
}
props的特点
可以给组件传递任意类型的数据
props是只读的,不允许修改props的数据,单向数据流: 数据流动从父组件流动到子组件 父组件数据变了子组件跟着更新, 子组件数据变了父组件不变. 除非子传父
注意:在类组件中使用的时候,需要把props传递给super(),否则构造函数无法获取到props
export default class Demo extends Component {
constructor(props) {
super(props);
console.log(this.props)
this.state = {
money: this.props.money + 100
}
}
render() {
console.log(this.props)
return (
<div>
<h3>类组件</h3>
<div>{this.state.money}</div>
</div>
);
}
}
组件通讯三种方式
- 父传子
- 子传父
- 非父子
父传子
- 父组件提供要传递的state数据
- 给子组件标签添加属性,值为 state 中的数据
- 子组件中通过 props 接收父组件中传递的数据
父组件提供数据并且传递给子组件
class Parent extends React.Component {
state = { lastName: '王' }
render() {
return (
<div>
传递数据给子组件:<Child name={this.state.lastName} />
</div>
)
}
}
子组件接收数据
function Child(props) {
return <div>子组件接收到数据:{props.name}</div>
}
评论列表案例
子传父
对比Vue:
// 父组件给子组件用@注册自定义事件, 比如@fn="fn"
// $emit(fn)触发fn事件
// 而react还是用props
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。
- 父组件提供一个回调函数(用于接收数据)
- 将该函数作为属性的值,传递给子组件
- 子组件通过 props 调用回调函数
- 将子组件的数据作为参数传递给回调函数
父组件提供函数并且传递给子组件
class Parent extends React.Component {
getChildMsg = (msg) => {
console.log('接收到子组件数据', msg)
}
render() {
return (
<div>
子组件:<Child getMsg={this.getChildMsg} />
</div>
)
}
}
子组件接收函数并且调用
class Child extends React.Component {
state = { childMsg: 'React' }
handleClick = () => {
this.props.getMsg(this.state.childMsg)
}
return (
<button onClick={this.handleClick}>点我,给父组件传递数据</button>
)
}
注意:回调函数中 this 指向问题!
兄弟
- 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
- 思想:状态提升
- 公共父组件职责:
- 提供共享状态
- 提供操作共享状态的方法
- 要通讯的子组件只需通过 props 接收状态或操作状态的方法
状态提升前
状态提升之后
组件通讯-context
基本概念
思考:App 组件要传递数据给 Child 组件,该如何处理?
处理方式:使用 props 一层层组件往下传递(繁琐)
更好的姿势:使用 Context
作用:跨组件传递数据(比如:主题、语言等)
实现思路
- 调用 React. createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件。
const { Provider, Consumer } = React.createContext()
- 使用 Provider 组件作为父节点。
<Provider>
<div className="App">
<Child1 />
</div>
</Provider>
- 设置 value 属性,表示要传递的数据。
<Provider value="pink">
- 调用 Consumer 组件接收数据。
<Consumer>
{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
总结:
- 如果两个组件是远方亲戚(比如,嵌套多层)可以使用Context实现组件通讯
- Context提供了两个组件:Provider 和 Consumer
- Provider组件:用来提供数据
- Consumer组件:用来消费数据
props深入
children属性
children属性:表示该组件的子节点,只要组件有子节点,props就有该属性
children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)
function Hello(props) {
return (
<div>
该组件的子节点:{props.children}
</div>
)
}
<Hello>我是子节点</Hello> // 等于如下
<Hello chiledren="我是子节点"></Hello>
props校验
目的:校验接收的props的数据类型,增加组件的健壮性
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据格式不对,可能会导致组件内部报错。组件的使用者不能很明确的知道错误的原因。
props校验允许在创建组件的时候,就约定props的格式、类型等
作用:规定接收的props的类型必须为数组,如果不是数组就会报错,增加组件的健壮性。
使用步骤
- 安装包 prop-types (yarn add prop-types / npm i props-types)
- 导入 prop-types 包
- 使用组件名.propTypes = {} 来给组件的props添加校验规则
- 校验规则通过 PropTypes 对象来指定
import PropTypes from 'prop-types'
function App(props) {
return (
<h1>Hi, {props.colors}</h1>
)
}
App.propTypes = {
// 约定colors属性为array类型
// 如果类型不对,则报出明确错误,便于分析错误原因
colors: PropTypes.array
}
约束规则
- 常见类型:array、bool、func、number、object、string
- React元素类型:element
- 必填项:isRequired
- 特定结构的对象:shape({ })
// 常见类型
optionalFunc: PropTypes.func,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})
props默认值
场景:分页组件 每页显示条数 作用:给 props 设置默认值,在未传入 props 时生效
function App(props) {
return (
<div>
此处展示props的默认值:{props.pageSize}
</div>
)
}
// 设置默认值
App.defaultProps = {
pageSize: 10
}
// 不传入pageSize属性
<App />
类的静态属性和实例属性
实例成员
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
sayHi() {
console.log('嗨')
}
}
const p = new Person('zs', 17)
console.log(p)
// p就是所谓的实例, 通过实例调用的属性或方法
// 称之为实例成员(属性或者方法)
// name和age就是实例的, 所以如何调用呢
// p.name p.age这样调用
// 这种通过实例访问的, 叫做实例成员, 或者叫做实例属性实例方法都可以
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
sayHi() {
console.log('嗨')
}
}
Person.aa = 'bb'
const p = new Person('zs', 17)
// Person.aa
// Person是个类, 他身上有个aa
// Person身上的aa不能用p去调用
// 因为class相当于 function Person() {}
// 但是原型链提到过
// 构造函数 原型 实例
// 实例只能继承原型的, 不能访问构造函数上面的aa
// 比如p.aa 没法访问 输出undefined
// 那要怎么访问?
// 只能通过类本身访问, 比如Person.aa
// 所以引出静态成员
静态成员
// 通过类或者构造函数本身才能访问到属性或者方法叫做静态成员
// 静态成员可以写到类里面去
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
sayHi() {
console.log('嗨')
}
aa = 'bb' // 这么写不是静态成员, 是类实例的
// aa = 'bb' 是直接增加到p身上的
// 如果加static关键字代表静态
static aa = 'bb' // 这么写就不是给实例增加的, 是给整个Person增加的
// 通过console.log(p.aa) 访问不到
// 而是通过Person.aa, 也就是类本身访问
}