摘要:產品需求產品需求,實現一個選擇器組件,要求浮在頁面上方。本文討論的主要是,在有類似于組件一樣,浮在頁面的組件時,如何設計組件樹方案一組件是組件的子組件。優勢的顯示狀態屬于節點控制,狀態管理成本低。包括,事件冒泡。
產品需求
產品需求,實現一個選擇器 Selector 組件,要求浮在頁面上方。在網上隨便找了個圖,如下:
實現方案實現這一的一個 Selector 組件并不難,不是本文的討論內容。
本文討論的主要是,在有類似于 Selector 組件一樣,“浮”在頁面的組件時,如何設計 React 組件樹?
方案一:Seletor 組件是 App 組件的子組件。
優勢:Selector 屬于 App 的子節點,子節點不受父節點的樣式屬性( position overflow )的干擾。
劣勢:Selector 的顯示狀態屬于 App 節點,跨分支傳遞狀態成本太高。使用 Redux 或 Mobx 跨分支傳遞狀態,依賴第三方組件,不利于復用;而手動傳遞,至少要 4 個步驟,如果 Button 節點更深,步驟會更多。并且這樣寫出的代碼,耦合性太強,不利于維護。
方案二:Selector(fixed) 組件是 Button 組件的子組件。
優勢:Selector 的顯示狀態屬于 Button 節點控制,狀態管理成本低。
劣勢:Selector 屬于 Button 的子節點。而當父節點 Button 有文字超出隱藏的需求時(overflow: hidden),子節點 Selector 會被隱藏。
那么,有沒有兩全齊美的方案呢?有。
方案三:在 React 組件樹設計上,Selector 是 Button 的子組件。但是在 DOM 樹的角度 Selector 是 Body 的子節點。
在這個方案中,Button 和 Selector 還是屬于 React 組件樹中的父子節點,享有父子組件狀態傳遞方便的優勢。
但是,Button 和 Selector 不再屬于 DOM 樹中的父子節點!Selector 被渲染到了 Body 節點下面,屬于 Body 的子節點。這樣 Selector 組件再也不會受到 Button 組件的樣式干擾了。
在 React 中如何做到這一點呢?使用 React 16 的 Portals。
這個新屬性的介紹文章很短,我就翻譯下一吧。翻譯只是意譯,只為更好理解。
Portals 提供了一種超級棒的方法,可以將 react 子節點的 DOM 結構,渲染到 react 父節點之外的 DOM 中。
ReactDOM.createPortal(child, container)
第一個參數 child 是任何可以被渲染的 ReactChild,比如 element, string 或者 fragment. 第二個參數 container 是 一個 DOM 元素。
使用方法一般來說,在 react 中是父子節點的關系,那么在 DOM 中也是父子節點的關系。
render() { // 在 react 中 div 和 children 是父子的關系,在 DOM 中 div 和 children 也是父子的關系。 return ({this.props.children}); }
然而,有時候打破了這種 react 父子節點和 DOM 父子節點的映射關系是非常有用的。使用 createPortal 可以將 react 的子節點插入到不同的 DOM 節點中。
render() { // React 并沒有創建一個新的 div,來包裹 children。它將 children 渲染到了 domNode 中。 // domNode 可以是任意一個合法的 DOM 節點,無論它在 DOM 節點中的哪個位置。 return ReactDOM.createPortal( this.props.children, domNode, ); }
portal 一個典型的用法是,當父組件有 overflow: hidden 或者 z-index 樣式時,但是子組件需要“打破”父組件容器,顯示在父組件之外。比如 dialogs,hovercards,tooltips 組件。
[在 CodePen 上嘗試一下(https://codepen.io/gaearon/pe...
Portals 的事件冒泡雖然 portal 可以在 DOM 樹中的任意位置,但是它的行為依舊和普通的 React child 一樣。比如上下文環境完全一樣,無論 child 是不是 portal; portal 也一直存在于在 React 樹上,無論它位于 DOM 樹中的什么位置。
包括,事件冒泡。portal 節點的事件會冒泡到它的 React 樹的祖先節點上,即使這些 React 樹上的祖先節點并不是 DOM 樹上的祖先節點。比如,有下面的 HTML 結構。
在 DOM 樹中是 portal 和它的 React 父組件兄弟節點,但是由于 React 的事件處理規則,讓 portal 的 React 父組件有能力捕獲 portal 的冒泡事件。
// These two containers are siblings in the DOM const appRoot = document.getElementById("app-root"); const modalRoot = document.getElementById("modal-root"); class Modal extends React.Component { constructor(props) { super(props); this.el = document.createElement("div"); } componentDidMount() { // The portal element is inserted in the DOM tree after // the Modal"s children are mounted, meaning that children // will be mounted on a detached DOM node. If a child // component requires to be attached to the DOM tree // immediately when mounted, for example to measure a // DOM node, or uses "autoFocus" in a descendant, add // state to Modal and only render the children when Modal // is inserted in the DOM tree. modalRoot.appendChild(this.el); } componentWillUnmount() { modalRoot.removeChild(this.el); } render() { return ReactDOM.createPortal( this.props.children, this.el, ); } } class Parent extends React.Component { constructor(props) { super(props); this.state = {clicks: 0}; this.handleClick = this.handleClick.bind(this); } handleClick() { // This will fire when the button in Child is clicked, // updating Parent"s state, even though button // is not direct descendant in the DOM. this.setState(prevState => ({ clicks: prevState.clicks + 1 })); } render() { return (); } } function Child() { // The click event on this button will bubble up to parent, // because there is no "onClick" attribute defined return (Number of clicks: {this.state.clicks}
Open up the browser DevTools to observe that the button is not a child of the div with the onClick handler.
); } ReactDOM.render(, appRoot);
[在 CodePen 上嘗試一下(https://codepen.io/gaearon/pe...
父組件能夠捕獲 portal 的冒泡事件的設計,允許開發者更加靈活的進行抽象,而這些抽象不依賴于 portal 。例如,如果你渲染一個
// 數據和選中的元素的狀態由 Selector 自己控制 // 不要將 data、index 狀態暴露給其他組件 // 暴露給父組件,越多和父組件耦合的就越重 class Selector extends Component { componentDidMount(){ fetch("xxx") .then(data => { this.setState({ data, }) }) } handleSelect = index => { this.setState({ index }) } render() { return (討論:屬性暴露的越多越好,還是越少越好?) } } // 控制 Modal 顯示狀態都封裝在 Button 中 class Button extends Component { handleClick = () => { this.setState( prevState => ({ show: !prevState.show })) } render() { return (
我是按鈕 // 為了保存 Selector 的狀態,不要 unmount Modal,用 display: none 實現隱藏。) } } class App extends Component { render() { return ( ) } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/90241.html
摘要:前端中的計算機領域的通常認為起源于。并對其主要內容作了自己的解讀。搬到另一個地區會導致名氣降低。年度報告,年最受歡迎的編程語言年上最流行的種編程語言及前十最火熱的項目排行榜,分別由及登頂。技術周刊由小組出品,匯聚一周好文章,周刊原文。 showImg(https://segmentfault.com/img/bVWHC4?w=1000&h=710); 本期推薦 反擊爬蟲,前端工程師的腦...
摘要:引言組件中有很多彈出式組件,常見的如,以及等。這樣一種層次結構在實踐中大大降低了各類彈層組件的實現和維護成本。但是的組件實現了一個大多數組件庫都沒有實現的功能彈層的嵌套處理。 引言 UI 組件中有很多彈出式組件,常見的如 Dialog,Tooltip 以及 Select 等。這些組件都有一個特點,它們的彈出層通常不是渲染在當前的 DOM 樹中,而是直接插入在 body (或者其它類似的...
寫在前面 當大多數人Vue理解的爐火純青的時候,你應該思考怎么讓vue頁面騷氣起來,下面就我個人在接觸Vue兩年的時間里,在實際工作中門戶網站在前端頁面交互應用和技巧,炒幾道小菜給大家分享一哈,我把它封裝成一個項目vue-portal-webUI(github源碼),不敢說是UI,但也是各種常見常遇到的情景吧,看懂代碼需要一些vue、axios、es6、scss基礎、數據基本上是mock,功能和場...
摘要:指定讀取當前的。它為其后代元素觸發額外的檢查和警告。嚴格模式檢查僅在開發模式下運行它們不會影響生產構建。作用識別不安全的生命周期關于使用過時字符串的警告關于使用廢棄的方法的警告檢測意外的副作用檢測過時的為高階組件。 react 進階 懶加載 React.lazy函數能讓你像渲染常規組件一樣處理動態引入(的組件)。Suspense加載指示器為組件做優雅降級。fallback屬性接受任何在...
閱讀 1915·2021-11-09 09:46
閱讀 2492·2019-08-30 15:52
閱讀 2455·2019-08-30 15:47
閱讀 1325·2019-08-29 17:11
閱讀 1750·2019-08-29 15:24
閱讀 3508·2019-08-29 14:02
閱讀 2449·2019-08-29 13:27
閱讀 1209·2019-08-29 12:32