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