<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Hakunamatata</title>
    <link>https://iborymagic.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 5 Apr 2026 22:18:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>iborymagic</managingEditor>
    <image>
      <title>Hakunamatata</title>
      <url>https://tistory1.daumcdn.net/tistory/4210278/attach/bfbbeebb602446208b9dfffe5e6ed328</url>
      <link>https://iborymagic.tistory.com</link>
    </image>
    <item>
      <title>RM과 Pharrell의 대담</title>
      <link>https://iborymagic.tistory.com/141</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=1H1Mh5KT8LQ&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://www.youtube.com/watch?v=1H1Mh5KT8LQ&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=1H1Mh5KT8LQ&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/f3woi/hyQqUuMDkA/RgYKmdcubkSVaXyzoAwkvK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/1H1Mh5KT8LQ&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;P&lt;/b&gt;: 우선, 대충 인터뷰에 초대해줘서 고맙다는 뜻&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 대충 나도 고맙다는 뜻&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 너도 느끼겠지만, 다른 사람들을 위해 곡을 쓰고 프로듀싱할 수 있는 건 나 자신에게도 개이득이야. 평소의 나라면 안할 것들을 해보기도 하고.&lt;br /&gt;보통, 솔로 아티스트로서 다른 사람의 곡에 기여할 때는 내가 뭘 하고 있는지 알고 작업해. 아마 사람들 대부분은 모르겠지만, 내 모든 솔로 작업물들은 원래는 다른 사람들을 위해서 만들거나 프로듀싱했던 곡들이야. 사실, 나 자신을 위한 곡을 만들거나 프로듀싱하려고 하면 대개는 너무 복잡해지더라고. 보통 최상의 작업물은 나 자신을 살짝 내려놓을 때 나오드라. 너는 어떻든?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 2005년 즈음, 초딩이었던 나는, 그냥 래퍼가 되고 싶었어. 아직 랩이 뭔지도 정확히 몰랐는데도, 리듬과 운율을 바탕으로 세상에 메시지를 던질 수 있다는 게 나를 힙합에 빠지게 만들었지.&lt;br /&gt;사실, 내 우상 앞에서 이런 것들에 대답한다는 게 좀 부끄럽긴 하지만, 나도 너처럼 다른 아티스트의 가사나 멜로디를 써주는 작업이 보통은 더 편안하긴 하지. 보통 그런 작업은 내가 다른 인격이나 성격을 가지게끔 해주는 것 같아.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: ㅇㅈ&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: ㅇㅈ. 근데 내 작업을 할 때는 일이 복잡해지지. 일종의 고백같은 걸 해야하는 게 항상 고통스러워. 근데, 그건 동시에 나한테 가장 중요한 부분이기도 해.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: ㄹㅇ. 그 가장 고통스러운 부분이 가장 재밌는 부분이기도 해. 아티스트로서 사용할 수 있는 가장 선명한 물감같은 거지. 펀치라인이 되었으면 하는 벌스가 나한테 충분히 고통스러운가? 충분히 좋은가? 뭐 그런 느낌. 벌스를 강력하게 하기 위해 고통을 주입하는 느낌이랄까. 여튼, 네 말에 동의.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 너 자신을 위한 곡을 만들 때와, 다른 사람들을 위한 곡을 만들 때의 창작 과정이 정확히 어떻게 다른 지가 궁금해.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 보통, 아티스트들은 요청 사항을 한 가지씩 가지고 들어와. 빡센 걸 원한다든지, 어두운 느낌을 원한다든지, 클럽 음악을 달라든지. 그러면 나는 그 사람들이 원하는 걸 고려해주지. 물론 그들의 목소리 질감이나 보통 그 사람들이 사용하는 멜로디 패턴 같은 것들도 고려하고. 그리고 나면, 어떻게 해야 그 사람들이 이전에 했던 것과 다른 것들을 줄 수 있을지를 고민해. 뭐 그런 것들을 전부 고려하지.&lt;br /&gt;많은 경우에 아티스트들은 '야, 나랑 좀 다른 느낌인데?' 라고 퇴짜를 놓고, 나는 그 곡을 그냥 다른 사람한테 팔아버리지. 그러면 퇴짜놨던 사람이 나한테 와가지고 '야! 그 곡 왜 나한테 안들려줬어?' 이러지. 나는 들려줬는데, 지들이 제대로 안들어놓고.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 꿀잼 ㅋㅋ&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: ㄹㅇ 꿀잼이지. 물론, 틀을 깨고 나아가 시도해보는 사람들도 당연히 있어. 내 지론은 '그냥 해봐라' 라는 거야. 겁낼 필요가 없어. 하지만, 사람들은 자기가 하던 걸 너무 잘하는 나머지, 자신의 다른 목소리, 다른 성격, 다른 커리어 등을 모험하기 두려워하고 망설이지. 근데 그런 걸 시도해보면 새로운 걸 경험할 수 있어. 물론 그게 무조건 옳다는 건 아니지만, 가장 중요한 건, 다른 면을 볼 수 있어야 한다는 거야. 그게 내가 다른 사람들의 작업을 할 때 가장 좋아하는 부분이지.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 사람들이 도전하게 하는 걸 좋아하는군. 그러면 다른 질문. 내가 2005년에 힙합 음악을 듣기 시작할 때는, 당연히 가장 클래식한 힙합인 Nas나 Eminem부터 시작했지. 근데 네 음악, 특히 'Take it off' 같은 것들을 듣기 시작하면서, 나는 네 넓은 스펙트럼이 너무 갖고싶었어. 참고로, Take it off는 내 플레이리스트에 있어. 내가 아마추어일 때는 그 곡을 한국어로 번역해보기도 하고, 녹음도 해봤지 뭐야!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 와! 쩌는데.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 여튼. 요즘은 물론 장르의 경계가 무너졌지만, 내 기억으로는 그 당시만 해도 싱잉을 하거나 오토튠을 사용하는 래퍼를 비판하는 힙합 꼰대들이 있었던 것 같거든. 네가 프로듀서로 커리어를 시작해서 그런 것들이 자연스러운건지는 몰라도, 너는 랩도 하고 노래도 하고, 팔세토도 내고, 다른 아티스트들을 위해서 훅을 불러주기도 하잖아. 그래서 질문은. 네가 플레이어로서 곡에 참여할 때, 너는 어떤 방식으로 네 포지션을 정하는 지가 궁금해.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 와우! 여태까지 한 번도 받아본 적 없는 개쩌는 질문이어서 감탄했어!&lt;br /&gt;보통, 나는 모든 것들을 '느낌' 으로 정해. 무슨 정해진 틀같은 게 따로 있진 않아. 그냥 그게 필요할 것 같은 느낌이 오지. 그리고 나는 그걸, 나보다 더 잘 소화할 수 있는 사람에게 최선을 다해서 연결해주려고 하지. 그러면 대개 아티스트들은 &quot;아니야, 그건 네가 해야지.&quot; 라고 하지만, 나는 &quot;하지만 그건 이 사람을 위한 건데&quot; 라는 식이지. 나는 뭔가 비어있는 것 같은 걸 채우려고 하는데, 그걸 내가 할 거라는 생각은 보통 하지 않아. 왜냐면, 내가 그걸 한다고 생각해버리면 별로 결과가 좋지도 않고, 자신감도 없어지더라고. 나는 다른 사람을 지도하고 디렉팅할 때 가장 자신 있어지는 것 같아. 예를 하나 들자면, 예전에 내가 미스티컬(Mystikal, 힙합 가수)과 작업한 적이 있는데..&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 미스티컬! 지리네.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 지리지. 'Shake ya azz' 작업이었어. 그걸 작업할 때 채드(Chad, 프로듀서인듯?)랑 같이 작업했는데, 그 노래 훅을 쓸 때 나는 The Temptations의 Eddie Kendricks가 훅을 부를 거라고 생각하고 썼어. 내가 &quot;Temptations가 훅을 조질거야!&quot; 라고 말했던 거도 기억나. 근데 그들은 &quot;엥, 음반 회사는 니가 훅을 조지길 원하던데&quot; 라고 하더라.&lt;br /&gt;그러면서 신기하게도, 나는 점점 내가 다른 사람들에게 내 작업물을 연결해주고(자기가 직접 작업 안한다는 뜻인듯), 내 자아나 감정의 개입 없이, 해당 음악이 진짜 필요로 하는 거에 집중하는 데에 장점이 있다는 걸 깨달아갔던 것 같아. 그러면서 더 나은 결정을 할 수 있었지. 결국 나중에 그걸 내가 부르게 되건 어쨌건, 빈 자리를 메꾸고 좋은 느낌을 줄 수가 있게 됐어. 가끔 내가 노래를 해야 할 때면 날카로워지기도 하지만, 느낌이 좋으니까 뭐. 그대로 가는거지.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 우리 2018년에 빌보드 뮤직 어워드에서 만났던 거 기억하니?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 물론. 사진도 찍었잖아. 그 때 바로 같이 일하고 싶었다고 ㅋㅋ. 저돌적이고 싶지 않아서 &quot;에~ 사진 찍어요~&quot; 했지만 바로 작업실로 델꼬 갈뻔했지. 내 기억으로는 우리가 백스테이지에서 사진 찍었던 것 같은데?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 맞아. 사실 네 대기실이었어.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 맞아. 내가 일하던 작업실이 그리 멀지 않았어서, 바로 당장 다 때려치우고(fuck all of this), 아 잠깐. 네 팬들이 나를 싫어하는 걸 원치 않으니까 바른 말 써야지 ㅎㅎ; 거길 당장 떠나서 개쩌는 음악을 같이 만들고 싶었지. 너네들(BTS)이 하는 모든 것들이 너무 좋았고, 에너지도 좋고, 너네들이 아시안 대표잖아. 아시안이라고 막 구분을 하는 게 아니고, 너도 알다시피 요즘 아시안 커뮤니티가 점점 활발해지고 있잖아. 아시안 커뮤니티가 나에게 너무 많은 걸 주고 있고.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 그치. 점점 더 커지고 있지.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 그리고 너네들은 사람들에게 '겸손' 이라는 걸 몸소 보여주잖아. 너네는 모두 그 겸손의 에너지를 내뿜고 있어. 개쩌는 에너지지. 또, 사람들이 잘 모르는 게 있는데, 너는 말 그대로 수백 수천만의 팬들을 한 번에 마주하잖아.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 그치. 얼굴을 알아보기도 힘들어. 말 그대로 그냥 'mass(군중)'이야. 앗, mess(엉망) 아님 오해 ㄴㄴ&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 맞아. 엄청난 군중이고 엄청난 목소리지. mess랑 헷갈리니까 massive(엄청난)라고 하자. 진짜로 엄청난 에너지잖아. 그 사람들이 네가 뛰라고 하면 뛰고, 네가 노래부르면 모든 마디를 따라부르지. 그리고 너는 그들의 목소리로부터, 그들의 삶이 너로 인해 영향받고 변화해왔다는 걸 느끼잖아. 그런 걸 도대체 어떻게 하는지 모르겠어. 왜냐면, 나도 그런 곡들이 몇 개 있는데, 나는 무대에서 그 노래들을 부를 때마다 너무 많은 책임감이 느껴져서 울어버리거든. 내가 뮤지션으로써 느끼는, 그런 엄청난 책임감이 느껴질 때마다 나는 항상 한 발짝 물러서게 돼.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 오. 왜 그런거야? 너무 부담돼?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 겁나 부담되지. 너무 엄청난 책임감이야. 그게 바로 내가 너나 BTS 같은 사람들을 존경하는 이유야. 나는 매일 밤 무대에서 그들을 마주할 때마다 겁나 겸손해지고, 압도당하고, 가끔은 우리의 신경계가 그걸 위해 만들어진건가 싶기도 해. 여기서 질문 하나. 너는 매일 밤 그런 놀라움과 충격들을 마주할텐데, 무대에서 내려와서는 어떻게 그것들을 감당하는 거야?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 흠. 내 첫 퍼포먼스는 내가 15살 때, 작은 클럽에서 10명 남짓한 사람들 앞에서 한 무대였어. 그 때 가사를 거의 다 까먹었는데, 그 때 나는 내가 스타가 될 타입은 아니라고 생각했지. 무대를 즐기는 Kurt Cobain이나 Mick Jagger 같은 프론트맨이 아니라, 그냥 그저 노래 만드는 거나, 그런 에너지를 좋아하는 평범한 인간같은 느낌. &lt;br /&gt;매일 밤, 음... 예를 들어, 지난 4월 라스베가스에서 네 번 공연을 했는데, 말 그대로 그냥 매일 밤이 도전이었지. 셋 리스트도 동일하고, 공연 자체도 아주 약간씩은 다르지만 거의 비슷한 흐름으로 흘러가는데도 말이야..&lt;br /&gt;네가 말했던 것처럼, 처음 오프닝 세 곡을 끝마치고 났을 때, 우리는 이어폰을 뽑고 &quot;We're fucking back!&quot; 했어. 그러니까 관객들이 소리를 막 지르더라고. 그러면 원래의 나는 사라지고, 내 또 다른 자아가 나머지 두시간 반을 잡아먹지.&lt;br /&gt;하지만 그 전에는, 예를 들어 리허설 혹은 더 나아가 비행기 안에서의 나는 겁나 긴장 상태고 엄청난 책임감에 빠져있어. 왜냐면 나는 팬들이 브라질, 일본, 한국 등 세계 각지에서 그 단 하룻밤을 위해 티켓을 사서 온다는 것을 겁나 의식하거든. 그럼 나는 그 값어치를 해야한다고 생각해. 그들 인생에서 최고의 하룻밤을 선물해야한다는 생각을 하게 돼. &lt;br /&gt;그래서, 사실 정말 엄청난 감정 소모를 하게 되지. 나도 결국 한 명의 사람일 뿐이라서, 진짜 많이 긴장해. 가끔은 우울해지기도 하고, 그 엄청난 에너지에 잡아먹히기도 해. 하지만 그것들을 다루기 위해 많이 노력하지.&lt;br /&gt;나는 음악과 팬들이 주는 사랑을 좋아해. 너도 알다시피, 진짜 사랑은 우리가 사랑을 받을 때가 아니라, 줄 때 생겨나잖아. 그래서 나는 그들에게 사랑을 되돌려주고 싶어. 그들은 한국의 작은 도시에서 라스베가스, LA, 뉴욕같은 음악 산업의 중심부로 나를 데려와줬고, 퍼렐과 인터뷰를 하게 해줬어. 이 모든 것들은 전 세계의 팬들 덕분에 가능했던 일이잖아? 그래서 항상 너무 감사해. 그들을 실망시키고 싶지 않아.&lt;br /&gt;너는 프로듀서이기도 하고, 래퍼, 가수, CEO이기도, 아빠, 남편이기도 하잖아. 물론, '퍼렐'이라는 이름이 모든 것들을 설명해주긴 하지만, 그건 일단 차치하고. 너는 네 자신을 어떻게 정의할 것 같아?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 글쎄. 일단은 공무원? &lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 공무원? 오 좋은디~&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 대중을 위해 뭔가를 하는.. 약간 신의 뜻이랄까. 굉장히 놀랍게도, 우주는 실존하지.. 나한테, 신은 우주거든.. 우리는 우주 안에 있고..&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: ? ㅎㅎ;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 아버지이기도 하고... 공무원이기도 하고.. 그리고, 음악은 내게 모든 것이기도 해. 음악은 모든 문을 여는 스켈레톤 키(마스터키)지.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 대충 맞장구&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 그치! 모든 것의 중심이기도 해. 음악이 없었다면, 나는 내가 지금 할 수 있는 것들을 하나도 할 수가 없었을 거야. 물론, 나도 목적 의식의 부재로 인해 굉장히 힘들었던 때도 있었지.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 언제?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 허.. 대충 한 2005년.. In My Mind 앨범 냈을 때였을거야. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 그 직후에?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 음, 내가 원하던 대로 흘러가지 않았을 때 즈음? 물론 문화적으로는 꽤 인상을 줬지만, 내 스스로는 만족스럽지 못했어. 내가 그 전에 하던 것만큼 잘하지 못했고, 꽤 충격이었지. 그래서 그 때부터 목적이라던가, '진짜' DNA를 가진다던가 하는 것들에 관해 생각해게 됐어. 예술적인 집착을 넘어선 진짜 의미. 사람들에게 뭔가 의미가 있으면서 동시에 재밌기도 한. 너도 알다시피, 난 항상 여자를 좋아했는데, 그것도 놓칠 수 없었지.&lt;br /&gt;그래서, 음. 나도 그게 뭔지 정확히 이해해. 네 커리어에 있어서, 지금 너 정도의 위치까지 도달한다는 게 정확히 어떤 의미인지. 어떤 이유가 됐든, 너희들은 물론 잘하고 있지만, 내가 듣고 이해한 바에 따르면, 너희들은.. &quot;우리가 지금 뭘 하고 있는거지? 우리는 누구지? 우리가 말했던 그 사람들이 맞나?&quot; 같은 생각을 하고 있는 것 같아. 사실 나는, 네가 누구인지, 어떤 의미를 가지는지, 어떤 의도를 가지고 있는지.. 같은 것들을 생각하는, 그 과정에서 네가 어떤 사람이 되고 싶은지가 결정된다고 생각하는데. 어때? 그 과정 중에, 넌 어디쯤 있는 것 같아? 왜냐면, 지금 솔로 음반 녹음 중이잖아.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 맞아. 음반은 한 90%쯤 된 것 같아. 물론 밴드(BTS) 멤버로서 믹스테잎을 낸 적은 몇 번 있지만, 그건 그냥 경험 삼아 하는 정도였고. 이번에는 진짜 내가 충분히 영향을 끼칠 수 있는, 내 첫 정규 솔로 앨범이 될거야. 흠, 나도 잘 모르겠어. 이 앨범을 발매하고 나면 네가 In My Mind를 낸 후에 느꼈던 거를 나도 느끼게 될지. 발매하면 알게 되겠지. 시간에 달렸어. &lt;br /&gt;우리가 팀으로 데뷔한 지가 10년 정도 됐거든? 너도 알겠지만, 케이팝에서는 자기가 속한 밴드나 그룹이 가장 중요해. 나는 개인적으로는 래퍼로써, 그리고 시인으로써 내 커리어를 시작했는데, 좀 까다로웠지. 알다시피, 케이팝이 약간 잡탕밥이잖아. 아메리칸 팝 음악, 시각적인 것들, 안무, 소셜 미디어 등 모든 것들이 섞여서 겁나 빡세고 정신없었어. 물론 장단점이 있었지만. &lt;br /&gt;그렇게 10년이 지나니까, 우리의 의도와는 상관없이, 우리가 약간 어떤 사회적인 위인(social figure)같은 게 돼있더라고. 유엔에서 연설하는 케이팝 밴드라거나.. 대통령을 만난다거나.. 엄청 혼란스러웠어. '나는 뭐지..? 뭐 외교관 같은건가..?'. 나는 어렸을 때는 그저 일개 래퍼나 작사가일 뿐이었는데..&lt;br /&gt;그래도 10년 동안 팀으로써 진짜 개빡세게 살았지. 팀에서 모든 인터뷰같은 것들을 거의 도맡다시피 했고, 다른 멤버들을 대신하여 팀을 대표하고.. 그런 게 내 역할이었어. 그래서, 내 생각에는.. 잘 모르겠어. 약간, '요, 이제 잠깐 멈춰야겠어. 모든 것을 중단하고, 좀 떨어져서 뭐가 어떻게 흘러가는지를 지켜봐야겠어' 하는 식으로 마음을 좀 가라앉혔지. 그렇게 하니까 솔로 앨범에 집중할 수 있겠드라.&lt;br /&gt;요즘에는 15살 때, 내가 처음 네 음악을 들었을 때. 그 때를 생각해. 처음 그 감정, 그 바이브.. 조명.. 온도.. 습도.. 내가 왜 음악을 내 업으로 선택했는지.. 그런 것들에 관해서.&lt;br /&gt;나는 14살에 처음 음악을 시작했고, 지금은 28살이야. 아, 뭐 물론 그래도 네 경력의 절반 정도밖에 안되는 짧은 경력이지만.. 여튼. 나는 네가 말한 그 과정 중에 있는데, 굉장히 까다롭고, 혼란스럽고, 어떻게 될지 진짜 모르겠어. 그래서 말인데, 니는 물론 케이팝이랑은 거리가 멀지만 밴드에 몸을 담기도 했었고, NERD나, Neptunes나, 네 솔로 앨범들은 물론이고, 그런 여러가지 프로젝트들을 해왔잖아. 혹시 떠오르는 조언같은 게 좀 있을까?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 사실, Neptunes가 되는 것, NERD가 되는 것, 솔로 작업을 하는 것 등등 그 모든 것들이 나에게 도움이 됐어. 하나를 하고, 좀 쉬고, 다른 하나를 하고, 또 좀 쉬고, 하, 쉬, 하 쉬..&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: 계속 바꾸면서?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 그치. 그렇게 하면 여러가지 다른 역할을 할 수 있어(put on different hats and masks). 그래서, 나는 알고있지. 그런 식으로 여러가지를 해보면 굉장히 신선할 거라는 걸.. 그리고 내 생각에는 그렇게 하는게 너한테도 더 좋을 수 있어. 왜냐면, 그렇게 하고 나서 나중에 팀으로 돌아갔을 때, 생각보다 엄청 신선할걸? 굉장히 많은 아이디어들이 떠오를거고..&lt;br /&gt;내가 말하고 싶은 건, 계속해서 전진하고, 계속해서 호기심을 가지고, 네가 하는 것에 있어 스스로를 압박하지 마. '절대 음악 다시는 안할거야' 라던가.. 절대라는 건 없어. 그냥 흘러가는 대로 가. 계속 가.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;R&lt;/b&gt;: Cruising(자동차의 크루즈 모드를 말하는 듯) 처럼 말이지.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;P&lt;/b&gt;: 그치. 그냥 네가 어디까지 갈지 한 번 지켜봐. 겁나 재밌을거야.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;R&lt;/b&gt;: 고마워. 너무 좋은 시간이었어.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;P&lt;/b&gt;: 나도 좋았어.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;기사&lt;br /&gt;&lt;a href=&quot;https://www.rollingstone.com/music/music-features/bts-rm-pharrell-williams-interview-1234610171/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://www.rollingstone.com/music/music-features/bts-rm-pharrell-williams-interview-1234610171/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;'What If I Don't Like Music Anymore?': A Wildly Honest Conversation Between BTS' RM and Pharrell Williams&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;BTS and Pharrell have a secret collab coming &amp;mdash; and that&amp;rsquo;s just one of the revelations from this meeting of two superstars&quot; data-og-host=&quot;www.rollingstone.com&quot; data-og-source-url=&quot;https://www.rollingstone.com/music/music-features/bts-rm-pharrell-williams-interview-1234610171/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bfQ0Ke/hyQqTvRPMt/b4Ro6wD9W6tUV8Q3NFek5k/img.jpg?width=1600&amp;amp;height=900&amp;amp;face=177_150_1407_605,https://scrap.kakaocdn.net/dn/vaeFF/hyQqXE2cOI/ZmrNYo87c2PdjedBOznKr1/img.jpg?width=1600&amp;amp;height=900&amp;amp;face=177_150_1407_605&quot; data-og-url=&quot;https://www.rollingstone.com/music/music-features/bts-rm-pharrell-williams-interview-1234610171/&quot;&gt;&lt;a href=&quot;https://www.rollingstone.com/music/music-features/bts-rm-pharrell-williams-interview-1234610171/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.rollingstone.com/music/music-features/bts-rm-pharrell-williams-interview-1234610171/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bfQ0Ke/hyQqTvRPMt/b4Ro6wD9W6tUV8Q3NFek5k/img.jpg?width=1600&amp;amp;height=900&amp;amp;face=177_150_1407_605,https://scrap.kakaocdn.net/dn/vaeFF/hyQqXE2cOI/ZmrNYo87c2PdjedBOznKr1/img.jpg?width=1600&amp;amp;height=900&amp;amp;face=177_150_1407_605');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;'What If I Don't Like Music Anymore?': A Wildly Honest Conversation Between BTS' RM and Pharrell Williams&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;BTS and Pharrell have a secret collab coming &amp;mdash; and that&amp;rsquo;s just one of the revelations from this meeting of two superstars&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.rollingstone.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;갑분영어공부..&lt;br /&gt;처음에는 그냥 '와 퍼렐 윌리엄스랑 RM이..!?' 하는 생각에&lt;br /&gt;신기하기도 하고, 듣고 보니 내용도 재밌고 해서 번역 한 번 해봐야겠다 했는데,,&lt;br /&gt;사실 RM은 편안했는데 퍼렐 영어가 개어려웠다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이거 보고 나서 RM 솔로 앨범 쭉 들어봤는데 너무 좋아서 깜짝 놀랬다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;오역, 의역 주의  &lt;/p&gt;</description>
      <category>et cetera/...</category>
      <category>Indigo</category>
      <category>pharrell</category>
      <category>rm</category>
      <category>Rolling Stone</category>
      <author>iborymagic</author>
      <guid isPermaLink="true">https://iborymagic.tistory.com/141</guid>
      <comments>https://iborymagic.tistory.com/141#entry141comment</comments>
      <pubDate>Sun, 8 Jan 2023 21:07:20 +0900</pubDate>
    </item>
    <item>
      <title>스크롤을 최적화하고 싶은데..</title>
      <link>https://iborymagic.tistory.com/142</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;스크롤 애니메이션이 뭔가 이상해요  &quot;&lt;br /&gt;&lt;br /&gt;스크롤 애니메이션을 고쳐달라는 요구사항이 새로 들어왔다.&lt;br /&gt;정확히 말하면 애니메이션은 아니고, 스크롤할 때 동작하는 간단한 로직에 문제가 있었다.&lt;br /&gt;문제가 된 스크롤 동작은 fixed 상태인 특정 컴포넌트가 스크롤로 인해 지정된 범위를 벗어나려고 하면&lt;br /&gt;해당 범위의 끄트머리를 넘어서지 않도록 위치를 고정해주는 동작이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NfzN5/btrRdyx4BdH/fHVE1IvfmSEQKRFElmKiCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NfzN5/btrRdyx4BdH/fHVE1IvfmSEQKRFElmKiCk/img.png&quot; data-alt=&quot;정확히는 '컴포넌트가 움직여야 하는 범위' 가 맞겠다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NfzN5/btrRdyx4BdH/fHVE1IvfmSEQKRFElmKiCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNfzN5%2FbtrRdyx4BdH%2FfHVE1IvfmSEQKRFElmKiCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;313&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정확히는 '컴포넌트가 움직여야 하는 범위' 가 맞겠다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;사실 요 동작 자체만으로는 문제가 없었지만, 여기에 throttle이 더해지자 문제가 생겼다.&lt;br /&gt;원래는 스크롤을 할 때마다 컴포넌트의 위치를 지속적으로 감시하면서 위치를 고정시켜줘야 하는데,&lt;br /&gt;throttle로 인해 로직이 띄엄띄엄 실행되는 바람에 감시를 못하고 놓치는 구간이 발생했다.&lt;br /&gt;그래서 컴포넌트가 범위 밖으로 튀어나갔다가 다시 들어오는 현상이 나타났고, 요 부분이 문제가 되었던 것.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lSTx8/btrRs9lJpfq/jvKqkqK6aKciapPWfmcX01/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lSTx8/btrRs9lJpfq/jvKqkqK6aKciapPWfmcX01/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lSTx8/btrRs9lJpfq/jvKqkqK6aKciapPWfmcX01/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/lSTx8/btrRs9lJpfq/jvKqkqK6aKciapPWfmcX01/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;318&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;사실, throttle만 없애면 간단하게 해결되는 문제였다.&lt;br /&gt;&lt;br /&gt;하지만 throttle을 없애고 아무런 최적화 없이 스크롤 로직을 그대로 내보내기에는&lt;br /&gt;우리 팀이 키워낸 이 아들래미같은 프로젝트가 누군가에게 트집 잡히지는 않을까 무서웠다.&lt;br /&gt;그래도 뭔가 최소한의 최적화는 해둬야하지 않을까 하는 생각.&lt;br /&gt;아들래미를 학교에 보내는데, 책가방도 안 맨 채로 보낼 수는 없는 노릇이었다.&lt;br /&gt;&lt;br /&gt;결국 throttle은 없애되, 다른 기법을 통해 스크롤 동작을 최적화해주기로 했다.&lt;br /&gt;마침 팀 동료인 아그라작님께서 과거에 유사한 이슈로 고생을 하신 적이 있던 터라&lt;br /&gt;반가워하며 옆자리로 달려와 도와주셨다.&lt;br /&gt;매번 느끼지만, 팀 동료를 잘 만나는 건 정말 큰 행운이 아닐 수 없다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;우선, 아그라작님께서 소개해주신 최적화 방법은 &lt;b&gt;passive event listener&lt;/b&gt;를 활용하는 것이었다.&lt;br /&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Passive event listener&lt;/span&gt;&lt;/a&gt;는 터치/휠을 통한 스크롤에 있어 성능적인 이득을 볼 수 있게 해주는 스펙이다.&lt;br /&gt;&lt;br /&gt;보통 브라우저는 사용자가 e.preventDefault로 이벤트의 기본 동작을 차단하는지 여부를 검사하는 시간이 필요하다.&lt;br /&gt;이로 인해, 자바스크립트의 이벤트 I/O는 기본적으로, 아주 미세하지만 약간의 방해를 받을 수 밖에 없다.&lt;br /&gt;브라우저가 해당 이벤트(특히 스크롤 이벤트)를 처리하는 과정에서 약간의 동작이 더해지고,&lt;br /&gt;그걸 처리하는 시간만큼 메인 쓰레드가 추가로 시간을 쓰게 되는 것.&lt;br /&gt;&lt;br /&gt;이 동작을 생략함으로써 이벤트 성능을 개선하기 위해 만들어진 스펙이 바로 passive event listener.&lt;br /&gt;addEventListener를 호출할 때 세 번째 인자로&lt;b&gt; { passive: true }&lt;/b&gt; 를 넣어주면 된다.&lt;br /&gt;해당 이벤트 리스너에서는 e.preventDefault를 호출하지 않을거라고 브라우저에게 미리 알려주는 것.&lt;br /&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(만약 해당 옵션을 적용하고 preventDefault를 호출하면 에러 콘솔 정도만 뜬다고 함)&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;그러면 이벤트 동작이 조금 더 빨라진다고 한다.&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;window.addEventListener('scroll', onScroll, { passive: true });&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;참고로, 사파리와 IE를 제외한 나머지 브라우저들에서는&lt;br /&gt;wheel, mousewheel, touchstart, touchmove 이벤트에서 passive 옵션의 기본값이 true라고 한다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;사실, passive event listener 정도만 적용해줘도 크게 문제는 없을 정도의 간단한 로직이긴 하다.&lt;br /&gt;하지만 아까전에 혼자 조사하면서 봤던 다른 최적화 방법도 한 번 적용해보고 싶은 마음에&lt;br /&gt;찝찝함이 가시지 않던 찰나, 아그라작님께서 먼저 말을 꺼내주셨다.&lt;br /&gt;&lt;br /&gt;&quot;아, 그리고 사실 여기서 최적화를 한 번 더 할 수는 있는데..&quot;&lt;br /&gt;&quot;오, 혹시 requestAnimationFrame을 이용하는 방법!?&quot;&lt;br /&gt;&quot;오 맞습니다. 가시죠&quot;&lt;br /&gt;&lt;br /&gt;내 마음을 읽으신 것일까. 척 하면 착이다.&lt;br /&gt;&lt;br /&gt;requestAnimationFrame(이하 rAF) API는 콜백함수를&lt;br /&gt;task queue, microtask queue와 구분되는 별도의 큐를 사용하여 비동기로 처리한다.&lt;br /&gt;큐를 부르는 명칭은 다양한 것 같은데, &lt;a href=&quot;https://www.w3.org/TR/animation-timing/#dfn-animation-frame-request-callback-list&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;요기&lt;/span&gt;&lt;/a&gt;에서는 그냥 animation frame request callback list 라고 주절주절 부르더라.&lt;br /&gt;어차피 내 블로그니까 그냥 내 맘대로 줄여서 animationFrames 큐라고 부르겠다.&lt;br /&gt;&lt;br /&gt;rAF을 사용하면 인자로 들어오는 콜백함수를 브라우저의 렌더링 주기에 맞춰, 다음 번 repaint가 실행되기 전에 호출한다.&lt;br /&gt;보통 일반적으로 60fps(1초에 60번, 약 16ms에 한 번)이지만 모니터의 주사율과 일치하게끔 유동적으로 맞춰진다고 한다.&lt;br /&gt;그래서 주사율에 딱 맞춘 부드러운 애니메이션 재생이 가능하다.&lt;br /&gt;심지어 백그라운드 탭에 있거나 하는 경우에는 함수 실행을 중단해주기까지 한단다.&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;const isScrolling = useRef(false);
