React JS 组件间沟通的一些方法

刚入门React可能会因为React的单向数据流的特性而遇到组件间沟通的麻烦,这篇文章主要就说一说如何解决组件间沟通的问题。


 

1.组件间的关系

1.1 父子组件

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

var MyContainer = React.createClass({getInitialState: function(){return {list: ['item1', 'item2'],curItem: 'item1'}},// 改变curItem的回调函数changeItem: function(item){this.setState({curItem: item});},render: function(){return (<div>The curItem is: {this.state.curItem}<List list={this.state.list} changeItem={this.changeItem}/></div>)}
});var List = React.createClass({onClickItem: function(item){this.props.changeItem(item);},render: function(){return (<ul>{(function(){var self = this;return this.props.list.map(function(item){return (<li onClick={self.onClickItem.bind(self, item)}>I am {item}, click me!</li>		)});}.bind(this))()}</ul>)}
})ReactDOM.render(<MyContainer />,document.getElementById('example')
);

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

 

1.2 兄弟组件

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

var MyContainer = React.createClass({getInitialState: function(){return {list: ['item1', 'item2'],curItem: 'item1'}},// 改变curItem的回调函数changeItem: function(item){this.setState({curItem: item});},render: function(){return (<div>The curItem is: {this.state.curItem}<List list={this.state.list} curItem={this.state.curItem} /><SelectionButtons changeItem={this.changeItem}/></div>)}
});var List = React.createClass({render: function(){var selectedStyle = {color: 'white',background: 'red'};return (<ul>{(function(){var self = this;return this.props.list.map(function(item){var itemStyle = (item == self.props.curItem) ? selectedStyle : {};return (<li style={itemStyle}>I am {item}!</li>		)});}.bind(this))()}</ul>)}
});var SelectionButtons = React.createClass({onClickItem: function(item){this.props.changeItem(item);},render: function(){return (<div><button onClick={this.onClickItem.bind(this, 'item1')}>item1</button><button onClick={this.onClickItem.bind(this, 'item2')}>item2</button></div>)}
});ReactDOM.render(<MyContainer />,document.getElementById('example')
);
如上述代码所示,共享数据curItem作为state放在父组件<MyContainer />中,将回调函数changeItem传给<SelectionButtons />用于改变curItem,将curItem传给<List />用于高亮当前被选择的item。

 

2. 组件层次太深的噩梦

兄弟组件的沟通的解决方案就是找到两个组件共同的父组件,一层一层的调用上一层的回调,再一层一层地传递props。如果组件树嵌套太深,就会出现如下惨不忍睹的组件亲戚调用图。

share-parent-components

 

下面就来说说如何避免这个组件亲戚图的两个方法:全局事件和Context。

 

3. 全局事件

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

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


