React 的各个组件是彼此相互独立的,所以组件与组件间的通信是很重要的。从嵌套关系上看,就会有三种不同的通信方式:
- 父组件向子组件通信
- 子组件向父组件通信
- 没有嵌套关系的组件通信
父组件向子组件通信
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 找了个代码贴上,类组件也有了
子组件向父组件通信
有时候我们想子组件返回些值给父组件,我们会有两种方式实现:
- 利用回调函数:这是 JavaScript 的灵活方便之处,这样就可以拿到运行时状态。
- 利用自定义事件机制:这种方法更通用,使用也更广泛,设计组件时,可以考虑加入事件机制往往可以达到简化组件的 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){ }
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()){ return amIFindWith(API_CHANGES && BUGGY_UDATES); } if(myUseCase === 'thening'|| myUseCase === 'localization'){ return iPromiseToWriteHOCInsteadOfUsingDisrecly(); } if(librarayAskMeToUseContext()){ throw new Error('File an issue with this library.') }
return yolo(); }
|
没有嵌套关系的组件通信
- 没有嵌套关系的,那只能通过可以影响全局的一些机制去考虑,自定义事件机制不失为一种上佳的方法。
- 在 componentDidMount 事件中,如果组件完成挂载,再订阅事件。
当组件卸载的时候,在 componentWillUnmount 事件中取消事件的订阅。
笔记参考来源:
React 组件间通信
深入 React 技术栈
React 中文文档
end