這篇文章主要介紹React組件間通信方式有哪些,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、微信平臺(tái)小程序開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了香河免費(fèi)建站歡迎大家使用!
父組件 => 子組件:
Props
Instance Methods
子組件 => 父組件:
Callback Functions
Event Bubbling
兄弟組件之間:
Parent Component
不太相關(guān)的組件之間:
Context
Portals
Global Variables
Observer Pattern
Redux等
這是最常見的react組件之間傳遞信息的方法了吧,父組件通過(guò)props把數(shù)據(jù)傳給子組件,子組件通過(guò)this.props
去使用相應(yīng)的數(shù)據(jù)
const Child = ({ name }) => { <div>{name}</div> } class Parent extends React.Component { constructor(props) { super(props) this.state = { name: 'zach' } } render() { return ( <Child name={this.state.name} /> ) } }
第二種父組件向子組件傳遞信息的方式有些同學(xué)可能會(huì)比較陌生,但這種方式非常有用,請(qǐng)務(wù)必掌握。原理就是:父組件可以通過(guò)使用refs
來(lái)直接調(diào)用子組件實(shí)例的方法,看下面的例子:
class Child extends React.Component { myFunc() { return "hello" } } class Parent extends React.Component { componentDidMount() { var x = this.foo.myFunc() // x is now 'hello' } render() { return ( <Child ref={foo => { this.foo = foo }} /> ) } }
大致的過(guò)程:
首先子組件有一個(gè)方法myFunc
父組件給子組件傳遞一個(gè)ref屬性,并且采用callback-refs的形式。這個(gè)callback函數(shù)接收react組件實(shí)例/原生dom元素作為它的參數(shù)。當(dāng)父組件掛載時(shí),react會(huì)去執(zhí)行這個(gè)ref回調(diào)函數(shù),并將子組件實(shí)例作為參數(shù)傳給回調(diào)函數(shù),然后我們把子組件實(shí)例賦值給this.foo
。
最后我們?cè)诟附M件當(dāng)中就可以使用this.foo
來(lái)調(diào)用子組件的方法咯
了解了這個(gè)方法的原理后,我們要考慮的問(wèn)題就是為啥我們要用這種方法,它的使用場(chǎng)景是什么?最常見的一種使用場(chǎng)景:比如子組件是一個(gè)modal彈窗組件,子組件里有顯示/隱藏這個(gè)modal彈窗的各種方法,我們就可以通過(guò)使用這個(gè)方法,直接在父組件上調(diào)用子組件實(shí)例的這些方法來(lái)操控子組件的顯示/隱藏。這種方法比起你傳遞一個(gè)控制modal顯示/隱藏的props給子組件要美觀多了。
class Modal extends React.Component { show = () => {// do something to show the modal} hide = () => {// do something to hide the modal} render() { return <div>I'm a modal</div> } } class Parent extends React.Component { componentDidMount() { if(// some condition) { this.modal.show() } } render() { return ( <Modal ref={el => { this.modal = el }} /> ) } }
講完了父組件給子組件傳遞信息的兩種方式,我們?cè)賮?lái)講子組件給父組件傳遞信息的方法。回調(diào)函數(shù)這個(gè)方法也是react最常見的一種方式,子組件通過(guò)調(diào)用父組件傳來(lái)的回調(diào)函數(shù),從而將數(shù)據(jù)傳給父組件。
const Child = ({ onClick }) => { <div onClick={() => onClick('zach')}>Click Me</div> } class Parent extends React.Component { handleClick = (data) => { console.log("Parent received value from child: " + data) } render() { return ( <Child onClick={this.handleClick} /> ) } }
這種方法其實(shí)跟react本身沒(méi)有關(guān)系,我們利用的是原生dom元素的事件冒泡機(jī)制。
class Parent extends React.Component { render() { return ( <div onClick={this.handleClick}> <Child /> </div> ); } handleClick = () => { console.log('clicked') } } function Child { return ( <button>Click</button> ); }
巧妙的利用下事件冒泡機(jī)制,我們就可以很方便的在父組件的元素上接收到來(lái)自子組件元素的點(diǎn)擊事件
講完了父子組件間的通信,再來(lái)看非父子組件之間的通信方法。一般來(lái)說(shuō),兩個(gè)非父子組件想要通信,首先我們可以看看它們是否是兄弟組件,即它們是否在同一個(gè)父組件下。如果不是的話,考慮下用一個(gè)組件把它們包裹起來(lái)從而變成兄弟組件是否合適。這樣一來(lái),它們就可以通過(guò)父組件作為中間層來(lái)實(shí)現(xiàn)數(shù)據(jù)互通了。
class Parent extends React.Component { constructor(props) { super(props) this.state = {count: 0} } setCount = () => { this.setState({count: this.state.count + 1}) } render() { return ( <div> <SiblingA count={this.state.count} /> <SiblingB onClick={this.setCount} /> </div> ); } }
通常一個(gè)前端應(yīng)用會(huì)有一些"全局"性質(zhì)的數(shù)據(jù),比如當(dāng)前登陸的用戶信息、ui主題、用戶選擇的語(yǔ)言等等。這些全局?jǐn)?shù)據(jù),很多組件可能都會(huì)用到,當(dāng)組件層級(jí)很深時(shí),用我們之前的方法,就得通過(guò)props一層一層傳遞下去,這顯然太麻煩了,看下面的示例:
class App extends React.Component { render() { return <Toolbar theme="dark" />; } } function Toolbar(props) { return ( <div> <ThemedButton theme={props.theme} /> </div> ); } class ThemedButton extends React.Component { render() { return <Button theme={this.props.theme} />; } }
上面的例子,為了讓我們的Button元素拿到主題色,我們必須把theme作為props,從App傳到Toolbar,再?gòu)腡oolbar傳到ThemedButton,最后Button從父組件ThemedButton的props里終于拿到了主題theme。假如我們不同組件里都有用到Button,就得把theme向這個(gè)例子一樣到處層層傳遞,麻煩至極。
因此react為我們提供了一個(gè)新api:Context,我們用Context改寫下上例
const ThemeContext = React.createContext('light'); class App extends React.Component { render() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } function Toolbar() { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { static contextType = ThemeContext; render() { return <Button theme={this.context} />; } }
簡(jiǎn)單的解析一下:
React.createContext
創(chuàng)建了一個(gè)Context對(duì)象,假如某個(gè)組件訂閱了這個(gè)對(duì)象,當(dāng)react去渲染這個(gè)組件時(shí),會(huì)從離這個(gè)組件最近的一個(gè)Provider
組件中讀取當(dāng)前的context值
Context.Provider
: 每一個(gè)Context對(duì)象都有一個(gè)Provider
屬性,這個(gè)屬性是一個(gè)react組件。在Provider組件以內(nèi)的所有組件都可以通過(guò)它訂閱context值的變動(dòng)。具體來(lái)說(shuō),Provider組件有一個(gè)叫value
的prop傳遞給所有內(nèi)部組件,每當(dāng)value
的值發(fā)生變化時(shí),Provider內(nèi)部的組件都會(huì)根據(jù)新value值重新渲染
那內(nèi)部的組件該怎么使用這個(gè)context對(duì)象里的東西呢?
a. 假如內(nèi)部組件是用class聲明的有狀態(tài)組件:我們可以把Context對(duì)象賦值給這個(gè)類的屬性contextType
,如上面所示的ThemedButton組件
class ThemedButton extends React.Component { static contextType = ThemeContext; render() { const value = this.context return <Button theme={value} />; } }
b. 假如內(nèi)部組件是用function創(chuàng)建的無(wú)狀態(tài)組件:我們可以使用Context.Consumer
,這也是Context對(duì)象直接提供給我們的組件,這個(gè)組件接受一個(gè)函數(shù)作為自己的child,這個(gè)函數(shù)的入?yún)⒕褪莄ontext的value,并返回一個(gè)react組件??梢詫⑸厦娴腡hemedButton改寫下:
function ThemedButton { return ( <ThemeContext.Consumer> {value => <Button theme={value} />} </ThemeContext.Consumer> ) }
最后提一句,context對(duì)于解決react組件層級(jí)很深的props傳遞很有效,但也不應(yīng)該被濫用。只有像theme、language等這種全局屬性(很多組件都有可能依賴它們)時(shí),才考慮用context。如果只是單純?yōu)榱私鉀Q層級(jí)很深的props傳遞,可以直接用component composition
Portals也是react提供的新特性,雖然它并不是用來(lái)解決組件通信問(wèn)題的,但因?yàn)樗采婕暗搅私M件通信的問(wèn)題,所以我也把它列在我們的十種方法里面。
Portals的主要應(yīng)用場(chǎng)景是:當(dāng)兩個(gè)組件在react項(xiàng)目中是父子組件的關(guān)系,但在HTML DOM里并不想是父子元素的關(guān)系。
舉個(gè)例子,有一個(gè)父組件Parent,它里面包含了一個(gè)子組件Tooltip,雖然在react層級(jí)上它們是父子關(guān)系,但我們希望子組件Tooltip渲染的元素在DOM中直接掛載在body節(jié)點(diǎn)里,而不是掛載在父組件的元素里。這樣就可以避免父組件的一些樣式(如overflow:hidden
、z-index
、position
等)導(dǎo)致子組件無(wú)法渲染成我們想要的樣式。
如下圖所示,父組件是這個(gè)紅色框的范圍,并且設(shè)置了overflow:hidden
,這時(shí)候我們的Tooltip元素超出了紅色框的范圍就被截?cái)嗔恕?/p>
怎么用portals解決呢?
首先,修改html文件,給portals增加一個(gè)節(jié)點(diǎn)
<html> <body> <div id="react-root"></div> <div id="portal-root"></div> </body> </html>
然后我們創(chuàng)建一個(gè)可復(fù)用的portal容器,這里使用了react hooks的語(yǔ)法,看不懂的先過(guò)去看下我另外一篇講解react hooks的文章:30分鐘精通React今年最勁爆的新特性——React Hooks
import { useEffect } from "react"; import { createPortal } from "react-dom"; const Portal = ({children}) => { const mount = document.getElementById("portal-root"); const el = document.createElement("div"); useEffect(() => { mount.appendChild(el); return () => mount.removeChild(el); }, [el, mount]); return createPortal(children, el) }; export default Portal;
最后在父組件中使用我們的portal容器組件,并將Tooltip作為children傳給portal容器組件
const Parent = () => { const [coords, setCoords] = useState({}); return <div style={{overflow: "hidden"}}> <Button> Hover me </Button> <Portal> <Tooltip coords={coords}> Awesome content that is never cut off by its parent container! </Tooltip> </Portal> </div> }
這樣就ok啦,雖然父組件仍然是overflow: hidden
,但我們的Tooltip再也不會(huì)被截?cái)嗔?,因?yàn)樗苯映摿?,它渲染到body節(jié)點(diǎn)下的<div id="portal-root"></div>
里去了。
總結(jié)下適用的場(chǎng)景: Tooltip、Modal、Popup、Dropdown等等
哈哈,這也不失為一個(gè)可行的辦法啊。當(dāng)然你最好別用這種方法。
class ComponentA extends React.Component { handleClick = () => window.a = 'test' ... } class ComponentB extends React.Component { render() { return <div>{window.a}</div> } }
觀察者模式是軟件設(shè)計(jì)模式里很常見的一種,它提供了一個(gè)訂閱模型,假如一個(gè)對(duì)象訂閱了某個(gè)事件,當(dāng)那個(gè)事件發(fā)生的時(shí)候,這個(gè)對(duì)象將收到通知。
這種模式對(duì)于我們前端開發(fā)者來(lái)說(shuō)是最不陌生的了,因?yàn)槲覀兘?jīng)常會(huì)給某些元素添加綁定事件,會(huì)寫很多的event handlers,比如給某個(gè)元素添加一個(gè)點(diǎn)擊的響應(yīng)事件elm.addEventListener('click', handleClickEvent)
,每當(dāng)elm元素被點(diǎn)擊時(shí),這個(gè)點(diǎn)擊事件會(huì)通知elm元素,然后我們的回調(diào)函數(shù)handleClickEvent會(huì)被執(zhí)行。這個(gè)過(guò)程其實(shí)就是一個(gè)觀察者模式的實(shí)現(xiàn)過(guò)程。
那這種模式跟我們討論的react組件通信有什么關(guān)系呢?當(dāng)我們有兩個(gè)完全不相關(guān)的組件想要通信時(shí),就可以利用這種模式,其中一個(gè)組件負(fù)責(zé)訂閱某個(gè)消息,而另一個(gè)元素則負(fù)責(zé)發(fā)送這個(gè)消息。javascript提供了現(xiàn)成的api來(lái)發(fā)送自定義事件: CustomEvent
,我們可以直接利用起來(lái)。
首先,在ComponentA中,我們負(fù)責(zé)接受這個(gè)自定義事件:
class ComponentA extends React.Component { componentDidMount() { document.addEventListener('myEvent', this.handleEvent) } componentWillUnmount() { document.removeEventListener('myEvent', this.handleEvent) } handleEvent = (e) => { console.log(e.detail.log) //i'm zach } }
然后,ComponentB中,負(fù)責(zé)在合適的時(shí)候發(fā)送該自定義事件:
class ComponentB extends React.Component { sendEvent = () => { document.dispatchEvent(new CustomEvent('myEvent', { detail: { log: "i'm zach" } })) } render() { return <button onClick={this.sendEvent}>Send</button> } }
這樣我們就用觀察者模式實(shí)現(xiàn)了兩個(gè)不相關(guān)組件之間的通信。當(dāng)然現(xiàn)在的實(shí)現(xiàn)有個(gè)小問(wèn)題,我們的事件都綁定在了document上,這樣實(shí)現(xiàn)起來(lái)方便,但很容易導(dǎo)致一些沖突的出現(xiàn),所以我們可以小小的改良下,獨(dú)立一個(gè)小模塊EventBus專門這件事:
class EventBus { constructor() { this.bus = document.createElement('fakeelement'); } addEventListener(event, callback) { this.bus.addEventListener(event, callback); } removeEventListener(event, callback) { this.bus.removeEventListener(event, callback); } dispatchEvent(event, detail = {}){ this.bus.dispatchEvent(new CustomEvent(event, { detail })); } } export default new EventBus
然后我們就可以愉快的使用它了,這樣就避免了把所有事件都綁定在document上的問(wèn)題:
import EventBus from './EventBus' class ComponentA extends React.Component { componentDidMount() { EventBus.addEventListener('myEvent', this.handleEvent) } componentWillUnmount() { EventBus.removeEventListener('myEvent', this.handleEvent) } handleEvent = (e) => { console.log(e.detail.log) //i'm zach } } class ComponentB extends React.Component { sendEvent = () => { EventBus.dispatchEvent('myEvent', {log: "i'm zach"})) } render() { return <button onClick={this.sendEvent}>Send</button> } }
最后我們也可以不依賴瀏覽器提供的api,手動(dòng)實(shí)現(xiàn)一個(gè)觀察者模式,或者叫pub/sub,或者就叫EventBus。
function EventBus() { const subscriptions = {}; this.subscribe = (eventType, callback) => { const id = Symbol('id'); if (!subscriptions[eventType]) subscriptions[eventType] = {}; subscriptions[eventType][id] = callback; return { unsubscribe: function unsubscribe() { delete subscriptions[eventType][id]; if (Object.getOwnPropertySymbols(subscriptions[eventType]).length === 0) { delete subscriptions[eventType]; } }, }; }; this.publish = (eventType, arg) => { if (!subscriptions[eventType]) return; Object.getOwnPropertySymbols(subscriptions[eventType]) .forEach(key => subscriptions[eventType][key](arg)); }; } export default EventBus;
最后終于來(lái)到了大家喜聞樂(lè)見的Redux等狀態(tài)管理庫(kù),當(dāng)大家的項(xiàng)目比較大,前面講的9種方法已經(jīng)不能很好滿足項(xiàng)目需求時(shí),才考慮下使用redux這種狀態(tài)管理庫(kù)。這里就先不展開講解redux了...否則我花這么大力氣講解前面9種方法的意義是什么???
以上是“React組件間通信方式有哪些”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
名稱欄目:React組件間通信方式有哪些
轉(zhuǎn)載來(lái)源:http://muchs.cn/article10/gddcgo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)建站、軟件開發(fā)、響應(yīng)式網(wǎng)站、網(wǎng)站導(dǎo)航、動(dòng)態(tài)網(wǎng)站、品牌網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)