안녕하세요 동료님. 영상 시청해주셔서 감사합니다. 고민하시던 부분이 정리가 되셨다니 저도 너무 뿌듯하네요! 게다가 쉽게 설명이라니... 🥹 제가 고민했던 부분들이었는데 동료님께서 저의 고민을 풀어주셨네요. 👍 다시 한번 영상 시청해주셔서 감사합니다. 그럼 좋은 저녁되세요!
재미있네요. 약간 Window.confirm() 처럼 블로킹을 하는 느낌이네요. 궁금한 점이 있는데요. 만약 한페이지에서 거의 동시에 여러개의 모달을 호출했을 경우 각각의 모달이 resolve혹은 reject 되기 전까지는 배열 속에서 대기하고 있을텐데, 만약 이 상태에서 navigate 같은걸로 페이지 이동했을 때 모달은 남아있게 되나요?
안녕하세요 동료님. 영상 시청해주셔서 감사합니다. 네 동료님 말씀이 맞아요! 배열에 대기하고 있을 거예요. 정확하게 따져보면 동작하는 환경에 따라 navigate이 다를 순 있겠지만, react를 생각을 하셨다면 refresh가 아니라 router.push 혹은 router.replace로는 남아있게 됩니다. 그래서 clear 명령어가 새로 생기게 되었어요!
평소에 모달을 어떻게 하면 역할과 책임을 잘 분배해서 구현할 수 있을까 고민했었는데 이 영상으로 그런 고민이 한번에 해결되었어요! 좋은 영상 너무 감사합니다!! 영상중에 const [userInfoController] = useState(new UserInfoController((flashState)); 의 부분에 질문이 있습니다! UserInfoController를 useState 내부에서 인스턴스를 생성하신 이유는 매번 리렌더링이 발생할 때 마다 각기 다른 인스턴스가 생성될 수 있는 문제가 있어서 useState에 넣어서 인스턴스를 생성하신걸까요?! + 타입스크립트가 적용된 원래 예시 코드도 볼 수 있으면 너무 좋을 것 같은데 예시 코드는 따로 없나요?! 좋은 영상감사합니다!
안녕하세요 동료님. 저의 영상을 좋게 봐주셔서 감사합니다. 고민이 해결이 되셨다니 너무 좋네요 👍 그리고 좋은 질문 주셔서 감사합니다! 📌 Q1> UserInfoController를 useState 내부에서 인스턴스를 생성하신 이유는 매번 리렌더링이 발생할 때 마다 각기 다른 인스턴스가 생성될 수 있는 문제가 있어서 useState에 넣어서 인스턴스를 생성하신걸까요?! 아주 좋은 질문을 주셨는데요! useState에 인스턴스를 생성한 이유가 2가지가 있어요. 첫 번째는 말씀하신 인스턴스 발생을 막기 위함이고, 두 번째는 flush 패턴의 본질에 있기도 해용. 대략적으로 설명을 드리자면 "인스턴스를 활용한 변경점"을 표시할 필요가 있기 때문이예요. 예를 들면 이런 코드가 있다고 가정해보겠습니다: ```javascript const modal = useModal() const top = (() => modal.top, [modal]) ``` 보통 이런 코드를 생각할텐데, 실제 modal 객체가 변화한다면 flush 패턴만으로는 저 코드에 대응할수가 없어요. 그래서 useState도 같이 대응을 해준거라고 보시면 됩니다. 아마 이 부분은 나중에 local storage, session storage를 다루는 편에서 자세하게 다룰수 있을 거 같아용! 📌 Q2> 타입스크립트가 적용된 원래 예시 코드도 볼 수 있으면 너무 좋을 것 같은데 예시 코드는 따로 없나요?! 나중에 리액트 나라 여행기에 나오는 코드들로오픈소스를 운영할 계획을 가지고 있긴 합니당 ㅠ 다만, 지금 당장은 시간이 없어 계획으로만 가지고 있어요 ㅠㅡㅠ. 그럼 도움이 되셧길 바라면서 좋은 하루 되세요! p.s. 아마 제가 짠 코드보다 동료님 코드가 더 멋지고 아름다울거예요!
안녕하세요 아기새님!!! 오랜만에 뵙네요~~~!!! 반갑습니다. 지난 번에 오픈소스에 관심이 있으셨다고 하셨는데 그 부분은 잘 해결이 되셨나요? 철학 영상의 핵심 메세지도 꽤뚫고 계시는군요! 👍 아기새님의 철학을 확장하시는데 도움이 되어 너무 기쁘네요!!! 그럼 좋은 저녁되세요! 다시 한번 영상 시청해주셔서 감사합니다. 🙇🏻♂️
안녕하세요 동료님. 영상 시청해주셔서 감사합니다. 🙇🏻♂️ 그리고 좋은 말씀 너무 감사합니다. 이번 영상은 리액트 나라 여행기의 첫 영상으로 방향성을 잡는데 많은 고민이 되었는데... 그 고민들이 헛되지 않았다는 사실이 기분이 좋네요! 더 좋은 영상으로 찾아뵐 수 있도록 노력해볼게요. 그럼 좋은 저녁되세요!
안녕하세요 동료님. 영상 시청해주셔서 감사합니다. 굉장히 중요한 질문을 주셨네요.👍 현재 현업에서 사용하고 있는 버전에는 훨씬 더 많은 로직들이 들어가 있어요. 해당 영상의 목적은 모달 관리에 대해 최대한 쉽고 핵심적인 것만 알려드리고 싶었어요. 말씀 주셨던 기능 + 영상에서 나온 기능 외에 원래 버전에서는 다음과 같은 로직들이 들어가 있어요: - 모달이 떠있으면 백그라운드 스크롤 얼리는 기능 - resolve에서 타입을 추론하는 기능 - props에 대해서 타입을 추론하는 기능 - 에니메이션 처리 기능 - SSR 관련 대응 기능 - 모달에 대한 caching 기능 - route 처리에 관한 기능 - 에러 처리에 관한 기능 - ... 혹시 해당 기능들은 어떻게 구현할까 궁금하셔서 질문을 주셨다면 저의 의견은 다음과 같습니다: 📌 Q1> 모달작업에 css 레이어 우선순위도 되어있나요? 제가 이해한게 맞다면, ModalContainer에서는 항상 최상단의 top 모달만 보여주고 있어서 이건 같은 모달들에 대해서는 필요 없을 거 같아용. 😅 혹여나 map으로 모든 모달들을 보여주더라도 modalInfos가 stack 구조를 가지고 있기 때문에 가장 마지막의 모달 컴포넌트만 보여질 거 같아요 ! 요게 제가 modalInfos를 stack 구조로 가져갔던 이유중에 하나기도 해요! 📌 Q2> 혹시 뒤로가기 이벤트를 감지해서 모달을 끄는기능도 개발되어있나요? 영상에서는 나와있진 않지만 현재 현업에서 쓰는 버전은 그렇습니다. 실제 현업에서는 모달이 띄워지는 상황이 단지 웹 뿐 아니라 다양한 환경(ex> 웹뷰)에서도 대응이 필요해서 코드가 너무 길어질거 같아요. 제가 생각한 가장 쉬운 방법으로는 다음과 같은 방법이 있을거 같아요: ```javascript // 뒤로가기 이벤트를 받을 수 있음 window.onpopstate = () => { // 모달이 없다면 그대로 뒤로가기 동작을 수행하면 된다. if (!modal.top) return /* guard*/ // ⭐️ 모달만 닫힐 수 있도록 이하 코드 작성 // 만약 모달이 떠있다면 최상단의 모달을 없앤다. modal.pop() // 뒤로가기 동작이 되어있기 떄문에 다시 앞으로 가는걸로 돌린다. history.go(1) } ``` 다만, 이 방법에 대해서는 한가지 이슈가 있어요. 그건 history.length가 1일때 onpopstate에 event를 캐치를 못한다는 거예요. 요 부분을 극복하기 위해 가장 쉽게 할 수 있는건 해당 이슈를 노운 이슈로 처리하고 history.length가 1일때는 모달을 띄우지 않는 방법이 있을거 같아요. 보통 이 부분은 노운 이슈로 가져가도 크리티컬하지 않기 때문에 괜찮을거 같긴 합니다! 영상에서는 기본적인 모달 관리의 원리와 핵심 개념에 대해서만 다루었어요. 아마 동료님께서 작업하시는 부분은 이러한 뼈대를 토대로 제가 작업했던 부분들 보다 더 깔끔하고 멋진 코드들이 만들어질거 같네요! 👍 그럼 도움이 되셨길 바라면서 글을 마쳐볼게요. 다시 한번 좋은 질문 주셔서 감사합니다. 🙇🏻♂️ 그럼 좋은 저녁 되세요~!
안녕하세요 동료님. 만나서 반갑습니다. 그리고 영상 시청해주셔서 감사합니다. 기승전결이 완벽하다니... 너무 감사합니다. 이번 영상은 리액트 나라 여행기의 첫 영상으로 많은 고민이 되었어요. 혹시 자칫 너무 어려워지면 어떻게 하지? 잘 전달이 될까? 같은 고민들이 있었는데 동료님의 말씀 한마디로 저의 고민이 해결된거 같아 다행이네요. 감사합니다. 🙇🏻♂️ 그럼 좋은 저녁 되세요!
저의 한참 부족한 실력으로 여쭤보지만 flush 패턴이 다른 js 프레임워크에는 어떻게 적용이 되나요? React 내부에서 state 를 바꿈으로 인한 강제 rerender 는 이해가 되는데 solid js 같은 ( signal 사용 ) rerender 는 컨트롤러의 더 많은 부분이 바뀌는걸 요구할것 같아서요
잘보았습니다. 궁금한게 있는데, 제안해주신 방법으로 공통 모달을 만들었습니다. 다만, 공통 모달인 만큼 '확인'버튼을 누르면 resolve가 실행이되고, 다른 곳에서 api 처리를 하려고하는데 api동작이 끝나기 전까지 '확인'버튼에 로딩이 되도록 하고싶은데, 좋은 아이디어가 있을까요?
안녕하세요 동료님. 영상 시청해주셔서 감사합니다. 🙇🏻♂️ 그리고 좋은 질문 남겨주셔서 감사합니다. 제가 해당 영상에서 삭제 한 스크립트가 있는데, 해당 스크립트를 말씀드리기 딱 좋은 질문인거 같아요. 동료님의 설계가 어떻게 되어있으며, 다른 곳이라고 표현한 곳이 어느 정도 다른 곳인지 몰라, 일단 제가 생각하는 방향으로 답변을 드려볼게용! 다음과 같은 가정을 진행하였습니다: - api 호출은 GET으로 진행 - react-query 사용 - 다른 곳이라고 표현한 구간은 호출하는 쪽 일단 일반적으로 딱 떠오르신 그 방법으로는 작동을 안할거예요: ```javascript const [isLoading] = useQuery() await modal.push('CommonModal', CommonModal, { isLoading }) ``` 이게 동작을 하지 않도록 한 이유는 *설계 의도*가 되어진 부분이예요. 사용자에게 *캡슐화*를 강제하도록 하기 위함 이예요. 왜 이렇게 설계 했냐? 라는 궁금증이 드실거 같아요. 이에 대해 제가 이렇게 설계한 철학도 같이 말씀을 드리고 싶은데 그 부분은 아래 영상을 참고 하시면 좋을거 같아요: czcams.com/video/TrfS2TiYIB4/video.html 해당 영상에서 "확정성(04:35)" 이라는 부분을 참고하시면 좋을 것 같아요. 그럼 다시 돌아와서 어떻게 하면 동작 하는지에 대해서는 몇 가지 방법이 있어요: - HOC 패턴을 이용 - 선언적 프로그래밍 설계 사용 - 캡슐화 HOC 패턴을 이용, 선언적 프로그래밍 설계 사용은 해당 댓글로 적기엔 너무 길어지네요. 또한 1) 어떻게 그동안 추상화 레벨을 잡으셨는지 2) 어떤 도메인에서 해당 모달이 개발되게 되었는지 3) 공통 모달의 실제 설계는 어떠한지 에 따라서도 판단이 달라질거 같아서, 가장 쉽게 접근할 수 있는 캡슐화 부분만 설명드려볼게요: ```javascript import React from 'react'; import { useQuery } from '@tanstack/react-query'; import { useModal } from './use-modal'; const fetchDataMock = async () => { const res = await new Promise((resolve) => { setTimeout(() => { resolve('API MODAL!!!'); }, 1000); }); return res; }; function CommonModal({ resolve, close, queryFn, queryKey }) { // enabled false를 통해 초기 api 호출은 하지 않는다. const { refetch, isLoading } = useQuery({ queryKey: queryKey ?? [], queryFn: queryFn, enabled: false, }); const handleApprove = async () => { // props. queryFn, queryKey가 있을 경우에만 데이터를 호출하여 받아온다: if (queryFn && queryKey) { const res = await refetch?.(); resolve(res?.data); } else { resolve('NORMAL MODAL!!!'); } }; const handleReject = () => { close(); }; return (
"에디 개발 여행기"를 구독하시겠습니까?
{isLoading ? '로딩 중...' : '예'} 아니오
); } ``` 이렇게 하면 쉽게 말씀하신 부분을 달성할수 있어요. 사용 방법은 queryKey, queryFn을 던져주는지에 따라 api 호출 혹은 api 호출을 하지 않을 수 있어요: ```javascript export default function Test1() { const modal = useModal(); // api를 호출할 수 있는 모달 const handleApiModal = async () => { const res = await modal.push('CommonModal', CommonModal, { queryKey: ['data'], queryFn: async () => await fetchDataMock(), }); // 'API MODAL!!!' 찍힘 console.log(res); }; // api를 호출하지 않는 모달 -> 그냥 예 아니오로만 정의 const handleNormalModal = async () => { const res = await modal.push('CommonModal', CommonModal, {}); // 'NORMAL MODAL!!!' 찍힘 console.log(res); }; return ( Test1 CALL API MODAL
CALL NORMAL MODAL
); } ``` 그럼 도움이 되셨길 바라면서, 글을 마쳐보겠습니다. 좋은 하루 되세요! 그리고 다시 한번 좋은 댓글 남겨주셔서 감사합니다. 🙇🏻♂️
안녕하세요 동료님. 영상 시청해주셔서 감사합니다. 동료님께서 꼼꼼하게 영상을 봐주셨네요. 👍혹시라도 있을 혼동을 바로잡을 기회를 주셔서 감사합니다. 🙇🏻♂ 먼저 동료님께서 말씀주셨던 것처럼 "리액트는 라이브러리가 맞습니다." 이에 관한 정확한 근거는 라이브러리와 프레임워크의 차이점을 보아도 그렇고, 리액트 공식 문서를 봐도 그렇게 나와있습니다: React is the "library" for web and native user interfaces 리액트 공식문서에서도 리액트를 "라이브러리"라고 함에도 불구하고 해당 영상에서 "리액트 프레임워크"라고 표현 했던 이유는 다음과 같습니다: > 리액트를 사용하여 개발하는 환경에서는 리액트 프레임워크(ex> nextjs, remix, cra, ...) 를 사용하기 때문입니다. 보통 리액트로 개발을 한다고 하는 환경을 보면, 순수 리액트 라이브러리만 사용하고 있지는 않습니다. 실제로는 리액트 프레임워크(혹은 리액트 기반 프레임워크 라고 불림)를 사용하고 있습니다. 예를 들어 nextjs, remix, cra, ...가 있어요. 저의 modal 영상 스크립트를 보면 다음과 같은 형태로 되어있습니다: 01:27 리액트의 의존성을 최소화 하기 위함입니다. 01:30 모든 팀에서 "리액트 프레임워크"를 사용하고 있지는 않거든요. 01:33 그래서 모든 "프레임워크"에 적용이 가능한 형태로 만들어 놓기 위해 이러한 패턴이 만들어지게 되었습니다. 01:27의 스크립트는 "리액트" 라고 하다가 01:30에서는 "리액트 프레임워크" 라고 표현한 이유는 react에 기반한 모든 프레임워크를 지칭하고자 하는 말이었어요. 😅 해당 스크립트가 나오는 주제인 Flush 패턴이 정확하게 어느 정도 수준까지를 염두하고 만들어진 패턴인지 알려드리기 위해 이렇게 작성했던 거 같아요. 단지, 리액트 환경의 프레임워크가 아닌 현업에서 사용하는 프론트엔드 모든 "환경"에 대응하기 위해 만든 패턴을 강조하고 싶었습니다. 지금 생각해보니... 스크립트 작성에 간단하지만 많은 의미를 함축하려다 보니, 혼란스러울 수 있겠네요. "리액트는 라이브러리인가? 프레임워크인가?"는 제가 면접을 볼 당시에 가장 단골 질문이었어요. 혹시라도 저의 영상을 보시고 혼동 되지 않으셨으면 하는 마음에 더 길게 답변을 적어보았어요. 과거에 저는 프레임워크라고 답변을 했었던 경험이 있기 때문이예요. 🥹 영상을 제작함에 있어 정보 전달은 쉬운 형태로 전달에 목적을 두기 때문에 딜레마에 빠지곤 합니다. 어느 정도까지 쉬운 수준으로 말씀을 드려야할까? 어느 정도까지 정보를 생략해도 되는걸까? 라는 고민이 드는 것 같아요. 하지만 언젠간 저도 이 딜레마에서 답을 찾게될 날을 약속드리며 글을 마쳐보겠습니다. 다시 한번 혼동스러울 부분을 말씀해주셔서 감사합니다. 🙇🏻♂ 그럼 좋은 주말 되세요~~!
좋은 접근이네요 역시 대기업 개발자라 다르군요. 좋은강의 많이 부탁드려요
좋아요 구독 눌렀습니다.
와 진짜 너무 감사합니다.
모달에 대해 요즘 고민이 많았는데
너무 정리를 잘해주시고
쉽게 설명해주셔서 너무 좋았습니다.
정말 정말 감사드립니다~~!!😊
안녕하세요 동료님. 영상 시청해주셔서 감사합니다.
고민하시던 부분이 정리가 되셨다니 저도 너무 뿌듯하네요!
게다가 쉽게 설명이라니... 🥹 제가 고민했던 부분들이었는데 동료님께서 저의 고민을 풀어주셨네요. 👍
다시 한번 영상 시청해주셔서 감사합니다. 그럼 좋은 저녁되세요!
단순 지식 전달이 아닌 철학과 스토리탤링을 함께해주시니 머리와 마음을 동시에 울리네요! 영상 감사합니다!
안녕하세요 동료님! 만나서 반갑습니다. 그리고 영상 시청해주셔서 감사합니다.
이번 영상은 만드는데 고민이 많았는데... 제가 원하는 방향대로 영상이 만들어 진거 같아 기분이 너무 좋네요!
그럼 좋은 저녁 되세요! 다시 한번 영상 시청해주셔서 감사합니다. 🙇🏻♂️
좋은 내용 감사드립니다. 개발을 하면 할수록 점점더 아키텍쳐에 대한 중요성을 많이 느낍니다
안녕하세요 동료님. 영상 시청해주셔서 감사합니다. 맞아요. 저에게는 아키텍쳐는 항상 어려운거 같아요... 그걸 깨달으셨다니... 실력이 출중하신 거 같네요! 👍
그럼 좋은 저녁되세요! 영상 시청해주셔서 다시 한번 감사합니다.
잘 보고 갑니다
오 훌륭하네요..! 바로 구독 박아요 ㅎㅎ 좋은 강의 감사합니다
어머나 유미라니... 저의 최애 캐릭은 애쉬인데 저랑 같이 바텀 라인전을 서주시겠어요...? 👉👈
항상 잘 보고 있습니다:)
영상 시청해주셔서 감사합니다! 🙇🏻♂️
좋은 패턴입니다. hook 에 의존성이 없는 객체를 만들고 원할 때 flush 를 호출하는게 마침 vue3 shallowRef, triggerRef 와 비슷해 보입니다. 개인적인 의견이지만 flush 보다는 trigger 가 더 의미가 빨리 와 닿는 듯 합니다.
재미있네요. 약간 Window.confirm() 처럼 블로킹을 하는 느낌이네요.
궁금한 점이 있는데요. 만약 한페이지에서 거의 동시에 여러개의 모달을 호출했을 경우 각각의 모달이 resolve혹은 reject 되기 전까지는 배열 속에서 대기하고 있을텐데, 만약 이 상태에서 navigate 같은걸로 페이지 이동했을 때 모달은 남아있게 되나요?
안녕하세요 동료님. 영상 시청해주셔서 감사합니다.
네 동료님 말씀이 맞아요! 배열에 대기하고 있을 거예요. 정확하게 따져보면 동작하는 환경에 따라 navigate이 다를 순 있겠지만, react를 생각을 하셨다면 refresh가 아니라 router.push 혹은 router.replace로는 남아있게 됩니다. 그래서 clear 명령어가 새로 생기게 되었어요!
평소에 모달을 어떻게 하면 역할과 책임을 잘 분배해서 구현할 수 있을까 고민했었는데 이 영상으로 그런 고민이 한번에 해결되었어요! 좋은 영상 너무 감사합니다!!
영상중에 const [userInfoController] = useState(new UserInfoController((flashState)); 의 부분에 질문이 있습니다!
UserInfoController를 useState 내부에서 인스턴스를 생성하신 이유는 매번 리렌더링이 발생할 때 마다 각기 다른 인스턴스가 생성될 수 있는 문제가 있어서 useState에 넣어서 인스턴스를 생성하신걸까요?!
+ 타입스크립트가 적용된 원래 예시 코드도 볼 수 있으면 너무 좋을 것 같은데 예시 코드는 따로 없나요?!
좋은 영상감사합니다!
안녕하세요 동료님. 저의 영상을 좋게 봐주셔서 감사합니다.
고민이 해결이 되셨다니 너무 좋네요 👍
그리고 좋은 질문 주셔서 감사합니다!
📌 Q1> UserInfoController를 useState 내부에서 인스턴스를 생성하신 이유는 매번 리렌더링이 발생할 때 마다 각기 다른 인스턴스가 생성될 수 있는 문제가 있어서 useState에 넣어서 인스턴스를 생성하신걸까요?!
아주 좋은 질문을 주셨는데요! useState에 인스턴스를 생성한 이유가 2가지가 있어요.
첫 번째는 말씀하신 인스턴스 발생을 막기 위함이고, 두 번째는 flush 패턴의 본질에 있기도 해용. 대략적으로 설명을 드리자면 "인스턴스를 활용한 변경점"을 표시할 필요가 있기 때문이예요.
예를 들면 이런 코드가 있다고 가정해보겠습니다:
```javascript
const modal = useModal()
const top = (() => modal.top, [modal])
```
보통 이런 코드를 생각할텐데, 실제 modal 객체가 변화한다면 flush 패턴만으로는 저 코드에 대응할수가 없어요. 그래서 useState도 같이 대응을 해준거라고 보시면 됩니다. 아마 이 부분은 나중에 local storage, session storage를 다루는 편에서 자세하게 다룰수 있을 거 같아용!
📌 Q2> 타입스크립트가 적용된 원래 예시 코드도 볼 수 있으면 너무 좋을 것 같은데 예시 코드는 따로 없나요?!
나중에 리액트 나라 여행기에 나오는 코드들로오픈소스를 운영할 계획을 가지고 있긴 합니당 ㅠ 다만, 지금 당장은 시간이 없어 계획으로만 가지고 있어요 ㅠㅡㅠ.
그럼 도움이 되셧길 바라면서 좋은 하루 되세요!
p.s. 아마 제가 짠 코드보다 동료님 코드가 더 멋지고 아름다울거예요!
@@eddie_the_traveler 답글이 너무 늦었네요 ㅠㅠㅠㅠ 답변 해주셔서 감사합니다!! 어서 관련된 영상도 너무 기대됩니다!! 감사합니다!
너무 감사합니다! 정말 코드를 어떠한 철학으로 짜야하는지 많이 배워요!
안녕하세요 아기새님!!! 오랜만에 뵙네요~~~!!! 반갑습니다. 지난 번에 오픈소스에 관심이 있으셨다고 하셨는데 그 부분은 잘 해결이 되셨나요?
철학 영상의 핵심 메세지도 꽤뚫고 계시는군요! 👍 아기새님의 철학을 확장하시는데 도움이 되어 너무 기쁘네요!!!
그럼 좋은 저녁되세요! 다시 한번 영상 시청해주셔서 감사합니다. 🙇🏻♂️
@@eddie_the_traveler oss 컨퍼런스에서 변정훈(당근마켓 sre) 분께도 물어보고 redhat 에서 어떻게 변역에 기여하는지 경험해보고 왔어요!
에디님 덕분에 좀더 좋은 질문을 할 수 있었습니다! ㅎㅎ
매번 감사합니다
영상 내용이 좋네요
안녕하세요 동료님. 영상 시청해주셔서 감사합니다. 🙇🏻♂️
그리고 좋은 말씀 너무 감사합니다. 이번 영상은 리액트 나라 여행기의 첫 영상으로 방향성을 잡는데 많은 고민이 되었는데... 그 고민들이 헛되지 않았다는 사실이 기분이 좋네요! 더 좋은 영상으로 찾아뵐 수 있도록 노력해볼게요.
그럼 좋은 저녁되세요!
지금 하신 모달작업에 css 레이어 우선순위도 되어있나요? 나중에 뜬게 더 화면앞에 뜬다던지 그런처리랑, 혹시 뒤로가기 이벤트를 감지해서 모달을 끄는기능도개발되어있나요..?
안녕하세요 동료님. 영상 시청해주셔서 감사합니다.
굉장히 중요한 질문을 주셨네요.👍
현재 현업에서 사용하고 있는 버전에는 훨씬 더 많은 로직들이 들어가 있어요. 해당 영상의 목적은 모달 관리에 대해 최대한 쉽고 핵심적인 것만 알려드리고 싶었어요. 말씀 주셨던 기능 + 영상에서 나온 기능 외에 원래 버전에서는 다음과 같은 로직들이 들어가 있어요:
- 모달이 떠있으면 백그라운드 스크롤 얼리는 기능
- resolve에서 타입을 추론하는 기능
- props에 대해서 타입을 추론하는 기능
- 에니메이션 처리 기능
- SSR 관련 대응 기능
- 모달에 대한 caching 기능
- route 처리에 관한 기능
- 에러 처리에 관한 기능
- ...
혹시 해당 기능들은 어떻게 구현할까 궁금하셔서 질문을 주셨다면 저의 의견은 다음과 같습니다:
📌 Q1> 모달작업에 css 레이어 우선순위도 되어있나요?
제가 이해한게 맞다면, ModalContainer에서는 항상 최상단의 top 모달만 보여주고 있어서 이건 같은 모달들에 대해서는 필요 없을 거 같아용. 😅 혹여나 map으로 모든 모달들을 보여주더라도 modalInfos가 stack 구조를 가지고 있기 때문에 가장 마지막의 모달 컴포넌트만 보여질 거 같아요 ! 요게 제가 modalInfos를 stack 구조로 가져갔던 이유중에 하나기도 해요!
📌 Q2> 혹시 뒤로가기 이벤트를 감지해서 모달을 끄는기능도 개발되어있나요?
영상에서는 나와있진 않지만 현재 현업에서 쓰는 버전은 그렇습니다. 실제 현업에서는 모달이 띄워지는 상황이 단지 웹 뿐 아니라 다양한 환경(ex> 웹뷰)에서도 대응이 필요해서 코드가 너무 길어질거 같아요. 제가 생각한 가장 쉬운 방법으로는 다음과 같은 방법이 있을거 같아요:
```javascript
// 뒤로가기 이벤트를 받을 수 있음
window.onpopstate = () => {
// 모달이 없다면 그대로 뒤로가기 동작을 수행하면 된다.
if (!modal.top) return /* guard*/
// ⭐️ 모달만 닫힐 수 있도록 이하 코드 작성
// 만약 모달이 떠있다면 최상단의 모달을 없앤다.
modal.pop()
// 뒤로가기 동작이 되어있기 떄문에 다시 앞으로 가는걸로 돌린다.
history.go(1)
}
```
다만, 이 방법에 대해서는 한가지 이슈가 있어요. 그건 history.length가 1일때 onpopstate에 event를 캐치를 못한다는 거예요. 요 부분을 극복하기 위해 가장 쉽게 할 수 있는건 해당 이슈를 노운 이슈로 처리하고 history.length가 1일때는 모달을 띄우지 않는 방법이 있을거 같아요. 보통 이 부분은 노운 이슈로 가져가도 크리티컬하지 않기 때문에 괜찮을거 같긴 합니다!
영상에서는 기본적인 모달 관리의 원리와 핵심 개념에 대해서만 다루었어요. 아마 동료님께서 작업하시는 부분은 이러한 뼈대를 토대로 제가 작업했던 부분들 보다 더 깔끔하고 멋진 코드들이 만들어질거 같네요! 👍
그럼 도움이 되셨길 바라면서 글을 마쳐볼게요. 다시 한번 좋은 질문 주셔서 감사합니다. 🙇🏻♂️
그럼 좋은 저녁 되세요~!
나만 알아야해!
어머나 😅
기승전결이 완벽하네요...!
안녕하세요 동료님. 만나서 반갑습니다. 그리고 영상 시청해주셔서 감사합니다.
기승전결이 완벽하다니... 너무 감사합니다. 이번 영상은 리액트 나라 여행기의 첫 영상으로 많은 고민이 되었어요. 혹시 자칫 너무 어려워지면 어떻게 하지? 잘 전달이 될까? 같은 고민들이 있었는데 동료님의 말씀 한마디로 저의 고민이 해결된거 같아 다행이네요. 감사합니다. 🙇🏻♂️
그럼 좋은 저녁 되세요!
개발자는 변기를 보면서도 디자인 패턴을...
😅 ;;;;;;;;;;
저의 한참 부족한 실력으로 여쭤보지만 flush 패턴이 다른 js 프레임워크에는 어떻게 적용이 되나요? React 내부에서 state 를 바꿈으로 인한 강제 rerender 는 이해가 되는데 solid js 같은 ( signal 사용 ) rerender 는 컨트롤러의 더 많은 부분이 바뀌는걸 요구할것 같아서요
안녕하세요 동료님. 영상 시청해주셔서 감사합니다.
그리고 멋진 질문을 주셔서 감사합니다. 수준 높은 질문을 주셔서 저도 생각을 해봤어용.
저는 solid.js를 처음 써보게 되었는데, 생각보다 프레임워크가 견고해서 놀랐습니다.
그리고 내부적으로도 조금 살펴보게 되었는데 놀랍더라구요. 저에게도 성장할 기회를 주셔서 감사합니다. 👍
일단 vue.js에서는 flush pattern이 이렇게 사용 가능 합니다:
```javascript
{{ count }}
{{ temp }}
FLUSH
import { ref } from 'vue';
const temp = { name: 'eddie' };
export default {
name: 'App',
setup(props, { expose }) {
const count = ref(0);
// ⭐️ flush 하는 동시에 temp.name은 eddie -> sam으로 바뀜
const flush = () => {
temp.name = 'sam';
count.value++;
};
return {
temp,
count,
flush,
};
},
};
```
solid.js는 이런 식으로 signal을 보내주면서 flush 패턴을 해줄 수 있을 거 같아요:
```javascript
import { createSignal, createMemo } from "solid-js";
import { render } from "solid-js/web";
class UserInfoController {
name = "eddie"
flagState;
constructor(flagState) {
this.flagState = flagState
return this
}
// ⭐️ flush는 기존과 같음.
flush() {
const [_, setState] = this.flagState;
setState(prev => prev + 1)
}
// ⭐️ 여기서 name을 바꿈
changeName() {
this.name = "sam"
this.flush()
}
}
const flagState = createSignal(0)
// ⭐️ flush 패턴 준비
const userInfoConroller = new UserInfoController(flagState)
// ⭐️ signal을 받는 쪽의 정의
const nameInfo = createMemo(() => {
const [count] = flagState
return {
id: count(),
name: userInfoConroller.name
}
})
const CountingComponent = () => {
return (
{nameInfo().name}
userInfoConroller.changeName()}>click
)
};
render(() => , document.getElementById("app"));
```
이렇게 하면 원래 Flush 패턴의 의도대로 flush 부분만 변경이 되고 정확하게는 ModalContainer쪽이 바뀔거 같아용. ModalContainer 쪽의 topModalInfo쪽 로직이 signal을 받는 것으로 되면 되지 않을까 싶습니다...!
그럼 도움이 되셨길 바라면서 글을 마쳐볼게요. 다시 한번 영상 시청해주셔서 감사합니다. 🙇🏻♂️
@@eddie_the_traveler 대단하네요
모달 개발중인데 갑자기 떠서 소름돋았어요
아무래도... 구글님들이 우리의 정보를 감청한다는게... 사실인거 같네요............!!
잘보았습니다.
궁금한게 있는데, 제안해주신 방법으로 공통 모달을 만들었습니다.
다만, 공통 모달인 만큼 '확인'버튼을 누르면 resolve가 실행이되고, 다른 곳에서 api 처리를 하려고하는데 api동작이 끝나기 전까지 '확인'버튼에 로딩이 되도록 하고싶은데, 좋은 아이디어가 있을까요?
안녕하세요 동료님. 영상 시청해주셔서 감사합니다. 🙇🏻♂️
그리고 좋은 질문 남겨주셔서 감사합니다. 제가 해당 영상에서 삭제 한 스크립트가 있는데, 해당 스크립트를 말씀드리기 딱 좋은 질문인거 같아요.
동료님의 설계가 어떻게 되어있으며, 다른 곳이라고 표현한 곳이 어느 정도 다른 곳인지 몰라, 일단 제가 생각하는 방향으로 답변을 드려볼게용!
다음과 같은 가정을 진행하였습니다:
- api 호출은 GET으로 진행
- react-query 사용
- 다른 곳이라고 표현한 구간은 호출하는 쪽
일단 일반적으로 딱 떠오르신 그 방법으로는 작동을 안할거예요:
```javascript
const [isLoading] = useQuery()
await modal.push('CommonModal', CommonModal, { isLoading })
```
이게 동작을 하지 않도록 한 이유는 *설계 의도*가 되어진 부분이예요. 사용자에게 *캡슐화*를 강제하도록 하기 위함 이예요. 왜 이렇게 설계 했냐? 라는 궁금증이 드실거 같아요. 이에 대해 제가 이렇게 설계한 철학도 같이 말씀을 드리고 싶은데 그 부분은 아래 영상을 참고 하시면 좋을거 같아요:
czcams.com/video/TrfS2TiYIB4/video.html
해당 영상에서 "확정성(04:35)" 이라는 부분을 참고하시면 좋을 것 같아요.
그럼 다시 돌아와서 어떻게 하면 동작 하는지에 대해서는 몇 가지 방법이 있어요:
- HOC 패턴을 이용
- 선언적 프로그래밍 설계 사용
- 캡슐화
HOC 패턴을 이용, 선언적 프로그래밍 설계 사용은 해당 댓글로 적기엔 너무 길어지네요. 또한
1) 어떻게 그동안 추상화 레벨을 잡으셨는지
2) 어떤 도메인에서 해당 모달이 개발되게 되었는지
3) 공통 모달의 실제 설계는 어떠한지
에 따라서도 판단이 달라질거 같아서, 가장 쉽게 접근할 수 있는 캡슐화 부분만 설명드려볼게요:
```javascript
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { useModal } from './use-modal';
const fetchDataMock = async () => {
const res = await new Promise((resolve) => {
setTimeout(() => {
resolve('API MODAL!!!');
}, 1000);
});
return res;
};
function CommonModal({ resolve, close, queryFn, queryKey }) {
// enabled false를 통해 초기 api 호출은 하지 않는다.
const { refetch, isLoading } = useQuery({
queryKey: queryKey ?? [],
queryFn: queryFn,
enabled: false,
});
const handleApprove = async () => {
// props. queryFn, queryKey가 있을 경우에만 데이터를 호출하여 받아온다:
if (queryFn && queryKey) {
const res = await refetch?.();
resolve(res?.data);
} else {
resolve('NORMAL MODAL!!!');
}
};
const handleReject = () => {
close();
};
return (
"에디 개발 여행기"를 구독하시겠습니까?
{isLoading ? '로딩 중...' : '예'}
아니오
);
}
```
이렇게 하면 쉽게 말씀하신 부분을 달성할수 있어요. 사용 방법은 queryKey, queryFn을 던져주는지에 따라 api 호출 혹은 api 호출을 하지 않을 수 있어요:
```javascript
export default function Test1() {
const modal = useModal();
// api를 호출할 수 있는 모달
const handleApiModal = async () => {
const res = await modal.push('CommonModal', CommonModal, {
queryKey: ['data'],
queryFn: async () => await fetchDataMock(),
});
// 'API MODAL!!!' 찍힘
console.log(res);
};
// api를 호출하지 않는 모달 -> 그냥 예 아니오로만 정의
const handleNormalModal = async () => {
const res = await modal.push('CommonModal', CommonModal, {});
// 'NORMAL MODAL!!!' 찍힘
console.log(res);
};
return (
Test1
CALL API MODAL
CALL NORMAL MODAL
);
}
```
그럼 도움이 되셨길 바라면서, 글을 마쳐보겠습니다.
좋은 하루 되세요! 그리고 다시 한번 좋은 댓글 남겨주셔서 감사합니다. 🙇🏻♂️
zustand를 이용해서 사용을 하다보니, 모달창이 띄워진채로 브라우저의 "뒤로가기" 누르면 모달창은 안닫히는 현상이 생겨서 조금 코드를 수정했습니다.
죄송합니다. 하나 짚고 갈게요. 리액트는 프레임워크가 아니라, “라이브러리”입니다.
안녕하세요 동료님. 영상 시청해주셔서 감사합니다.
동료님께서 꼼꼼하게 영상을 봐주셨네요. 👍혹시라도 있을 혼동을 바로잡을 기회를 주셔서 감사합니다. 🙇🏻♂
먼저 동료님께서 말씀주셨던 것처럼 "리액트는 라이브러리가 맞습니다."
이에 관한 정확한 근거는 라이브러리와 프레임워크의 차이점을 보아도 그렇고, 리액트 공식 문서를 봐도 그렇게 나와있습니다:
React is the "library" for web and native user interfaces
리액트 공식문서에서도 리액트를 "라이브러리"라고 함에도 불구하고 해당 영상에서 "리액트 프레임워크"라고 표현 했던 이유는 다음과 같습니다:
> 리액트를 사용하여 개발하는 환경에서는 리액트 프레임워크(ex> nextjs, remix, cra, ...) 를 사용하기 때문입니다.
보통 리액트로 개발을 한다고 하는 환경을 보면, 순수 리액트 라이브러리만 사용하고 있지는 않습니다. 실제로는 리액트 프레임워크(혹은 리액트 기반 프레임워크 라고 불림)를 사용하고 있습니다. 예를 들어 nextjs, remix, cra, ...가 있어요. 저의 modal 영상 스크립트를 보면 다음과 같은 형태로 되어있습니다:
01:27 리액트의 의존성을 최소화 하기 위함입니다.
01:30 모든 팀에서 "리액트 프레임워크"를 사용하고 있지는 않거든요.
01:33 그래서 모든 "프레임워크"에 적용이 가능한 형태로 만들어 놓기 위해 이러한 패턴이 만들어지게 되었습니다.
01:27의 스크립트는 "리액트" 라고 하다가 01:30에서는 "리액트 프레임워크" 라고 표현한 이유는 react에 기반한 모든 프레임워크를 지칭하고자 하는 말이었어요. 😅 해당 스크립트가 나오는 주제인 Flush 패턴이 정확하게 어느 정도 수준까지를 염두하고 만들어진 패턴인지 알려드리기 위해 이렇게 작성했던 거 같아요. 단지, 리액트 환경의 프레임워크가 아닌 현업에서 사용하는 프론트엔드 모든 "환경"에 대응하기 위해 만든 패턴을 강조하고 싶었습니다. 지금 생각해보니... 스크립트 작성에 간단하지만 많은 의미를 함축하려다 보니, 혼란스러울 수 있겠네요.
"리액트는 라이브러리인가? 프레임워크인가?"는 제가 면접을 볼 당시에 가장 단골 질문이었어요. 혹시라도 저의 영상을 보시고 혼동 되지 않으셨으면 하는 마음에 더 길게 답변을 적어보았어요. 과거에 저는 프레임워크라고 답변을 했었던 경험이 있기 때문이예요. 🥹
영상을 제작함에 있어 정보 전달은 쉬운 형태로 전달에 목적을 두기 때문에 딜레마에 빠지곤 합니다. 어느 정도까지 쉬운 수준으로 말씀을 드려야할까? 어느 정도까지 정보를 생략해도 되는걸까? 라는 고민이 드는 것 같아요. 하지만 언젠간 저도 이 딜레마에서 답을 찾게될 날을 약속드리며 글을 마쳐보겠습니다. 다시 한번 혼동스러울 부분을 말씀해주셔서 감사합니다. 🙇🏻♂
그럼 좋은 주말 되세요~~!