const timerIdRef = useRef&amp;lt;number | null&amp;gt;(null);

const scrollHandler = () =&amp;gt; {
&amp;nbsp;&amp;nbsp;// 스크롤 어쩌구 저쩌구
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;if (isScrolling.current) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requestAnimationFrame(scrollHandler);
&amp;nbsp;&amp;nbsp;}
};

useEffect(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;const onScroll = () =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isScrolling.current = true;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requestAnimationFrame(scrollHandler);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (timerIdRef.current) window.clearTimeout(timerIdRef.current);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timerIdRef.current = window.setTimeout(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isScrolling.current = false;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}, 100);
&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;window.addEventListener(&quot;scroll&quot;, onScroll, { passive: true });

&amp;nbsp;&amp;nbsp;return () =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;window.removeEventListener(&quot;scroll&quot;, onScroll);
&amp;nbsp;&amp;nbsp;};
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드를 보면, 단순히 rAF 말고도 아그라작님께서 약간의 최적화를 더해주셨다.&lt;br /&gt;&lt;br /&gt;요 스크롤 로직이 붙어있는 페이지는 저 위에 GIF에서도 보이듯이,&lt;br /&gt;보내는 사람이나 받는 사람같은 몇 가지 정보를 입력하는 form이 포함된 페이지다.&lt;br /&gt;그래서 사실상 스크롤이 멈춰있는 시간이 훨씬 많다고 볼 수 있는데, 그 동안 계속 rAF이 돌아가는 건 상당한 비효율이다.&lt;br /&gt;&lt;i&gt;(rAF은 인자로 들어가는 콜백함수 내부에서 &lt;/i&gt;&lt;i&gt;또 다시 rAF을 호출하는 것이 원칙이므로, &lt;/i&gt;&lt;br /&gt;&lt;i&gt;한&lt;/i&gt;&lt;i&gt; 번 실행하고 나면 해당 콜백이 재귀로 계속 실행된다)&lt;/i&gt;&lt;br /&gt;그래서 별도의 타이머를 두어 스크롤 이후 0.1초까지만 콜백을 호출하도록 처리해준 것.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 웬걸, 요렇게 하고 나서 다시 CDD(Console.log Driven Development)를 통해 확인을 해봤더니..&lt;br /&gt;휠로 스크롤 한 틱 굴렸을 뿐인데 로그가 거의 180개 가까이 찍혔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLotpb/btrUJj4vcrW/WGlPG1VnvVyowpuQZ2XkM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLotpb/btrUJj4vcrW/WGlPG1VnvVyowpuQZ2XkM0/img.png&quot; data-alt=&quot;나중에 사진을 찍으려니 또 170개로 줄었네&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLotpb/btrUJj4vcrW/WGlPG1VnvVyowpuQZ2XkM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLotpb%2FbtrUJj4vcrW%2FWGlPG1VnvVyowpuQZ2XkM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;33&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;나중에 사진을 찍으려니 또 170개로 줄었네&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;분명 rAF는 주사율에 맞게 최적화를 해주는 함수라고 했는데..&lt;br /&gt;운 좋게도 180Hz의 주사율을 가진 초고사양 게이밍 맥북프로에 당첨된 것일까?&lt;br /&gt;그럴리는 없으니, 아무래도 뭔가 잘못된 것이 틀림없다.&lt;br /&gt;&lt;br /&gt;로그가 주사율을 훌쩍 뛰어넘도록 반복해서 찍힌 이유는 뭘까.&lt;br /&gt;rAF 함수가 실행되는 도중에 rAF가 또 호출되어서 멀티 스레딩이 되는 것일까? 하지만 호출 스택은 하나 뿐이다.&lt;br /&gt;궁금증을 가지고 조사를 하던 중, 캘리포니아에 거주 중이신 구글 크롬 팀의 폴 아이리쉬씨에게 &lt;a href=&quot;https://medium.com/@paul_irish/requestanimationframe-scheduling-for-nerds-9c57f7438ef4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;힌트&lt;/span&gt;&lt;/a&gt;를 얻을 수 있었다.&lt;br /&gt;거기에 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;MDN&lt;/span&gt;&lt;/a&gt;과 약간의 실험을 더하고 나니, 다음과 같은 결론에 도달했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wAfco/btrUDh1fsbM/0nGU2tUubZYynjnlVKuJoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wAfco/btrUDh1fsbM/0nGU2tUubZYynjnlVKuJoK/img.png&quot; data-alt=&quot;여러 개의 콜백들이 한 프레임 안에서 한꺼번에 실행될 수 있다는 뜻?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wAfco/btrUDh1fsbM/0nGU2tUubZYynjnlVKuJoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwAfco%2FbtrUDh1fsbM%2F0nGU2tUubZYynjnlVKuJoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1614&quot; height=&quot;430&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;여러 개의 콜백들이 한 프레임 안에서 한꺼번에 실행될 수 있다는 뜻?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&lt;/b&gt; animationFrames 큐에는 rAF에 의해 실행된 콜백들이 들어간다.&lt;br /&gt;&lt;b&gt;2.&lt;/b&gt; 이벤트 핸들러에서 처음 실행된 rAF는 이번 프레임에, 해당 rAF 내에서 실행된 또 다른 rAF는 다음 프레임에 실행된다.&lt;br /&gt;즉, 기본적으로 한 프레임 당 한 번 콜백 함수를 실행시킨 후 해당 결과를 반영하여 화면을 업데이트한다.&lt;br /&gt;&lt;b&gt;3.&lt;/b&gt; 콜백 함수는 반드시 의도된 프레임 내에 모두 실행된다.&lt;br /&gt;&lt;b&gt;4. &lt;/b&gt;즉, 만약 콜백의 실행 시간이 한 프레임보다 길거나, 여러 개의 rAF를 실행하여 큐에 여러 개의 콜백들이 쌓이게 되면&lt;br /&gt;브라우저는 이 콜백(들)을 프레임에 맞게 나누어 실행하는 게 아니다. 그냥 전부 실행할 때까지 화면을 업데이트하지 않는다.&lt;br /&gt;&lt;i&gt;(즉, 화면 업데이트도 미뤄진다. 이는 animation jank로 이어짐)&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;여기서 내 경우는 스크롤 이벤트가 좌라라락 발생하면서 스크롤 이벤트 핸들러를 여러 번 실행시키는데,&lt;br /&gt;이 때 여러 개의 rAF 콜백들이 큐에 들어가게 되면서 해당 콜백들이 한 프레임 내에서 함께 실행된 것이렷다.&lt;br /&gt;&lt;br /&gt;즉, 주사율보다 훨씬 많은 횟수의 호출이 가능했던 건 한 프레임동안 콜백 함수가 여러 번 실행됐기 때문이라는 결론.&lt;br /&gt;&lt;br /&gt;어차피 화면 업데이트는 한 프레임에 한 번인데, 그 동안 동일한 콜백함수를 여럿 실행하는 건 너무 낭비라는 생각이 든다.&lt;br /&gt;그렇다면, 화면 업데이트 횟수에 맞춰 한 프레임에 콜백함수를 딱 한 번만 실행하는 방법은 없을까?&lt;br /&gt;이를 위해 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;MDN&lt;/span&gt;&lt;/a&gt;은 &lt;b&gt;DOMHighResTimeStamp&lt;/b&gt;를 해결책으로 제시하고 있다. (MDN 보면 예제도 있음)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7SQcx/btrUGHSBK4Y/tFCjAcSMLaKMWFIBC7zEbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7SQcx/btrUGHSBK4Y/tFCjAcSMLaKMWFIBC7zEbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7SQcx/btrUGHSBK4Y/tFCjAcSMLaKMWFIBC7zEbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7SQcx%2FbtrUGHSBK4Y%2FtFCjAcSMLaKMWFIBC7zEbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1636&quot; height=&quot;326&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;rAF의 콜백 함수는 첫 번째 인자로 'DOMHighResTimeStamp'를 받는다.&lt;br /&gt;이름이 뭔 뜻인지는 모르겠고, 대충 현재 시각을 의미하는 timestamp라고 한다.&lt;br /&gt;큐에 들어가있다가 동일한 프레임 내에 실행되는 콜백함수들은 전부 동일한 timestamp를 부여받는데,&lt;br /&gt;이는 곧 프레임이 달라지면 이 timestamp도 달라진다는 것을 의미한다.&lt;br /&gt;&lt;br /&gt;그리하야, 이 timestamp를 비교함으로써 동일한 프레임 내에 실행되는 나머지 콜백들을 전부 걸러주면 되는 것이렷다.&lt;br /&gt;참고로, 이 timestamp는 &lt;b&gt;performance.now()&lt;/b&gt; 값으로 대신해서 사용할 수 있다고 함.&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;const isScrolling = useRef(false);
const timerIdRef = useRef&amp;lt;number | null&amp;gt;(null);
const prevTimestampRef = useRef&amp;lt;number | null&amp;gt;(null);

