javascript
React JS 组件间沟通的一些方法
剛?cè)腴TReact可能會(huì)因?yàn)镽eact的單向數(shù)據(jù)流的特性而遇到組件間溝通的麻煩,這篇文章主要就說(shuō)一說(shuō)如何解決組件間溝通的問(wèn)題。
?
1.組件間的關(guān)系
1.1 父子組件
ReactJS中數(shù)據(jù)的流動(dòng)是單向的,父組件的數(shù)據(jù)可以通過(guò)設(shè)置子組件的props傳遞數(shù)據(jù)給子組件。如果想讓子組件改變父組件的數(shù)據(jù),可以在父組件中傳一個(gè)callback(回調(diào)函數(shù))給子組件,子組件內(nèi)調(diào)用這個(gè)callback即可改變父組件的數(shù)據(jù)。
var MyContainer = React.createClass({getInitialState: function(){return {list: ['item1', 'item2'],curItem: 'item1'}},// 改變curItem的回調(diào)函數(shù)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 />通過(guò)props傳遞list數(shù)據(jù)給<List />組件,如果<MyContainer />中的list改變,<List />會(huì)重新渲染列表數(shù)據(jù)。而<List />可以通過(guò)<MyContainer />傳來(lái)的changeItem函數(shù),改變<MyContainer />的curItem數(shù)據(jù)。
?
1.2?兄弟組件
當(dāng)兩個(gè)組件不是父子關(guān)系,但有相同的父組件時(shí),將這兩個(gè)組件稱為兄弟組件。兄弟組件不能直接相互傳送數(shù)據(jù),此時(shí)可以將數(shù)據(jù)掛載在父組件中,由兩個(gè)組件共享:如果組件需要數(shù)據(jù)渲染,則由父組件通過(guò)props傳遞給該組件;如果組件需要改變數(shù)據(jù),則父組件傳遞一個(gè)改變數(shù)據(jù)的回調(diào)函數(shù)給該組件,并在對(duì)應(yīng)事件中調(diào)用。
var MyContainer = React.createClass({getInitialState: function(){return {list: ['item1', 'item2'],curItem: 'item1'}},// 改變curItem的回調(diào)函數(shù)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') );如上述代碼所示,共享數(shù)據(jù)curItem作為state放在父組件<MyContainer />中,將回調(diào)函數(shù)changeItem傳給<SelectionButtons />用于改變curItem,將curItem傳給<List />用于高亮當(dāng)前被選擇的item。
?
2.?組件層次太深的噩夢(mèng)
兄弟組件的溝通的解決方案就是找到兩個(gè)組件共同的父組件,一層一層的調(diào)用上一層的回調(diào),再一層一層地傳遞props。如果組件樹(shù)嵌套太深,就會(huì)出現(xiàn)如下慘不忍睹的組件親戚調(diào)用圖。
?
下面就來(lái)說(shuō)說(shuō)如何避免這個(gè)組件親戚圖的兩個(gè)方法:全局事件和Context。
?
3. 全局事件
可以使用事件來(lái)實(shí)現(xiàn)組件間的溝通:改變數(shù)據(jù)的組件發(fā)起一個(gè)事件,使用數(shù)據(jù)的組件監(jiān)聽(tīng)這個(gè)事件,在事件處理函數(shù)中觸發(fā)setState來(lái)改變視圖或者做其他的操作。使用事件實(shí)現(xiàn)組件間溝通脫離了單向數(shù)據(jù)流機(jī)制,不用將數(shù)據(jù)或者回調(diào)函數(shù)一層一層地傳給子組件,可以避免出現(xiàn)上述的親戚圖。
事件模塊可以使用如EventEmitter或PostalJS這些第三方庫(kù),也可以自己簡(jiǎn)單實(shí)現(xiàn)一個(gè):
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];}} }
組件代碼如下:
?
事件綁定和解綁可以分別放在componentDidMount和componentWillUnMount中。由于事件是全局的,最好保證在componentWillUnMount中解綁事件,否則,下一次初始化組件時(shí)事件可能會(huì)綁定多次。 使用事件模型,組件之間無(wú)論是父子關(guān)系還是非父子關(guān)系都可以直接溝通,從而解決了組件間層層回調(diào)傳遞的問(wèn)題,但是頻繁地使用事件實(shí)現(xiàn)組件間溝通會(huì)使整個(gè)程序的數(shù)據(jù)流向越來(lái)越亂,因此,組件間的溝通還是要盡量遵循單向數(shù)據(jù)流機(jī)制。
?
4. context(上下文)
使用上下文可以讓子組件直接訪問(wèn)祖先的數(shù)據(jù)或函數(shù),無(wú)需從祖先組件一層層地傳遞數(shù)據(jù)到子組件中。
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用于驗(yàn)證上下文的數(shù)據(jù)類型,這個(gè)屬性是必須要有的,否則會(huì)報(bào)錯(cuò)。getChildContext用于指定子組件可直接訪問(wèn)的上下文數(shù)據(jù)。
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 />通過(guò)this.context.curItem屬性訪問(wèn)curItem,無(wú)需讓<CurItemWrapper />將curItem傳遞過(guò)來(lái)。必須在contextTypes中設(shè)置curItem的驗(yàn)證類型,否則this.context是訪問(wèn)不了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 />可以通過(guò)this.context.changeItem獲取<MyContainer />的改變curItem的changeItem函數(shù)。
?
5. Redux
為了在React中更加清晰地管理數(shù)據(jù),Facebook提出了Flux架構(gòu),而redux則是Flux的一種優(yōu)化實(shí)現(xiàn)。
關(guān)于redux,另外一個(gè)比我?guī)洑獾耐乱呀?jīng)寫了一篇詳細(xì)的redux介紹博文,傳送門在下面,有興趣的可以去看看。
http://www.alloyteam.com/2015/09/react-redux/
?
當(dāng)Redux與React搭配使用時(shí),一般都是在最頂層組件中使用Redux。其余內(nèi)部組件僅僅是展示性的,發(fā)起dispatch的函數(shù)和其他數(shù)據(jù)都通過(guò)props傳入。然后,我們又會(huì)看到那熟悉的組件親戚調(diào)用圖:
?
如果使用全局事件解決方案,那么redux中漂亮的,優(yōu)雅的單向數(shù)據(jù)管理方式就會(huì)遭到破壞。于是,使用context就成了解決這種層層回調(diào)傳遞問(wèn)題的首選方案,下面給出一個(gè)簡(jiǎn)單例子:
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數(shù)據(jù)curItem和dispatch函數(shù)changeItem作為props傳給了<App />。在<App />中將curItem數(shù)據(jù)和changeItem函數(shù)作為上下文,作為子組件的笨拙組件就可以之間通過(guò)上下文訪問(wèn)這些數(shù)據(jù),無(wú)需通過(guò)props獲取。
注:
1.redux的官方文檔中是使用ES6語(yǔ)法的,所以這里的React代碼也使用ES6做例子
2.運(yùn)行上述代碼需要構(gòu)建代碼,大家可以在redux的github中下載redux帶構(gòu)建代碼的examples,然后將代碼替換了再構(gòu)建運(yùn)行。
?
?
6. transdux
偶爾之間發(fā)現(xiàn)一個(gè)叫transdux的東西。這是一個(gè)類redux的數(shù)據(jù)溝通框架,作者的初衷是為了讓用戶寫出比redux更簡(jiǎn)潔的代碼,同時(shí)還能獲得[fl|re]dux的好處。用戶端使用該框架的話,可以解決下面一些redux中不好看的代碼寫法:
1)redux中需要?jiǎng)?chuàng)一個(gè)全局的store給Provider。Transdux中省略這個(gè)store。
2)redux與react搭配使用時(shí),redux需要通過(guò)connect方法將數(shù)據(jù)和dispatch方法傳給redux。Transdux沒(méi)有connect。
3)redux需要把a(bǔ)ction當(dāng)props傳下去,跟傳callback一樣。Trandux不會(huì)出現(xiàn)這種傳遞。
?
使用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的狀態(tài)如何改變
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() {// 應(yīng)該傳入調(diào)用了store.dispatch回調(diào)函數(shù)給笨拙組件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方法擴(kuò)為<ChangeButton />擴(kuò)展了一個(gè)dispatch方法。dispatch方法需要三個(gè)參數(shù):接手消息的組件、改變組件的actions、傳遞的對(duì)象。<ChangeButton />的按鈕事件處理函數(shù)調(diào)用了該dispatch后,會(huì)改變<App />中的狀態(tài)。
?
使用了Clojure的Channel通信機(jī)制,實(shí)現(xiàn)了組件與組件之間的直接通信。這種通信的效果類似與events,每個(gè)組件可以維護(hù)著自己的state,然后用mixin包裝自己傳給其他組件改變狀態(tài)。
?
Transdux的傳送門在下面,有興趣的同學(xué)可以去看看:
https://blog.oyanglul.us/javascript/react-transdux-the-clojure-approach-of-flux.html
?
小結(jié)
簡(jiǎn)單的的組件溝通可以用傳props和callback的方法實(shí)現(xiàn),然而,隨著項(xiàng)目規(guī)模的擴(kuò)大,組件就會(huì)嵌套得越來(lái)越深,這時(shí)候使用這個(gè)方法就有點(diǎn)不太適合。全局事件可以讓組件直接溝通,但頻繁使用事件會(huì)讓數(shù)據(jù)流動(dòng)變得很亂。如果兄弟組件共同的父組件嵌套得太深,在這個(gè)父組件設(shè)置context從而直接傳遞數(shù)據(jù)和callback到這兩個(gè)兄弟組件中。使用redux可以讓你整個(gè)項(xiàng)目的數(shù)據(jù)流向十分清晰,但是很容易會(huì)出現(xiàn)組件嵌套太深的情況,events和context都可以解決這個(gè)問(wèn)題。Transdux是一個(gè)類redux框架,使用這個(gè)框架可以寫出比redux簡(jiǎn)潔的代碼,又可以得到redux的好處。
原博客地址:http://www.alloyteam.com/2016/01/some-methods-of-reactjs-communication-between-components/?
轉(zhuǎn)載于:https://www.cnblogs.com/songchunmin/p/7789670.html
總結(jié)
以上是生活随笔為你收集整理的React JS 组件间沟通的一些方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 帆软报表和jeecg的进一步整合--aj
- 下一篇: Centos 6.5安装MySQL-py