[关闭]
@brizer 2016-02-08T14:32:11.000000Z 字数 5442 阅读 1282

React之组件间沟通


前言

本文主要学习React中组件之间的通信方式。


父子组件

前面我们学到使用props和state来进行父子间的通信,父亲通过属性传递给子级,子级通过回调改变挂载父亲上的属性:

  1. var MyContainer = React.createClass({
  2. getInitialState: function(){
  3. return {
  4. list: ['item1', 'item2'],
  5. curItem: 'item1'
  6. }
  7. },
  8. // 改变curItem的回调函数
  9. changeItem: function(item){
  10. this.setState({
  11. curItem: item
  12. });
  13. },
  14. render: function(){
  15. return (
  16. <div>
  17. The curItem is: {this.state.curItem}
  18. <List list={this.state.list} changeItem={this.changeItem}/>
  19. </div>
  20. )
  21. }
  22. });
  23. var List = React.createClass({
  24. onClickItem: function(item){
  25. this.props.changeItem(item);
  26. },
  27. render: function(){
  28. return (
  29. <ul>
  30. {
  31. (function(){
  32. var self = this;
  33. return this.props.list.map(function(item){
  34. return (
  35. <li onClick={self.onClickItem.bind(self, item)}>I am {item}, click me!</li>
  36. )
  37. });
  38. }.bind(this))()
  39. }
  40. </ul>
  41. )
  42. }
  43. })
  44. ReactDOM.render(
  45. <MyContainer />,
  46. document.getElementById('example')
  47. );

ReactJS中数据的流动是单向的,父组件的数据可以通过设置子组件的props传递数据给子组件。如果想让子组件改变父组件的数据,可以在父组件中传一个callback(回调函数)给子组件,子组件内调用这个callback即可改变父组件的数据。

<MyContainer /><List />的父组件,<MyContainer />通过props传递list数据给<List />组件,如果<MyContainer />中的list改变,<List />会重新渲染列表数据。而<List />可以通过<MyContainer />传来的changeItem函数,改变<MyContainer />的curItem数据。


兄弟组件

兄弟组件不能直接相互传送数据,此时可以将数据挂载在父组件中,由两个组件共享:如果组件需要数据渲染,则由父组件通过props传递给该组件;如果组件需要改变数据,则父组件传递一个改变数据的回调函数给该组件,并在对应事件中调用。

  1. var MyContainer = React.createClass({
  2. getInitialState: function(){
  3. return {
  4. list: ['item1', 'item2'],
  5. curItem: 'item1'
  6. }
  7. },
  8. // 改变curItem的回调函数
  9. changeItem: function(item){
  10. this.setState({
  11. curItem: item
  12. });
  13. },
  14. render: function(){
  15. return (
  16. <div>
  17. The curItem is: {this.state.curItem}
  18. <List list={this.state.list} curItem={this.state.curItem} />
  19. <SelectionButtons changeItem={this.changeItem}/>
  20. </div>
  21. )
  22. }
  23. });
  24. var List = React.createClass({
  25. render: function(){
  26. var selectedStyle = {
  27. color: 'white',
  28. background: 'red'
  29. };
  30. return (
  31. <ul>
  32. {
  33. (function(){
  34. var self = this;
  35. return this.props.list.map(function(item){
  36. var itemStyle = (item == self.props.curItem) ? selectedStyle : {};
  37. return (
  38. <li style={itemStyle}>I am {item}!</li>
  39. )
  40. });
  41. }.bind(this))()
  42. }
  43. </ul>
  44. )
  45. }
  46. });
  47. var SelectionButtons = React.createClass({
  48. onClickItem: function(item){
  49. this.props.changeItem(item);
  50. },
  51. render: function(){
  52. return (
  53. <div>
  54. <button onClick={this.onClickItem.bind(this, 'item1')}>item1</button>
  55. <button onClick={this.onClickItem.bind(this, 'item2')}>item2</button>
  56. </div>
  57. )
  58. }
  59. });
  60. ReactDOM.render(
  61. <MyContainer />,
  62. document.getElementById('example')
  63. );

共享数据curItem作为state放在父组件<MyContainer />中,将回调函数changeItem传给<SelectionButtons />用于改变curItem,将curItem传给<List />用于高亮当前被选择的item。


全局事件

本来上面说到的单向传递是可以解决绝大多数时候问题的,但是如果组件的嵌套层级过多, 一层层往上调就会显得很麻烦,这个时候可以采用几种方式来解决,第一种就是全局事件。

改变数据的组件发起一个事件,使用数据的组件监听这个事件,在事件处理函数中触发setState来改变视图或者做其他的操作。使用事件实现组件间沟通脱离了单向数据流机制,不用将数据或者回调函数一层一层地传给子组件。

事件模块可以使用如EventEmitter或PostalJS这些第三方库,也可以自己简单实现一个:

  1. var EventEmitter = {
  2. _events: {},
  3. //触发事件
  4. dispatch: function (event, data) {
  5. if (!this._events[event]) return; // no one is listening to this event
  6. for (var i = 0; i < this._events[event].length; i++)
  7. this._events[event][i](data);
  8. },
  9. //监听事件
  10. subscribe: function (event, callback) {
  11. if (!this._events[event]) this._events[event] = []; // new event
  12. this._events[event].push(callback);
  13. },
  14. //注销事件
  15. unSubscribe: function(event){
  16. if(this._events && this._events[event]) {
  17. delete this._events[event];
  18. }
  19. }
  20. }

