React 组件通信 组件传值

一、父子关系组件通信

1)props传递数据与props传递方法

a)父组件向子组件传递数据使用props传递属性

思路:父组件给子组件传递属性,在子组件中通过{this.props.属性名}来获得数据

//父组件
import React from 'react';
import Son1 from '../../components/Son1/Son1';
import Son2 from '../../components/Son2/Son2';
export default class Father extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是父级",
            msg: "传给儿子1的数据",
            sendson2: {
                name: "哈哈哈",
                age: 12,
                sex: "男"
            }
        }
    }
    render() {
        return (
            <div>
                <p>父级组件的标题:{this.state.titile}</p>
                <hr />
                <Son1 name={this.state.msg}></Son1>
                <hr />
                {/*传给子组件的属性是一个对象,使用...将对象展开*/}
                <Son2 {...this.state.sendson2}></Son2>
            </div>
        )
    }
}
//子组件1
//父组件传递的props是一个值
import React from 'react';
export default class Son1 extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return (
            <div>
                <p>父级传来的数据:{this.props.name}</p>
            </div>
        )
    }
}
//子组件2
//父组件传递的props是一个对象
import React from 'react';
export default class Son2 extends React.Component {
    constructor(props) {
        super(props)
    render() {
        return (
            <div>
                <p>爸爸传来的数据:{this.props.name}</p>
                <p>爸爸传来的数据:{this.props.age}</p>
                <p>爸爸传来的数据:{this.props.sex}</p>
            </div>
        )
    }
}

b)子组件向父组件传递数据使用props传递方法

子传父的本质还是父给子传属性,只是父传过去的属性值是一个函数,子可以调这个函数,通过调这个函数传参来传值。父传给儿子属性,属性是函数,儿子调这个函数实现传值,但是要注意,此时的this指向已经改变,在父级定义被调用的函数时就使用箭头函数可以解决this丢失的问题。

父组件传方法给子组件时,函数名自定义,且不可加()调用;子组件调用父组件的方法时,不用写父组件的函数名,通过属性名即可调用,但必须要加()来调用方法。

//父组件
import React from 'react';
import Son1 from '../../components/Son1/Son1';
import Son2 from '../../components/Son2/Son2';
export default class Father extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是父级",
            msg: "传给儿子1的数据",
            sendson2: {
                name: "哈哈哈",
                age: 12,
                sex: "男"
            },
            fromson2: ""
        }
    }
    
    // 子传父的本质还是父给子传属性,只是父传过去的属性值是一个函数,子可以调这个函数,通过调这个函数传参来传值
    fn1(str) {
        console.log(str);
    }
    
    // 父传给儿子属性,属性是函数,儿子调这个函数实现传值,但是要注意,此时的this指向已经改变,在父级定义函数时就使用箭头函数可以解决this丢失的问题
    fn2 = (str) => {
        console.log(str);
        this.setState({
            fromson2: str
        })
    }

    render() {
        return (
            <div>
                <p>父级组件的标题:{this.state.titile}</p>
                <p>从Son2来的数据{this.state.fromson2}</p>
                <hr />
                <Son1 onmyclick={this.fn1}></Son1>
                <hr />
                <Son2 onmyclick={this.fn2}></Son2>
            </div>
        )
    }
}
//子组件1
import React from 'react';
export default class Son1 extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是儿子1"
        }
    }
    render() {
        return (
            <div>
                <input value="传给老父亲" type="button" onClick={() => this.props.onmyclick("传给老父亲的值")} />
            </div>
        )
    }
}
//子组件2
import React from 'react';
export default class Son2 extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是儿子2",
            age: 1333
        }
    }

    chuan(str) {
        this.props.onmyclick(str)
    }

    render() {
        return (
            <div>
                <input value="传给老父亲" type="button" onClick={() => this.chuan(this.state.age)} />
            </div>
        )
    }
}

2)onRef标记

思路:当在子组件中调用onRef函数时,正在调用从父组件传递的函数。this.props.onRef(this)这里的参数指向子组件本身,父组件接收该引用作为第一个参数:onRef = {ref =>(this.child = ref)}然后它使用this.child保存引用。之后,可以在父组件内访问整个子组件实例,并且可以调用子组件函数。官方文档建议不要过度依赖onRef。

ref标记的实质依旧是父给子传属性,只是父传过去的属性值是一个函数(向子组件传函数时并不调用这个函数,即不加()),这个属性的属性名刻意规定为onRef;子可以调这个属性携带的函数,通过调这个函数并传参,来传值。那就意味着子组件可以向父组件回传this,就是把自己传给父组件,在父组件中定义一个属性来接住子组件的this,这样在父组件中就可以任意获取子组件中的数据,甚至是方法(那么既然可以调方法就可以传值)。注:调用方法时需要加(),来调用父组件的函数。