var EventEmitter = {_events: {},dispatch: function (event, data) {if (!this._events[event]) return; // no one is listening to this eventfor (var i = 0; i < this._events[event].length; i++)this._events[event][i](data);},subscribe: function (event, callback) {if (!this._events[event]) this._events[event] = []; // new eventthis._events[event].push(callback);},unSubscribe: function(event){if(this._events && this._events[event]) {delete this._events[event];}}
}


组件代码如下:


var MyContainer = React.createClass({render: function(){return (<div><CurItemPanel /><SelectionButtons/></div>)}
});var CurItemPanel = React.createClass({getInitialState: function(){return {curItem: 'item1'}},componentDidMount: function(){var self = this;EventEmitter.subscribe('changeItem', function(newItem){self.setState({curItem: newItem});})},componentWillUnmount: function(){EventEmitter.unSubscribe('changeItem');},render: function(){return (<p>The curItem is:  {this.state.curItem}</p>)}});var SelectionButtons = React.createClass({onClickItem: function(item){EventEmitter.dispatch('changeItem', item);},render: function(){return (<div><button onClick={this.onClickItem.bind(this, 'item1')}>item1</button><button onClick={this.onClickItem.bind(this, 'item2')}>item2</button></div>)}
});ReactDOM.render(<MyContainer />,document.getElementById('example')
);

 

事件绑定和解绑可以分别放在componentDidMount和componentWillUnMount中。由于事件是全局的,最好保证在componentWillUnMount中解绑事件,否则,下一次初始化组件时事件可能会绑定多次。 使用事件模型,组件之间无论是父子关系还是非父子关系都可以直接沟通,从而解决了组件间层层回调传递的问题,但是频繁地使用事件实现组件间沟通会使整个程序的数据流向越来越乱,因此,组件间的沟通还是要尽量遵循单向数据流机制。

 

4. context(上下文)

使用上下文可以让子组件直接访问祖先的数据或函数,无需从祖先组件一层层地传递数据到子组件中。

MyContainer组件:

var MyContainer = React.createClass({getInitialState: function(){return {curItem: 'item1'}},childContextTypes: {curItem: React.PropTypes.any,changeItem: React.PropTypes.any},getChildContext: function(){return {curItem: this.state.curItem,changeItem: this.changeItem}},changeItem: function(item){this.setState({curItem: item});},render: function(){return (<div><CurItemWrapper /><ListWrapper changeItem={this.changeItem}/></div>)}
});

childContextTypes用于验证上下文的数据类型,这个属性是必须要有的,否则会报错。getChildContext用于指定子组件可直接访问的上下文数据。

CurItemWrapper组件和CurItemPanel组件:

var CurItemWrapper = React.createClass({render: function(){return (<div><CurItemPanel /></div>)}
});var CurItemPanel = React.createClass({contextTypes: {curItem: React.PropTypes.any},render: function(){return (<p>The curItem is: {this.context.curItem}</p>)}});


在<CurItemPanel />通过this.context.curItem属性访问curItem,无需让<CurItemWrapper />将curItem传递过来。必须在contextTypes中设置curItem的验证类型,否则this.context是访问不了curItem的。

ListWrapper组件和List组件:

var ListWrapper = React.createClass({render: function(){return (<div><List /></div>)}
});var List = React.createClass({contextTypes: {changeItem: React.PropTypes.any},onClickItem: function(item){this.context.changeItem(item);},render: function(){return (<ul><li onClick={this.onClickItem.bind(this, 'item1')}>I am item1, click me!</li><li onClick={this.onClickItem.bind(this, 'item2')}>I am item2, click me!</li></ul>)}
});


同上,<List />可以通过this.context.changeItem获取<MyContainer />的改变curItem的changeItem函数。

 

5. Redux

为了在React中更加清晰地管理数据,Facebook提出了Flux架构,而redux则是Flux的一种优化实现。

关于redux,另外一个比我帅气的同事已经写了一篇详细的redux介绍博文,传送门在下面,有兴趣的可以去看看。

http://www.alloyteam.com/2015/09/react-redux/

 

当Redux与React搭配使用时,一般都是在最顶层组件中使用Redux。其余内部组件仅仅是展示性的,发起dispatch的函数和其他数据都通过props传入。然后,我们又会看到那熟悉的组件亲戚调用图:

share-parent-components

 

如果使用全局事件解决方案,那么redux中漂亮的,优雅的单向数据管理方式就会遭到破坏。于是,使用context就成了解决这种层层回调传递问题的首选方案,下面给出一个简单例子:

index.js:

import { createStore, applyMiddleware } from 'redux';
import reducers from "./reducers"
import { Provider } from 'react-redux'import React, {Component} from 'react';
import { render } from 'react-dom';
import App from './App';let store = createStore(reducers);render(<Provider store={store}><App /></Provider>,document.getElementById('root')
);


 

reducers.js:

export default function changeItem(state = {'curItem': 'item1'}, action){switch(action.type) {case 'CHANGE_ITEM':return Object.assign({}, {curItem: action.curItem});default:return state;}
}



actions.js:

export function changeItem(item) {return {type: 'CHANGE_ITEM',curItem: item}
}


App.js(组件代码):


import React, {Component} from 'react';
import { connect, Provider } from 'react-redux';
import { changeItem } from './actions';class App extends Component{constructor(props, context) {super(props, context);}getChildContext() {return {curItem: this.props.curItem,changeItem: this.props.changeItem}}render() {return (<div><CurItemPanel /><List /></div>)}
}App.childContextTypes = {curItem: React.PropTypes.any,changeItem: React.PropTypes.any
};class CurItemPanel extends Component {constructor(props, context) {super(props, context);}render() {return (<div>The curItem is: {this.context.curItem}</div>)}
}
CurItemPanel.contextTypes = {curItem: React.PropTypes.any
};class List extends Component {constructor(props, context) {super(props, context);}onClickItem (item){this.context.changeItem(item);}render() {return (<ul><li onClick={this.onClickItem.bind(this, 'item1')}>I am item1, click me!</li><li onClick={this.onClickItem.bind(this, 'item2')}>I am item2, click me!</li></ul>)}
}List.contextTypes = {changeItem: React.PropTypes.any
};let select = state => { return state};function mapDispatchToProps(dispatch) {return {changeItem: function(item) {dispatch(changeItem(item));}};
}export default(connect(select, mapDispatchToProps))(App);



上述代码中,Store是直接与智能组件<App />交互的,所以Store将state数据curItem和dispatch函数changeItem作为props传给了<App />。在<App />中将curItem数据和changeItem函数作为上下文,作为子组件的笨拙组件就可以之间通过上下文访问这些数据,无需通过props获取。

注:

1.redux的官方文档中是使用ES6语法的,所以这里的React代码也使用ES6做例子

2.运行上述代码需要构建代码,大家可以在redux的github中下载redux带构建代码的examples,然后将代码替换了再构建运行。

 

 

6. transdux

偶尔之间发现一个叫transdux的东西。这是一个类redux的数据沟通框架,作者的初衷是为了让用户写出比redux更简洁的代码,同时还能获得[fl|re]dux的好处。用户端使用该框架的话,可以解决下面一些redux中不好看的代码写法:

1)redux中需要创一个全局的store给Provider。Transdux中省略这个store。

