- vừa được xem lúc

React Portal - Cánh cổng không gian và ứng dụng

0 0 278

Người đăng: Pham Tuan

Theo Viblo Asia

Giới thiệu

React Portal là gì?

Đầu tiên là cái tên Portal. Có thể tạm dịch nó là cánh cổng (cổng vào đường hầm, cổng lên thiên đường, cổng sang thế giới khác chẳng hạn...). Nó cũng là tên của một tựa game khá nổi tiếng.

https://www.geekgirlauthority.com/jj-abrams-portal-half-life-movies-still-development/

Vậy thì nó liên quan gì đến React Portal? Chính xác là React cũng sử dụng cái tên này để mô tả cho API mà nó cung cấp. Theo React docs thì API cho phép bạn render component con vào một DOM node tồn tại ở bên ngoài parent component.

ReactDOM.createPortal(child, container)

Tham số thứ nhất (child) là component React hoặc bất kỳ value nào có thể render được.

Tham số thứ hai (container) là một DOM element (thường được lấy từ document.getElementById());

Hay có thể hiểu, React Portal cung cấp cho bạn một trang bị đặc biệt, cho phép bạn có thể mở ra một cánh cổng và ném đồ đạc ra bên ngoài ?

Ứng dụng

Ví dụ bạn có file template:

<!DOCTYPE html>
<html lang="en">
<head> <meta charset="utf-8"> <title>React App</title>
</head>
<body> <div id="root"></div>
</body>
</html>

Ví dụ đơn giản

Bạn có component sử dụng Portal:

function Box({ name }) { return ( <div style={{ border: "1px solid #AAA", padding: "5px" }}> <h1>I am {name}</h1> {createPortal( <h2>I was defined in {name}, but I am outside it.</h2>, document.getElementById("root") )} </div> );
}

Mặc dù thẻ <h2> được khai báo bên trong thẻ div, nhưng nó lại được render ở bên ngoài:

Cấu trúc DOM được generate sẽ như thế này:

Mặc dù Portal component được render bên ngoài component nhưng nó vẫn được tính là component con, nên nếu component cha re-render thì nó cũng được render lại:

function Box({ name }) { const [clickCount, setClickCount] = useState(0); return ( <div style={{ border: "1px solid #AAA", padding: "5px" }}> <h1> I am {name}, click count state is: {clickCount} </h1> <button onClick={() => setClickCount((prevClickCount) => prevClickCount + 1)} > Click me </button> {createPortal( <h2> I was defined in {name}, but I am outside it. <br /> Still, I can show parent state click count {clickCount} </h2>, document.getElementById("root") )} </div> );
}

Edit React Portal simple demo

Modal

Ví dụ bạn sử dụng Bootstrap Modal và Bootstrap docs có note:

Modals use position: fixed, which can sometimes be a bit particular about its rendering. Whenever possible, place your modal HTML in a top-level position to avoid potential interference from other elements. You’ll likely run into issues when nesting a .modal within another fixed element.

Bạn muốn đóng gói component và muốn đặt Modal ở level trên cùng nhất có thể? React Portal là cách đơn giản nhất để làm được điều này.

Ví dụ bên dưới là một component DeleteButton, mỗi khi click button sẽ có Modal confirmation hiển thị. Mặc dù viết PopupConfirm là component con của DeleteButton nhưng bằng việc sử dụng React Portal nó sẽ không được render như là con của DeleteButton mà sẽ được render ở element có id = root, thường react template sẽ có sẵn element #root:

