重学React基础知识整理——React组件通信(四)

学前知识准备

props和state、受控组件和非受控组件

重学React基础知识整理——React组件是不是受?(三)这里有对相关知识做了比较详细的描述,请各位看管移步先看,先理解这些概念。

各种通信实现

第一种父子间的通信

1)父传子的通信,父组件中通过给子组件设置props属性传递给子组件。
如下面代码所示:其中props1,props2就是在父组件里传递给子组件Child的属性,在子组件中能够通过this.props这对象去展示出来。

import React, { Component } from 'react'

export default class Parent extends Component {
    render() {
    return (
      <div style={{margin:'20px'}}>
        <div>Parent.....</div>
        <Child props1="aa" props2="bb"/>
      </div>
    )
  }
}


class Child extends Component {
    render() {
      return (
        <div>
            <div>Child</div>
            <div>{this.props.props1}</div>
            <div>{this.props.props2}</div>
        </div>
      )
    }
  }
  

执行结果
在这里插入图片描述
2)子传父的通信,子组件通过回调函数给父组件传值。 既然父组件能传递一些普通的属性,那函数能不能传呢?毋庸置疑是可以的,闲话免谈,上代码,还是用上面代码改吧改吧进行呈现。

import React, { Component } from 'react'

export default class Parent extends Component {
    constructor(){
        super()
        this.state= {
            fnText:''
        }
    }
    render() {
        return (
        <div style={{margin:'20px'}}>
            <div>Parent.....</div>
            {/* eventFn不是固定写法,这个名字你可以随意进行命名,只要团队内部能够理解你传递是一个回调函数就可以了 */}
            <Child props1="aa" props2="bb" eventFn={(value)=>{
                console.log('Parent---eventFn',value)
                this.setState({
                    fnText:value
                })
            }}/>
            <div>{this.state.fnText}</div>
        </div>
        )
  }
}


class Child extends Component {
    render() {
      return (
        <div>
            <div>Child</div>
            <div>{this.props.props1}</div>
            <div>{this.props.props2}</div>
            <button onClick={()=>{this.props.eventFn('CC')}}>传给父组件一个CC</button>
        </div>
      )
    }
  }
  

点击按钮后,执行后的结果如下图所示:
在这里插入图片描述
从结果上显示CC是在父组件中显示出来了,说明我的Child子组件给Parent组件了。

3)父传子通信,通过ref标记(父组件拿到子组件的引用,从而调用子组件的方法)。具体代码如下:

import React, { Component } from 'react'

export default class Parent extends Component {
    constructor(){
        super()
        this.state= {
            fnText:''
        }
    }
    myref = React.createRef()
    render() {
        return (
        <div style={{margin:'20px'}}>
            <div>Parent.....</div>
            {/* 二胎通过ref标记(父组件拿到子组件的引用,从而调用子组件的方法) */}
            <ChildTwo ref={this.myref} props1="cc" props2="dd"/>
            <button onClick={()=>{
                console.log('Parent----清空二胎输入值',this.myref.current)
                // this.myref.current找到子组件的React节点
                let value = '' // 可以是父组件传给子组件的任何值
                this.myref.current.resize(value)
            }}>清空二胎输入值</button>
        </div>
        )
  }
}

  class ChildTwo extends Component {
    constructor(){
        super()
        this.state={
            inputChild:''
        }
    }
    render() {
      return (
        <div>
            <div>ChildTwo二胎组件</div>
            <input value={this.state.inputChild} onChange={this.handle}></input>
        </div>
      )
    }
    handle= (evt)=>{
        this.setState({
            inputChild:evt.target.value
        }) 
    }
    resize = (value)=>{
        this.setState({
            inputChild:value
        })
    }
  }
  

得到的运行结果后input输入内容后变成如下所示:
在这里插入图片描述
点击清空后得到如下图所示:
在这里插入图片描述
以下是对上述示例发生情况的逐步解释:

1、我们在Parent父组件通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量。
2、我们通过指定 ref 为 JSX 属性,将其向下传递给 。
3、我们向下转发该 ref 参数到 ,将其指定为 JSX 属性。
4、当 ref 挂载完成,ref.current 将指向 这个组件的DOM 节点。

3.1)ref传值官方文档上有一种写法。

import React, { Component } from 'react'

export default class Parent extends Component {
    myref = React.createRef()
    render() {
        return (
        <div style={{margin:'20px'}}>
            <div>Parent.....</div>
           <FancyButton ref={this.myref} ss='wwww' >Click me!</FancyButton>
        </div>
        )
  }
}
const FancyButton =React.forwardRef((props, ref) => {
    console.log('FancyButton',props, ref)
    return (
    <button ref={ref} className="FancyButton">
      {props.children}
    </button>
  )})


