父组件向子组件通信
父组件通过 props 向子组件传递需要的信息
子组件向父组件通信
在用 React 之前的组件开发模式时,常常需要接收组件运行时的状态,这时我们常用的方法 有以下两种:
- 利用回调函数:这是 JavaScript 灵活方便之处,这样就可以拿到运行时状态。
- 利用自定义事件机制:这种方法更通用,使用也更广泛。设计组件时,考虑加入事件机 制往往可以达到简化组件 API 的目的。
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}))
};
}
onItemChange(entry) {
const {list} = this.state;
this.setState({
list: list.map(prevEntry => ({
text: prevEntry.text,
checked: prevEntry.text === entry.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
.onItemChange
.bind(this, entry)}/>))}
</ul>
</div>
);
}
}
跨级组件通信
在 React 中,我们还可以使用 context 来 实现跨级父子组件间的通信
class ListItem extends Component {
static contextTypes = {
color: PropTypes.string
};
render() {
const {value} = this.props;
return (
<li style={{
background: this.context.color
}}>
<span>{value}</span>
</li>
);
}
}
class List extends Component {
static childContextTypes = {
color: PropTypes.string
};
getChildContext() {
return {color: 'red'};
}
render() {
const {list} = this.props;
return (
<div>
<ListTitle title={title}/>
<ul>
{list.map((entry, index) => (<ListItem key={`list-${index}`} value={entry.text}/>))}
</ul>
</div>
);
}
}
可以看到,我们并没有给 ListItem 传递 props,而是在父组件中定义了 ChildContext,这样从 这一层开始的子组件都可以拿到定义的 context ,例如这里的 color。
注意:React 官方并不建议大量使用 context ,因为尽管它可以减少逐层传递,但当组件结 构复杂的时候,我们并不知道 context 是从哪里传过来的。 Context 就像一个全局变量一样,而 全局变量正是导致应用走向混乱的罪魁祸首之一,给组件带来了外部依赖的副作用。在大部分情 况下,我们并不推荐使用 context 。使用 context 比较好的场景是真正意义上的全局信息且不会 更改,例如界面主题、用户信息等
没有嵌套关系的组件通信
没有嵌套关系的,那只能通过可以影响全局的一些机制去考虑。刚才讲到的自定义事件机制 不失为一种上佳的方法。
import {EventEmitter} from 'events';
export default new EventEmitter();
然后把 EventEmitter 实例输出到各组件中使用:
import ReactDOM from 'react-dom';
import React, {Component, PropTypes} from 'react';
import emitter from './events';
class ListItem extends Component {
static defaultProps = {
checked: false
}
constructor(props) {
super(props);
}
render() {
return (
<li>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.props.onChange}/>
<span>{this.props.value}</span>
</li>
);
}
}
class List extends Component {
constructor(props) {
super(props);
this.state = {
list: this
.props
.list
.map(entry => ({
text: entry.text,
checked: entry.checked || false
}))
};
}
onItemChange(entry) {
const {list} = this.state;
this.setState({
list: list.map(prevEntry => ({
text: prevEntry.text,
checked: prevEntry.text === entry.text
? !prevEntry.checked
: prevEntry.checked
}))
});
emitter.emit('ItemChange', entry);
}
render() {
return (
<div>
<ul>
{this
.state
.list
.map((entry, index) => (<ListItem
key={`list-${index}`}
value={entry.text}
checked={entry.checked}
onChange={this
.onItemChange
.bind(this, entry)}/>))}
</ul>
</div>
);
}
}
class App extends Component {
componentDidMount() {
this.itemChange = emitter.on('ItemChange', (data) => {
console.log(data);
});
}
componentWillUnmount() {
emitter.removeListener(this.itemChange);
}
render() {
return (<List
list={[
{
text: 1
}, {
text: 2
}
]}/>);
}
}
一般情况下,组件之间的通信尽可能保持简洁。如果说程序中出现多级传递或跨级传递时, 那么首先要重新审视一下是否有更合理的方式。Pub/Sub 模式实现的过程非常容易理解,即利用 全局对象来保存事件,用广播的方式去处理事件。这种常规的设计方法在软件开发中处处可见, 但这种模式带来的问题就是逻辑关系混乱。
在上述几种通信模式中,跨级通信往往是反模式的典型案例。对于应用开发来说,应该尽力 避免仅仅通过例如 Pub/Sub 实现的设计思路,加入强依赖与约定来进一步梳理流程是更好的方 法