const scrollAnimationHandler = (timestamp: number) =&amp;gt; {
&amp;nbsp;&amp;nbsp;// timestamp가 이전과 동일하다면 걸러주는 로직 추가 
&amp;nbsp;&amp;nbsp;if (prevTimestampRef.current === timestamp) return;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;// 스크롤 어쩌구 저쩌구
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;if (isScrolling.current) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// timestamp 갱신
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;prevTimestampRef.current = timestamp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requestAnimationFrame(scrollAnimationHandler);
&amp;nbsp;&amp;nbsp;}
};

// ...&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;짜잔! 요렇게 호출 횟수를 훨씬 줄일 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdBFa6/btrUE4tNV8M/wadBFpSyPDsE8PQ4460Ta0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdBFa6/btrUE4tNV8M/wadBFpSyPDsE8PQ4460Ta0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdBFa6/btrUE4tNV8M/wadBFpSyPDsE8PQ4460Ta0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdBFa6%2FbtrUE4tNV8M%2FwadBFpSyPDsE8PQ4460Ta0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;34&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;66&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 구현한 건 requestAnimationFrame 까지지만, 이것저것 보다보니 frame lifecycle에 관심이 생겼다.&lt;br /&gt;그리고 '한 프레임 동안 rAF 콜백을 실행하고 남은 시간을 활용하는 방법은 없을까?' 라는 생각에 도달하게 됐는데,&lt;br /&gt;이는 &lt;b&gt;requestIdleCallback&lt;/b&gt; API를 통해 구현 가능하다고 한다.&lt;br /&gt;&lt;br /&gt;rAF 콜백을 실행하고나면 그 뒤에는 스타일 계산, 레이아웃, 페인트 등의 브라우저 작업들이 뒤따라오게 되는데,&lt;br /&gt;이 작업들을 처리하고 남는 시간이 어느 정도인지는 우리가 정확히 알 수 없다.&lt;br /&gt;이 때 rIC를 활용해주면, 위의 작업들을 모두 실행하고 프레임의 끝에 남는 시간에 처리할 작업을 지정할 수 있게 된다.&lt;br /&gt;&lt;i&gt;(브라우저의 메인 스레드가 잠깐 비게 되면 그 때를 틈타 콜백을 실행시켜주는 것)&lt;/i&gt;&lt;br /&gt;프레임 별로 남는 시간을 효율적으로 활용할 수 있게끔 도와준다고 할 수 있다.&lt;br /&gt;&lt;br /&gt;보통 스크립트들은 가능한 한 바로바로 실행되는데,&lt;br /&gt;몇 가지 작업들은 굳이 유저가 상호작용 중이거나 할 때는 바로바로 처리할 필요가 없는 작업들도 있다.&lt;br /&gt;이런 경우, 우리는 유저가 상호작용 중인지를 알려면 지구 상 존재하는 모든 이벤트 리스너를 달아서 판별해야 하지만,&lt;br /&gt;브라우저는 이를 손쉽고 정확하게 판단할 수 있기 때문에 rIC를 통해 처리해주면 훨씬 일이 간단해진다.&lt;br /&gt;&lt;br /&gt;예를 들면 Google analytics 데이터를 보낸다던가 하는 작업들은&lt;br /&gt;유저가 스크롤을 하는 중에 보내진다던가 하면 유저의 사용성을 저해할 수 있다.&lt;br /&gt;그래서 유저의 동작을 방해하지 않고 남는 시간에 처리하게끔 하는 용도로 rIC를 활용할 수 있다고 한다.&lt;br /&gt;&lt;br /&gt;얘는 딱히 적용해보지는 않았고, 나중에 기회가 될 때 적용해 보는 걸로..&lt;br /&gt;참고 자료.&lt;br /&gt;Chrome developers: &lt;a href=&quot;https://developer.chrome.com/blog/using-requestidlecallback/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://developer.chrome.com/blog/using-requestidlecallback/&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;W3C: &lt;a href=&quot;https://www.w3.org/TR/requestidlecallback/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://www.w3.org/TR/requestidlecallback/&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;MDN:&amp;nbsp;&amp;nbsp;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Window/requestIdleCallback&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://developer.mozilla.org/ko/docs/Web/API/Window/requestIdleCallback&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;LINE Engineering: &lt;a href=&quot;https://engineering.linecorp.com/ko/blog/line-securities-frontend-4/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://engineering.linecorp.com/ko/blog/line-securities-frontend-4/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지..&lt;br /&gt;틀린 부분 있으면 알려주십쇼&lt;br /&gt;&lt;br /&gt;혹시 스크롤이 개선된 모습을 보고 싶으시다면..&lt;br /&gt;겸사겸사 &lt;a href=&quot;https://gift-pc.baemin.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;u&gt;&lt;span style=&quot;color: #1ef0ba;&quot;&gt;선물하기 PC 버전&lt;/span&gt;&lt;/u&gt;&lt;/b&gt;&lt;/a&gt; 으로 와서 구경하는 동시에 주변인들과 따뜻한 밥 한끼 나눠보는 것은 어떨까요..?&lt;br /&gt;그냥 말해봤습니다..&lt;br /&gt;&lt;br /&gt;옙 수고하십쇼&lt;/p&gt;</description>
      <category>Web/자바스크립트</category>
      <category>passive event listener</category>
      <category>RAF</category>
      <category>requestAnimationFrame</category>
      <category>스크롤</category>
      <category>최적화</category>
      <author>iborymagic</author>
      <guid isPermaLink="true">https://iborymagic.tistory.com/142</guid>
      <comments>https://iborymagic.tistory.com/142#entry142comment</comments>
      <pubDate>Fri, 30 Dec 2022 17:47:06 +0900</pubDate>
    </item>
    <item>
      <title>useEffect가 나를 열받게 했다</title>
      <link>https://iborymagic.tistory.com/140</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 새로운 기능을 개발하면서 상태의 변화에 따른 side effect들을 처리해주기 위해 useEffect를 많이 사용하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능의 생명 주기를 stage라는 단위로 구분했는데, 원래는 페이지 단위로 사용자의 행동을 구분하는 것이 보편적이겠지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 페이지의 형태도 거의 비슷하고 로직만 약간씩 바뀌는 식이어서 굳이 페이지를 나눌 필요가 없겠다는 생각이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-15 오후 8.18.49.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;978&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG5axV/btrHo6LGFEH/Ir0OHTmJonrbNZJYjM1l4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG5axV/btrHo6LGFEH/Ir0OHTmJonrbNZJYjM1l4k/img.png&quot; data-alt=&quot;대충 요런 느낌&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG5axV/btrHo6LGFEH/Ir0OHTmJonrbNZJYjM1l4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG5axV%2FbtrHo6LGFEH%2FIr0OHTmJonrbNZJYjM1l4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;390&quot; data-filename=&quot;스크린샷 2022-07-15 오후 8.18.49.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;978&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대충 요런 느낌&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&amp;nbsp;사용자가 '이번 페이지에서 할 작업을 다 마쳤으니 다음 페이지로 넘어간다' 라는 의도로 '다음' 버튼을 누르면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 페이지 내에서 stage만 바뀌고, stage의 변화에 따라 useEffect에서 여러 작업을 수행하는 방식으로 구현을 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1657180471727&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  if (stage === &amp;lsquo;first-stage&amp;rsquo;) {    
    // 여러 가지 side effect들
    // ex) 로그, 이미지 처리, 로더 등등
    return;
  }

  if (stage === &amp;lsquo;second-stage&amp;rsquo;) {
    // ...
    return;
  }
}, [stage]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 고민 없이, 'side effect는 useEffect 훅에서 처리한다' 라는 기존의 관념대로 코드를 짰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 기능 구현 초반에는 로직도 복잡하지 않고 코드도 길지 않다보니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'뭐, stage라는 구분 단위도 명확하고, stage 관련 로직들도 페이지 안에 잘 모여있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽는 것도 어렵지 않고.. 의도도 뚜렷하고.. 이대로 가도 될 듯? ㅋ' 하고 생각했지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 프로젝트를 진행하면서 기능이 복잡해지고, 각 stage의 로직이 비대해지면서 점점 처음의 의도와 다르게 흘러갔다.&lt;/p&gt;
&lt;pre id=&quot;code_1657299250638&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  if (stage === &amp;lsquo;first-stage&amp;rsquo;) {
    if (a === &amp;lsquo;something&amp;rsquo;) {
      // 대충 side effect 처리
    }
    // 대충 다른 조건 + 다른 side effect
  }

  if (stage === &amp;lsquo;second-stage&amp;rsquo;) {
     if (b === &amp;lsquo;something2&amp;rsquo; &amp;amp;&amp;amp; c) {
       // 대충 또 다른 side effect 처리
     }
    // 대충 또 다른 조건 + 또 다른 side effect
  }
  
  // 대충 다른 stage 로직
  // 대충 이것저것
  // 대충 뭐가 많음
}, [stage, a, b, c, d, e, ...]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황이 이렇게 되자, 몇몇 문제점들이 피부로 느껴지기 시작했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;useEffect들이 여기저기에 산재하게 되고, 각각의 크기 또한 점점 불어났다.&lt;/li&gt;
&lt;li&gt;useEffect의 트리거(dependency들을 변화시키는)들 또한 곳곳에 산재되어 있었다.&lt;/li&gt;
&lt;li&gt;처음에 의도했던 'stage 변화에 따른 side effect의 수행'과는 점점 거리가 멀어지고 있다고 느낄 정도로&lt;br /&gt;side effect의 수행에 영향을 미치는 상태값들이 점점 늘어났고, 그만큼 deps 배열(dependencies)도 점점 불어났다.&lt;/li&gt;
&lt;li&gt;side effect를 담고 있는 useEffect가 자꾸 여러 번 실행되는 것도 꽤 거슬렸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등등.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 즈음 와서는, 뭔가 에러가 발생했을 때 대체 어디서 이 side effect가 트리거 되는지를 파악하는 것이나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 에러가 도대체 어떤 dependency 때문에 발생한 에러인지를 파악하는 게 거의 어떤 '정신적인 노가다'의 경지였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 그걸 파악한 후에 에러를 해결하는 건 또 다른 문제였고.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnlDcT/btrHp2odkHG/t8tac8ukN8mrVtY87ww9I0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnlDcT/btrHp2odkHG/t8tac8ukN8mrVtY87ww9I0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnlDcT/btrHp2odkHG/t8tac8ukN8mrVtY87ww9I0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bnlDcT/btrHp2odkHG/t8tac8ukN8mrVtY87ww9I0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;310&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 디버깅의 과정이 점점 고통스러워지면서, 함께 페어로 개발하던 동료 개발자 &lt;b&gt;아그라작&lt;/b&gt;님과 나는 굳게 다짐했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언젠가는 저 useEffect놈을 산산조각내리라. 형체도 없이 분해해버리고 말겠다.   &lt;s&gt;(이정도는 아니었고..)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 우리의 우선 순위는 코드 퀄리티보다는 마감 기한 준수였기 때문에 고민은 제껴두고 일단 기능 개발에 집중했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도, 개발되는 기능의 크기에 비례하여 useEffect의 체중 또한 점점 늘어만 갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 기능을 릴리즈하고 약간의 텀이 생기면서, 다음 기능 개발에 들어가기 전에 호다닥 리팩토링에 돌입하기로 결정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기억들이 잊혀지기 전에, 아그라작님과 나는 개발하면서 고통스러웠던 점들을 하나하나 나열해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 가장 큰 화두가 되었던 부분은 역시나 useEffect에 관한 내용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이미 말했듯이 우리 둘 다 useEffect를 개선해야겠다는 생각은 진작에 하고 있었지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워낙에 기능 개발로 바빴던 탓에 제대로 이야기해 본 적이 없었기에 어떤 식으로 개선해야할 지가 제법 막막했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 때 아그라작님께서 '이렇게 해보는 건 어때요' 하고 요런 영상을 가져오셨다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=HPoC-k7Rxwo&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bv1MoO/hyO0MMtfy6/UHZ5huVOqKxtrh2TwluKPk/img.jpg?width=480&amp;amp;height=360&amp;amp;face=106_119_175_194&quot; data-video-width=&quot;640&quot; data-video-height=&quot;480&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/HPoC-k7Rxwo&quot; width=&quot;640&quot; height=&quot;480&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 내용만 요약하자면 대충 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;side effect는 synchronized effects와 action effects 두 가지로 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;◼︎&lt;/span&gt; synchronized effects&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React와 외부 시스템과의 동기화를 위해 사용&lt;/li&gt;
&lt;li&gt;예를 들면 컴포넌트가 mount 될 때 DOM 이벤트 리스너를 subscribe하고, unmount 될 때 unsubscribe 해주는 식.&lt;/li&gt;
&lt;li&gt;얘는 useEffect에서 처리해줘도 된다. &lt;br /&gt;(요 용도로만 사용해야한다고 주장함. 그래서 이름도 useSynchronization으로 바꾸란다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;◼︎&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;action effects&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 번 fire 시키고 잊어버리는 단발성 동작들&lt;/li&gt;
&lt;li&gt;useEffect에처 처리하면 문제가 많기 때문에 다시 한 번 생각해보는 게 좋단다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대표적으로, React 18이 strict mode에서 mount 시점에 useEffect를 두 번 실행시키는 문제가 있다. 그 외에도 side effect를 컴포넌트의 rendering cycle 안에 두면(쉽게 말해 useEffect 안에 두면) &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;side effect가 여러 번 실행되는 일이 종종 발생하곤 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;내가 겪었던 코드가 복잡해지는 문제는 덤. (이건 사실 useEffect 자체의 문제에 가까운 듯)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그러면 action effects는 어디서 처리해줘야 하나? 바로 &lt;b&gt;이벤트 핸들러&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;. (onSubmit, onClick 같은 애들)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;side effect를 이벤트 핸들러에서 처리해주면 side effect가 여러 번 실행되는 일이 없다.&lt;/li&gt;
&lt;li&gt;이벤트 핸들러는 컴포넌트가 몇 번 렌더링 되는지와 전혀 관계가 없고, 무조건 한 번만 실행된다는 게 보장되기 때문.&lt;/li&gt;
&lt;li&gt;&lt;u style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이벤트 핸들러는 컴포넌트의 rendering cycle과 따로 놀아서 side effect들이 렌더링의 영향 밖으로 벗어날 수 있다.&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 핸들러에서 처리해주라는 건 알겠는데, 어떻게 처리해줘야 하나?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;state + event&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;rarr; nextState 이므로, 이 event로 인해 발생하는 state transition과 함께 처리해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, state + event&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;rarr; nextState + effects. 예를 들면 idle state + LOAD &amp;rarr; loading state + fetchData 같이.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 훅이 코드로 나타낸 예시.&lt;/p&gt;
&lt;pre id=&quot;code_1657564250806&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function useSpicyReducer(reducer, initialState, executeEffect) {
  const [state, setState] = useState(initialState);
  
  const spicyDispatch = useCallback(
    (event) =&amp;gt; {
      const nextState = reducer(state, event);
      
      executeEffect(state, event, nextState);
      
      setState(nextState);
    },
    [reducer, state, executeEffect]
  );
  
  return [state, spicyDispatch];
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로, 이벤트 핸들러에서 side effect를 처리한다는 개념은 저 수염난 아저씨만의 주장은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 개발자 로 유명한 댄 형(Dan Abramov)을 필두로 리액트 공식 문서를 다시 쓰는 프로젝트가 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기서도 &lt;a href=&quot;https://beta.reactjs.org/learn/responding-to-events&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;'물론이죠! 이벤트 리스너는 side effect를 처리하기에 최적의 장소입니다!'&lt;/a&gt; 하고 언급돼있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, &lt;a href=&quot;https://beta.reactjs.org/learn/keeping-components-pure#where-you-can-cause-side-effects&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;useEffect는 side effect를 다루는 마지막 수단이어야 한다고도 언급&lt;/a&gt;되어 있다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 이름만을 근거로 곧이곧대로 useEffect에서 side effect를 처리해주던 나에게는 꽤나 충격으로 다가왔달까.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-09 오전 5.07.10.png&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Fn5q/btrGP5NL79z/BDodLKvpKe2lyGtEjTPdw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Fn5q/btrGP5NL79z/BDodLKvpKe2lyGtEjTPdw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Fn5q/btrGP5NL79z/BDodLKvpKe2lyGtEjTPdw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Fn5q%2FbtrGP5NL79z%2FBDodLKvpKe2lyGtEjTPdw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1914&quot; height=&quot;538&quot; data-filename=&quot;스크린샷 2022-07-09 오전 5.07.10.png&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 밖에 이런저런 내용들이 많지만, 아그라작님이 꽂히신 부분은 이벤트 핸들러와 예시에 나오는 reducer 부분이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reducer로 side effect 로직들을 한 데 모아두고, 이벤트 핸들러에 dispatch를 달아서 처리하면 useEffect를 거치지 않아&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원치 않는 동작을 줄이고 디버깅을 수월하게 할 수 있으리라 생각하셨던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect가 줄어드니까, 로직이 여러 번 실행되는 문제에서도 벗어날 수 있었고.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 보자면, reducer를 직접 코드에 도입한다거나 하진 않았지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요 영상은 우리가 망설임 없이 recoil을 도입할 수 있게 이끌어주는 역할을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뜬금없이 왠 recoil이냐 싶다면.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recoil을 도입하면 비슷한 효과를 누림과 동시에 다른 장점들까지 챙길 수 있었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. reducer를 도입하려는 가장 큰 이유는 side effect 로직들을 따로 빼서 useEffect의 크기를 줄이는 것 때문이었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recoil을 도입해도 atom + selector를 활용하여 유사한 효과를 챙길 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어, selector로 빼기 애매한 로직들은 useRecoilCallback을 활용해서 분리해줬는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRecoilCallback을 활용하면 리렌더링을 시키지 않고도 특정 atom을 읽거나 조작할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요녀석들을 이벤트 핸들러에 넣음으로써 코드 개선뿐만 아니라 성능 측면에서도 이점을 얻을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흩어져있던 useEffect들 중 밖으로 빼지 못하고 남은 애들은 &amp;lt;StageEffect /&amp;gt;라는 컴포넌트를 만들어 모아두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 리팩토링을 하면서 과하게 커진 컴포넌트들은 더 작은 컴포넌트로 분리를 하게 됐는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stage와 같은 상태값들은 root에서 전달해줘야하는 단일 상태값이었기 때문에 props drilling이 점점 심해졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recoil을 사용하면 이러한 상태들을 전역으로 빼서 props drilling을 없앨 수 있다는 장점이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 보통 로그를 찍는 작업은 기능적인 부분들을 먼저 구현하고 맨 나중에 하게 되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 보니 기존에 예상치 못했던 상태값들이 로그만을 위해 컴포넌트에 props로 들어가게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 props들도 recoil에 '~AtomForLog, ~SelectorForLog' 라는 식으로 전역 상태를 정의함으로써 없애줄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요런저런 점들을 생각하다보니 그냥 아예 recoil을 도입해버리는 게 합리적이라는 판단이 섰다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 도입해보니 꽤 효과적이기도 했고, 생각했던 것보다 더 사용감이 좋아서 굉장히 만족스러웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;다만 여전히 &amp;lt;StageEffect /&amp;gt; 컴포넌트 내부에 stage를 deps로 가지는 useEffect들이 남아있다는 게 찝찝한데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 stage별로 이미지를 blobURL로 다루거나, base64 혹은 remote URI로 다루는 등&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 각각 서로 다르게 처리해줘야 하는 부분이 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이걸 또 반드시 SVG 컴포넌트가 그려지고 난 이후에 처리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 점들을 모두 고려해봤을 때 얘는 그냥 useEffect로 처리하는 것이 맞겠다고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(얘네도 어찌 됐건 StageEffect 안에 옹기종기 잘 모아놨으니 이전처럼 사방을 헤맬 필요는 없어졌다는 것에 만족)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 밖에 찝찝한 부분들이 이곳저곳 남아있기도 하고, 새롭게 보이는 문제도 있고.. 하지만.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맘에 안드는 부분은 항상 있기 마련이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 원래 파일 당 6~700줄이 되던 코드들을 최대 약 2~400줄 정도로 줄일 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일도 제법 합리적인 단위로 잘 나누어졌고, 코드를 읽는 피곤함도 이전보다 훨씬 덜해졌다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stage 관련해서 문제가 생겨도 이제 어떤 파일을 찾아가야 하는지가 명확해져서 디버깅도 수월해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 건 몰라도 stage와 useEffect에 한해서는 제법 만족스러운 리팩토링이었다는 생각.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여담이지만, 이번에 리팩토링을 진행하면서 아그라작님과 전체적인 프로젝트의 구조를 도식화 시켜놓고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그걸 참고하면서 리팩토링을 진행했는데, 이게 너무 많은 도움이 됐다. 도식화의 효과를 절감했다고나 할까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 사실 아그라작님과 같이 도식화할 때는 항상 먼저 스타트를 끊어주셨던지라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 따로 과제를 수행하거나 리팩토링할 때 밑바닥부터 혼자 도식화를 해보려고 하니까 생각보다 너무 어려웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 상태값을 그리면 너무 더러워져서 어떤 주제로 어떤 상태값들에 초점을 맞춰서 도식화 할지를 잘 결정해야하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 부분을 아그라작님이 너무 자연스럽게 잘 해주셨던 것. 근데 나는 한 번도 안해봤으니 뭐가 잘 될 리가 있나.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 연습하다보면 되겠지..&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 이번에 페어로 함께 진행한 리팩토링이 꽤나 만족스러웠기에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect에서 문제를 느낀 것부터 시작해서, 어떻게 리팩토링할지 함께 논의하고 조사하여 아이디어를 채택하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고민 끝에 더 나은 방법인 recoil을 선택하여 도입한 이유까지 자연스러운 흐름으로 어렵지 않은 글을 쓰고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이거 뭐 막상 써보니 핵심 주제도 없고 뭔 말인지 알아듣기 어렵기만 하고 아주 요상한 글이 탄생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 어차피 블로그에 사람도 잘 안 들어오는데.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아몰랑&amp;nbsp;&lt;/p&gt;</description>
      <category>Web/React</category>
      <category>REACT</category>
      <category>recoil</category>
      <category>useEffect</category>
      <author>iborymagic</author>
      <guid isPermaLink="true">https://iborymagic.tistory.com/140</guid>
      <comments>https://iborymagic.tistory.com/140#entry140comment</comments>
      <pubDate>Fri, 15 Jul 2022 20:32:02 +0900</pubDate>
    </item>
    <item>
      <title>잠</title>
      <link>https://iborymagic.tistory.com/139</link>
      <description>&lt;div id=&quot;post-view221678484287&quot;&gt;
&lt;div id=&quot;SE-d6edc3ca-a8c0-4a78-9ea1-4545c0a5163d&quot;&gt;
&lt;p id=&quot;SE-039ee9d1-3c29-4473-8699-9d2bc7d50611&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;잠을 자고 나면, 전날에는 도저히 풀리지 않던 문제들이 술술 풀리는 경우가 많다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-813c8ec8-cd33-46ac-8d0e-9d8487b7d638&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아무리 머리를 쥐어짜내도 도저히 생각나지 않던 문제의 해답이 &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-045dbec1-9f43-4121-ab92-5cd9ef5d6da6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;잠을 자고 나서 조금만 생각하다보면 어느샌가 불쑥하고 떠오르곤 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-eea997bc-beb6-4677-a4ea-64cc7131d892&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-ca1cf830-9eb3-4c90-ad1d-9356edf99fa7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;잠에 들기 직전까지 괴롭게 하는 여러 복잡한 감정들이&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;잠을 자고 나면 말끔히 정리되드라.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-46f91561-9000-40dd-9035-0a334284aa62&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;말끔히 정리되고나면&lt;/span&gt;&lt;span&gt;&amp;nbsp;다시 온전한 상태로 되돌아갈 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-b2ec7f28-22d6-448b-b970-2e0e8464ab9a&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-1b3f5df4-92bd-4307-8146-5fcae24da652&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그러면, 그 때서야 전 날의 사건 사고, 혹은 문제들을 &lt;/span&gt;&lt;span&gt;침착하&lt;/span&gt;&lt;span&gt;게 되돌아볼 수 있게 되는 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-572708b7-1506-42fc-b909-ddcdec7fedae&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;span&gt;감정에 휘둘리지 않은 판단을 내릴 수 있게 되더라.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-662ac843-d772-4096-9909-220e9da76cbe&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-be305547-e5a1-4565-bac1-40f35887691e&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;나는 순간순간에 매번 냉철한 판단을 할 수 있는 사람이 아니라서&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f943bc44-42dc-4e9b-ac63-e8dc3aae577d&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;당일 날 급하게 결정을 내렸다가 바로 &lt;/span&gt;&lt;span&gt;다음 날 후회하는 경우가 종종 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-574d8117-d882-43f6-95b2-a9d724a1c03d&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f4096baf-e3c4-4f56-89f7-604f497192c7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;앞으로는 &lt;/span&gt;&lt;span&gt;올바른 판단을 내리기 힘든 상황이라고 생각되면&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-09806d07-3d29-43fc-9587-ba3aa5614e80&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다 접어두고 &lt;/span&gt;&lt;span&gt;우선 그냥 한 숨 자고 생각해봐야겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;는 &lt;/span&gt;2시간 쳐자고 나서 헬스장 시간 놓침&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일찍 자자..&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Diary</category>
      <author>iborymagic</author>
      <guid isPermaLink="true">https://iborymagic.tistory.com/139</guid>
      <comments>https://iborymagic.tistory.com/139#entry139comment</comments>
      <pubDate>Fri, 6 May 2022 03:03:30 +0900</pubDate>
    </item>
    <item>
      <title>목표의 재설정</title>
      <link>https://iborymagic.tistory.com/138</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;목표의 재설정이 필요한 시점이 아닐까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상치 못한 계기로 코딩을 며칠간 손에서 놓게 되었고 그로 인해 약간의 현자타임이 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 사실은 훨씬 전부터, 아마 입사 즈음부터 이미 공부 의욕이 많이 사라졌던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입사 이후에도 팀에서 1인분을 하기 위해 열심히 공부하긴 했지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의욕 없이 눈 앞에 닥치는 일들만 하나하나 처리하기에 급급한 공부였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스스로 뭔갈 찾아서 공부하지 않고, 업무를 받아서 업무와 관련된 공부들만 해치워나갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그에는 공들여서 쓴 유익한 글들만 올리자는 처음의 다짐과는 달리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 새 TIL이라는 이름의, 나조차도 보지 않는 똥글들만 주루룩 올라오고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그러다가 업무가 중단되고 내게 주어졌던 일들이 사라지고 나니,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;나는 말 그대로 그냥 퍼져버리고 말았다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여느 때처럼 다시 정신 차리고 공부할 수 있을 줄 알았는데.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 공부가 손에 아예 잡히질 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 오늘 책을 읽다가, 그건 목표가 없었기 때문이구나, 하고 생각했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지치는 게 뭔지도 몰랐고 평생동안 끊임없이 공부할 수 있을 거라고 생각했던&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 달 전과 지금의 나는 뭐가 달라졌을까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 그 때는 좀 더 명확하고 눈에 보이는 목표를 가지고 있었다는 점이겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 달 전, 내가 우아한테크코스라는 교육과정을 밟던 당시에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별 생각 없이 가졌던 'OOO에 프론트엔드 개발자로 입사하기' 라는 목표는 우연히도 잘 짜여진 목표였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 구체적이었고, 너무 먼 미래의 일도 아니었으며, 부단한 노력이 더해진다면 어쩌면 성취할 수 있는 목표였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 나는 그 목표만을 바라보며 한 길로 쭉 달릴 수 있었고, 목표를 성취해냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 내 목표는 '모두가 찾는 유능한 개발자 되기'나 '30억 부자 되기' 정도.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 추상적이고, 언제 이뤄질지도 모르겠고, 사실 그렇게 절박한 목표인지도 잘 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 마저도 '성장해야 한다' 라는 외부의 분위기에 휩쓸리듯 만들어 낸 감이 없지 않아 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내게 아무런 동기부여가 되지 못하고 있으며, 사실 목표라고 하기도 민망하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명확한 목표가 있을 때는 조금 버거워도 잠시 앉아서 쉬면 다시 달려나갈 수 있었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표가 거의 없다시피 한 지금은 한 번 앉고 나니 다시 일어설 수가 없게 되어버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거의 일주일 가까이 앉아서 쉬는 중.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표를 설정하는 건 생각보다 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 목표를 '잘' 설정하는 건 그보다 더 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애매하게 세워진 목표는 '내겐 목표가 있다!' 라는 착각만 심어줄 뿐 아무런 도움이 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일이 잘 풀릴 때 부스터를 달아주지도 못하고, 일이 안 풀릴 때 나를 일으켜 세워주지도 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 '잘 세워진 목표' 라는 건 뭘까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 목표는 단기적인 목표와 장기적인 목표로 나눠서 세우는 편이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장기적인 목표는 무조건 높게 잡는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표를 최대한 높게 잡아야 떨어져도 남들 위에 떨어질 수 있으니까!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 중단기적인 목표는 최대한 실현 가능한 범위 내에서 잡는 편이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실현 가능하되, 최선을 다해 노력해야만 성취할 수 있는 정도로 적당히 높은 목표.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래야만 그 목표들을 하나하나 성취하면서 장기 목표를 향해 지치지 않고 달려갈 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 정도가 지금 내 시점에서의 '잘 세워진 목표'라고 할 수 있겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 내가 작년에 'OOO 기업의 프론트엔드 엔지니어로 입사한다'는 목표가 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'스페이스 X에 우주선 터치 스크린 UI를 만드는 프론트엔드 엔지니어로 입사한다!' 라는 목표를 세웠다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 그건 중단기 목표로써의 역할을 전혀 하지 못했을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 년 내로 당장 캘리포니아로 취업 비자를 취득해서 날라갈 수도 없을 뿐더러&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우주 관련 도메인 지식도 전무하고, 낯선 도메인에 관해 전문가들과 소통할 만큼 영어를 잘하지도 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어, 스페이스 X와 같은 우주 산업은 외국인 채용을 받지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 세상은 어떻게 될 지 모르는 법이니까 세월이 지나면 어쩌면 가능할지도 모르지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누가 봐도 허황된 목표임은 틀림이 없고 이런 목표는 중단기 목표로써는 최악의 목표겠지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 지금 뭔 소리를 하고 있는건지.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에 잘 세워뒀던 나의 중단기 목표는 올해 초에 그 역할을 다했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 시점에서 내게 남은 건 저 놈의 스페이스 X같은 멀고 허황된 목표뿐이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방향을 제시해 줄 목표가 없으니 길을 잃고 헤매는 건 어찌보면 당연했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 팀에 도움이 되기 위해서, 그리고 개발자로써 더 길게 살아남기 위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표를 재설정할 시점이 온 게 아닌가 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리하여 내가 공부를 왜 해야 하는지에 대해 내 자신을 설득시키고 동기를 부여해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 개발을 좋아하니까 회사에 들어와서도 마냥 재미있을 줄 알았지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 개발을 좋아하는 것과 회사에서 개발로 일을 한다는 건 꽤나 다른 차원의 이야기였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 정도의 동기 부여가 필요하다는 걸 인정하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 쓰면서 뭔가 생각이 정리가 됐고, 복잡했던 머리가 차분해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;며칠 간 푹 쉬고 잘 잤으니, 이제 다음 목표를 세우고 달려나가야지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 이상 낭비할 시간이 없다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보면, 공부해야할 게 너무 많다는 것도 좀 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남들을 자꾸 신경쓰다보니 다른 사람들이 공부하는 모든 것들을 알려고 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다보니 공부량에 압도되어 이도저도 안되는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택과 집중을 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남들을 신경쓰지말고, 경쟁심을 버리고 나만 보면서 달려가자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느리더라도 천천히, 나에게 필요한 내용들을 나만의 언어로 바꿔 익히는 연습을 해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포기만 안하면 언젠가는 뭐라도 돼있겠지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Diary</category>
      <author>iborymagic</author>
      <guid isPermaLink="true">https://iborymagic.tistory.com/138</guid>
      <comments>https://iborymagic.tistory.com/138#entry138comment</comments>
      <pubDate>Wed, 2 Mar 2022 04:14:32 +0900</pubDate>
    </item>
    <item>
      <title>부드러운 애니메이션을 위한 FLIP 테크닉</title>
      <link>https://iborymagic.tistory.com/136</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;height, width, top, left 같은 속성의 변화는 레이아웃(리플로우)을 유발한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 브라우저는 다른 엘리먼트에도 레이아웃 변화가 있는지를 재귀적으로 확인해야하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 계산하는 데에는 많은 비용이 든다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 그 계산이 애니메이션의 1 프레임(약 16.7ms)보다 오래 걸리면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션의 프레임은 제 시간에 렌더되지 못하고 스킵되기 때문에 사용자 입장에서는 버벅거리는 것처럼 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 height 같은&amp;nbsp;속성들을 이용한 애니메이션은 종종 버벅거리는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부드러운 애니메이션을 위해서는 최대한 적은 변화를 최대한 빨리 계산해야하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해서는 transform과 opacity로만 애니메이션을 구현해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 FLIP 테크닉은 transform 속성만을 활용하여 애니메이션을 구현하는 방법을 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;우선, FLIP은 '&lt;u&gt;First, Last, Invert, Play&lt;/u&gt;'의 약자이고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chrome Dev Summit에서 Paul Lewis에 의해 처음 소개된 테크닉이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(처음에는 FLIP이라는 명시적인 네이밍 없이 소개되었다고 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FLIP 테크닉의 개념은 생각보다 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;First&lt;/b&gt; - 애니메이션에 포함되는 요소의 처음 상태(주로 위치)를 저장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getBoundingClientRect를 주로 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;Last&lt;/b&gt; - 요소의 마지막 상태(애니메이션이 끝난 후의 상태)를 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;Invert&lt;/b&gt; - 현재 우리는 요소의 처음 상태와 마지막 상태를 알고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서, 마지막 상태에다가 transform이나 opacity의 변화를 적용하여 처음 상태를 만들어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이걸 보고 invert라는 표현을 사용한 듯 하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 요소가 처음 위치에서 아래로 90px만큼 움직이는 애니메이션이라고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 요소를 마지막 위치에 놓는 대신 transform Y에다가 -90px를 적용해주는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 해당 요소는 처음 위치에 있는 것 같지만, 사실은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(원래는 마지막 위치에 놓여져 있지만 transform에 의해 처음 위치로 옮겨진 상태다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;b&gt;Play&lt;/b&gt; - 애니메이션(transition)을 적용하고, transform이나 opacity 속성을 사용해 만들어줬던&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'가짜' 처음 상태를 삭제해주면 된다. (위의 예시로 치면 transform Y에 -90px을 적용해줬던 걸 삭제)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 처음 상태에서 마지막 상태로 자연스럽게 전환되는 애니메이션 완성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 통한 구체적인 예시는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aerotwist.com/blog/flip-your-animations/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://aerotwist.com/blog/flip-your-animations/&lt;/a&gt;&amp;nbsp;나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://css-tricks.com/animating-layouts-with-the-flip-technique/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://css-tricks.com/animating-layouts-with-the-flip-technique/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 가면 확인해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;element.style.transform 말고 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Web Animations API&lt;/a&gt;라는 게 있드라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그걸 활용한 예시가 많은 듯.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FLIP 테크닉을 손쉽게 구현하기 위한 &lt;a href=&quot;https://github.com/davidkpiano/flipping&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flipping&lt;/a&gt;이나 &lt;a href=&quot;https://github.com/aholachek/react-flip-toolkit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;react-flip-toolkit&lt;/a&gt; 같은 라이브러리들도 있다.&lt;/p&gt;</description>
      <category>Web/CSS</category>
      <author>iborymagic</author>
      <guid isPermaLink="true">https://iborymagic.tistory.com/136</guid>
      <comments>https://iborymagic.tistory.com/136#entry136comment</comments>
      <pubDate>Mon, 7 Feb 2022 00:42:51 +0900</pubDate>
    </item>
    <item>
      <title>S.W.R = Stale-While-Revalidate</title>
      <link>https://iborymagic.tistory.com/135</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Stale-While-Revalidate&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 서버로부터 API를 통해 데이터를 받아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 클라이언트에 한 번 전해진 이상, 클라이언트는 해당 데이터가 최신 데이터인지 더 이상 알 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 서버에서 클라이언트로 전해져 최신 데이터인지 알 수 없는, 즉 신선함을 잃은 데이터를 'stale response'라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stale-while-revalidate는 '캐싱 전략'의 일종이다. (아직 비표준 단계의 HTTP Cache-Control)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 stale response인지 아닌지는 Cache-Control 헤더에 함께 포함된 max-age로 판단한다.&lt;/p&gt;
&lt;pre id=&quot;code_1643990499561&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Cache-Control: max-age=600, stale-while-revalidate=30&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱된 데이터가 아직 최신 상태라면 해당 데이터를 그대로 응답해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 데이터가 최신 상태가 아니라고(max-age보다 오래됐다고) 해도,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 요청에 최대한 빠르게 응답하기 위해서 캐싱된 데이터를 응답으로 바로 보내준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 사이에, 다음 클라이언트 요청이 올 것을 대비해 '재검증(revalidate)' 요청이 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 새로 가져온 데이터가 이전에 캐시된 데이터와 동일한지를 검증하여 아니라면 캐시를 갱신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;max-age 뿐만 아니라 stale-while-revalidate 값보다도 오래된 'truly' stale 데이터라면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stale-while-revalidate는 동작을 멈추고 (캐싱된 데이터를 사용하지 않고)&lt;span&gt;&amp;nbsp;&lt;/span&gt;그냥 서버에 새로 요청을 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시성과 최신성 사이의 균형을 맞추기 위해 고안된 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SWR&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stale-while-revalidate 전략을 클라이언트의 서버 상태(server state)에 적용한 프론트엔드 라이브러리.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 데이터 페칭(fetching)을 리액트 훅(hook)의 형태로 간단하게 사용할 수 있다는 장점도 있지만 주 용도는 캐싱이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 최신 데이터가 아니더라도 캐싱된 데이터를 먼저 사용자에게 보여준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 사이에 서버를 한 번 찔러서 재검증을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 받아온 데이터가 캐싱된 데이터와 다를 경우, 리렌더링을 통해 새로운 데이터를 사용자에게 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 말해, 재검증을 하는 동안 사용자에게 캐시 데이터를 먼저 보여주는 방식이라고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useSWR이라는 hook이 핵심이며 반환값으로 { data, error, isValidating, mutate }를 받을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1643959694915&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { data, error, isValidating, mutate } = useSWR(key, fetcher, options);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 데이터를 로딩중일 때는 data가 undefined이고, 데이터를 가져오면 해당 데이터가 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;error는 undefined이다가 에러가 발생하는 경우에만 해당 에러를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isValidating은 revalidate 중인지 여부를 나타내는 boolean 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mutate는 인위적으로 revalidate를 시킬 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 밖에도 infinite scroll 기능이나 실패 시 retry 등 여러 부가 기능들이 SWR에 붙어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 default로 활성화되어있는 기능들이 많고, 그것들을 옵션으로 비활성화시킬 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여담으로, Google Workbox의 문서를 보면&lt;span&gt;&amp;nbsp;걔네들은&amp;nbsp;&lt;/span&gt;stale-while-revalidate 말고도&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Cache-First: 캐싱된 데이터가 존재한다면 요청에 대한 응답은 무조건 캐시에서 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 캐싱된 데이터가 존재하지 않는다면 API 요청을 보내 응답한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 응답은 캐싱되어,&lt;span&gt;&amp;nbsp;&lt;/span&gt;다음 요청이 오면 캐시에서 응답을 보내게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Network-First:&lt;span&gt;&amp;nbsp;&lt;/span&gt;기본적으로 항상 API로 최신 데이터를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공적으로 요청이 응답되면, 해당 응답을 캐싱해놓는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 요청이 실패하면 캐싱된 데이터가 보내진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Network-Only:&lt;span&gt;&amp;nbsp;&lt;/span&gt;캐시를 사용하지 않고 무조건 API를 찌른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Cache-Only:&lt;span&gt;&amp;nbsp;&lt;/span&gt;무조건 캐시에서만 데이터를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 다양한 캐싱 전략들을 사용한다고 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RFC 5861 문서를 보면 stale-while-revalidate와 함께 제안된 Cache-Control 헤더 중에 stale-if-error같은 것도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 한 번씩 눈에 슥- 담아두면 좋을 것 같아서.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출처&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Developers workbox strategies -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://developers.google.com/web/tools/workbox/modules/workbox-strategies#cache_only&quot;&gt;https://developers.google.com/web/tools/workbox/modules/workbox-strategies#cache_only&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RFC 5861 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc5861#section-4&quot;&gt;https://datatracker.ietf.org/doc/html/rfc5861#section-4&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;web.dev stale-while-revalidate로 최신 상태 유지 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://web.dev/i18n/ko/stale-while-revalidate/&quot;&gt;https://web.dev/i18n/ko/stale-while-revalidate/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cache-Control MDN -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web/Network</category>
      <author>iborymagic</author>
      <guid isPermaLink="true">https://iborymagic.tistory.com/135</guid>
      <comments>https://iborymagic.tistory.com/135#entry135comment</comments>
      <pubDate>Sat, 5 Feb 2022 00:57:46 +0900</pubDate>
    </item>
    <item>
      <title>Parcel이 tsconfig의 path alias를 인식하지 못하는 문제</title>
      <link>https://iborymagic.tistory.com/132</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt; 상황&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Parcel-bundler를 typescript와 함께 사용하면서,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;path alias를 사용하기 위해 tsconfig 파일의 'paths' 속성을 다음과 같이 설정했다.&lt;/p&gt;
&lt;pre id=&quot;code_1642846644790&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;paths&quot;: {
      &quot;@src/*&quot;: [&quot;src/*&quot;],
    },
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 원하는 대로 동작하지 않고 계속해서 에러가 발생하는 상황.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt; 상세&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에디터에 컴파일 에러는 발생하지 않는데 빌드 과정에서 에러가 발생하는 것을 보니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsconfig의 path alias 설정을 parcel은 인식하지 못해서 문제가 발생하는 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(tsconfig 파일은 typescript 설정 파일이니깐)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대해, 내가 찾은 해결책은 babel의 plugin을 사용하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(tsconfig의 paths도 당연히 위와 같이 설정을 해줘야 함)&lt;/p&gt;
&lt;pre id=&quot;code_1642391585044&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .babelrc
{
  &quot;plugins&quot;: [
    [
      &quot;module-resolver&quot;,
      {
        &quot;root&quot;: [&quot;./src&quot;],
        &quot;alias&quot;: {
          &quot;@src&quot;: &quot;./src&quot;
        }
      }
    ]
  ],
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나서 에디터를 껐다가 키고, 다시 빌드하면 잘 동작할 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt;&amp;nbsp;비고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 parcel에서의 설정이 아닌 babel을 이용하는 게 맞는건가 자꾸 찝찝하긴 하지만 아직까지는 방법을 찾지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/parcel-bundler/parcel/issues/202&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/parcel-bundler/parcel/issues/202&lt;/a&gt;&lt;/p&gt;</description>
      <category>et cetera/Trouble Shooting</category>
      <author>iborymagic</author>
      <guid isPermaLink="true">https://iborymagic.tistory.com/132</guid>
      <comments>https://iborymagic.tistory.com/132#entry132comment</comments>
      <pubDate>Sat, 22 Jan 2022 19:22:36 +0900</pubDate>
    </item>
    <item>
      <title>Parcel의 Cannot find module './style.module.scss' or its corresponding type declarations. 에러</title>
      <link>https://iborymagic.tistory.com/131</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt; 상황&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parcel로 번들링한 타입스크립트 프로젝트에서 css modules를 사용하기 위해 파일을 만들고 import를 했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'Cannot find module './style.module.scss' or its corresponding type declarations.' 라는 ts 에러가 발생하면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트가 css 모듈을 찾지 못하는 상황이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt; 상세&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parcel에는 css 모듈 기능이 기본으로 탑재되어 있지만, 타입스크립트는 이를 알지 못하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parcel.d.ts 파일에 모듈의 타입을 선언하여 타입스크립트에게 알려주도록 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1642391585044&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// parcel.d.ts
declare module '*.module.scss' {
  const value: Record&amp;lt;string, string&amp;gt;;
  export default value;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시나 ts 파일이므로 tsconfig.json 파일의 include에 'parcel.d.ts'를 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt;&amp;nbsp;비고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parcel이 css 모듈도 default로 지원해준다는 걸 이번에 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://issueexplorer.com/issue/parcel-bundler/parcel/7231&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://issueexplorer.com/issue/parcel-bundler/parcel/7231&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://parceljs.org/features/dependency-resolution/#typescript&quot;&gt;https://parceljs.org/features/dependency-resolution/#typescript&lt;/a&gt;&lt;/p&gt;</description>
      <category>et cetera/Trouble Shooting</category>
      <author>iborymagic</author>
      <guid isPermaLink="true">https://iborymagic.tistory.com/131</guid>
      <comments>https://iborymagic.tistory.com/131#entry131comment</comments>
      <pubDate>Mon, 17 Jan 2022 13:37:16 +0900</pubDate>
    </item>
    <item>
      <title>객체지향 프로그래밍 입문 강의 (5) - 의존과 DI</title>
      <link>https://iborymagic.tistory.com/130</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt; 의존&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 구현을 위해 다른 구성 요소를 사용하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 객체 생성, 메서드 호출, 데이터 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 의존은 &lt;b&gt;변경이 전파될 가능성&lt;/b&gt;을 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 의존하는 대상이 바뀌면 덩달아 바뀔 가능성이 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 호출하는 메서드의 파라미터가 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출하는 메서드가 발생할 수 있는 익셉션 타입이 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 그 메서드를 사용하는 코드도 덩달아 바뀌게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt;&lt;span&gt; 순환&amp;nbsp;&lt;/span&gt;의존&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순환 의존 &amp;rarr; 변경 연쇄 전파 가능성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JGhg2/btrplijkmTi/Hq6B2E44kauv0xHdpVo2o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JGhg2/btrplijkmTi/Hq6B2E44kauv0xHdpVo2o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JGhg2/btrplijkmTi/Hq6B2E44kauv0xHdpVo2o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJGhg2%2FbtrplijkmTi%2FHq6B2E44kauv0xHdpVo2o0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;440&quot; height=&quot;178&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;367&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A가 B에 의존하고, B가 C에 의존하고, C는 다시 A에 의존하고 있다고 치자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 A의 변경이 C에 영향을 주고, C의 변경은 B에, B의 변경은 다시 A에 영향을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 클래스, 패키지 모듈 등 &lt;u&gt;모든 수준에서 순환 의존이 없도록 해야한다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;b&gt;※&lt;/b&gt;&lt;/span&gt; 의존하는 대상이 많다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A가 의존하고 있는 대상들(B, C, D, E, F)이 각자의 이유로 바뀌게 되면, 그만큼 A도 바뀔 가능성이 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;u&gt;의존하는 대상은 적을수록 좋다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt;&lt;span&gt;&lt;span&gt; 의존 대상이 많은 경우&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;대표적인 케이스가 바로 &lt;u&gt;한 클래스에서 많은 기능을 제공하는 경우.&lt;/u&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1641105000190&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserService {
    public void register(RegReq regReq) {
        // ...
    }
    
    public void changePW(ChangeReq changeReq) {
        // ...
    }
    
    public void blockUser(String id, String reason) {
        // ...
    }
    
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 기능마다 의존하는 대상이 다를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 한 기능 변경이 다른 기능에 영향을 줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 테스트하기가 더욱 힘들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 기능만 테스트하고 싶어도 나머지 기능들에서 필요한 의존성 대상들까지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함께 초기화해야 하는 불상사가 발생할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 한 클래스가 제공하는 기능이 많으면 이를 &lt;b&gt;기능 별로 분리&lt;/b&gt;하는 것을 고려해보는 것이 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1641105212590&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserRegisterService {
    public void register(RegReq regReq) {
        // ...
    }
}

public class ChangePwService {
    public void changePw(ChangeReq changeReq) {
        // ...
    }
}

public class UserBlockService {
    public void blockUser(String id, String reason) {
        // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 별로 클래스를 분리하면 클래스의 갯수는 늘어나지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 클래스마다 필요로 하는 의존이 줄어들게 되고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 기능을 수정할 때 다른 기능과 관련된 코드를 수정하는 일이 발생하지 않게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어, 개별 기능을 테스트하는 것도 조금 더 수월해지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;※ 의존 대상을 줄이는 또 다른 방법&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;추상화&lt;/b&gt;를 통해 여러 의존 대상을 단일 기능으로 묶을 수 있는지 검토해보는 것.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;※ 의존 대상 객체를 직접 생성하면?&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;- 생성 클래스가 바뀌면 의존하는 코드도 바뀐다. (추상화에서 언급)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;- 의존 대상 객체를 직접 생성하지 않는 방법?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; - Factory, Builder&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; - 의존성 주입(Dependency Injection, DI)&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; - 서비스 로케이터(Service Locator)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;의존 주입(Dependency Injection, DI)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존하는 대상을 직접 생성하지 않고, 외부에서 &lt;u&gt;생성자나 메소드를 이용해서 전달&lt;/u&gt;받는 방식&lt;/p&gt;
&lt;pre id=&quot;code_1641111169855&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ScheduleService {
    private UserRepository repository;
    private Calculator cal;
    
    public ScheduleService(UserRepository repository) {
        this.repository = repository;
    }
    
    public void setCalculator(Calculator cal) {
        this.cal = cal;
    }
}

// 초기화 코드
UserRepository userRepo = new DbUserRepository();
Calculator cal = new Calculator();

ScheduleService schSvc = new ScheduleService(userRepo);
schSvc.setCalculator(cal);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 보면 ScheduleService라는 생성자와 setCalculator 메소드를 통해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존하는 객체를 전달받고 전달받은 객체를 필드에 할당하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 생성자와 메소드를 통해서 객체를 전달하는 것은 초기화 코드에서 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 초기화하는 코드에서 알맞은 객체를 생성하여 전달하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 DI의 전부이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어가 어려워보이지만, 사실 간단한 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■&lt;/span&gt;&lt;span&gt;&amp;nbsp;조립기&lt;/span&gt;(Assembler)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 시작하는 메인 메서드에서 의존 객체를 생성하고 주입할 수도 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 조립기(Assembler)를 이용해서 객체를 생성하고 의존 주입을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) Spring Framework(대표적인 조립기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 다음과 같이 객체를 생성하고 의존 대상을 주입하는 코드를 설정으로 작성하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 설정 코드를 이용해서 ApplicationContext라고 불리는 조립기를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1641112858248&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class Config {
    @Bean
    public ScheduleService scheduleSvc() {
        ScheduleService svc = new ScheduleService(repo());
        svc.setCalculator(expCal());
        return svc;
    }
    
    @Bean
    public UserRepository repo() { ... }
    
    @Bean 
    public Calculator expCal() { ... }
}

// 초기화
ctx = new AnnotationConfigApplicationContext(Config.class);

// 사용할 객체 구함
ScheduleService svc = ctx.getBean(ScheduleService.class);

// 사용
svc.getSchedule(...);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단의 AnnotationConfigApplicationContext가 바로 스프링에서 제공하는 조립기에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조립기를 초기화하는 시점에 설정 클래스를 이용해서 객체를 만들고, 의존 주입이 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기화 이후에는 조립기에서 필요한 객체를 구하고, 그 객체를 사용하면 되는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #c1bef9;&quot;&gt;■ &lt;span style=&quot;color: #333333;&quot;&gt;DI의 장점&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 의존 대상이 바뀌면 조립기(설정)만 변경하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 의존하는 객체의 실제 구현이 없어도, 대역 객체를 사용하여 테스트하기가 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 실제 DB 연동을 구현한 객체를 사용하지 않고도, 메모리를 이용해서 구현한 객체로 테스트할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(DB가 없어도 DB와 연동하는 기능을 테스트할 수 있다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 대역 객체를 사용하면 원하는 대로 상태를 초기화할 수 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 경우의 수를 테스트하기가 훨씬 수월해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;b&gt;※&lt;/b&gt;&lt;/span&gt; DI는 습관처럼 사용하기!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존 객체는 항상 주입받도록 코드를 작성하는 습관을 들이도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>et cetera/TIL</category>
      <author>iborymagic</author>
      <guid isPermaLink="true">https://iborymagic.tistory.com/130</guid>
      <comments>https://iborymagic.tistory.com/130#entry130comment</comments>
      <pubDate>Sun, 2 Jan 2022 17:59:34 +0900</pubDate>
    </item>
  </channel>
</rss>