React组件间通信

组件间不同的嵌套关系,会导致不同的通信方式。常见的有:父组件向子组件通信、子组件向父组件通信、没有嵌套关系的组件之间的通信,还有一种特殊形式:跨级组件通信。

1、父组件向子组件通信

这是React中最为常见的一种通信方式,父组件通过props向子组件传递需要的信息。示例如下:

class Child extends Component{
    render(){
        const { name } = this.props;
        return <p>hello, { name }</p>;
    }
}

class Parent extends Component{
    render(){
        return (
            <div>
                <Child name='Bob' />
            </div>
        );
    }
}

2、子组件向父组件通信

子组件向父组件通信有两种方式

  • 利用回调函数
  • 利用自定义事件机制

相较而言回调函数更为简单,一般多用这种方式。其原理为:父组件将一个函数作为props传递给子组件,子组件调用这个回调函数,将想要传递的信息,作为参数,传递给父组件。示例如下:

class Parent extends Component{
    constructor(props){
        super(props);
        this.handleClick = this.handleClick.bind(this);
        this.state={
            visible: false。
        }
    }

    handleClick(){
        this.setState({
            visible: true,
        });
    }

    render(){
        return (
            <React.Fragment>
                <div style={{display: this.state.visible ? 'block' : 'none'}}>
                    我是被隐藏的文字
                </div>
                <Child name='Bob' handleClick={this.handleClick} />
            </React.Fragment>
        );
    }
}
class Child extends Component{
    render(){
        const { handleClick } = this.props;
        return (<button onClick={handleClick}>点击显示隐藏的文字</button>);
    }
}

3、跨级组件通信

跨级组件通信有两种方法:(1)向层层传递props。(2)利用context。

对于第一种方式,如果组件结构较深,那么中间每一层都需要传递props,增加了复杂度且造成了冗余。而context 相当于一个全局变量,是一个大容器,我们可以把要通信的内容放在这个容器中,这样一来,不管嵌套有多深,都可以随意取用。使用 context 也很简单,需要满足两个条件:

  • 上级组件要声明自己支持 context,并提供一个函数来返回相应的 context 对象
  • 子组件要声明自己需要使用 context

示例:

export default class GrandParent extends Component{
    // 父组件声明自己支持 context
    static childContextTypes = {
        color:PropTypes.string,
        callback:PropTypes.func,
    }

    // 父组件提供一个函数,用来返回相应的 context 对象
    getChildContext(){
        return{
            color:"red",
            callback:this.callback.bind(this)
        }
    }

    callback(msg){
        console.log(msg)
    }

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


const Parent = (props) =>{
    return(
        <div>
            <Child />
        </div>
    );
}


export default class Child extends Component{
    // 子组件声明自己需要使用 context
    static contextTypes = {
        color:PropTypes.string,
        callback:PropTypes.func,
    }

    render(){
        const style = { color:this.context.color }
        const handleConsoleLog = (msg) => {
            return () => {
                this.context.callback(msg);
            }
        }

        return(
            <div style = { style }>
                Child组件
                <button onClick = { handleConsoleLog("我胡汉三又回来了!") }>点击我</button>
            </div>
        );
    }
}

总结:如果是父组件向子组件单向通信,可以使用变量,如果子组件想向父组件通信,同样可以由父组件提供一个回调函数,供子组件调用,回传参数。

注意:如果组件中使用构造函数(constructor),还需要在构造函数中传入第二个参数 context,并在 super 调用父类构造函数是传入 context,否则会造成组件中无法使用 context。

constructor(props,context){
  super(props,context);
}

Context就像全局变量一样,而全局变量正是导致应用走向混乱的罪魁祸首之一,给组件带来了外部依赖的副作用,因此,不推荐使用context。其比较好的应用场景是:真正意义上的全局信息且不会更改,如界面主题,用户信息。总体原则是:如果真的需要使用,建议写成高阶组件来实现。

补充

1、context对象的更改。

我们不应该也不能直接改变context对象中的属性。要想改变 context 对象,只有让其和父组件的 state 或者 props 进行关联,在父组件的 state 或 props 变化时,会自动调用 getChildContext 方法,返回新的 context 对象,而后子组件进行相应的渲染。

    constructor(props) {
        super(props);
        this.state = {
            color:"red"
        };
    }
    // 父组件声明自己支持 context
    static childContextTypes = {
        color:PropTypes.string,
        callback:PropTypes.func,
    }

    // 父组件提供一个函数,用来返回相应的 context 对象
    getChildContext(){
        return{
            color:this.state.color,
            callback:this.callback.bind(this)
        }
    }

2、context同样可以引用在无状态组件上,只需将context作为第二个参数即可。

const Child = (props,context) => {
    const style = { color:context.color }
    const handleConsoleLog = (msg) => {
        return () => {
            context.callback(msg);
        }
    }

    return(
        <div style = { style }>
            Child组件
            <button onClick = { handleConsoleLog("我胡汉三又回来了!") }>点击我</button>
        </div>
    );
}

Child.contextTypes = {
    color:PropTypes.string,
    callback:PropTypes.func,
}

4、没有嵌套关系的组件通信

没有嵌套关系的组件通信包括兄弟组件通信和不在同一个父级中的非兄弟组件。同样有两种通信方式:

  • 利用二者共同父组件的context对象进行通信
  • 利用自定义事件

第一种方法利用父组件中转,会增加子组件和父组件之间的耦合度,如果组件层次较深,找到二者公共父组件不太容易。一般使用自定义事件实现。

自定义事件需要借用node.js的events模块:

安装:npm install events --save
引入:import { EventEmitter } from "events";
     export default new EventEmitter(); // 初始化实例并输出给其他组件使用
export default class App extends Component{
    render(){
        return(
            <div>
                <Foo />
                <Boo />
            </div>
        );
    }
}


export default class Foo extends Component{
    constructor(props) {
        super(props);
        this.state = {
            msg:null,
        };
    }
    componentDidMount(){
        // 声明一个自定义事件
        this.eventEmitter = emitter.on("callMe",(msg)=>{
            this.setState({
                msg,
            })
        });
    }
    // 组件销毁前移除事件监听
    componentWillUnmount(){
        emitter.removeListener(this.eventEmitter);
    }
    render(){
        return(
            <div>
                { this.state.msg }
                我是非嵌套 1 号
            </div>
        );
    }
}


export default class Boo extends Component{
    render(){
        const cb = (msg) => {
            return () => {
                // 触发自定义事件。参一为事件名,后面为传递给事件的参数,可多个。
                emitter.emit("callMe","Hello")
            }
        }
        return(
            <div>
                我是非嵌套 2 号
                <button onClick = { cb("blue") }>点击我</button>
            </div>
        );
    }
}

5、总结:

几种通信情况下,最适用的方式:

  • 父组件向子组件通信:使用 props
  • 子组件向父组件通信:使用 props 回调
  • 跨级组件间通信:使用 context 对象
  • 非嵌套组件间通信:使用事件订阅
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容