执行结果如下图所示:
在这里插入图片描述
以下是对上述示例发生情况的逐步解释:

1、我们通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量。
2、我们通过指定 ref 为 JSX 属性,将其向下传递给 。
3、React 传递 ref 给 forwardRef 内函数 (props, ref) => …,作为其第二个参数。
4、我们向下转发该 ref 参数到 ,将其指定为 JSX 属性。
当 ref 挂载完成,ref.current 将指向 DOM 节点。

注意:第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。

Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中。
在class组件中需要HOC高阶组件进行包裹。在高阶组件中转发 refs。
下面是一个涵盖父子通信的完整例子,用到了上面3种技术集合,代码如下:

import React, { Component } from 'react'

export default class Parent extends Component {
    constructor(){
        super()
        this.state= {
            fnText:''
        }
    }
    myref = React.createRef()
    render() {
        return (
        <div style={{margin:'20px'}}>
            <div>Parent.....</div>
            {/* eventFn不是固定写法,这个名字你可以随意进行命名,只要团队内部能够理解你传递是一个回调函数就可以了 */}
            {/* 一胎通过props传递数值 */}
            <Child props1="aa" props2="bb" eventFn={(value)=>{
                console.log('Parent---eventFn',value)
                this.setState({
                    fnText:value
                })
            }}/>
            <div>{this.state.fnText}</div>
            {/* 二胎通过ref标记(父组件拿到子组件的引用,从而调用子组件的方法) */}
            <ChildTwo ref={this.myref} props1="cc" props2="dd"/>
            <button onClick={()=>{
                console.log('Parent----清空二胎输入值',this.myref.current)
                // this.myref.current找到子组件的React节点
                let value = '' // 可以是父组件传给子组件的任何值
                this.myref.current.resize(value)
            }}>清空二胎输入值</button>
        </div>
        )
  }
}


class Child extends Component {
    render() {
      return (
        <div>
            <div>Child</div>
            <div>{this.props.props1}</div>
            <div>{this.props.props2}</div>
            <button onClick={()=>{this.props.eventFn('CC')}}>传给父组件一个CC</button>
        </div>
      )
    }
  }
  class ChildTwo extends Component {
    constructor(){
        super()
        this.state={
            inputChild:''
        }
    }
    render() {
      return (
        <div>
            <div>ChildTwo二胎组件</div>
            <input value={this.state.inputChild} onChange={this.handle}></input>
        </div>
      )
    }
    handle= (evt)=>{
        this.setState({
            inputChild:evt.target.value
        }) 
    }
    resize = (value)=>{
        this.setState({
            inputChild:value
        })
    }
  }
  

第二种亲兄弟间的通信

状态提升(中间人模式),简单的说就是以父组件为中间人,A组件通过回调函数将要传递的aa传递给Parent父组件,通过props传递给B组件,从而实现A、B组件之间的通信。具体的流程图如下
在这里插入图片描述
下面我们用代码来实现下:

import React, { Component } from 'react'

export default class Parent extends Component {
    constructor(){
        super()
        this.state= {
            fnText:''
        }
    }
    myref = React.createRef()
    render() {
        return (
        <div style={{margin:'20px'}}>
            <div>Parent.....</div>
            <div>{this.state.fnText}</div>
            {/* eventFn不是固定写法,这个名字你可以随意进行命名,只要团队内部能够理解你传递是一个回调函数就可以了 */}
            {/* 一胎通过props传递数值 */}
            <Child eventFn={(value)=>{
                this.setState({
                    fnText:value
                })
            }}/>
            <ChildTwo  props1={this.state.fnText}  />
        </div>
        )
  }
}

class Child extends Component {
    constructor(){
        super()
        this.state={
            inputChild:''
        }
    }
    render() {
      return (
        <div>
            <div>Child组件</div>
            <input value={this.state.inputChild} onChange={this.handle}></input>
        </div>
      )
    }
    handle= (evt)=>{
        this.setState({
            inputChild:evt.target.value
        }) 
        this.props.eventFn(evt.target.value)
    }
    resize = (value)=>{
        this.setState({
            inputChild:value
        })
    }
  }

class ChildTwo extends Component {
    render() {
      return (
        <div>
            <div>ChildTwo接受Child的输入值如下:</div>
            <div>{this.props.props1}</div>
        </div>
      )
    }
  }
  
  

实现的结果如下所示:
在这里插入图片描述
从上面结果看Child组件的信息成功的传递给到ChildTwo组件了。

