Ignite

React组件间通信

2017-12-10

处理 React 组件之间的交流方式,主要取决于组件之间的关系,
下面我们根据不同情况,来讲讲React的组件通信。

  • 【父组件】向【子组件】传值;
  • 【子组件】向【父组件】传值;
  • 没有任何嵌套关系的组件之间传值(PS:比如:兄弟组件之间的传值)

父子传值:


父向子传

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Parent extends Component {
constructor() {
super();
this.state = {
value: '',
}
}

handleChange = e => {
this.value = e.target.value;
}

handleClick = () => {
this.setState({
value: this.value,
})
}

render() {
return (
<div>
我是parent
<input onChange={this.handleChange} />
<div className="button" onClick={this.handleClick}>通知</div>
<div>
<Child value={this.state.value} />
</div>
</div>
);
}
}

子组件:

1
2
3
4
5
6
7
8
9
10
class Child extends Component {
render() {
const { value } = this.props;
return (
<div>
我是Child,得到传下来的值:{value}
</div>
);
}
}

父组件做的就是定义好 state ,定义好事件函数,在input onChange 的时候,去缓存 value 值,然后点击 button 的时候,改变 state , 子组件只负责展示 value 。

子向父传

child 组件通知 parent 组件, 主要是依靠 parent 传下来的 callback 函数执行,改变 parent 组件的状态,或者把 child 自己的 state 通知 parent 。分两种情况:

  • state 定义在 parent 组件
    父组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

class Parent extends Component {
constructor() {
super();
this.state = {
value: '',
}
}

setValue = value => {
this.setState({
value,
})
}

render() {
return (
<div>
<div>我是parent, Value是:{this.state.value}</div>
<Child setValue={this.setValue} />
</div>
);
}
}

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Child extends Component {

handleChange = e => {
this.value = e.target.value;
}

handleClick = () => {
const { setValue } = this.props;
setValue(this.value);
}

render() {
return (
<div>
我是Child
<div className="card">
state 定义在 parent
<input onChange={this.handleChange} />
<div className="button" onClick={this.handleClick}>通知</div>
</div>
</div>
);
}
}

parent 组件把改变 state 的 setValue 函数传给 child ,child 组件自己处理内部的状态(这里是表单的value值),当 child 组件分发消息的时候, 执行 parent 的 setValue 函数,从而改变了 parent 的 state,state发生变化, parent 组件执行 re-render

  • state 定义在 child 组件

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Parent extends Component {

onChange = value => {
console.log(value, '来自 child 的 value 变化');
}

render() {
return (
<div>
<div>我是parent
<Child onChange={this.onChange} />
</div>
);
}
}

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Child extends Component {

constructor() {
super();
this.state = {
childValue: ''
}
}

childValChange = e => {
this.childVal = e.target.value;
}

childValDispatch = () => {
const { onChange } = this.props;
this.setState({
childValue: this.childVal,
}, () => { onChange(this.state.childValue) })
}

render() {
return (
<div>
我是Child
<div className="card">
state 定义在 child
<input onChange={this.childValChange} />
<div className="button" onClick={this.childValDispatch}>通知</div>
</div>
</div>
);
}
}

有时候 state 是需要定义在 child 组件的,比如弹窗, CheckBox 这种开关性质的,逻辑是重复的,state 定义在组件内部更好维护, 复用性更好。但是 child 的 state 是需要告知我的 parent 组件的, 同样还是执行 parent 传下来的 change 函数。

兄弟组件:


有时候可能出现页面中的某两部分通信,比如省市的级联选择,点击 button 改变颜色等等,组件并不是父子级,没有嵌套关系的时候。这种时候通常是依赖共有的顶级 Container 处理或者第三方的状态管理器。其实原理都是相通的,兄弟 A 的 value 发生变化,分发的时候把 value 值告诉一个中间者 C ,C 会自动告知 B,实现 B 的自动render 。

利用共有的Container

container

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Container extends Component {
constructor() {
super();
this.state = {
value: '',
}
}

setValue = value => {
this.setState({
value,
})
}

render() {
return (
<div>
<A setValue={this.setValue}/>
<B value={this.state.value} />
</div>
);
}
}

