学前知识准备
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等的。