在父组件内读取子组件的数据,实质上就实现了子组件向父组件传递数据;在父组件内调用子组件的方法,那么通过调用方法时的传参,实质上就实现了父组件向子组件传递数据。

//父组件
import React from 'react';
import Son1 from '../../components/Son1/Son1';
import Son2 from '../../components/Son2/Son2';
export default class Father extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是父级",
        }
    }

    fn1(child) {
        this.Children1 = child
    }

    fn2() {
        console.log(this.Children1.state.titile);
    }

    fn3(child) {
        this.Children2 = child
    }

    fn4 = () => {
        //父组件可以通过保存子组件实例的Children2,来调用子组件中的任何东西(数据和方法)
        this.Children2.son2log("老父亲传来的")
    }

    render() {
        return (
            <div>
                <input value="测试1" type="button" onClick={this.fn2.bind(this)} />
                <input value="测试2" type="button" onClick={() => this.fn4()} />
                <hr />
                {/*onRef={(son) => this.fn1(son)}可以理解为一个事件处理函数(但这样说不对,onRef并不是一个事件);其中第一个son是子组件调用的真正的处理函数(这个无名箭头函数的目的就是为了解决类似事件处理函数不能传参,因为这个函数不能加圆括号被直接调用,否则这段代码在被挂载的时候就直接调用了)的参数,箭头后面的是真正做事情的函数(所以要加圆括号调用它);第一个son是子组件通过this.props.onRef(this)拿到父组件传过去的函数并执行之后的形参,第二个son是这个形参被传给父组件里函数的形参*/}
                <Son1 onRef={(son) => this.fn1(son)}></Son1>
                <hr />
                <Son2 onRef={(son) => this.fn3(son)}></Son2>
            </div>
        )
    }
}
//子组件1
import React from 'react';
export default class Son1 extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是儿子1"
        }
    }

    //生命周期钩子函数渲染完成后,此时数据挂载完成、DOM渲染完成,接收父组件传来的方法,并调用它,然后传入this(就是自己)作为参数。
    componentDidMount() {
        this.props.onRef(this)
    }

    render() {
        return (
            <div>
            </div>
        )
    }
}
//子组件2
import React from 'react';
export default class Son2 extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是儿子2",
            age: 18
        }
    }

    componentDidMount() {
        this.props.onRef(this)
    }

    son2log(fromFather) {
        console.log("son2log", fromFather);
    }

    render() {
        return (
            <div>
            </div>
        )
    }
}

二、非父子组件通信方式

1)订阅发布(PubSub模块)

思路:通过PubSub模块提供的PubSub.subscribe方法,在订阅的组件中声明一个函数名及函数,然后再发布组件中通过PubSub模块提供的PubSub.publish方法去调用指定函数名的函数,调用的时候传的参就是数据。

在所有类型的组件关系中,都可以使用订阅发布(PubSub模块)的方式进行传值。

  1. 安装PubSub模块
    yarn add PubSub --save
  2. 新建pubSub.js文件,引入安装的模块,并对外开放PubSub实例。(最好将pubSub.js文件放在scr文件夹下的utils文件夹中,便于管理)
    import PubSub from "PubSub";
    export default new PubSub();
  3. 在需要通信的组件中各自引入pubSub.js文件
    import PubSub from '../../utils/PubSub';
  4. 添加发布:即在传出数据的一方添加。(示例Son1向Son2传数据)
    //组件1;发布组件(传出数据的组件)
    import React from 'react';
    import PubSub from '../../utils/PubSub.js';
    export default class Son1 extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                titile: "这是儿子1"
            }
        }
    
        send() {
            // 绑定发布
            PubSub.publish("fn", "Son1发布的消息")
        }
    
        render() {
            return (
                <div>
                    <p>{this.state.titile}</p>
                    <input type="button" value="传给Son2" onClick={this.send} />
                     {/*此处写onClick={this.send}是因为send函数中并没有使用this;如果有this就需要写成onClick={()=>this.send()}或bind之类等等*/}
                </div>
            )
        }
    }
  5. 添加订阅:即在接收数据的一方添加。(示例Son1向Son2传数据)
    //组件2;订阅组件(接收数据的组件)
    import React from 'react';
    import PubSub from '../../utils/PubSub.js';
    export default class Son2 extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                titile: "这是儿子2",
            }
        }
    
        // 订阅(接收数据):此处可以理解为定义了一个函数,函数名叫fn,fn分别有两个参数(m,n),其中第一个参数m是接收到的数据,是核心需要的东西,第二个参数n是一个对象,记录着消息名,例如{name: "fn", token: 0}。订阅消息安排在componentDidMount钩子函数中,确保渲染完成后第一时间订阅数据。
        componentDidMount() {
            PubSub.subscribe("fn", function (m, n) {
                console.log("Son2中接收到的消息(数据)", m);
                console.log("Son2中拿到的消息名(是个对象)", n);
            })
        }
    
        render() {
            return (
                <div>
                    <p>{this.state.titile}</p>
                </div>
            )
        }
    }