组件代码如下:

  1. var MyContainer = React.createClass({
  2. render: function(){
  3. return (
  4. <div>
  5. <CurItemPanel />
  6. <SelectionButtons/>
  7. </div>
  8. )
  9. }
  10. });
  11. var CurItemPanel = React.createClass({
  12. getInitialState: function(){
  13. return {
  14. curItem: 'item1'
  15. }
  16. },
  17. //挂载成功
  18. componentDidMount: function(){
  19. var self = this;
  20. EventEmitter.subscribe('changeItem', function(newItem){
  21. self.setState({
  22. curItem: newItem
  23. });
  24. })
  25. },
  26. //卸载
  27. componentWillUnmount: function(){
  28. EventEmitter.unSubscribe('changeItem');
  29. },
  30. render: function(){
  31. return (
  32. <p>
  33. The curItem is: {this.state.curItem}
  34. </p>
  35. )
  36. }
  37. });
  38. var SelectionButtons = React.createClass({
  39. onClickItem: function(item){
  40. //触发事件
  41. EventEmitter.dispatch('changeItem', item);
  42. },
  43. render: function(){
  44. return (
  45. <div>
  46. <button onClick={this.onClickItem.bind(this, 'item1')}>item1</button>
  47. <button onClick={this.onClickItem.bind(this, 'item2')}>item2</button>
  48. </div>
  49. )
  50. }
  51. });
  52. ReactDOM.render(
  53. <MyContainer />,
  54. document.getElementById('example')
  55. );

由于事件是全局的,最好保证在componentWillUnMount中解绑事件,否则,下一次初始化组件时事件可能会绑定多次。

使用事件模型,组件之间无论是父子关系还是非父子关系都可以直接沟通,从而解决了组件间层层回调传递的问题,但是频繁地使用事件实现组件间沟通会使整个程序的数据流向越来越乱,因此,组件间的沟通还是要尽量遵循单向数据流机制


context

如果我们希望组件直接获取父亲的父亲级的属性,可以直接使用context,而不用一层层的传下来:

  1. var MyContainer = React.createClass({
  2. getInitialState: function(){
  3. return {
  4. curItem: 'item1'
  5. }
  6. },
  7. //验证数据类型,这个必须要
  8. childContextTypes: {
  9. curItem: React.PropTypes.any,
  10. changeItem: React.PropTypes.any
  11. },
  12. getChildContext: function(){
  13. return {
  14. curItem: this.state.curItem,
  15. changeItem: this.changeItem
  16. }
  17. },
  18. changeItem: function(item){
  19. this.setState({
  20. curItem: item
  21. });
  22. },
  23. render: function(){
  24. return (
  25. <div>
  26. <CurItemWrapper />
  27. <ListWrapper changeItem={this.changeItem}/>
  28. </div>
  29. )
  30. }
  31. });
  32. var CurItemWrapper = React.createClass({
  33. render: function(){
  34. return (
  35. <div>
  36. <CurItemPanel />
  37. </div>
  38. )
  39. }
  40. });
  41. //通过this.context.***直接调用祖先级别的属性
  42. var CurItemPanel = React.createClass({
  43. contextTypes: {
  44. curItem: React.PropTypes.any
  45. },
  46. render: function(){
  47. return (
  48. <p>
  49. The curItem is: {this.context.curItem}
  50. </p>
  51. )
  52. }
  53. });
  54. var ListWrapper = React.createClass({
  55. render: function(){
  56. return (
  57. <div>
  58. <List />
  59. </div>
  60. )
  61. }
  62. });
  63. var List = React.createClass({
  64. contextTypes: {
  65. changeItem: React.PropTypes.any
  66. },
  67. //this.context.changeItem祖先级方法
  68. onClickItem: function(item){
  69. this.context.changeItem(item);
  70. },
  71. render: function(){
  72. return (
  73. <ul>
  74. <li onClick={this.onClickItem.bind(this, 'item1')}>I am item1, click me!</li>
  75. <li onClick={this.onClickItem.bind(this, 'item2')}>I am item2, click me!</li>
  76. </ul>
  77. )
  78. }
  79. });

简单的的组件沟通可以用传props和callback的方法实现,然而,随着项目规模的扩大,组件就会嵌套得越来越深,这时候使用这个方法就有点不太适合。全局事件可以让组件直接沟通,但频繁使用事件会让数据流动变得很乱。如果兄弟组件共同的父组件嵌套得太深,在这个父组件设置context从而直接传递数据和callback到这两个兄弟组件中。


感悟

组件间通讯的基本方法已经学习,还有一些其他的方法如redux,transdux等框架暂时还没有了解,等到项目中需要用到时再做了解吧。
学习react一段时间后,发现其实有时候还是不方便呢。相信会有更好的组件化解决方案出现。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注