React Portalã®åãè§£ãæŸã¡ãã¢ã¯ã»ã·ãã«ã§é åçãªã¢ãŒãã«ãšããŒã«ããããäœæãããŠãŒã¶ãŒäœéšãšã³ã³ããŒãã³ãæ§é ãåäžãããŸãããã
React PortalïŒã¢ãŒãã«ãšããŒã«ãããããã¹ã¿ãŒãUXã匷åãã
çŸä»£ã®Webéçºã«ãããŠãçŽæçã§é åçãªãŠãŒã¶ãŒã€ã³ã¿ãŒãã§ãŒã¹ãäœæããããšã¯æãéèŠã§ããUIæ§ç¯ã®ããã®äººæ°JavaScriptã©ã€ãã©ãªã§ããReactã¯ããããéæããããã®æ§ã ãªããŒã«ãæè¡ãæäŸããŠããŸãããã®äžã§ã匷åãªããŒã«ã®äžã€ãReact Portalã§ãããã®ããã°èšäºã§ã¯ãã¢ã¯ã»ã·ãã«ã§èŠèŠçã«é åçãªã¢ãŒãã«ãããŒã«ãããã®æ§ç¯ã«ãããå¿çšãäžå¿ã«ãReact Portalã®äžçãæãäžããŠãããŸãã
React Portalãšã¯ïŒ
React Portalã¯ãã³ã³ããŒãã³ãã®åèŠçŽ ãã芪ã³ã³ããŒãã³ãã®DOMéå±€ã®å€ã«ååšããDOMããŒãã«ã¬ã³ããªã³ã°ããæ¹æ³ãæäŸããŸããç°¡åã«èšãã°ãæšæºã®Reactã³ã³ããŒãã³ãããªãŒããæãåºããŠãHTMLæ§é ã®å¥ã®éšåã«çŽæ¥èŠçŽ ãæ¿å ¥ããããšãã§ããŸããããã¯ãã¹ã¿ããã³ã°ã³ã³ããã¹ããå¶åŸ¡ãããã芪ã³ã³ããã®å¢çå€ã«èŠçŽ ãé 眮ãããããå¿ èŠãããå Žåã«ç¹ã«äŸ¿å©ã§ãã
åŸæ¥ãReactã³ã³ããŒãã³ãã¯DOMå ã§èŠªã³ã³ããŒãã³ãã®åãšããŠã¬ã³ããªã³ã°ãããŸããããã¯ãä»ã®ã³ã³ãã³ãã®äžã«è¡šç€ºããããããã¥ãŒããŒãã«å¯ŸããŠé 眮ãããããå¿ èŠãããã¢ãŒãã«ãããŒã«ãããã®ãããªèŠçŽ ãæ±ãéã«ãã¹ã¿ã€ãªã³ã°ãã¬ã€ã¢ãŠãäžã®èª²é¡ãåŒãèµ·ããããšããããŸããReact Portalã¯ããããã®èŠçŽ ãDOMããªãŒã®å¥ã®éšåã«çŽæ¥ã¬ã³ããªã³ã°ããããšã§ããããã®å¶çŽãåé¿ãã解決çãæäŸããŸãã
ãªãReact Portalã䜿çšããã®ãïŒ
ããã€ãã®éèŠãªå©ç¹ã«ãããReact Portalã¯ããªãã®ReactéçºããŒã«ã®äžã§äŸ¡å€ãããã®ã«ãªããŸãïŒ
- ã¹ã¿ã€ãªã³ã°ãšã¬ã€ã¢ãŠãã®æ¹åïŒ Portalã䜿çšãããšã芪ã³ã³ããŒãã³ãã®ã³ã³ããã®å€ã«èŠçŽ ãé
眮ã§ããããã
overflow: hiddenãz-indexã®å¶éããŸãã¯è€éãªã¬ã€ã¢ãŠãå¶çŽã«ãã£ãŠåŒãèµ·ããããã¹ã¿ã€ãªã³ã°ã®åé¡ãå æã§ããŸãã芪ã³ã³ããã«overflow: hiddenãèšå®ãããŠããŠããç»é¢å šäœãèŠãå¿ èŠãããã¢ãŒãã«ãæ³åããŠã¿ãŠãã ãããPortalã䜿ãã°ãã¢ãŒãã«ãçŽæ¥bodyã«ã¬ã³ããªã³ã°ããŠããã®å¶éãåé¿ã§ããŸãã - ã¢ã¯ã»ã·ããªãã£ã®åäžïŒ Portalã¯ãç¹ã«ã¢ãŒãã«ãæ±ãéã«ã¢ã¯ã»ã·ããªãã£ã«ãšã£ãŠéåžžã«éèŠã§ããã¢ãŒãã«ã®ã³ã³ãã³ããçŽæ¥
bodyã«ã¬ã³ããªã³ã°ããããšã§ããã©ãŒã«ã¹ãã©ããã®ç®¡çã容æã«ãªããã¹ã¯ãªãŒã³ãªãŒããŒãããŒããŒãããã²ãŒã·ã§ã³ã䜿çšãããŠãŒã¶ãŒãã¢ãŒãã«ãéããŠããéããã®äžã«çãŸãããšãä¿èšŒããŸããããã¯ãã·ãŒã ã¬ã¹ã§ã¢ã¯ã»ã·ãã«ãªãŠãŒã¶ãŒäœéšãæäŸããããã«äžå¯æ¬ ã§ãã - ããã¯ãªãŒã³ãªã³ã³ããŒãã³ãæ§é ïŒ ã¢ãŒãã«ãããŒã«ãããã®ã³ã³ãã³ããã¡ã€ã³ã®ã³ã³ããŒãã³ãããªãŒã®å€ã«ã¬ã³ããªã³ã°ããããšã§ãã³ã³ããŒãã³ãæ§é ãããã¯ãªãŒã³ã§ç®¡çããããä¿ã€ããšãã§ããŸãããã®é¢å¿ã®åé¢ã«ãããã³ãŒããèªã¿ããããçè§£ãããããä¿å®ãããããªããŸãã
- ã¹ã¿ããã³ã°ã³ã³ããã¹ãåé¡ã®åé¿ïŒ CSSã®ã¹ã¿ããã³ã°ã³ã³ããã¹ãã¯ç®¡çãéåžžã«é£ããããšã§ç¥ãããŠããŸããPortalã¯ãèŠçŽ ãDOMã®ã«ãŒãã«çŽæ¥ã¬ã³ããªã³ã°ããããšã§ãããã®åé¡ãåé¿ããã®ã«åœ¹ç«ã¡ãããŒãžäžã®ä»ã®èŠçŽ ã«å¯ŸããŠåžžã«æ£ããé 眮ãããããšãä¿èšŒããŸãã
React Portalã䜿çšããã¢ãŒãã«ã®å®è£
ã¢ãŒãã«ã¯ãéèŠãªæ å ±ã衚瀺ãããããŠãŒã¶ãŒã«å ¥åãä¿ãããããããã«äœ¿çšãããäžè¬çãªUIãã¿ãŒã³ã§ããReact Portalã䜿çšããŠã¢ãŒãã«ãäœæããæ¹æ³ãèŠãŠãããŸãããã
1. Portalã«ãŒãã®äœæ
ãŸããã¢ãŒãã«ãã¬ã³ããªã³ã°ãããDOMããŒããäœæããå¿
èŠããããŸããããã¯éåžžãHTMLãã¡ã€ã«ïŒéåžžã¯bodyå
ïŒã«ç¹å®ã®IDãæã€divèŠçŽ ã远å ããããšã§è¡ãããŸãïŒ
<div id="modal-root"></div>
2. ã¢ãŒãã«ã³ã³ããŒãã³ãã®äœæ
次ã«ãã¢ãŒãã«ã衚ãReactã³ã³ããŒãã³ããäœæããŸãããã®ã³ã³ããŒãã³ãã«ã¯ãã¢ãŒãã«ã®ã³ã³ãã³ããšããžãã¯ãå«ãŸããŸãã
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, onClose, children }) => {
const [mounted, setMounted] = useState(false);
const modalRoot = useRef(document.getElementById('modal-root'));
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
if (!isOpen) return null;
const modalContent = (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>
);
return mounted && modalRoot.current
? ReactDOM.createPortal(modalContent, modalRoot.current)
: null;
};
export default Modal;
解説ïŒ
isOpenprop: ã¢ãŒãã«ã衚瀺ããããã©ãããæ±ºå®ããŸããonCloseprop: ã¢ãŒãã«ãéããããã®é¢æ°ã§ããchildrenprop: ã¢ãŒãã«å ã«è¡šç€ºãããã³ã³ãã³ãã§ããmodalRootref: ã¢ãŒãã«ãã¬ã³ããªã³ã°ãããDOMããŒãïŒ#modal-rootïŒãåç §ããŸããuseEffecthook: ããŒã¿ã«ã«ãŒããããã«å©çšã§ããªãåé¡ãé¿ãããããã³ã³ããŒãã³ããããŠã³ããããåŸã«ã®ã¿ã¢ãŒãã«ãã¬ã³ããªã³ã°ãããããã«ããŸããReactDOM.createPortal: ãããReact Portalã䜿çšããããã®éµã§ããããã¯2ã€ã®åŒæ°ãåããŸãïŒã¬ã³ããªã³ã°ããReactèŠçŽ ïŒmodalContentïŒãšããããã¬ã³ããªã³ã°ãããã¹ãDOMããŒãïŒmodalRoot.currentïŒã§ãã- ãªãŒããŒã¬ã€ã®ã¯ãªãã¯ïŒ ã¢ãŒãã«ãéããŸããã¢ãŒãã«å
ã§ã®ã¯ãªãã¯ãã¢ãŒãã«ãéããŠããŸããªãããã«ã
modal-contentã®divã§e.stopPropagation()ã䜿çšããŸãã
3. ã¢ãŒãã«ã³ã³ããŒãã³ãã®äœ¿çš
ããã§ãã¢ããªã±ãŒã·ã§ã³ã§Modalã³ã³ããŒãã³ãã䜿çšã§ããŸãïŒ
import React, { useState } from 'react';
import Modal from './Modal';
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal isOpen={isModalOpen} onClose={closeModal}>
<h2>Modal Content</h2>
<p>This is the content of the modal.</p>
</Modal>
</div>
);
};
export default App;
ãã®äŸã¯ãisOpen propãšopenModalãcloseModal颿°ã䜿çšããŠã¢ãŒãã«ã®è¡šç€º/é衚瀺ãå¶åŸ¡ããæ¹æ³ã瀺ããŠããŸãã<Modal>ã¿ã°å
ã®ã³ã³ãã³ããã¢ãŒãã«å
ã«ã¬ã³ããªã³ã°ãããŸãã
4. ã¢ãŒãã«ã®ã¹ã¿ã€ãªã³ã°
ã¢ãŒãã«ãé 眮ããã¹ã¿ã€ã«ãèšå®ããããã®CSSã远å ããŸãã以äžã«åºæ¬çãªäŸã瀺ããŸãïŒ
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
display: flex;
justify-content: center;
align-items: center;
z-index: 1000; /* Ensure it's on top of other content */
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
CSSã®è§£èª¬ïŒ
position: fixed: ã¹ã¯ããŒã«ã«é¢ä¿ãªããã¢ãŒãã«ããã¥ãŒããŒãå šäœãèŠãããã«ããŸããbackground-color: rgba(0, 0, 0, 0.5): ã¢ãŒãã«ã®èåŸã«åéæã®ãªãŒããŒã¬ã€ãäœæããŸããdisplay: flex, justify-content: center, align-items: center: ã¢ãŒãã«ãæ°Žå¹³ã»åçŽæ¹åã®äžå€®ã«é 眮ããŸããz-index: 1000: ã¢ãŒãã«ãããŒãžäžã®ä»ã®ãã¹ãŠã®èŠçŽ ã®äžã«ã¬ã³ããªã³ã°ãããããã«ããŸãã
5. ã¢ãŒãã«ã®ã¢ã¯ã»ã·ããªãã£ã«é¢ããèæ ®äºé
ã¢ãŒãã«ãå®è£ ããéã«ã¯ãã¢ã¯ã»ã·ããªãã£ãéåžžã«éèŠã§ãã以äžã«ããã€ãã®éèŠãªèæ ®äºé ã瀺ããŸãïŒ
- ãã©ãŒã«ã¹ç®¡çïŒ ã¢ãŒãã«ãéããšããã©ãŒã«ã¹ã¯èªåçã«ã¢ãŒãã«å
ã®èŠçŽ ïŒäŸïŒæåã®å
¥åãã£ãŒã«ããéãããã¿ã³ïŒã«ç§»åããå¿
èŠããããŸããã¢ãŒãã«ãéãããšãã¯ããã©ãŒã«ã¹ã¯ã¢ãŒãã«ãéããã£ãããšãªã£ãèŠçŽ ã«æ»ãã¹ãã§ããããã¯ãReactã®
useRefããã¯ã䜿çšããŠä»¥åã«ãã©ãŒã«ã¹ãããŠããèŠçŽ ãä¿åããããšã§å®çŸãããããšãå€ãã§ãã - ããŒããŒãããã²ãŒã·ã§ã³ïŒ ãŠãŒã¶ãŒãããŒããŒãïŒTabããŒïŒã䜿çšããŠã¢ãŒãã«å
ãããã²ãŒãã§ããããã«ããŸãããã©ãŒã«ã¹ã¯ã¢ãŒãã«å
ã«ãã©ããããããŠãŒã¶ãŒã誀ã£ãŠã¢ãŒãã«ããã¿ãã§ç§»åããŠããŸãã®ãé²ããŸãã
react-focus-lockã®ãããªã©ã€ãã©ãªããããæ¯æŽããŸãã - ARIA屿§ïŒ ARIA屿§ã䜿çšããŠãã¹ã¯ãªãŒã³ãªãŒããŒã«ã¢ãŒãã«ã«é¢ããæå³çãªæ
å ±ãæäŸããŸããäŸãã°ãã¢ãŒãã«ã³ã³ããã«
aria-modal="true"ã䜿çšããaria-labelãaria-labelledbyã䜿çšããŠã¢ãŒãã«ã«èª¬æçãªã©ãã«ãæäŸããŸãã - éããä»çµã¿ïŒ éãããã¿ã³ããªãŒããŒã¬ã€ã®ã¯ãªãã¯ãEscapeããŒã®æŒäžãªã©ãã¢ãŒãã«ãéããããã®è€æ°ã®æ¹æ³ãæäŸããŸãã
ãã©ãŒã«ã¹ç®¡çã®äŸïŒuseRefã䜿çšïŒïŒ
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, onClose, children }) => {
const [mounted, setMounted] = useState(false);
const modalRoot = useRef(document.getElementById('modal-root'));
const firstFocusableElement = useRef(null);
const previouslyFocusedElement = useRef(null);
useEffect(() => {
setMounted(true);
if (isOpen) {
previouslyFocusedElement.current = document.activeElement;
if (firstFocusableElement.current) {
firstFocusableElement.current.focus();
}
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
if (previouslyFocusedElement.current) {
previouslyFocusedElement.current.focus();
}
};
}
return () => setMounted(false);
}, [isOpen, onClose]);
if (!isOpen) return null;
const modalContent = (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<h2>Modal Content</h2>
<p>This is the content of the modal.</p>
<input type="text" ref={firstFocusableElement} /> <!-- First focusable element -->
<button onClick={onClose}>Close</button>
</div>
</div>
);
return mounted && modalRoot.current
? ReactDOM.createPortal(modalContent, modalRoot.current)
: null;
};
export default Modal;
ãã©ãŒã«ã¹ç®¡çã³ãŒãã®è§£èª¬ïŒ
previouslyFocusedElement.current: ã¢ãŒãã«ãéãããåã«ãã©ãŒã«ã¹ããã£ãèŠçŽ ãä¿åããŸããfirstFocusableElement.current: ã¢ãŒãã«*å *ã®æåã®ãã©ãŒã«ã¹å¯èœãªèŠçŽ ïŒãã®äŸã§ã¯ããã¹ãå ¥åïŒãåç §ããŸãã- ã¢ãŒãã«ãéããšïŒ
isOpenãtrueã®å ŽåïŒïŒ- çŸåšãã©ãŒã«ã¹ãããŠããèŠçŽ ãä¿åãããŸãã
- ãã©ãŒã«ã¹ã
firstFocusableElement.currentã«ç§»åããŸãã - EscapeããŒããªãã¹ã³ããã¢ãŒãã«ãéããããã®ã€ãã³ããªã¹ããŒã远å ãããŸãã
- ã¢ãŒãã«ãéãããšãïŒã¯ãªãŒã³ã¢ãã颿°ïŒïŒ
- EscapeããŒã®ã€ãã³ããªã¹ããŒãåé€ãããŸãã
- 以åã«ãã©ãŒã«ã¹ãããŠããèŠçŽ ã«ãã©ãŒã«ã¹ãæ»ãããŸãã
React Portalã䜿çšããããŒã«ãããã®å®è£
ããŒã«ãããã¯ããŠãŒã¶ãŒãèŠçŽ ã«ã«ãŒãœã«ãåããããšãã«è¡šç€ºãããå°ããªæ å ±ãããã¢ããã§ããReact Portalã䜿çšãããšã芪èŠçŽ ã®ã¹ã¿ã€ãªã³ã°ãã¬ã€ã¢ãŠãã«é¢ä¿ãªããæ£ããé 眮ãããããŒã«ããããäœæã§ããŸãã
1. Portalã«ãŒãã®äœæïŒãŸã äœæããŠããªãå ŽåïŒ
ãŸã ã¢ãŒãã«çšã®ããŒã¿ã«ã«ãŒããäœæããŠããªãå Žåã¯ãHTMLãã¡ã€ã«ïŒéåžžã¯bodyå
ïŒã«ç¹å®ã®IDãæã€divèŠçŽ ã远å ããŸãïŒ
<div id="tooltip-root"></div>
2. ããŒã«ãããã³ã³ããŒãã³ãã®äœæ
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Tooltip = ({ text, children, position = 'top' }) => {
const [isVisible, setIsVisible] = useState(false);
const [positionStyle, setPositionStyle] = useState({});
const [mounted, setMounted] = useState(false);
const tooltipRoot = useRef(document.getElementById('tooltip-root'));
const tooltipRef = useRef(null);
const triggerRef = useRef(null);
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
const handleMouseEnter = () => {
setIsVisible(true);
updatePosition();
};
const handleMouseLeave = () => {
setIsVisible(false);
};
const updatePosition = () => {
if (!triggerRef.current || !tooltipRef.current) return;
const triggerRect = triggerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
let top = 0;
let left = 0;
switch (position) {
case 'top':
top = triggerRect.top - tooltipRect.height - 5; // 5px spacing
left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
break;
case 'bottom':
top = triggerRect.bottom + 5;
left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
break;
case 'left':
top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
left = triggerRect.left - tooltipRect.width - 5;
break;
case 'right':
top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
left = triggerRect.right + 5;
break;
default:
break;
}
setPositionStyle({
top: `${top}px`,
left: `${left}px`,
});
};
const tooltipContent = isVisible && (
<div className="tooltip" style={positionStyle} ref={tooltipRef}>
{text}
</div>
);
return (
<span
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children}
{mounted && tooltipRoot.current ? ReactDOM.createPortal(tooltipContent, tooltipRoot.current) : null}
</span>
);
};
export default Tooltip;
解説ïŒ
textprop: ããŒã«ãããã«è¡šç€ºããããã¹ãã§ããchildrenprop: ããŒã«ããããããªã¬ãŒããèŠçŽ ïŒãŠãŒã¶ãŒãã«ãŒãœã«ãåãããèŠçŽ ïŒã§ããpositionprop: ããªã¬ãŒèŠçŽ ã«å¯ŸããããŒã«ãããã®äœçœ®ïŒ'top', 'bottom', 'left', 'right'ïŒã§ããããã©ã«ãã¯'top'ã§ããisVisiblestate: ããŒã«ãããã®è¡šç€º/é衚瀺ãå¶åŸ¡ããŸããtooltipRootref: ããŒã«ããããã¬ã³ããªã³ã°ãããDOMããŒãïŒ#tooltip-rootïŒãåç §ããŸããtooltipRefref: ããŒã«ãããèŠçŽ èªäœãåç §ãããã®å¯žæ³ãèšç®ããããã«äœ¿çšãããŸããtriggerRefref: ããŒã«ããããããªã¬ãŒããèŠçŽ ïŒchildrenïŒãåç §ããŸããhandleMouseEnterãšhandleMouseLeave: ããªã¬ãŒèŠçŽ ã«ã«ãŒãœã«ãåããããé¢ãããããããã®ã€ãã³ããã³ãã©ã§ããupdatePosition:positionpropãšããªã¬ãŒããã³ããŒã«ãããèŠçŽ ã®å¯žæ³ã«åºã¥ããŠãããŒã«ãããã®æ£ããäœçœ®ãèšç®ããŸããgetBoundingClientRect()ã䜿çšããŠããã¥ãŒããŒãã«å¯ŸããèŠçŽ ã®äœçœ®ãšå¯žæ³ãååŸããŸããReactDOM.createPortal: ããŒã«ãããã®ã³ã³ãã³ããtooltipRootã«ã¬ã³ããªã³ã°ããŸãã
3. ããŒã«ãããã³ã³ããŒãã³ãã®äœ¿çš
import React from 'react';
import Tooltip from './Tooltip';
const App = () => {
return (
<div>
<p>
Hover over this <Tooltip text="This is a tooltip!\nWith multiple lines."\nposition="bottom">text</Tooltip> to see a tooltip.
</p>
<button>
Hover <Tooltip text="Button tooltip" position="top">here</Tooltip> for tooltip.
</button>
</div>
);
};
export default App;
ãã®äŸã¯ãTooltipã³ã³ããŒãã³ãã䜿çšããŠããã¹ãããã¿ã³ã«ããŒã«ãããã远å ããæ¹æ³ã瀺ããŠããŸããtextãšposition propã䜿çšããŠãããŒã«ãããã®ããã¹ããšäœçœ®ãã«ã¹ã¿ãã€ãºã§ããŸãã
4. ããŒã«ãããã®ã¹ã¿ã€ãªã³ã°
ããŒã«ããããé 眮ããã¹ã¿ã€ã«ãèšå®ããããã®CSSã远å ããŸãã以äžã«åºæ¬çãªäŸã瀺ããŸãïŒ
.tooltip {
position: absolute;
background-color: rgba(0, 0, 0, 0.8); /* Dark background */
color: white;
padding: 5px;
border-radius: 3px;
font-size: 12px;
z-index: 1000; /* Ensure it's on top of other content */
white-space: pre-line; /* Respect line breaks in the text prop */
}
CSSã®è§£èª¬ïŒ
position: absolute: ããŒã«ããããtooltip-rootã«å¯ŸããŠé 眮ããŸããReactã³ã³ããŒãã³ãå ã®updatePosition颿°ããããªã¬ãŒèŠçŽ ã®è¿ãã«ããŒã«ããããé 眮ããããã®æ£ç¢ºãªtopãšleftã®å€ãèšç®ããŸããbackground-color: rgba(0, 0, 0, 0.8): ããŒã«ãããçšã«ãããã«éæãªæãèæ¯ãäœæããŸããwhite-space: pre-line: ããã¯ãtextpropã«å«ããå¯èœæ§ã®ããæ¹è¡ãç¶æããããã«éèŠã§ããããããªããšãããŒã«ãããã®ããã¹ãã¯ãã¹ãŠ1è¡ã§è¡šç€ºãããŠããŸããŸãã
ã°ããŒãã«ãªèæ ®äºé ãšãã¹ããã©ã¯ãã£ã¹
ã°ããŒãã«ãªãªãŒãã£ãšã³ã¹åãã«Reactã¢ããªã±ãŒã·ã§ã³ãéçºããéã«ã¯ã以äžã®ãã¹ããã©ã¯ãã£ã¹ãèæ ®ããŠãã ããïŒ
- åœéåïŒi18nïŒïŒ
react-i18nextãFormatJSã®ãããªã©ã€ãã©ãªã䜿çšããŠã翻蚳ãšããŒã«ãªãŒãŒã·ã§ã³ãåŠçããŸããããã«ãããã¢ããªã±ãŒã·ã§ã³ãç°ãªãèšèªãå°åã«ç°¡åã«é©å¿ãããããšãã§ããŸããã¢ãŒãã«ãããŒã«ãããã«ã€ããŠã¯ãããã¹ãã³ã³ãã³ããé©åã«ç¿»èš³ãããŠããããšã確èªããŠãã ããã - å³ããå·ŠïŒRTLïŒãžã®ãµããŒãïŒ ã¢ã©ãã¢èªãããã©ã€èªãªã©ãå³ããå·Šã«èªãŸããèšèªã®ããã«ãã¢ãŒãã«ãããŒã«ããããæ£ãã衚瀺ãããããã«ããŸããRTLã¬ã€ã¢ãŠãã«å¯Ÿå¿ããããã«ãèŠçŽ ã®äœçœ®ãã¹ã¿ã€ãªã³ã°ã調æŽããå¿
èŠããããããããŸãããCSSã®è«çããããã£ïŒäŸïŒ
margin-leftã®ä»£ããã«margin-inline-startïŒã圹ç«ã¡ãŸãã - æåçãªé æ ®ïŒ ã¢ãŒãã«ãããŒã«ãããããã¶ã€ã³ããéã«ã¯ãæåçãªéãã«æ³šæããŠãã ãããç¹å®ã®æåã§äžå¿«ãŸãã¯äžé©åãšèŠãªãããå¯èœæ§ã®ããç»åãã·ã³ãã«ã®äœ¿çšã¯é¿ããŠãã ããã
- ã¿ã€ã ãŸãŒã³ãšæ¥ä»åœ¢åŒïŒ ã¢ãŒãã«ãããŒã«ããããæ¥ä»ãæå»ã衚瀺ããå Žåã¯ããŠãŒã¶ãŒã®ãã±ãŒã«ãšã¿ã€ã ãŸãŒã³ã«åŸã£ãŠãã©ãŒããããããŠããããšã確èªããŠãã ããã
moment.jsïŒã¬ã¬ã·ãŒã§ãããŸã åºã䜿çšãããŠããŸãïŒãdate-fnsã®ãããªã©ã€ãã©ãªããããæ¯æŽããŸãã - 倿§ãªèœåãæã€äººã ã®ããã®ã¢ã¯ã»ã·ããªãã£ïŒ ã¢ã¯ã»ã·ããªãã£ã¬ã€ãã©ã€ã³ïŒWCAGïŒãéµå®ããé害ãæã€äººã ãã¢ãŒãã«ãããŒã«ãããã䜿çšã§ããããã«ããŸããããã«ã¯ãç»åã®ä»£æ¿ããã¹ãã®æäŸãååãªè²ã®ã³ã³ãã©ã¹ãã®ç¢ºä¿ãããŒããŒãããã²ãŒã·ã§ã³ã®ãµããŒããå«ãŸããŸãã
çµè«
React Portalã¯ãæè»ã§ã¢ã¯ã»ã·ãã«ãªãŠãŒã¶ãŒã€ã³ã¿ãŒãã§ãŒã¹ãæ§ç¯ããããã®åŒ·åãªããŒã«ã§ãããã®å¹æçãªäœ¿ç𿹿³ãçè§£ããããšã§ããŠãŒã¶ãŒäœéšãåäžãããReactã¢ããªã±ãŒã·ã§ã³ã®æ§é ãšä¿å®æ§ãæ¹åããã¢ãŒãã«ãããŒã«ããããäœæã§ããŸãã倿§ãªãªãŒãã£ãšã³ã¹åãã«éçºããéã«ã¯ãã¢ã¯ã»ã·ããªãã£ãšã°ããŒãã«ãªèæ ®äºé ãåªå ããã¢ããªã±ãŒã·ã§ã³ã誰ã«ãšã£ãŠãå æ¬çã§å©çšå¯èœã§ããããšã確èªããŠãã ããã