PubSub核心方法:

  • 发布:pubsub.publish('消息名',数据)

  • 订阅:token=pubsub.subscribe('消息名',回调函数(数据,'消息名'))

  • 清除指定订阅:pubsub.unsubscribe(token | '消息名');

  • 清除所有:pubsub.unsubscribeAll()

 

2)状态提升

本来是多个无关组件,但需要共用一部分数据时,将这部分数据放在一个离他们最近的父组件中进行管理。即把原本在子组件中state放在父组件中进行管理。

思路:父组件先给子组件1传一个属性,属性值是一个方法,子组件1通过调用这个方法时传参先把数据传给父组件,父组件的方法被调用后先把数据保存,再给子组件2传一个属性,属性值是刚才保存的数据,在子组件2中在通过获取父组件传来的属性最终获取子组件1传来的数据。

注意:因为涉及到React框架的数据挂载、模板渲染、数据更新等环节,实际上React框架并不是按这个顺序进行处理的,这里只是借此梳理思路。

//父组件
//这次演示Son1组件向Son2组件传值
import React from 'react';
import Son1 from '../../components/Son1/Son1';
import Son2 from '../../components/Son2/Son2';
export default class Father extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是父级",
            // 〇:设置好这个value为空,用来中转保存子组件中需要的数据
            value: null
        }
    }

    fn(val) {
        // 5).Son1调用父组件中的方法,父组件的方法把从Son1中获得的数据保存在this.state中,并通过this.setState刷新数据。
        // 建议fn的形参和键名写成一样,这样就可以简写为this.setState({val});此处是为了区分理解。
        this.setState({
            value: val
        })
    }

    render() {
        return (
            <div>
                {/* 1).父组件先通过属性,给Son1传一个方法 */}
                <Son1 onMyClick={(val) => this.fn(val)}></Son1>
                {/* 4).此处的val接到的就是Son1调用父组件onMyClick属性时传来的数据 */}
                <hr />
                {/* 6).父组件把接收到的值传给Son2 */}
                <Son2 val={this.state.value}></Son2>
                {/* 此处val为传给Son2组件的自定义属性名(只要Son2中获取时一致即可),this.state.value为父组件中接收到Son1组件的值;实际使用时写成一样省事,这里是为了理解区分 */}
            </div>
        )
    }
}
//子组件1;就是它要传出数据
import React from 'react';
export default class Son1 extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是儿子1"
        }
    }

    // 3).调用父组件的方法,调用时就可以传值,数据不就可以来自任何地方。
    send() {
        this.props.onMyClick("son1中的数据")
    }

    render() {
        return (
            <div>
                <p>{this.state.titile}</p>
                {/* 2).子组件点击按钮时通过调用事件处理函数来调用父组件传来的方法 */}
                <input type="button" value="传" onClick={() => this.send()} />
            </div>
        )
    }
}
//子组件2;就是它要接收数据
import React from 'react';
export default class Son2 extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是儿子2",
        }
    }

    render() {
        return (
            <div>
                <p>{this.state.titile}</p>
                {/* 7).Son2组件中获取父组件传来的值,结束。 对了,接来的数据可以在render中食用,也可以在方法中食用。*/}
                <p>{this.props.val}</p>
            </div>
        )
    }
}

3)context 状态树传参

示例中的组件关系:App==>Father==>Son1

a)状态树由外向内的传参