第三种非父子组件间的信息传递

1)订阅发布模式实现非父子组件间通信。
父子间的说完了,表兄弟组件的通信该安排上了。订阅发布模式是通过造一个全局的订阅发布对象,该对象最基本能实现功能的话必须包含一个订阅器数据集(也就是一个数组),一个订阅器函数(不断把订阅的函数push进数组),一个发布器函数(发布信息,让订阅者访问到,即将信息作为一个参数传递给所有的订阅器,并触发订阅的函数)。
订阅发布模式是vue中event bus 的指导思想,也是redux的核心指导思想。
具体上代码如下:

import React, { Component } from 'react'
let bus = {
    list:[],
    //订阅器函数
    subscribe(callback){
        this.list.push(callback)
    },
    // 发布器函数
    publish(value){
        this.list.forEach((fn)=>{
            fn(value)
        })
    }
}

export default class EvenBus extends Component {
    constructor(){
        super()
        bus.subscribe((val)=>{
            console.log('父组件订阅器',val)
        })
    }
  render() {
    return (
      <div style={{textAlign:'center'}}>
        EvenBus
        <A/>
        <B/>
        <C/>
      </div>
      
    )
  }
}
class A extends Component {
    constructor(){
        super()
        bus.subscribe((val)=>{
            console.log('A组件订阅器',val)
        })
    }
    render() {
        return (
          <div>AClass</div>
        )
      }
}
class B extends Component {
    constructor(){
        super()
        bus.subscribe((val)=>{
            console.log('B组件订阅器',val)
        })
    }
    render() {
        return (
          <div>BClass</div>
        )
      }
}

class C extends Component {
    constructor(){
        super()
        bus.subscribe((val)=>{
            console.log('C组件订阅器',val)
        })
    }
    render() {
        return (
          <div>
            <div>cccccccccc</div>
            <D/>
          </div>
        )
      }
}
class D extends Component {
    constructor(){
        super()
        let obj = {
            a:'今夜打老虎',
            b:'dddd'
        }
        bus.publish('今夜打老虎')
        bus.publish(obj)
    }
    render() {
        return (
          <div>DClass</div>
        )
      }
}

执行结果如下:
在这里插入图片描述
由此可知在D组件发布,在Parent组件还有A、B、C组件中都能订阅到,以后我们会用到redux进行状态管理,也是一处发布,其他各处可接受,保证状态的可溯源,不易混乱。

2)context状态树传参。 官方是这样解释它的;“Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
从一个在组件树中嵌套很深的组件中更新 context 是很有必要的。在这种场景下,你可以通过 context 传递一个函数,使得 consumers 组件更新 context。
a、首先创建一个Context对象。React.createContext

const MyContext = React.createContext(defaultValue);

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。这有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。
b、<MyContext.Provider value={/* 某个值 */}>
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新

通过新旧值检测来确定变化,使用了与 Object.is 相同的算法
c、子组件中渲染api是 Context.Consumer。

<MyContext.Consumer>
  {value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>

注意:这里Consumer里面要使用箭头函数返回一个React节点。
这里,React 组件也可以订阅到 context 变更。这能让你在函数式组件中完成订阅 context。

这需要函数作为子元素(function as a child)这种做法。这个函数接收当前的 context 值,返回一个 React 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。
具体的完整一个例子:


import React, { Component } from 'react'

// createContext可以传递数值和对象等,不限。
const SimpleContext = React.createContext({
    a:'aa'
})//{a:'aa'}是一个默认值

export default class ContextPage extends Component {
  render() {
    return (
      <SimpleContext.Provider value={{a:'cc'}}>
        <div>
            <div>ContextPage</div>
            <Child/>
        </div>
      </SimpleContext.Provider>
    )
  }
}

class Child extends Component {
    render() {
        return (
          <div>
            <div>ChildClass</div>
            <Child1/>
          </div>
        )
      }
}
class Child1 extends Component {
    render() {
        return (
          <div>
            <div>ChildClass1</div>
            {/* 这里取到了obj */}
            <SimpleContext.Consumer>
                {
                    obj=>(
                        <Child11 obj={obj}/>
                    )
                }
            </SimpleContext.Consumer>
            
          </div>
        )
      }
}
class Child11 extends Component {
    render() {
        return (
          <div>
             <div>ChildClass11</div>
             {/* cc给传递过来了 */}
             <div>{this.props.obj.a}</div>
          </div>
        )
      }
}

执行结果如下图所示:
在这里插入图片描述
由此已经实现了context的通信。
后续补充其他外部库的实现的组件间的通信
,比如redux等的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值