组件通讯
组件是独立作用域,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能 拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据 。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯。
props
- props的作用:接收传递给组件的数据
- props的值:是标签属性组成对象。
- props名字:属性-property单词的缩写
prop
,+ s
使用步骤:
- 传递数据:给组件标签添加属性。 -- 与vue类似
- 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
父组件:
export default class App extends Component {
render() {
return <div>
<Tie name={'daodao'} age={21}></Tie>
<Chao name={'chaochao'} age={21}></Chao>
</div>
}
}
函数组件通讯
通过参数props接收数据,由于是参数,因此可以修改名字
function Tie({ name, age }) {
// console.log(props);
return <div>{name},{age}</div>
}
类组件通讯
通过this.props接收数据,由于是对象属性,因此不能修改名字
class Chao extends React.Component {
render() {
console.log(this);
const { name,age } = this.props
return <div>{name},{age}</div>
}
}
注意:
- 推荐解构
props
- props是对象,标签属性组成的对象。
- React中props绕不过引用地址。
props的特点
-
可以给组件传递任意类型的数据,包括虚拟 DOM 结构
-
string、number、null、undefined、boolean
null、undefined、boolean传是可以传,控制台打印出来也能看到,但是直接使用的话不会显示,想要显示要用
String()
转为字符串。 -
数组、对象
对象不能直接放进插值里,会报错,要通过
JSON.stringify()
转为字符串;数组可以直接使用,会把每一项转为单独的DOM
结构。 -
函数、JSX
相当于父组件传递一个回调函数,子组件形参接收回调函数并使用。
export default class App extends Component { render() { return ( <div> {/* null undefined boolean */} <Child name={null} age={undefined} gender={true}></Child> {/* object */} <Child person={{ zs: 'zs' }}></Child> {/* 数组 */} <Child list={[1, 2, 3, 4]}></Child> {/* function */} <Child fn={() => alert(123)}></Child> {/* JSX */} <Child msg={<i>我是props传来的JSX</i>}></Child> </div> ); } } function Child({ msg, name, age, gender, person, list, fn }) { console.log('fn -----> ', fn); return ( <div> {/* number string */} 我是子组件 - {String(name)} - {age} <h1>{gender}</h1> {JSON.stringify(person)} <h2>{list}</h2> <button onClick={fn}>点我</button> {msg} </div> ); }
-
-
props是只读的,不允许修改props的数据 - 单向数据流
export default class App extends Component { render() { return ( <div> {/* JSX */} <Child msg={<i>我是props传来的JSX</i>}></Child> </div> ); } } function Child() { return ( <div> {msg} <button onClick={() => { msg = '123'; }} > 点我修改props </button> </div> ); }
-
不必先定义后使用,可以只传不接收。 (与Vue不同)
export default class App extends Component { render() { return ( <div> {/* 1. props不必先定义后使用 */} <Child name="zs123" age={18123} gender={'xxxx'}></Child> </div> ); } } function Child() { return ( <div>我是子组件</div> ); }
总结:
- props是对象
- props可以是任意值,注意,function是对象,不能作为标签的内容
组件通讯三种方式
- 父传子
- 子传父
- 兄弟组件
父传子
- 父组件提供要传递的state数据
- 给子组件标签添加属性,值为 state 中的数据
- 子组件中通过 props 接收父组件中传递的数据
import React, {Component} from 'react';
export default class Parent extends Component {
state = {
money: 1000,
};
handleMakeMoney = () => {
this.setState({
money: 1000 + this.state.money,
});
};
render() {
return (
<div>
<button onClick={this.handleMakeMoney}>爸爸开始赚钱了</button>
<Child money={this.state.money}></Child>
</div>
);
}
}
class Child extends React.Component {
render() {
const { money } = this.props
return (
<div>
<h1>爸爸给我钱了: {money}</h1>
</div>
);
}
}
子传父
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。
- 父组件提供一个回调函数(用于接收数据)
- 将该函数作为标签属性的值,传递给子组件
- 子组件通过 props.语法找到函数,再 调用回调函数
- 将子组件的数据作为参数传递给回调函数
export default class Parent extends Component {
state = {
money: 1000,
};
// 1. 定义一个函数
handleCostMoney = (num) => {
this.setState({
money: this.state.money - num,
});
};
render() {
return (
<div>
// 2. 为子组件传递该回调函数
<Child handleCostMoney={this.handleCostMoney}></Child>
</div>
);
}
}
class Child extends React.Component {
render() {
const { handleCostMoney } = this.props
return (
<div>
// 3. 子组件在点击事件中调用
<button onClick={() => handleCostMoney(100)}>点我花钱</button>
</div>
);
}
}
兄弟组件
状态提升,把state内的数据从组件中提取到两个兄弟组件的父组件上,通过子传父和父传子来共同操作父组件的数据。
拓展:快速重构小技巧
提取父组件中的某个部分封装为子组件,可以把那部分代码选中,点击右键,选择重构,选择 “提取到 class App
的 method
中 ” 即可。
组件通讯
实现步骤
-
调用 React. createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件。
const { Provider, Consumer } = React.createContext()
-
使用 Provider 组件作为父节点。设置 value 属性,表示要传递的数据。
<Provider value="pink"> <Son></Son> </Provider>
-
调用 Consumer 组件接收数据。
<Consumer> {(data) => { return <span>{data}</span> }} </Consumer>
总结:
- 如果两个组件是远方亲戚(比如,嵌套多层)可以使用
Context
实现组件通讯Context
提供了两个组件:Provider
和Consumer
。createContext()
可以调用多次,多次解构会重名,需要重命名。每次解构出来的Provider
和Consumer
要成对使用。Provider
组件:用来存入数据Consumer
组件:用来取出数据
优缺点
优点:React
自带,不需要借助其它包,即可跨组件通信
缺点:Provider
和 Consumer
增加嵌套结构,代码理解成本加大。
props深入
children属性
children属性:表示该组件的子节点,只要组件有子节点,props就有该属性
children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)
function Hello(props) {
return (
<div>
该组件的子节点:{props.children}
</div>
)
}
<Hello>我是子节点</Hello>
模拟插槽
-
具名插槽
使用父组件传过来的对象或数组。
-
作用域插槽
children是一个函数,可以主动调用传参,传递什么参数父组件就能用什么参数。
export default class App extends Component { render() { return <div> <Child>{(data) => { console.log(data); }}</Child> </div> } } class Child extends Component { state = { list: ['chao', 'yu', 'ze'] } render() { return <h1>{this.props.children(this.state.list)}</h1> } }
复习
props校验
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据。
如果传入的数据格式不对,可能会导致组件内部报错。组件的使用者不能很明确的知道错误的原因。 props
校验允许在创建组件的时候,就约定props的格式、类型等。
作用:规定接收的props的类型,如果就会报错,增加组件的健壮性。
使用步骤
-
导入 prop-types 包 (不用下载,脚手架自带)
import PropTypes from "prop-types";
-
使用:
组件名.propTypes = {}
来给组件的props添加校验规则对象子组件名.propTypes = { 数据变量名: PropTypes.类型 }
-
规则的数据类型,通过
PropTypes
对象来指定
import PropTypes from "prop-types";
export default class App extends Component {
render() {
return <div>
<Child msg={[1,2,3]}></Child>
</div>
}
}
class Child extends Component {
render() {
return <div></div>
}
}
Child.propTypes = {
msg: PropTypes.string
}
约束规则
- 常见类型: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默认值
通过 defaultProps
可以给组件的props设置默认值,在未传入props的时候生效。
-
函数组件
function App({pageSize: 10}) { return ( <div> 此处展示props的默认值:{pageSize} </div> ) }
-
类组件
class App extends Component { render() { return ( <div> 此处展示props的默认值:{this.props.pageSize} </div> ) } } // 设置默认值 App.defaultProps = { pageSize: 10 }
类的静态属性
- 实例成员:通过实例对象调用的属性或者方法,叫做实例成员(属性或者方法)
- 静态成员:通过类或者构造函数本身才能访问的属性或者方法
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() // 调用静态方法