transform, perspective, filter
속성을 가지고있는 부모가 없다고 가정이 되어있어야 한다!위와 같은 장점이 있고 사용법도 어렵지 않아 포탈을 이용해서 구현해보기로 했다.
creaetPortal을 사용하기위해서 그릴 것과 위치를 정해야한다. 나는 아래와 같이 해주었는데, 토스트 리스트를 id=toast인 dom element 내부에 렌더링한다는 의미다.
createPortal(
<ToastList>
{toasts.map(({ message, type }, index) => (
<Toast type={type} key={index}>
{message}
</Toast>
))}
</ToastList>,
document.getElementById('toast')
)}
이때, index.html에는 id=root인 블록 하나만 존재하기 때문에, id=toast인 블록은 직접 추가해야한다.
토스트 상태를 각 페이지 내에서 지역적으로 관리할지, 아니면 모든 페이지들이 접근할 수 있게 전역에서 관리되도록 할지에 대해서 고민했다.
1️⃣ 지역적
// 예시 코드
function PageComponent () {
const [toasts, setToasts] = useState([]);
const onClick = () => {
setToast([...toasts, { message: `${toasts.length}번째 메시지` }]);
}
return (
<div>
<ToastList>
{toasts.map(({ message }) =>
<Toast>{message}</Toast>)}
</ToastList>
<div onClick={onClick}>
</div>
);
}
2️⃣ 전역적
Context API를 사용해서 토스트 상태를 중앙 집중식으로 관리한다.
export default function ToastsContextProvier({ children }) {
const [toasts, setToasts] = useState([]);
const data = useMemo(() => [toasts, setToasts], [toasts]);
return (
<ToastContext.Provider value={data}>
{children}
{createPortal(
<ToastList>
{toasts.map(({ message, type }, index) => (
<Toast type={type} key={index}>
{message}
</Toast>
))}
</ToastList>,
document.getElementById('toast')
)}
</ToastContext.Provider>
);
}
비교 결과, Context API를 사용한 관리 방법을 선택했다.
UX 관점에서 전역적으로 관리하는 방식이 더 낫다고 생각했다. 또한 2번 방식을 개선하면 성능면에서도 더 나았다. 지역적으로 구현할 경우, toast가 추가되면 페이지 전체에 리렌더링이 발생한다. 하지만 context api를 이용해서 toast list 컴포넌트만 리렌더링이 되도록 구현할 수 있다.
Toast가 거의 모든 사용자 상호작용에 대한 응답을 렌더링하는데 사용이 될 예정이었다.
따라서 다른 팀원들은 이 기능을 사용해야했고, 빠르게 기능 구현하기위해 팀원들이 쉽게 사용할 방법에 대해서 생각해야했다.
팀원들이 사용하기 편하도록 useToast 훅을 만들어서 제공했다.
export default function useToast() {
const toastContext = useContext(ToastContext);
if (!toastContext) throw new Error('Toast provider를 추가해주세요');
const [toasts, setToasts] = toastContext;
const createToast = (toast) => {
setToasts([...toasts, toast]);
};
return createToast;
}
팀원들이 사용하기 너무 쉽고 편하다는 피드백을 해주었다. 정말 뿌듯했다.
동작만 하는 코드를 구현하는 것이 아닌, 생산성을 높여주고 팀원들의 시간을 아끼는 또 미래의 내 시간을 아껴주는 코드에 시간을 쓰는 것에 가치를 느꼈다.
한 번에 띄울 수 있는 토스트 개수를 제한한다.
연속적으로 화면에 많이 띄울경우, 나열된 토스트에 화면이 가려져서 기능 사용에 제한이 있는 문제를 식별했다. (3개 정도면 괜찮지 않을까?)
DOM에서는 일정시간 이후 토스트가 제거되지만, 실제 토스트 상태값에서는 제거되지않는다.
큰 문제는 발생하지 않으나 메모리를 불필요하게 낭비하고있는 상황이다.
→ shift의 속도는 O(n)이며 이는 연결리스트를 통해 O(1)만에 해결할 수 있다. 최악의 상황을 고려하면 shift에 더 빠른 속도를 제공하는 연결리스트 구조를 이용하는 방법이 좋겠다.
토스트가 사라질 때 애니메이션이 버벅이는 현상이 있다. 왜 그런지 알아봐야겠다..
리액트 관련 글 검색하면 항상 이 블로그네요