兄弟A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A extends Component {

handleChange = (e) => {
this.value = e.target.value;
}

handleClick = () => {
const { setValue } = this.props;
setValue(this.value);
}

render() {
return (
<div className="card">
我是Brother A, <input onChange={this.handleChange} />
<div className="button" onClick={this.handleClick}>通知</div>
</div>
)
}
}

兄弟B:

1
2
3
4
5
6
7
const B = props => (
<div className="card">
我是Brother B, value是:
{props.value}
</div>
);
export default B;

组件 A 中的表单 value 值,告知了父级 Container 组件(通过 setValue 函数改变 state),组件 B 依赖于 Container 传下来的 state,会做出同步更新。这里的中间者是 Container。

利用Context

上面的方式,如果嵌套少还可以,如果嵌套特别多,比如一级导航栏下的二级导航栏下的某个按钮,要改变页面中 content 区域的 table 里的某个列的值…他们同属于一个 page 。这样传递 props 就会很痛苦,每一层组件都要传递一次。

顶级公共组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Context extends Component {

constructor() {
super();
this.state = {
value: '',
};
}

setValue = value => {
this.setState({
value,
})
}

getChildContext() { // 必需
return {
value: this.state.value,
setValue: this.setValue,
};
}
render() {
return (
<div>
<AParent />
<BParent />
</div>
);
}
}
// 必需
Context.childContextTypes = {
value: PropTypes.string,
setValue: PropTypes.func,
};

A 的 parent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

class AParent extends Component {
render() {
return (
<div className="card">
<A />
</div>
);
}
}
// A
class A extends Component {

handleChange = (e) => {
this.value = e.target.value;
}

handleClick = () => {
const { setValue } = this.context;
setValue(this.value);
}

render() {
return (
<div>
我是parentA 下的 A, <input onChange={this.handleChange} />
<div className="button" onClick={this.handleClick}>通知</div>
</div>
);
}
}
// 必需
A.contextTypes = {
setValue: PropTypes.func,
};

B 的 parent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class BParent extends Component {
render() {
return (
<div className="card">
<B />
</div>
);
}
}

// B
class B extends Component {

render() {
return (
<div>
我是parentB 下的 B, value是:
{this.context.value}
</div>
);
}
}

B.contextTypes = {
value: PropTypes.string,
};

组件 A 仍是 消息的发送者,组件 B 是接收者, 中间者是 Context 公有 Container 组件。context是官方文档的一个 API ,通过 getChildContext 函数定义 context 中的值,并且还要求 childContextTypes 是必需的。这样属于这个 Container 组件的子组件,通过 this.context 就可以取到定义的值,并且起到跟 state 同样的效果。中间者其实还是 Container,只不过利用了上下文这样的 API ,省去了 props 的传递。另外:这个功能是实验性的,未来可能会有所改动。

Context 也存在自己的问题。如果你用过 context,你可能会发现一个问题,当 context 发生改变的时候,比如数据流向是从 Container(context 定义) -> A -> B -> C(接收 context),组件 A, B 也会发生 render,这样 C 组件才能拿到更新后的 context。万一你在 A, B 使用 shouldComponentUpdate: false 拦截了,或者某个组件是 PureComponent,context 发生变化,C 没有重新渲染,故拿不到最新的 context。

针对这种情况,我们要做的不是想方设法让 A,B render,而是通过其他手段,来实现 C 的重新渲染。通常是使用 context 做依赖注入,即 context 只注入一次,后续不会发生变化,这样各种无视组件层级透传属性。context 里面的数据进行改造,添加 subscribe 这样的函数,然后当某个数据变化的时候做 patch。子组件可能会加这样的代码:

子组件:

1
2
3
componentDidMount() {
this.context.theme.subscribe(() => this.forceUpdate())
}

这种思想可以安全的使用 context,事实上 react-redux 也是这样做的。Provider 提供 context,connect 去做订阅。

Redux || Mobx

Redux 或者 Mobx 是第三方的状态管理器,是这里我们通信的中间者。大型项目最直接的就是上库… 更方便,更不容易出错。 但其实小项目就没什么必要了。东西比较多,这里不再阐述它们的实现和做了什么。

总结

react 特殊的自上而下的单向数据流,和 state 的特性,造就以这样的思想实现组件通信。除去发布订阅和 Redux 等,其他的都是 props 自上而下传递的理念,子组件需要的总是通过父组件传递下来的,关于 state 的定义,还是看具体的应用场景了。

Tags: React
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章