2)redux与react搭配使用时,redux需要通过connect方法将数据和dispatch方法传给redux。Transdux没有connect。

3)redux需要把action当props传下去,跟传callback一样。Trandux不会出现这种传递。

 

使用transdux需要以下步骤

(1)安装trandux

npm install transdux –save

 

(2)把component包到Transdux里

import React, {Component} from 'react';
import Transdux from 'transdux';
import App from './TransduxApp.js';
import { render } from 'react-dom';render(<Transdux><App /></Transdux>,document.getElementById('root')
);

 

(3)定义component能干什么,component的状态如何改变


import React, {Component} from 'react';
import {mixin} from 'transdux'
import ChangeButton from './ChangeButton';// 定义action是怎么变的
let actions = {addHello(obj, state, props) {// 返回statereturn {msg: obj.msg}}
};class App extends Component{constructor(props){super(props);this.state = {msg: 'init'};}render() {// 应该传入调用了store.dispatch回调函数给笨拙组件return (<div>{this.state.msg}<ChangeButton /></div>)}
}export default mixin(App, actions);



(4)使用dispatch


import React, {Component} from 'react';
import {mixin} from 'transdux'
import minApp from './TransduxApp';
class ChangeButton extends Component{click() {this.dispatch(minApp, 'addHello', {'msg': 'hello world'});}render() {return (<div><button onClick={this.click.bind(this)}>change content</button></div>)}
}
export default mixin(ChangeButton, {});


mixin方法扩为<ChangeButton />扩展了一个dispatch方法。dispatch方法需要三个参数:接手消息的组件、改变组件的actions、传递的对象。<ChangeButton />的按钮事件处理函数调用了该dispatch后,会改变<App />中的状态。

 

使用了Clojure的Channel通信机制,实现了组件与组件之间的直接通信。这种通信的效果类似与events,每个组件可以维护着自己的state,然后用mixin包装自己传给其他组件改变状态。

 

Transdux的传送门在下面,有兴趣的同学可以去看看:

https://blog.oyanglul.us/javascript/react-transdux-the-clojure-approach-of-flux.html

 

小结

简单的的组件沟通可以用传props和callback的方法实现,然而,随着项目规模的扩大,组件就会嵌套得越来越深,这时候使用这个方法就有点不太适合。全局事件可以让组件直接沟通,但频繁使用事件会让数据流动变得很乱。如果兄弟组件共同的父组件嵌套得太深,在这个父组件设置context从而直接传递数据和callback到这两个兄弟组件中。使用redux可以让你整个项目的数据流向十分清晰,但是很容易会出现组件嵌套太深的情况,events和context都可以解决这个问题。Transdux是一个类redux框架,使用这个框架可以写出比redux简洁的代码,又可以得到redux的好处。

原博客地址:http://www.alloyteam.com/2016/01/some-methods-of-reactjs-communication-between-components/ 


转载于:https://www.cnblogs.com/songchunmin/p/7789670.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/390898.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据可视化分析票房数据报告_票房收入分析和可视化

数据可视化分析票房数据报告Welcome back to my 100 Days of Data Science Challenge Journey. On day 4 and 5, I work on TMDB Box Office Prediction Dataset available on Kaggle.欢迎回到我的100天数据科学挑战之旅。 在第4天和第5天&#xff0c;我将研究Kaggle上提供的TM…

sql limit子句_SQL子句解释的位置:之间,之间,类似和其他示例

sql limit子句什么是SQL Where子句&#xff1f; (What is a SQL Where Clause?) WHERE子句(和/或IN &#xff0c; BETWEEN和LIKE ) (The WHERE Clause (and/or, IN , BETWEEN , and LIKE )) The WHERE clause is used to limit the number of rows returned.WHERE子句用…

在Java里面使用instanceof的性能影响

问题&#xff1a;在Java里面使用instanceof的性能影响 我正在写一个应用程序&#xff0c;其中一种设计方案包含了instanceof操作的大量使用。虽然我知道面向对象设计通常试图避免使用instanceof&#xff0c;但那是另一回事了&#xff0c;这个问题纯粹只是讨论与性能有关。我想…

Soot生成控制流图

1.将soot.jar文件复制到工程bin目录下&#xff1b;2.在cmd中执行如下命令java -cp soot-trunck.jar soot.tools.CFGViewer --soot-classpath .;"%JAVA_HOME%"\jre\lib\rt.jar com.wauoen.paper.classes.Activity其中&#xff0c;JAVA_HOME是jdk目录&#xff1b;com.w…

Centos 6.5安装MySQL-python

报错信息&#xff1a;Using cached MySQL-python-1.2.5.zip Complete output from command python setup.py egg_info: sh: mysql_config: command not found Traceback (most recent call last): File "<string>", line 1, in <module&g…

react 最佳实践_最佳React教程

react 最佳实践React is a JavaScript library for building user interfaces. It was voted the most loved in the “Frameworks, Libraries, and Other Technologies” category of Stack Overflow’s 2017 Developer Survey.React是一个用于构建用户界面JavaScript库。 在S…

先知模型 facebook_Facebook先知

先知模型 facebook什么是先知&#xff1f; (What is Prophet?) “Prophet” is an open-sourced library available on R or Python which helps users analyze and forecast time-series values released in 2017. With developers’ great efforts to make the time-series …

Java里面的静态代码块

问题&#xff1a;Java里面的静态代码块 I was looking over some code the other day and I came across: 前几天我在看一些代码时发现&#xff1a; static {... }我是c转来的&#xff0c;我不知道为啥要这样干。这个代码也编译成功了&#xff0c;没出错误。这里的"stat…

搭建Maven私服那点事

摘要&#xff1a;本文主要介绍在CentOS7.1下使用nexus3.6.0搭建maven私服&#xff0c;以及maven私服的使用&#xff08;将自己的Maven项目指定到私服地址、将第三方项目jar上传到私服供其他项目组使用&#xff09; 一、简介 Maven是一个采用纯Java编写的开源项目管理工具, Mave…

lee最短路算法_Lee算法的解释:迷宫运行并找到最短路径

lee最短路算法Lee算法是什么&#xff1f; (What is the Lee Algorithm?) The Lee algorithm is one possible solution for maze routing problems. It always gives an optimal solution, if one exists, but is slow and requires large memory for dense layout.Lee算法是迷…

gan训练失败_我尝试过(但失败了)使用GAN来创作艺术品,但这仍然值得。

gan训练失败This work borrows heavily from the Pytorch DCGAN Tutorial and the NVIDA paper on progressive GANs.这项工作大量借鉴了Pytorch DCGAN教程 和 有关渐进式GAN 的 NVIDA论文 。 One area of computer vision I’ve been wanting to explore are GANs. So when m…

怎么样实现对一个对象的深拷贝

问题&#xff1a;怎么样实现对一个对象的深拷贝 使用深拷贝的方法有点难实现啊。要保证原来的对象和克隆对象不是共享同一个引用的步骤是什么啊&#xff1f; 回答一 一种安全的方法是先序列化对象&#xff0c;然后反序列化。这保证了所有东西都是一个新的引用。 这里有一篇…

19.7 主动模式和被动模式 19.8 添加监控主机 19.9 添加自定义模板 19.10 处理图形中的乱码 19.11 自动发现...

2019独角兽企业重金招聘Python工程师标准>>> 19.7 主动模式和被动模式 • 主动或者被动是相对客户端来讲的 • 被动模式&#xff0c;服务端会主动连接客户端获取监控项目数据&#xff0c;客户端被动地接受连接&#xff0c;并把监控信息传递给服务端 服务端请求以后&…

Codeforces Round #444 (Div. 2) C.Solution for Cube 模拟

向题解低头&#xff0c;向大佬低头(。﹏。)orz……模拟也不能乱模啊……要好好分析题意&#xff0c;简化简化再简化orz敲黑板 六个面的魔方&#xff0c;能一步还原的情况一定是只有2个面是单色&#xff0c;其余四个面&#xff0c;每个面2种颜色&#xff0c;而且不会出现任意两面…

fcc认证_介绍fCC 100:我们对2019年杰出贡献者的年度总结

fcc认证2019 has been a big year for the global freeCodeCamp community.对于全球freeCodeCamp社区来说&#xff0c;2019年是重要的一年。 More people are answering questions on the forum. 越来越多的人在论坛上回答问题。 Our publication has several new, rising aut…

华盛顿特区与其他地区的差别_使用华盛顿特区地铁数据确定可获利的广告位置...

华盛顿特区与其他地区的差别深度分析 (In-Depth Analysis) Living in Washington DC for the past 1 year, I have come to realize how WMATA metro is the lifeline of this vibrant city. The metro network is enormous and well-connected throughout the DMV area. When …

Windows平台下kafka环境的搭建

近期在搞kafka&#xff0c;在Windows环境搭建的过程中遇到一些问题&#xff0c;把具体的流程几下来防止后面忘了。 准备工作&#xff1a; 1.安装jdk环境 http://www.oracle.com/technetwork/java/javase/downloads/index.html 2.下载kafka的程序安装包&#xff1a; http://kafk…

deeplearning.ai 改善深层神经网络 week2 优化算法

这一周的主题是优化算法。 1. Mini-batch&#xff1a; 上一门课讨论的向量化的目的是去掉for循环加速优化计算&#xff0c;X [x(1) x(2) x(3) ... x(m)]&#xff0c;X的每一个列向量x(i)是一个样本&#xff0c;m是样本个数。但当样本很多时&#xff08;比如m500万&#xff09…

gcc汇编汇编语言_什么是汇编语言?

gcc汇编汇编语言Assembly Language is the interface between higher level languages (C, Java, etc) and machine code (binary). For a compiled language, the compiler transforms higher level code into assembly language code.汇编语言是高级语言(C &#xff0c;Java等…

铺装s路画法_数据管道的铺装之路

铺装s路画法Data is a key bet for Intuit as we invest heavily in new customer experiences: a platform to connect experts anywhere in the world with customers and small business owners, a platform that connects to thousands of institutions and aggregates fin…