组件通信的意义
组件是独立且封闭的单元,默认情况下组件只能使用自己的数据(state)
组件化开发的过程中,完整的功能会拆分为多个组件,在这个过程中不可避免的需要互相传递一些数据
为了能让组件之间可以进行相互沟通,数据传递,这个过程就是组件通信
分类:
- 父子关系(最重要)
- 兄弟关系(自定义事件模式产生技术方法eventBus/通过共同的父组件通信)
- 其他关系(mobx/redux/基于hook的方案)
父传子
实现步骤:
- 父组件提供要传输的数据
state
- 给子组件标签
添加属性
,值为state
中的数据 - 子组件通过
props
接收父组件传过来的数据- 类组件通过
this.props
获取props对象 - 函数式组件直接通过参数获取props对象
- 类组件通过
举例:
子组件获取父组件的数据:
import React from 'react';
//App父组件 Son子组件
//函数式组件的Son
function SonF(props){
//props是一个对象 里边存着父组件传入的所有数据
return(
<div>我是函数子组件,{props.msg}</div>
)
}
//类组件的Son
class SonC extends React.Component{
render(){
//类组件必须通过this关键词来获取,这里的props是固定的
return(
<div>我是类子组件{this.props.msg}</div>
)
}
}
//父组件
class App extends React.Component{
//准备数据
state = {
message:'this is message'
}
render(){
return(
<div>
{/* 在子组件身上绑定属性 属性名可以自定义 保持语义化 */}
<SonF msg={this.state.message} />
<SonC msg={this.state.message}/>
</div>
)
}
}
export default App;
结果如图:
对props进行说明
- props是只读对象(readonly)
根据单向数据流(单向往下传,不能修改)的要求,子组件只能读取props中的数据,不能进行修改。
this.props.msg = 'new msg' //这是不可以的 不可以直接修改
- props可以传递任意数据
数字,字符串,布尔值,数组,对象,函数,JSX(一块模板)
举例:
import React from 'react';
//函数式组件的Son
function Son(props){
console.log(props)
//props是一个对象 里边存着父组件传入的所有数据
return(
<div>
{/* 传递数组 */}
{props.list.map(item =><p key={item}>{item}</p>)}
{/* 传递对象 */}
{props.userInfo.name}
{/* 传递字符串 */}
{props.messaeg}
{/* 传递函数 点击按钮执行父组件传递的函数*/}
<button onClick={props.getMessage}>触发父组件传入的函数</button>
{/* 传递JSX */}
{props.child}
</div>
)
}
//父组件
class App extends React.Component{
//准备数据
state = {
list:[1,2,3],
userInfo:{
name:'hh',
age:'30'
},
message:'this is message',
}
getMessage=()=>{
console.log('父组件里的函数')
}
render(){
return(
<div>
{/* 在子组件身上绑定属性 属性名可以自定义 保持语义化 */}
<Son
// 数组
list={this.state.list}
// 对象
userInfo = {this.state.userInfo}
// 字符串
messaeg = {this.state.message}
// 函数
getMessage = {this.getMessage}
// JSX(传过去的是React的一个element对象)
child = {<span>this is span</span>}
/>
</div>
)
}
}
export default App;
浏览器显示:
控制台显示:
props的解构赋值
对props进行解构赋值,会使写法更加简洁
写法一:
//函数式组件的Son
function Son(props){
//对props解构赋值
const {list,userInfo,message,getMessage,child} = props
//对props进行解构赋值后,子组件接受数据时可以不用写props,用如下方式替代
<div>
传递数组
{list.map(item =><p key={item}>{item}</p>)}
{/* 传递对象 */}
{userInfo.name}
{/* 传递字符串 */}
{message}
{/* 传递函数 点击按钮执行父组件传递的函数*/}
<button onClick={getMessage}>触发父组件传入的函数</button>
{/* 传递JSX */}
{child}
</div>
)
}
写法二:
//函数式组件的Son
//对props解构赋值,直接在传参的时候解构
//这里写的就是原生函数语法 props也是一个普通的js对象,所以原生支持的写法 这里都是可以的 放心大胆写
function Son({list,userInfo,message,getMessage,child}){
//对props进行解构赋值后,子组件接受数据时可以不用写props,用如下方式替代
<div>
传递数组
{list.map(item =><p key={item}>{item}</p>)}
{/* 传递对象 */}
{userInfo.name}
{/* 传递字符串 */}
{message}
{/* 传递函数 点击按钮执行父组件传递的函数*/}
<button onClick={getMessage}>触发父组件传入的函数</button>
{/* 传递JSX */}
{child}
</div>
)
}
效果还和以前一样但是写法简洁了
子传父
子传父,实质上是子组件调用父组件传递过来的函数,将要传递给父组件的数字据作为函数的实参传入。
举例:
import React from 'react';
//App父组件 Son子组件
//父传子 props 函数
//子传父:子传父实质上是子组件调用父组件传递过来的函数,并且把想要传递的数据当成函数的实参传入即可
function Son(props){
//对props进行解构赋值
const {getSonMsg} = props;
return (
<div>this is Son<button onClick = {()=>{getSonMsg('这是来自于子组件的数据')}}>click</button></div>
)
}
//父组件
class App extends React.Component{
//准备数据
state = {
list : [1,2,3]
}
// 1.准备一个函数 传给子组件 SonMsg为要传的实参
getSonMsg = (SonMsg)=>{
console.log(SonMsg)
}
render () {
return (
<div>
<Son getSonMsg = {this.getSonMsg}/>
</div>
)
}
}
export default App;
点击按钮,控制台打印出子组件传递给父组件的数据
上述子组件还可以这么写,代码如下:
function Son(props){
//对props进行解构赋值
const {getSonMsg} = props;
function clickHander (){
getSonMsg('这是来自子组件的数据')
}
return (
<div>this is Son
<button onClick = {clickHander}>click</button>
</div>
)
}
将getSonMsg('这是来自于子组件的数据')
单独拎出来写成一个独立的函数,然后再调用,可以保持模板的精简整洁。
兄弟组件传参
通过状态提升机制,利用共同的父组件实现兄弟通信。
举例:
import React from "react";
//App父组件 SonA SonB为子组件
// 目标:将B组件中的数据传给A
// 方案:
// 1.先把B中的数据通过子传父 传给App
// 2.再把App接收到的Son中的数据 通过父传子 传给A
function SonA(props) {
const {sendAMsg} = props
return (
<div>
this is A,{sendAMsg}
</div>
)
}
function SonB(props) {
const { getBMsg } = props;
const BMsg = "这里是来自于B组件的数据";
return (
<div>
this is B<button onClick={() => getBMsg(BMsg)}>B给父发数据</button>
</div>
);
}
//父组件
class App extends React.Component {
//声明一个传给A组件的数据
state = {
sendAMsg:'测试这里是否会变成父给A传来的数据'
}
//声明一个得到B组件数据的方法
getBMsg = (msg) => {
console.log(msg);
//把msg数据交给sendAMsg
this.setState({
sendAMsg: msg
})
};
render() {
return (
<div>
<SonA sendAMsg ={this.state.sendAMsg}/>
<SonB getBMsg={this.getBMsg} />
</div>
);
}
}
export default App;
点击按钮前:
点击按钮后:
跨组件通信Context
即跨层传递,Context提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传输的方法
实现步骤:
- 创建Context对象 导出 Provider 和 Consumer 对象
const {Provider,Consumer} = createContext()
- 使用Provider包裹根组件提供数据
<Provider value = {this.state.message}>
{/*根组件*/}
</Provider>
- 需要用到数据的组件使用Consumber包裹获取数据
<Consumer>
{value => /*基于 context 值进行渲染 */}
</Consumer>
举例:
import React, { createContext } from "react";
//App父组件 SonA SonB为子组件
//App -> A -> C
//App数据 -> C (跨过A传数据)
// 注意事项:1.上层组件和下层组件关系是相对的只要存在就可以使用 通常我们都会通过App作为数据提供方
// 2.这里涉及到的语法不是固定的,有两处,提供的位置 value提供数据 获取的位置{value=> 使用value做什么都可以}
//1.导入CreateContext()方法并执行,解构提供者和消费者
const { Provider, Consumer } = createContext();
function ComA() {
return (
<div>
this is ComA
<ComC />
</div>
);
}
function ComC() {
return(
<div>
this is ComC
{/* 3. 通过Comsumer使用数据 */}
<Consumer>
{value=><span>{value}</span>}
</Consumer>
</div>
)
}
//父组件
class App extends React.Component {
// 准备数据
state = {
message: "this is message",
};
render() {
return (
// 2.使用provider包裹根标签
<Provider value={this.state.message}>
<div>
<ComA />
</div>
</Provider>
);
}
}
export default App;
结果如图:
C组件后显示了this is message ,说明传递成功
练习
需求:
App组件提供数据,ListItem负责渲染数据,点击ListItem中的删除按钮,会删除相应的数据。
代码:
import React from "react";
//渲染列表
function ListItem({ item , delItem }) {
return (
<>
<h3>{item.name}</h3>
<p>{item.price}</p>
<p>{item.info}</p>
<button onClick={() => delItem(item.id)}>删除</button>
</>
);
}
//数据提供者 渲染ListItem组件 App-ListItem
//先不抽离组件 完成基础渲染后再去抽离
class App extends React.Component {
state = {
list: [
{ id: "1", name: "超级好吃的棒棒糖", price: 18.8, info: "开业大酬宾" },
{ id: "2", name: "超级好吃的大鸡腿", price: 34.2, info: "开业大酬宾" },
{ id: "3", name: "超级好吃的冰激凌", price: 14.2, info: "开业大酬宾" },
],
}
//给子组件传递的函数
delItem = (id) => {
this.setState({
//filter函数 保留item.id !== id为true的item
list: this.state.list.filter(item => item.id !== id)
})
}
render() {
return (
<>
{this.state.list.map((item) => (
<ListItem item={item} key={item.id} delItem = {this.delItem}/>
))}
</>
);
}
}
export default App;
结果:
点击删除会删除相对应的内容