export default function PopupConfirm({ isOpen, onCancel, onConfirm, children }) { return React.createPortal( isOpen ? ( <Modal> {children} <button onClick={onCancel}>Cancel</button> <button onClick={onConfirm}>Confirm</button> </Modal> ) : null, document.getElementById('root') );
}
export default function DeleteButton({ handleDelete }) { const [isOpen, setIsOpen] = useState(false); return ( <> <PopupConfirm isOpen={isOpen} setIsOpen={setIsOpen} onCancel={() => setIsOpen(false)} onConfirm={() => { handleDelete(); setIsOpen(false); } > Are you sure? </PopupConfirm> <button onClick={() => { setIsOpen(true); }} > Delete </button> </> );
}
export default function NotesList({ notes }) { return ( <div className="table-responsive"> <table className="table table-hover"> <thead> <tr> <th scope="col">#</th> <th scope="col">Title</th> <th scope="col">Action</th> </tr> </thead> <tbody> {notes.map((note) => ( <tr key={note.id}> <td scope="col">{note.id}</td> <td scope="col">{note.title}</td> <td scope="col"> <DeleteButton handleDelete={() => console.log('Delete: ', note)} /> </td> </tr>; ))} </tbody> </table> </div> );
}

Khi inspect trình duyệt bạn sẽ thấy HTML có cấu trúc:

<html>
<head></head>
<body> <div id="root"> <div class="table-responsive"> <table class="table table-hover"> <thead>...</thead> <tbody> <tr> <td scope="col">1</td> <td scope="col">Title</td> <td scope="col"> <button>Delete</button> </td> </tr> </tbody> </table> </div> <div class="modal">...</div> </div>
</body>
</html>

Breadcrumbs

Trường hợp bạn có layout cho content được đặt trong class container, nhưng trong mỗi page lại cần render breadcrumbs full width và breadcrumbs không được generate tự động, tức là tùy từng page mới hiển thị breadcrumbs. Có thể dùng Portal:

export default function Layout({ children }) { return ( <> <Header /> <div className="container-fluid"> <div id="breadcrumbs-portal"></div> </div> <main className="container">{children}</main> <Footer /> </> );
}

Breadcrumbs component:

export default function Breadcrumbs({ items }) { const [breadcrumbNode, setBreadcrumbNode] = useState(null); useEffect(() => { setBreadcrumbNode(document.getElementById('breadcrumbs-portal')); }, []); if (!items || !items.length) { return null; } return ( breadcrumbNode && createPortal( <nav aria-label="breadcrumb"> <ol className="breadcrumb"> <li className="breadcrumb-item"> <Link to="/">Home</Link> </li> {items.map((item, index) => ( <React.Fragment key={index}> {item.url ? ( <li className="breadcrumb-item"> <Link to={item.url}>{item.title}</Link> </li> ) : ( <li className="breadcrumb-item active" aria-current="page" > {item.title} </li> )} </React.Fragment> ))} </ol> </nav>, breadcrumbNode ) );
};

Ở từng trang sẽ sử dụng breadcrumbs và truyền vào các breadcrumbs item tương ứng:

export default function SearchResult() { return ( <> <Breadcrumbs items={[{ title: 'Search result' }]} /> <div>...</div> </> );
}
export default function ItemDetail() { return ( <> <Breadcrumbs items={[{url: '/category', title: 'Category'}, { title: 'Item A' }]} /> <div>...</div> </> );
}

Code mẫu mình để ở github: https://github.com/tuanpht/react-portal-demo

Demo:

https://tuanpht.github.io/react-portal-demo

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tìm hiểu về Redux Thunk

Chào mọi người, nếu bạn là người đã biết về React và đang làm quen với Redux chắc hẳn bạn đang rất mơ hồ về các khái niệm cơ bản của Redux như dispatch, store, action creator,... bạn còn đang vật lộn với đống document của Redux để hiểu những khái niệm đó và bạn nghe ai đó trong team nói về Redux Thu

0 0 371

- vừa được xem lúc

[React] Giới thiệu tổng quát về Redux Toolkit

1. Redux Toolkit (RTK) là gì và tại sao lại có nó. . .

0 0 6.6k

- vừa được xem lúc

Uống Pepsi code Vue đi - Uống Cocacola code React nha ;)

. (Nguồn ảnh: Internet). Chào các bạn, chào các bạn. Let's go . 1.

0 0 130

- vừa được xem lúc

Cài đặt taillwind css cho dự án React

Trong bài viết cùng mình tìm hiểu cách cài đặt tailwind css cho một dự án React sẵn có. .

0 0 127

- vừa được xem lúc

Formik vs React Hook Form (Phần 1)

Các lập trình viên Front End đều làm việc rất nhiều với form cùng sự phức tạp của ứng dụng. Do vậy chúng ta cần những thư viện form mạnh mẽ hỗ trợ quản lý các form state, form validation... Thành phần module. Formik bao gồm có 9 dependencies khác. . React Hook Form thì không có.

0 0 344

- vừa được xem lúc

Hướng dẫn React Redux cho người mới bắt đầu - Phần 1

Lời mởi đầu. Chào các bạn, ở thời điểm thực hiện bài viết này mình cũng là một người đang bắt đầu tìm hiểu và học với ReactJs và Redux, trong quá trình tìm hiểu đọc các tài liệu về thư viện này mình có tìm được một bài hướng dẫn khá hay nên đã quyết định chia sẻ với mọi người .

0 0 262