# 通信机制

### 通信

### 常见的通信有哪几种形式？

1. 父组件向子组件通信
2. 子组件向父组件通信
3. 跨级组件通信
4. 兄弟组件通信

#### 父组件向子组件通信

父组件向子组件传递 props，子组件得到 props 后进行相应的处理。

```javascript
function Sup(props) {
  const { user } = props;
  return (
    <p className="sup-context">
      <span className="sup-context-title">Who am I, you are</span>: {user}
    </p>
  );
}
```

```js
export default class Container extends Component {
  constructor() {
    super();
    this.state = {
      userName: 'shenxuxiang',
    };
  }

  render() {
    return (
      <div className="page-one">
        <h1 className="page-one-title">父组件向子组件通信</h1>
        <Sup user={this.state.userName} />
      </div>
    );
  }
}

```

Container 是一个父组件，Sup 作为子组件。我们将 user 属性传递到了 Sup 内部。这时我们就可以发现，在 Sup 内部是可以拿到 user 的值的。

### 子组件向父组件通信

利用回调函数，可以实现子组件向父组件通信： 父组件将一个函数作为 props 传递给子组件，子组件调用该回调函数，便可以向父组件通信。

```javascript
export default class Sup extends PureComponent {
  handleClick = (event) => {
    const name = event.target.getAttribute("data-name");
    // 这种直接修改 props 的形式会是页面报错
    // this.props.user = name;
    this.props.onChange(name);
  };

  render() {
    return (
      <Fragment>
        <p className="page-two-sup-context">
          <span className="page-two-sup-context-title">Who am I, you are</span>:{" "}
          {this.props.user}
        </p>
        <ul onClick={this.handleClick} className="page-two-user-list">
          <li className="page-one-user-list-item" data-name="小明">
            小明
          </li>
          <li className="page-one-user-list-item" data-name="小强">
            小强
          </li>
          <li className="page-one-user-list-item" data-name="小李">
            小李
          </li>
        </ul>
      </Fragment>
    );
  }
}
```

```javascript
export default class Container extends Component {
  constructor() {
    super();
    this.state = {
      userName: "shenxuxiang",
    };
  }

  handleChangeUserName = (name) => {
    this.setState({ userName: name });
  };

  render() {
    return (
      <div className="page-two">
        <h1 className="page-two-title">子组件向父组件通信</h1>
        <Sup user={this.state.userName} onChange={this.handleChangeUserName} />
      </div>
    );
  }
}
```

#### 跨组件之间通信

跨组件通信，就是父组件向子组件的子组件通信，一般嵌套的层级超过三层我们就可以称为跨级。跨级组件通信可以采用下面两种方式：

* 中间组件层层传递 props
* 使用 context 对象

```javascript
function Sup(props) {
  return (
    <div className="page-three-sup">
      <div>这是 sup</div>
      <Sub user={props.user} />
    </div>
  );
}
function Sub(props) {
  return (
    <div className="page-three-sub">
      <div>这是 sub</div>
      <div>{`props.user = ${props.user}`}</div>
    </div>
  );
}

class Container extends Component {
  constructor() {
    super();
    this.state = {
      userName: "shenxuxiang",
    };
  }
  render() {
    return (
      <div className="page-three">
        <h1 className="page-three-title">跨级组件通信</h1>
        <Sup user={this.state.userName} />
      </div>
    );
  }
}
```

使用 context 对象

使用 context 我们可以把要通信的内容放在这个容器中，这样一来，不管嵌套有多深，都可以随意取用。

创建一个 context 对象

```javascript
const ThemContext = React.createContext("skyblue");
```

必须使用 React.createContext 方法进行创建，参数可以是任意类型的值

Context.Provider

```js
  <ThemContext.Provider value={this.state.themColor}>
    <Sup />
  </ThemContext.Provider>

```

每个 Context 对象都会返回一个 Provider 组件，它允许消费组件订阅 context 的变化。

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

当 Provider 的 value 值发生变化时，它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数。

