摘要:如果某個組件訂閱該事件太晚,那發布者之前所發布的該類事件,它都接收不到,而方案一和二的優點則在于,無論如何,組件都能拿到該的最終狀態值有存在內存泄漏的風險。
原文地址 - 歡迎關注我的博客
在我們react項目日常開發中,往往會遇到這樣一個問題:如何去實現跨組件通信?
為了更好的理解此問題,接下來我們通過一個簡單的栗子說明。
實現一個視頻播放器假設有一個這樣的需求,需要我們去實現一個簡易的視頻播放器,基于對播放器的理解,我們可以把這個視頻播放器大致分為如下幾個部分:
視頻窗口組件Screen
底部播放控件BottomCtrl
對于視頻窗口組件,它包含一個播放/暫停按鈕CenterPlayBtn;而底部播放控件又是由以下幾種組件組合而成:
播放/暫停按鈕BottomPlayBtn
進度控制條ProgressCtrl
音量按鈕Volume
于是乎它的構成應該如下圖所示:
同樣的,我們的組件組織方式應該也長這樣:(這里簡化了代碼實現)
class MyVideo { render() { return () } } // 底部視頻控件 class BottomCtrl { render() { return ( ) } } // 視頻窗口組件 class Screen { render() { return ( ) } }
對于視頻播放器而言,有一個很常見的交互,即當我們點擊屏幕中心的播放按鈕CenterPlayBtn時,不僅需要改變自身的狀態(隱藏起來),而且還要更新底部播放按鈕BottomPlayBtn的樣式
由于中心播放按鈕與底部控件按鈕分別屬于Screen、BottomCtrl組件的部分,因此這就是一個很常見的跨組件通信問題:如何將CenterPlayBtn的狀態同步到BottomPlayBtn?
方案一:祖先組件的狀態管理一個非常常用的方式,就是讓祖先組件通過狀態管理的方式把信息同步到其他子組件中:
class MyVideo { constructor(props) { super(props); this.state = { isPlay: false, } } updatePlayState = isPlay => { this.setState({ isPlay }); } render() { const { isPlay } = this.state; return () } }
我們通過在祖先組件的state定義相應的狀態,并把修改state的方法傳遞給了子組件,那么當一個子組件通過調用updatePlayState后,它所設置的新狀態亦可通過react本身的state更新機制傳遞給其他的子組件,實現跨組件通信。
這種方案雖然簡單,但在一些復雜的場景下卻顯得不夠友好:
狀態和方法需要通過層層props傳遞到相應的子組件,一旦組件嵌套過深,不好編寫與維護,且對于中間傳遞的組件而言,增加了不必要的邏輯;
管理狀態的祖先組件將變得更加臃腫。試想一下,假設我們為了實現兩個嵌套很深的子組件的通信,卻需要在祖先組件上去額外添加狀態和方法,這增加了祖先組件的維護成本。
方案二:redux提供的跨組件通信能力熟悉redux的童鞋都知道,redux提供的訂閱發布機制,可以讓我們實現任何兩個組件的通信:首先我們需要在state上去添加一個key,在兩個需要通信的組件上通過connect的封裝,即可訂閱key值的改變。
// CenterPlayBtn class CenterPlayBtn { play() { this.props.updatePlayStatus(); } } const mapDispatchToProps = dispatch => { return { updatePlayStatus: isPlay => { dispatch(updatePlayStatus(isPlay)) } } } export default connect(null, mapDispatchToProps)(BottomPlayBtn)
class BottomPlayBtn { componentWillReceiveProps(nextProps) { if (this.props.isPlay !== nextProps.isPlay) { // do something } } } const mapStateToProps = state => ({ isPlay: state.isPlay }) export default connect(mapStateToProps, null)(BottomPlayBtn)
使用redux的方式去實現跨組件通信是一種很常見的方式,在項目開發中也經常用到。那問題又來了,由于使用這種方案的前提是必須得在項目中加入redux,如果我的項目本來就比較簡單,不需要使用到redux,難道為了實現兩個組件簡單的通信而要去做一系列redux的配置工作嗎?這顯然把簡單的問題又復雜化了。
方案三:EventEmitterEventEmitter也可以實現跨組件通信,當然這種基于事件訂閱的設計模式本身也與react關系不大,但我們的項目很小的時候,使用EventEmitter也不失為一種簡單且高效的方式:
class CenterPlayBtn { constructor(props) { super(props); event.on("pause", () => { // do something }) } play() { event.emit("play"); } } class BottomPlayBtn { constructor(props) { super(props); event.on("play", () => { // do something }) } pause() { event.emit("pause"); } }
當然這種方案也是有缺陷的:
組織方式過于離散。發送者emit與接收者on分散在各個組件里,如果不細看每個組件的代碼,我們難以從整體去觀察、跟蹤、管理這些事件;
有可能出現錯過某個事件的情況。如果某個組件訂閱該事件太晚,那發布者之前所發布的該類事件,它都接收不到,而方案一和二的優點則在于,無論如何,組件都能拿到該key的最終狀態值;
有存在內存泄漏的風險。如果組件銷毀了而不及時取消訂閱,那就有內存泄漏的風險;
方案四:利用react原生的context實現跨組件通信原生react提供了context,它的原文描述是這樣的:
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
簡單來說就是react提供了一種方式,讓你可以跨多層嵌套組件去訪問數據,而不需要手動的將props一個一個地傳遞下去。通過這種方式我們也可以實現跨組件通信方式,這個方案和方案一很相似,但區別在于我們無需手動將props傳遞給經歷的每一個中間層組件。更為具體的用法可以直接參考官網示例,下面只是拋磚引玉,給出個簡單示例:
首先我們定義一個player-context.js文件
import { createContext } from "react"; const PlayerContext = createContext(); export default PlayerContext;
然后在MyVideo組件中使用PlayerContext.Provider:
import PlayerContext from "./player-context"; class MyVideo { constructor(props) { super(props); this.state = { isPlay: false, updatePlayState: this.updatePlayState, } } updatePlayState = isPlay => { this.setState({ isPlay }); } render() { return () } }
接著在需要消費數據的地方CenterPlayBtn和BottomPlayBtn中使用到它,這里只給出CenterPlayBtn的示例:
import PlayerContext from "./player-context"; class CenterPlayBtn { constructor(props) { super(props); } play() { this.props.updatePlayStatus(!this.props.isPlay); } componentWillReceiveProps(nextProps) { if (this.props.isPlay !== nextProps.isPlay) { // do something... } } } export default props => ({ ({isPlay, updatePlayStatus}) => )}
其實個人認為這種方案是方案一的“增強版”:
首先它像方案一一樣,對數據作了集中控制管理,即把提供數據內容和修改數據的能力集中到了上層組件身上,使得上層組件成為唯一的Provider,供下層各處的消費者Consumer使用;
其次它無須像方案一一樣繁瑣地將props手動向下傳遞;
總得來說,如果你的項目沒有使用到redux的話,使用context是個不錯的選擇。
總結上面列舉的方案各有優劣,我們很難去判定哪種方案是最好的,而真正重要的,是要學會分析哪個場景下使用哪種方案更佳。
btw,其實跨組件通信的方式多種多樣,遠不止這些,本人才疏學淺,這里只能列舉出一些自己常用的解決方案,希望此文能拋磚引玉,引出更棒的方案和見解:)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/53903.html
摘要:如果某個組件訂閱該事件太晚,那發布者之前所發布的該類事件,它都接收不到,而方案一和二的優點則在于,無論如何,組件都能拿到該的最終狀態值有存在內存泄漏的風險。 原文地址 - 歡迎關注我的博客 在我們react項目日常開發中,往往會遇到這樣一個問題:如何去實現跨組件通信? 為了更好的理解此問題,接下來我們通過一個簡單的栗子說明。 實現一個視頻播放器 假設有一個這樣的需求,需要我們去實現一個...
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
摘要:并總結經典面試題集各種算法和插件前端視頻源碼資源于一身的文檔,優化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快速搭建項目。 本文是關注微信小程序的開發和面試問題,由基礎到困難循序漸進,適合面試和開發小程序。并總結vue React html css js 經典面試題 集各種算法和插件、前端視頻源碼資源于一身的文檔,優化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快...
閱讀 3398·2023-04-25 22:04
閱讀 2196·2021-11-22 15:29
閱讀 2160·2021-10-11 10:57
閱讀 1401·2021-09-24 09:48
閱讀 3146·2021-09-09 09:34
閱讀 2543·2021-09-02 15:21
閱讀 2392·2019-08-30 15:53
閱讀 1119·2019-08-30 14:07