Provider==>Consumer

  1. 新建myContext.js文件,从react中解构createContext,之后调用createContext函数,并对外开放Provider, Consumer。(最好将文件放在scr文件夹下的utils文件夹中,便于管理)
    import { createContext } from "react";
    export const { Provider, Consumer } = createContext({
        msg: "myContext中默认的数据"
    });
    // 此处的msg是默认值;这个默认值的意义是,在使用时并未使用Provider时,Consumer组件获得的值,而不是使用Provider没有value时自动获取的值。

    在myContext.js中也可以发请求,因为模块中的代码也是会被执行的,模块代码的执行时机,是首次引入该模块的地方,而且多次引入,只执行一次。如果发请求的话,那么export const { Provider, Consumer }......这些代码就要写在请求的回调函数之中。

  2. 在App组件中引入myContext.js文件,并解构出Provider;再用Provider包裹住需要使用数据的位置。
    //App组件(爷爷组件)App组件是函数式组件
    import Father from "../src/views/Father/Father"
    import { Provider } from '../src/utils/myContext';
    
    function App() {
      return (
        <div className="App">
          <p>这是App.js</p>
          <hr />
          <Provider value={{ msg: "App.js中的数据" }}>
            <Father></Father>
          </Provider>
        </div>
      );
    }
    
    export default App;

    Provider是一个组件,接收一个将要被往下层层传递的props,该值需在组件树最顶层设置。一个Provider可以关联到多个Consumers。这是一个顶层用于提供context的组件,包含一个value的props,value是实际的context数据。

    在顶层组件也可以不写Provider,在Consumer中也可以获取到Context中的值,这个值就是myContext.js中的默认值。但在顶层组件写了Provider却没有写value就会报错(这种情况也不符合逻辑,既然不写value那么反正都要用默认值,那何苦呢,不如连Provider也别写了)

  3. 在Son1组件中引入myContext.js文件,并解构出Consumer;再用Consumer包裹住使用数据的位置。此时并不需要经过父级组件Father。
    //Son1组件(孙级组件)
    import React from 'react';
    import { Consumer } from '../../utils/myContext';
    
    export default class Son1 extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                titile: "这是儿子1"
            }
        }
    
        render() {
            return (
                <div>
                    <p>{this.state.titile}</p>
                    <Consumer>
                        {
                            (val) => {
                                return <p>{val.msg}</p>
                            }
                        }
                    </Consumer>
                </div>
            )
        }
    }

    Consumer是一个组件,接收一个函数作为子节点,函数接收当前 context 的值。这是一个底层用于获取context的组件,需要一个函数作为其子元素,该函数包含一个val的参数,这个参数就是上层所传递数据。

 

b)状态树由内向外的传参

Consumer==>Provider

使用步骤同由外向内传参一样,首先新建myContext.js文件,各个组件引入myContext.js文件,并解构处对应的Provider和Consumer,并包裹在使用数据的地方。

思路:在App组件的this.state中不仅保存数据,再保存一个App组件的方法,通过Provider的value属性,把this.state这个对象整体传给Consumer,Consumer在接到这个对象之后通过调用App组件中this.state里保存的App组件的方法时传参,来传递数据。

//App组件
import React from 'react';
import Father from "../src/views/Father/Father"
import { Provider } from '../src/utils/myContext';

class App extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
      msg: "App.js中this.state的数据",
      // 此处的this就是App对象
      // 1).定义一个fn变量名,它的值是App组件中的一个方法,写在this.state,日后可以传给Consumer组件
      fn: this.fn
    }
  }

  // 如果此处不使用箭头函数,那么内部的this指向的是this.state这个对象,this就不是我们想要的App对象,就无法调用this.setState方法刷新数据。
  // 5).将Son1组件中调用fn函数时传入的数据接住,通过this.setState刷新数据,之后被修改的数据又会被响应给Son1
  fn = (str) => {
    this.setState({
      msg: str
    })
  }

  render() {
    return (
      <div className="App">
        <p>这是App.js</p>
        <p>{this.state.msg}</p>
        <hr />
        {/* 2).传给Consumer的value属性的值是一个对象,这个对象里不仅有数据,还有App对象中的方法 */}
        <Provider value={this.state}>
          <Father></Father>
        </Provider>
      </div>
    )
  }
}

export default App;
//Son1组件
import React from 'react';
import { Consumer } from '../../utils/myContext';

export default class Son1 extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            titile: "这是儿子1"
        }
    }

    render() {
        return (
            <div>
                <p>{this.state.titile}</p>
                <Consumer>
                    {
                        // 3).在Consumer中接到的obj是一个对象,就是App组件中的state
                        (obj) => {
                            return (
                                // React脚手架中如果根标签没有意义,允许使用空标签当作唯一的根标签包裹
                                <>
                                    <p>{obj.msg}</p>
                                    <input type="button" value="修改"
                                        onClick={() => obj.fn("Son1里来的数据")}
                                    />
                                    {/* 4).通过点击事件,来调用App组件传来的this.state中的fn方法,既然可以调用App的方法,那就传个参,带点货回去。 */}
                                </>
                            )
                        }
                    }
                </Consumer>
            </div>
        )
    }
}

都这里了...就说明你看完了...

如果你看完的话我们就相互鼓励一下吧,我纯手写的不容易,你看的肯定更不容易...

如果有错误或理解不对的地方欢迎指出,感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值