```javascript
const ThemContext = React.createContext("skyblue");
export default class Container extends Component {
  constructor() {
    super();
    this.state = {
      themColor: "#f80",
    };
  }
  render() {
    return (
      <div className="page-three">
        <h1 className="page-three-title">跨级组件通信</h1>
        <ThemContext.Provider value={this.state.themColor}>
          <Sup />
        </ThemContext.Provider>
      </div>
    );
  }
}
Container.contextType = ThemContext;
function Sup(props) {
  return (
    <div className="page-three-sup">
      <div>这是 sup</div>
      <Sub />
    </div>
  );
}
function Sub(props) {
  const context = useContext(ThemContext);
  return (
    <div className="page-three-sub" style={{ background: context }}>
      <div>这是 sub</div>
    </div>
  );
}
```

#### 兄弟组件通信

兄弟组件通信，指的是在同一个父组件中的两个及以上的组件之间如何进行通信。针对这种情况，一般的做法就是将数据来源存放在它们共同的父级组件中。

```javascript
export default class Container extends Component {
  constructor() {
    super();
    this.state = {
      userName: "shenxuxiang",
    };
  }
  handleClick = (name) => {
    this.setState({ userName: name });
  };
  render() {
    const { userName } = this.state;
    return (
      <div className="page-four">
        <h1 className="page-four-title">兄弟组件通信</h1>
        <h2>userName={userName}</h2>
        <Foo user={userName} onClick={this.handleClick} />
        <Bar user={userName} onClick={this.handleClick} />
      </div>
    );
  }
}

function Bar(props) {
  return (
    <div onClick={() => props.onClick("bar")}>
      我是组件 Bar。{`【${props.user}】`}
    </div>
  );
}
function Foo(props) {
  return (
    <div onClick={() => props.onClick("foo")}>
      我是组件 Foo。{`【${props.user}】`}
    </div>
  );
}
```

* 初始化时的参数传递
* 实例化阶段的方法调用

#### 子组件向父组件传递消息

* 回调函数

```js
class Child {
    constructor(cb) {
        ／／ 调用父组件传入的回调函数， 发送消息
        setTimeout(() => {
          cb()
        }, 2000);

    }
}
class Parent {
    constructor() {
        ／／
        初始化阶段， 传入回调函数
        this.child = new Child(function() {
            console.log(’child update’)
        })
    }
}
```

* 子组件部署消息接口

```js
//event.js

class Event {
  /** on 方法把订阅者所想要订阅的事件及相应的回调函数记录在 Event 对象的 _cbs 属性中*/
  on(event, fn) {
    if (typeof fn != "function") {
      console.error("fn must be a function");
      return;
    }
    this._cbs = this._cbs || {};
    (this._cbs[event] = this._cbs[event] || []).push(fn);
  }
  /**emit 方法接受一个事件名称参数，在 Event 对象的 _cbs 属性中取出对应的数组，并逐个执行里面的回调函数 */
  emit(event) {
    this._cbs = this._cbs || {};
    var callbacks = this._cbs[event],
      args;
    if (callbacks) {
      callbacks = callbacks.slice(0);
      args = [].slice.call(arguments, 1);
      for (var i = 0, len = callbacks.length; i < len; i++) {
        callbacks[i].apply(null, args);
      }
    }
  }
  /** off 方法接受事件名称和当初注册的回调函数作参数，在 Event 对象的 _cbs 属性中删除对应的回调函数。*/
  off(event, fn) {
    this._cbs = this._cbs || {};
    // all
    if (!arguments.length) {
      this._cbs = {};
      return;
    }
    var callbacks = this._cbs[event];
    if (!callbacks) return;
    // remove all handlers
    if (arguments.length === 1) {
      delete this._cbs[event];
      return;
    }
    // remove specific handler
    var cb;
    for (var i = 0, len = callbacks.length; i < len; i++) {
      cb = callbacks[i];
      if (cb === fn || cb.fn === fn) {
        callbacks.splice(i, 1);
        break;
      }
    }
    return;
  }
}

const myEvent = new Event();
export default myEvent;
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://shenjunhong.gitbook.io/blog/yuan-ma/react/tong-xin-ji-zhi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
