1.组件通讯介绍
- 组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据(state)。
- 在组件化过程中,我们将一个完整的功能 拆分成多个组件,以更好的完成整个应用的功能。
- 而在这个过程中,多个组件之间不可避免的要共享某些数据。
- 为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通(为了能让各组件之间可以进行互相沟通,数据传递)。这个过程就是组件通讯。
2.组件的 props
每一个组件对象都有props;组件标签的所有的属性都保存到props中
1.组件是封闭的,
要接收外部数据应该通过 props 来实现
2.props的作用:
接收传递给组件的数据
3.传递数据:
给组件标签添加属性
<Hello name="jack"/>
4.接收数据:
函数组件通过参数props接收数据。
//function Hello(props){
const Hello = (props) => {
console.log(props)
return (
<div>
<h1>props:{props.name}</h1>
</div>
)
}
类组件通过 this.props 接收数据
class Hello extends React.Component {
render() {
console.log(this.props)
return (
<div>
<h1>props:{this.props.name}</h1>
</div>
)
}
}
1.通过this去访问props,需要在constructor进行接收,还需要在super(props)写props
class Counter extends React.Component {
constructor(props) {
super(props)
console.log(props)
this.state = { Uname: this.props.name }
}
render () {
console.log(this)//里面由props了,在render中可以直接通过this去访问props,而不需要像构造器一样
return (
<div>
<h2>{this.state.Uname}</h2>
<h1>{this.props.name}</h1>
</div>
)
}
}
ReactDOM.render(<Counter name="xx" />, document.getElementById('root'));
props的结果
this的结果
2.如果这个props里面还有对象的话,这样写就比较麻烦,可以通过解构进行简化
render () {
const { name } = this.props
return (
<div>
<h2>{this.state.Uname}</h2>
<h1>{name}</h1>
</div>
)
}
3.批量操作
如果要传很多东西进去的话,可以把要传的值拿出来,单独书写。
- {}:不是对象的意思,而是在里面要写js表达式
- 如果传过去的是对象的话:data={data}
使用扩展运算符的话,就不需要多一个层级data
const data = { name: "xx", age: 10, sex: 'nan' }
ReactDOM.render(<Counter {...data} />, document.getElementById('root'));
传对象过去也可以,只不过会多一个层级data
render () {
const { name, data } = this.props
return (
<div>
<h1>{data.name}</h1>
</div>
)
}
}
const data = { name: "xx", age: 10, sex: 'nan' }
ReactDOM.render(<Counter data={data} />, document.getElementById('root'));
5.props是一个对象
6.传递非字符串的数据,需要加{ },
<Hello name="jack" age={19} />
7.可以传递数组,函数,JSX结构
<Hello
colors={['red', 'green']}
fn={() => console.log('object')}
tag={<p>xx</p>} />
3.组件的 props的特点
1.可以给组件传递任意类型的数据
数字、字符串、布尔值、数组、对象、函数、JSX
class App extends React.Component {
state = {
message: 'this is message'
}
render() {
return (
<div>
<div>父组件</div>
<FSon
msg={this.state.message}
age={20}
isMan={true}
cb={() => { console.log(1) }}
child={<span>this is child</span>}
/>
<CSon msg={this.state.message} />
</div>
)
}
}
2.props 是只读的对象(readonly),只能读取属性的值,无法修改对象
//根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改
// 错误,不能直接进行修改
this.props.msg = 'new msg'
3.注意:
使用类组件时,如果写了构造函数,应该将 props 传递给 super(),
否则,无法在构造函数中获取到 props!
class Hello extends React.Component {
// 推荐将props传递给父类构造函数
constructor(props) {
super(props)
}
render() {
return <div>接收到的数据:{this.props.age}</div>
}
}
演示代码
// 函数式Son
function Son (props) {
// props:对象,里面存着通过父组件传入的所有数据[1,2,3]
console.log(props)
return (
// 数组是用来遍历的,不是当成字符串渲<染的{props.list}
<div>
函数子组件:
{props.list.map(item => <p key={item}>{item}</p>)}
{props.userInfo.name}
<button onClick={props.getMes}>
触发父组件传入的函数
</button>
{props.child}
</div>
)
}
class App extends React.Component {
state = {
list: [1, 2, 3],
userInfo: {
name: 'zz',
age: 11
}
}
getMes = () => {
console.log('父组件中的函数')
}
render () {
return (
<div>
<Son
list={this.state.list}
userInfo={this.state.userInfo}
getMes={this.getMes}
child={<span>this is span</span>}
/>
</div>
)
}
}
4.使用解构赋值
const { list, userInfo, getMes, child } = props
return (
<div>
函数子组件:
{list.map(item => <p key={item}>{item}</p>)}
{userInfo.name}
<button onClick={getMes}>触发父组件传入的函数</button>
{child}
</div>
)
第二种解构赋值---在参数上
function Son ({ list, userInfo, getMes, child }) {
// const { list, userInfo, getMes, child } = props
return (
<div>
函数子组件:
{list.map(item => <p key={item}>{item}</p>)}
{userInfo.name}
<button onClick={getMes}>触发父组件传入的函数</button>
{child}
</div>
)
}
这里写的就是原生的函数语法,props是一个普通的js对象---原生支持的写法,这里都是可以写的。
4.组件通讯的三种方式
组件之间的通讯分为 3 种:
- 父子关系 - 最重要
- 兄弟关系 - 自定义事件模式产生技术方法 eventBus / 通过共同的父组件通信
- 其它关系 - mobx / redux / 基于hook的方案
1.父组件 -> 子组件
实现父子通信中的父传子,把父组件中的数据传给子组件
通过props进行传值
1.实现步骤
1.父组件提供要传递的state数据
class Parent extends React.Component {
state = {
lastName: 'wang'
}
}
2.给子组件标签添加属性,值为 state 中的数据
<Child name={this.state.lastName}/>
3.子组件中通过 props 接收父组件中传递的数据
- 类组件使用this.props获取props对象
- 函数式组件直接通过参数获取props对象
const Child = (props) => {
console.log('子组件:',props)
return (
<div className="child">
<p>子组件,接收父组件的数据:{props.name}</p>
</div>
)
}
完整代码
// 函数式子组件
function ASon(props) {
// props:对象,里面存着通过父组件传入的所有数据。接收的是一个对象
console.log(props)
return (
<div>
子组件1
{props.msg}
</div>
)}
// 类子组件
class BSon extends React.Component {
render() {
return (
<div>
子组件2
//类组件必须通过this关键词,去获取这里的props是固定的
{this.props.msg}
</div>
)}}
// 父组件
class App extends React.Component {
//通过props传递值给子组件
state = {
// 准备数据
message: 'this is message'
}
render() {
return (
<div>
<div>父组件</div>
// 子组件身上绑定属性,属性名可以自定义,保持语义化
<ASon msg={this.state.message} />
<BSon msg={this.state.message} />
</div>
)}}
2.子组件 -> 父组件
子传父:子组件调用父组件传递过来的函数,并且把想要传递的数据当作函数的实参传入
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。父组件给子组件传递回调函数,子组件调用
父组件将组件的某个方法传给子组件,子组件通过props接收到父组件传递过来的方法进行调用,并且将要传递的参数作为形参传递过去。
1.父组件提供一个回调函数(用于接收数据)
class Parent extends React.Component {
getChlidMsg = data =>{
console.log('接收到子组件中传递过来的数据:',data)
}
}
2.将该函数作为属性的值,传递给子组件
<Child getMSg={this.getChlidMsg} />
3.子组件通过 props 调用回调函数
handleClick= () =>{
// 子组件调用父组件中传递过来的回调函数
this.props.getMSg()
}
{/* 绑定单击事件 */}
<button onClick={this.handleClick}>点击,给父组件传递数据</button>
4. 将子组件的数据作为参数传递给回调函数
state = {
// 子组件传递数据
msg:'xx'
}
// 子组件this.state.msg作为回调函数getMSg的参数,传递给父组件
this.props.getMSg(this.state.msg)
5.父组件把数据展示在页面上
-
提供父组件自己的状态
state={ parentMsg:'' }
-
打印状态的值
父组件:{this.state.parentMsg}
-
一开始时无显示的,拿到数据后,把parentMsg更新一下
getChlidMsg = data => { this.setState({ parentMsg:data }) }
注意:回调函数中 this 指向问题
代码实现
一:
import React from 'react'
// 子组件,函数式Son
function Son(props) {
function handleClick() {
// 调用父组件传递过来的回调函数 并注入参数
props.changeMsg('this is newMessage')
}
return (
<div>
{props.msg}
<button onClick={handleClick}>change</button>
</div>
)
}
class App extends React.Component {
state = {
message: 'this is message'
}
// 提供回调函数
//准备一个函数,传给子组件
changeMessage = (newMsg) => {
console.log('子组件传过来的数据:',newMsg)
this.setState({
message: newMsg
})
}
render() {
return (
<div>
<div>父组件</div>
<Son
msg={this.state.message}
// 传递给子组件
changeMsg={this.changeMessage}
/>
</div>
)}}
export default App
二:
// 函数式Son
function Son (props) {
const { getSonMsg } = props
function clickHandler () {
const sonMsg = '来自子组件的数据'
getSonMsg(sonMsg)
}
return (
<div>
this is son
<button onClick={clickHandler}>click</button>
</div>
)
}
class App extends React.Component {
// 1.准备一个函数,传给子组件
getSonMsg = (sonMsg) => {
console.log(sonMsg)
}
render () {
return (
<div>
<Son getSonMsg={this.getSonMsg} />
</div>
)}}
三:
// 函数式子组件
function ASon (props) {
return (
<div>
<h2>A组件</h2>
<button onClick={() => { props.getChildrenValue('A组件传递过来的数据') }}>将数据传递给父组件</button>
</div>
)}
// 类子组件
class BSon extends React.Component {
state = { msg: 'B组件传递过来的数据' }
render () {
return (
<div>
<h2>B组件</h2>
<button onClick={() => { this.props.getChildrenValue(this.state.msg) }}>将数据传递给父组件</button>
</div>
)}}
// 父组件
class App extends React.Component {
// 在父组件中定义一个方法,接收到参数的就是子组件传过来的参数
getChildrenValue = (value) => {
console.log(value)
//做任意操作
//更改状态
}
render () {
return (
<div>
<h2>父组件</h2>
<ASon getChildrenValue={this.getChildrenValue} />
<BSon getChildrenValue={this.getChildrenValue} />
</div>
)}}
四:如果放在不同的文件夹下
App.js
import BSon from './Hello'
BSon.js
import React from 'react'
// 类子组件
class BSon extends React.Component {
state = { msg: 'B组件传递过来的数据' }
render () {
return (
<div>
<h2>B组件</h2>
<button onClick={() => { this.props.getChildrenValue(this.state.msg) }}>将数据传递给父组件</button>
</div>
)}}
// 导出组件
export default BSon
3.兄弟组件
核心思路:通过状态提升机制,利用共同的父组件实现兄弟通信
1.将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
- 提供共享状态
- 提供操作共享状态的方法
2.思想:状态提升
3.公共父组件职责:1. 提供共享状态 2. 提供操作共享状态的方法
4.要通讯的子组件只需通过 props 接收状态或操作状态的方法
实现步骤
- 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态(提供共享状态,提供操作共享状态的方法)
- 要接收数据状态的子组件通过 props 接收数据
- 要传递数据状态的子组件通过props接收方法,调用方法传递数据
完整代码1
class Parent extends React.Component {
// 提供共享状态
state = {
count: 0
}
// 提供修改状态的方法
onIncrement = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<Child1 count={this.state.count} />
<Child2 onIncrement={this.onIncrement} />
</div>
)
}
}
const Child1 = (props) => {
return <h1>计数器:{props.count}</h1>
}
const Child2 = (props) => {
// 一点击按钮,方法就会调用
return <button onClick={() => props.onIncrement()}>+1</button>
}
完整代码2
import React from 'react'
//目标:B组件中的数据传给A
// 技术方案:
// 1.先把B中的数据通过子传父,传给App
// 2.再把App接收到的Son中的数据,通过父传子 传给A
// 子组件A
function SonA(props) {
return (
<div>
SonA
{props.msg}
-----------------------------------------------------------------
{props.sendMsg}
</div>
)}
// 子组件B
function SonB(props) {
const bMsg = '来自B组件的数据'
return (
<div>
SonB
<button onClick={() => props.changeMsg('new message')}>
changeMsg
</button>
------------------------------------------------------------------
<button onClick={() => props.getBMsg(bMsg)}>
发送数据
</button>
</div>
)}
// 父组件
class App extends React.Component {
// 父组件提供状态数据
state = {
message: 'this is message',
------------------------------------------------------------------
sendMsg: '测试父传子初始值'
}
// 父组件提供修改数据的方法
changeMsg = (newMsg) => {
this.setState({
message: newMsg
})
}
------------------------------------------------------------------
// 声明一个传给B组件的方法
getBMsg = (msg) => {
console.log(msg)
// 把msg数据交给sendMsg
this.setState({
sendMsg: msg
})
}
render() {
return (
<>
{/* 接收数据的组件 */}
<SonA msg={this.state.message} />
{/* 修改数据的组件 */}
<SonB changeMsg={this.changeMsg} />
-----------------------------------------------------------------
<SonA sendMsg={this.state.sendMsg} />
<SonB getBMsg={this.getBMsg} />
</>
)}}
export default App
类组件和函数组件传递案例:B传给A
// 函数式子组件A
function ASon (props) {
return (
<div>
<h2>A组件</h2>
<h3>B--{props.msgA}</h3>
</div>
)}
// 类子组件B
class BSon extends React.Component {
state = { msgB: 'B组件传递过来的数据' }
render () {
return (
<div>
<h2>B组件</h2>
<button onClick={() => { this.props.getChildrenValue(this.state.msgB) }}>将数据传递给父组件</button>
</div>
)}}
// 父组件
class App extends React.Component {
state = { msg: '' }
getChildrenValue = (value) => {
console.log(value)
this.setState({ msg: value })
}
render () {
return (
<div>
<h2>父组件</h2>
<ASon msgA={this.state.msg} />
<BSon getChildrenValue={this.getChildrenValue} />
</div>
)}}
案例:有一个文本框的组件,使用了两次,在一个文本框中输入内容的时候,另一个文本框也跟着一起动
// 类子组件
class BSon extends React.Component {
// 4.子组件改变,将改变的值传入父组件
changeValue = (e) => {
//5.通过调用父组件的方法
this.props.getChildrenValue(e.target.value)
}
render () {
return (
<div>
{/* props是单一性的 */}
{/* 3.由父组件的状态来控制子组件的value值 */}
<input type="text" onChange={this.changeValue} value={this.props.msg} />
</div>
)}}
// 父组件
class App extends React.Component {
// 1.状态提升:将共享的状态提升到共同的父组件中
state = { msg: 'nihao' }
getChildrenValue = (value) => {
this.setState({ msg: value })
}
render () {
return (
<div>
<h2>父组件</h2>
{/* 虽然两个都是B,但是它们是单独的两个组件;现在想要他们成一个,即可以互相影响 */}
<BSon msg={this.state.msg} getChildrenValue={this.getChildrenValue} />
{/* 2.将共享的状态传给子组件 */}
<BSon msg={this.state.msg} getChildrenValue={this.getChildrenValue} />
</div>
)}}
5.跨组件通信Context
思考:App 组件要传递数据给 Child 组件,该如何处理?
处理方式:使用 props 一层层组件往下传递(繁琐)
更好的姿势:使用 Context。通过context api 实现跨组件通信
作用:跨组件传递数据(比如:主题、语言等)
思考:想从App组件向任意一个下层组件传递数据,该怎么办呢?
目前我们能采取的方式就是一层一层的props往下传,显然很繁琐。
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法
6.Context使用步骤
1.调用 React. createContext() 创建 Provider(提供数据) 和 Consumer(消费数据:要用数据的组件) 两个组件。
const { Provider, Consumer } = React.createContext()
2.使用 Provider 组件作为父节点(Provider包裹根组件提供数据)
<Provider>
{/* 根组件 */}
<div className="App">
<Child1 />
</div>
</Provider>
<Provider value="pink">
4.调用 Consumer 组件接收数据(需要用到数据的组件使用Consumer包裹获取数据)。
<Consumer>
/* {value => /* 基于 context 值进行渲染*/} */
{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
完整代码
// 创建context得到两个组件
const { Provider, Consumer } = React.createContext()
// 函数组件
class App extends React.Component {
render() {
return (
<Provider value="pink">
<div className="app">
<Node />
</div>
</Provider>
)
}
}
const Node = (props) => {
return (
<div className="node">
<SubNode />
</div>
)
}
const SubNode = (props) => {
return (
<div className="subnode">
<Child />
</div>
)
}
const Child = (props) => {
return (
<div className="child">
<Consumer>
{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
</div>
)
}
代码实现
App->A->C 变成 App->C
import React, { createContext } from 'react'
// 1. 创建Context对象
const { Provider, Consumer } = createContext()
// 3. 消费数据
function ComC() {
return (
<Consumer >
{value => <div>{value}</div>}
</Consumer>
)
}
function ComA() {
return (
<ComC/>
)
}
// 2. 提供数据
class App extends React.Component {
state = {
message: 'this is message'
}
render() {
return (
<Provider value={this.state.message}>
<div className="app">
<ComA />
</div>
</Provider>
)
}
}
export default App
步骤解析:
1.初始代码:
import React from "react"
// 函数式Son
function ComA (props) {
return (
<div>
A
<ComC />
</div>
)
}
function ComC (props) {
return (
<div>
C
</div>
)
}
class App extends React.Component {
// 1.准备一份数据
state = {
message: 'message'
}
render () {
return (
<div>
<ComA />
</div>
)
}
}
export default App
2.导入createContext方法并执行,解构提供者和消费者
import React,{createContext} from "react"
const {Provider,Consumer} = createContext()
3.App中,模板位置用Provider包裹根组件
<Provider value={this.state.message}>
<div>
<ComA />
</div>
</Provider>
4.通过固定的value,把数据放过来
<Provider value={this.state.message}>
5.在ComC组件用Consumer包裹获取数据,通过Consumer使用数据
<div>
C
<Consumer>
{value => <span>{value}</span>}
</Consumer>
</div>
不解构案例:
import React, { createContext } from 'react'
const Context = createContext()
class ASon extends React.Component {
render () {
return (
<div>
<BSon />
//不单单时跨级通信
<Context.Consumer>
{value => <span>A组件{value}</span>}
</Context.Consumer>
</div>
)}}
class BSon extends React.Component {
render () {
return (
<div>
<h2>B组件</h2>
<Context.Consumer>
{value => <h2>App的数据{value}</h2>}
</Context.Consumer>
</div>
)}}
// 父组件
class App extends React.Component {
state = { msg: 'nihao' }
render () {
return (
<Context.Provider value={this.state.msg}>
<div>
<ASon />
</div>
</Context.Provider>
)}}
注意事项:
- 上层组件和下层组件关系是相对的,只要存在就可以使用,通常我们会通过app作为数据提供方。
- 这里涉及的语法都是固定的,
- 由两处提供的位置 value,提供数据
- 获取的位置 {value =>使用value做什么都可以}
总结:
- 如果两个组件是远方亲戚(比如,嵌套多层)可以使用Context实现组件通讯
- Context提供了两个组件:Provider 和 Consumer
- Provider组件:用来提供数据
- Consumer组件:用来消费数据
回顾练习
要求:App为父组件用来提供列表数据 ,ListItem为子组件用来渲染列表数据
// 列表数据
[
{ id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: '开业大酬宾,全场8折' },
{ id: 2, name: '超级好吃的大鸡腿', price: 34.2, info: '开业大酬宾,全场8折' },
{ id: 3, name: '超级无敌的冰激凌', price: 14.2, info: '开业大酬宾,全场8折' }
]
1.完整代码
import React from 'react'
// 子组件
function ListItem(props) {
const { name, price, info, id, delHandler } = props
return (
<div>
<h3>{name}</h3>
<p>{price}</p>
<p>{info}</p>
<button onClick={() => delHandler(id)}>删除</button>
</div>
)
}
// 父组件
class App extends React.Component {
state = {
list: [
{ id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: '开业大酬宾,全场8折' },
{ id: 2, name: '超级好吃的大鸡腿', price: 34.2, info: '开业大酬宾,全场8折' },
{ id: 3, name: '超级无敌的冰激凌', price: 14.2, info: '开业大酬宾,全场8折' }
]
}
delHandler = (id) => {
this.setState({
list: this.state.list.filter(item => item.id !== id)
})
}
render() {
return (
<>
{
this.state.list.map(item =>
<ListItem
key={item.id}
{...item}
delHandler={this.delHandler}
/>
)
}
</>
)
}
}
export default App
2.代码讲解
1.初始代码:
import React, { createContext } from "react"
// 渲染列表
function ListItem (props) {
return (
<div>
</div>
)
}
// 数据提供者 渲染ListItem组件 App-ListItem
// 先不抽离组件,完成基础渲染后,再去抽离
class App extends React.Component {
render () {
return (
<div>
</div>
)
}
}
export default App
2.App中初始化list数据
state = {
// 列表数据
list: [
{ id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: '开业大酬宾,全场8折' },
{ id: 2, name: '超级好吃的大鸡腿', price: 34.2, info: '开业大酬宾,全场8折' },
{ id: 3, name: '超级无敌的冰激凌', price: 14.2, info: '开业大酬宾,全场8折' }
]
}
3.UI部分
render () {
return (
<div>
<h3>标题</h3>
<p>价格</p>
<p>说明</p>
<button >删除</button>
</div>
)
}
4.遍历返回的结果
render () {
return (
<div>
{this.state.list.map(item => (
<>
<h3>标题</h3>
<p>价格</p>
<p>说明</p>
<button >删除</button>
</>
))}
</div>
)
}
5.把渲染数据替换进去
<h3>{item.name}</h3>
<p>{item.price}</p>
<p>{item.info}</p>
以上为不借助组件,完成的渲染
1.把以下部分变成组件,需要将其从App中移到ListItem中
<h3>{item.name}</h3>
<p>{item.price}</p>
<p>{item.info}</p>
<button >删除</button>
2.通过父传子,把ListItem传递到App父组件中
{this.state.list.map(item => <ListItem key={item.id} item={item}/>)}
3.ListItem通过props接收数据
function ListItem ({item}) {
处理删除,需要用到子传父
4.添加绑定事件
delItem={this.delItem}
// 给子组件传递的函数
delItem=()=>{}
5.解构delItem,ListItem的button中添加点击事件,改造成箭头函数
function ListItem ({item,delItem}) {
return (
<div>
<h3>{item.name}</h3>
<p>{item.price}</p>
<p>{item.info}</p>
<button onClick={()=>delItem(item.id)}>删除</button>
</div>
)
}
6.delItem接收要删的id,删除操作
delItem = (id) => {
this.setState({
list: this.state.list.filter(item => item.id !== id)
})
}