React 组件间通信

React 的各个组件是彼此相互独立的,所以组件与组件间的通信是很重要的。从嵌套关系上看,就会有三种不同的通信方式:

  1. 父组件向子组件通信
  2. 子组件向父组件通信
  3. 没有嵌套关系的组件通信

父组件向子组件通信

React 的数据流动是单向的,父组件向子组件通信也是最常见的方式。父组件通过props向子组件传递需要的信息。祭源码:

1
2
3
4
5
6
7
8
9
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);

简单粗暴了一点,因为用了无状态组件,没有Class没有constructor,也没有生命周期。关于无状态组件和类组件,可以去官网看看。

解释一下:在使用Welcome组件时,在Welcome标签中像 HTML 标签写属性一样,把"Sara"传递给Welcome组件的name属性。Welcome组件通过props.name拿到"Sara"这个值。

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
import React, { Component } from 'react';
import { render } from 'react-dom';
import ListTitle from '../components/ListTitle.js';
import ListItem from '../components/listItem.js';

class List extends Component {
render() {
let title = '父组件向子组件通信';
let list = [1, 2, 3, 4, 5];

return (
<div>
<ListTitle title={ title } />
<ul>
{
list.map((item, index) => {
return (
<ListItem key={ index } value={ item } />
)
})
}
</ul>
</div>
);
}
}

render(<List />, document.getElementById('root'));

segmentfault 找了个代码贴上,类组件也有了

子组件向父组件通信

有时候我们想子组件返回些值给父组件,我们会有两种方式实现:

  1. 利用回调函数:这是 JavaScript 的灵活方便之处,这样就可以拿到运行时状态。
  2. 利用自定义事件机制:这种方法更通用,使用也更广泛,设计组件时,可以考虑加入事件机制往往可以达到简化组件的 API 的目的。

在简单的场景下使用自定义时间显然过于复杂,为了达到目的,一般选择较为简单的方法。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import React, {Component} from 'react'

class ListItem extends Component {
static defaultProps = {
text: '',
checked: false,
};

render() {
return (
<li>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.props.onChange}
/>
<span>{this.props.value}</span>
</li>
);
}
}

class List extends Component {
static defaultProps = {
list = [],
handleItemChange: () => {}
};
constructor(props) {
super(props)
this.state = {
list: this.props.list.map(entry => ({text: entry.text, checked: entry.checked})),
};
}

onItemeChange(entry) {
const {list} = this.state;

this.setState({
list: list.map(prevEntry => ({
text: prevEntry.text,
checked: entry.text === prevEntry.text
? !prevEntry.checked
: prevEntry.checked
})),
});
this.props.handleItemChange(entry)
}
render() {
return (
<div>
<ul>
{
this.state.list.map((entry, index)=>{
<ListItem
key = {`list-${index}`}
value = {entry.text}
checked = {entry.checked}
onChange = {this.onItemeChange.bind(this, entry)}
/>
})
}
</ul>
</div>
);
}
}

上面的栗子中,我们在 List 组件中构建了 handleItemChange 方法,这样在使用 List 组件时,就可以在运行是拿到改变的项对应的值。比如,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, {Component} from 'react'

class App extends Component {
constructor(props){
super(props);

this.handleItemChange = this.handleItemChange.bind(this);
}
handleItemChange(item){
// console.log(item)
}

render() {
return (
<List
list={[{text:1},{text:2}]}
handleItemChange={this.handleItemChange}
/>
);
}
}

我们看到setState一般与回调函数均会成对出现,这是因为回调函数即是转换内部状态时的函数传统。

上面的栗子太难看懂?又去抄了一份代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
onItemChange = (item) => {
const { list } = this.state;

this.setState({
list: list.map((prevItem) => {
return {
text: prevItem.text,
checked: prevItem.text === item.text ? !prevItem.checked : prevItem.checked
}
})
});
}
onTitleChange = () => {
this.setState({
title: '利用回掉函数,子组件向父组件通信'
})
}

跨级组件通信

如果我们有多层嵌套,传递用 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
class Button extends React.Component {
render() {
return (
<button style={{background: this.props.color}}>
{this.props.children}
</button>
);
}
}

class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button color={this.props.color}>Delete</Button>
</div>
);
}
}

class MessageList extends React.Component {
render() {
const color = "purple";
const children = this.props.messages.map((message) =>
<Message text={message.text} color={color} />
);
return <div>{children}</div>;
}
}

context是这样做的:

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
36
37
38
39
40
41
42
const PropTypes = require('prop-types');

class Button extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
}

Button.contextTypes = {
color: PropTypes.string
};

class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
);
}
}

class MessageList extends React.Component {
getChildContext() {
return {color: "purple"};
}

render() {
const children = this.props.messages.map((message) =>
<Message text={message.text} />
);
return <div>{children}</div>;
}
}

MessageList.childContextTypes = {
color: PropTypes.string
};

警告:React 官方并不推荐大量使用context,虽然它可以减少逐层传递,但当组件结构复杂的时候,我们并不知道context是从哪里传过来的。Context就像一个全局变量,而全局变量正是导致应用走向混乱的罪魁祸首之一,给组件带来了外部依赖的副作用。使用context比较好的场景正真意义上的全局信息且不会改变。例如界面主题,用户信息等。

Redux 作者总结了一个非常有意思的 cheatsheet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function shouldIUseReactContextFeature(){
if(amILirarayAuthor()&& doINeedToPassSomeThingDownDeeply()){
// 一个自定义的 <option> 组件可能想于它的 <select> 对话
// 这是可以的,弹药记住,这是一个实验性的 API,如果在一些情况下不能成功。
// 那么需要回滚改变它
return amIFindWith(API_CHANGES && BUGGY_UDATES);
}
if(myUseCase === 'thening'|| myUseCase === 'localization'){
// 在应用中, context 一般用于不会改变的全局变量
// 如果你坚持使用它,建议提供一个高阶函数
// 当我们要改变这个 API 的时候,只需要改变一个地方就可以了
return iPromiseToWriteHOCInsteadOfUsingDisrecly();
}
if(librarayAskMeToUseContext()){
//向它提供一个高阶组件
throw new Error('File an issue with this library.')
}

// 祝你好运
return yolo();
}

没有嵌套关系的组件通信

  1. 没有嵌套关系的,那只能通过可以影响全局的一些机制去考虑,自定义事件机制不失为一种上佳的方法。
  2. 在 componentDidMount 事件中,如果组件完成挂载,再订阅事件。
    当组件卸载的时候,在 componentWillUnmount 事件中取消事件的订阅。

笔记参考来源:
React 组件间通信

深入 React 技术栈

React 中文文档

end


React 组件间通信
https://bubao.github.io/posts/d2113aae.html
作者
一念
发布于
2017年7月29日
许可协议