아래 출처의 글을 정리하고 약간의 생각을 가미하려 합니다. 허락해주신 테오님께 갑사합니다 ㅎㅎ
MVC패턴 MVVM패턴 등등이 많이 언급되고 사용된다 하지만 저는 MVC 패턴만 간략하게 이해가 되고 다른 것들은 쉽게 이해가 되지 않았습니다. 그러던 와중 좋은 글을 읽게 되었고 저자의 허락을 얻어 저의 언어로 정리하고 인사이트를 작성해 보고자 합니다.
1. 아키텍쳐란 무엇일까요?
2. 웹 프론트엔드 아키텍쳐 이야기
3. 현대 웹 프론트엔드의 아키텍쳐 방향성
1. 아키텍쳐란 무엇일까요?
아키텍처란 영어로는 '건축학'이라는 뜻입니다.
이 단어가 개발분야로 유입되면 조금 다른 뜻을 가지게 됩니다.
아래 사진들은 옷장에 대한 사진들 입니다.
두개의 옷장은 전부 같은 양의 옷을 보관하고 있다고 가정하겠습니다.
그리고 옷은 하나의 기능, 즉 코드로 작성된 함수라고 하겠습니다.
두 옷장은 같은 동작을 수행합니다. 같은 양의 옷이 보관되어있고 양말을 꺼내고 싶다(input) 양말이 나온다(output)과 같은 기능을 수행할 수 있습니다.
하지만 a 양말을 버려야 한다. b 티셔츠를 꺼내서 당근마켓에 올려야 한다. 라는 요청사항이 들어왔을때 어떤 옷장이 동작을 수행하기 빠를까요?
아키텍쳐는 이러한 옷장과 비슷하다고 합니다. 물론 옷장은 옷을 보관하는 용도만 수행하지만 아키텍쳐는 아닙니다. 각 모듈들은 서로 데이터를 주고 받고 영향을 끼칠 수도 있습니다.
마틴 파울러의 유투브 발표영상을 보면 https://www.youtube.com/watch?v=4E1BHTvhB7Y 경제학적인 관점이 등장합니다.
소프트웨어 아키텍처는 아쉽게도 외부에 드러나지 않습니다.
같은 기능을 수행하는 a, b 프로그램이 있습니다.
a 프로그램은 잘 설계된 아키텍처를 가지고 있습니다. 가격은 200달러 입니다.
b 프로그램은 좋지 못한 아키텍처를 가지고 있습니다. 가격은 100달러 입니다.
프로그램을 구매해야 하는 사람의 입장에서 보면 a, b중 어떤 프로그램을 구매할까요?
b를 구매하는 사람이 많지 않을까 싶습니다.
우리는 물건을 구매할때 비용과 품질의 트레이드오프가 일어납니다. 아반떼와 포르쉐 카이옌의 가격은 다르고 더 좋은 품질을 비싼 가격에 기대하게 됩니다.
프로그램의 내부 아키텍처는 외부에 드러나지 않습니다. 그래서 아키텍처는 간과되기 쉽습니다.
개발자들이 아키텍처의 설계에 아무리 공을 들여도 비용을 관리하는 입장에서는 같은 동작이 수행되니 프로그램에서의 비용과 품질의 트레이드 오프를 이해할 수가 없습니다
프로그램에서의 비용과 품질의 트레이드 오프는 장기적인 관점에서 바라봐야 합니다.
잘 설계된 아키텍처는 기능의 추가와 기존 기능의 수정이 편리합니다.
상단의 두 개 옷장에서 같은 옷을 꺼내야 하고 상의와 겉옷 칸의 위치를 바꿔야 한다고 생각해보세요.
어떤 옷장이 수월할 까요?
그래서 좋은 아키텍처를 가진 프로그램은 장기적으로 유지 보수와 기능의 추가가 상대적으로 매우매우 용이하여 결과적으로는 비용을 줄여줍니다.
하지만 초기 비용은 증가합니다. 옷이 적을때는 옷장을 더럽게 해도 저의 요청사항들(같은 옷을 꺼내기, 상의와 겉옷 칸의 위치를 바꾸기)들을 수행하기 쉽습니다. 오히려 더 빠를 수도 있습니다. 그래서 프로그램의 규모, 예산등이 고려되어야 하지 않을까 싶습니다. 그래도 경험상 무조건 좋은 아키텍쳐를 설계하고 가는 것이 좋지 않을까 생각이 듭니다
2. 웹 프론트엔드 아키텍쳐 이야기
아키텍처는 좋은 구조를 만드는 것이고 좋은 구조를 위해서는 좋은 분류가 필요합니다
MVC 아키텍처
Model + View + Controller 의 조합입니다.
Model(모델) :
화면에 보여줄 데이터를 담당하는 영역입니다.
Model의 정의는 주어진 환경에 따라 다를수 있습니다.
javascript의 Object, 서버의 DB, 서버에서 API로 요청한 데이터 일 수도 있습니다.
View(화면) :
실제 사용자에게 보여지는 화면입니다.
Controller(컨트롤러) :
모델과 View사이에서 중간 역할을 합니다.
Rails 에서는
컨트롤러에서 사용자의 요청을 받습니다.
요청에 필요한 데이터를 모델에서 가져옵니다.
가져온 데이터를 화면에 적용하여 사용자의 요청에 맞는 화면을 그려냅니다.
그려진 화면을 사용자에게 보여줍니다.
와 같이 동작합니다.
이렇게 MVC를 나눈 이유는
1. 화면을 다루는 문제, 데이터를 다루는 문제는 서로의 관심사가 다릅니다. 두개의 문제가 동일한 곳에서 관리되어 진다면 기능의 수정이 필요할때 개발자는 코드로의 모험을 떠나야 할 겁니다.
2. Model과 View의 의존관계를 최소화 함으로써 화면의 수정은 데이터의 수정에, 데이터의 수정은 화면의 수정에 영향을 미치지 않을 수 있습니다.
가장 이해가 되지 않던 부분이 프론트엔드에서의 Model 부분이었습니다.
하지만 해당 블로그에서 시간의 흐름에 맞게 Model, View, Controller의 정의에 대한 변화 또한 친절하게 설명해주어 많이 와닿았습니다.
초창기 웹 서비스 -> jQuery 시절 -> MVVM 아키텍쳐 (angular, react, vue) -> 컴포넌트 그리고 Container-Presenter 패턴 -> Flux 패턴, Redux -> MVI 패턴 의 흐름으로 이어집니다.
흐름을 이해하는 것이 뭔가를 이해하는 데 있어 매우 유용한 것 같습니다.
Flux 패턴은 기존의 어떤 문제를 해결하려 했는지, 기존의 문제는 어떤걸로 인하여 발생하였는지를 알수 있다면 다음 문제를 예측하고 기존의 문제와 같은 문제를 발생하는 것을 피해갈 수 있습니다.
"역사를 잊은 민족에게 미래는 없다" 와 같은 문장도 같은 맥락에 있지 않을까 싶습니다
초창기 웹 서비스
아키텍처의 잣대와 범주는 언어마다 각 환경마다 다르다고 합니다.
그래서 저는 현대의 아키텍처에 기존에 가지고 있던 시야와 뜻을 접목했었고 잘 이해를 못했었습니다(지금은 공부중입니다!)
Model : 데이터 베이스
View : Html, css. js를 포함한 클라이언트 영역
Controller : Model과 View 가운데서 라우터를 통해 데이터를 처리하고 새로운 HTML을 만들어서 보여주는 백엔드 영역
jQuery 시절의 MVC 아키텍쳐
프론트엔드에 ajax가 등장하였습니다. Ajax(Asynchronous javascript and xml)의 뜻으로 간단하게 js에서 페이지의 새로고침 없이 XMLHttpRequest 객체를 통해 리소스를 요청할 수 있는 것을 의미합니다
초창기 시절과 MVC의 개념이 조금씩 바뀌게 됩니다
Model : ajax로 부터 받는 데이터
View : HTML과 CSS로 만들어지는 화면
Controller : javascript가 중간에서 서버의 데이터를 받아 화면을 바꾸고 이벤트를 처리해서 서버에 데이터를 전달하는 컨트롤러의 역할을 수행하게 됩니다. => javascript가 서버에 데이터를 요청하고 받아온 데이터를 화면에 그려줍니다.
아래는 jquery에서 소개하는 간략한 소개에 나오는 주요개념 들입니다.
컨트롤러의 역할을 수행하기 위해 모델에 요청을 보내고 Dom을 탐색하고 조작하는 기능이 주요개념에 포함되어 있음을 확인할 수 있습니다
* Dom Traversal and Manipulation
* Event Handling
* Ajax
당시 가장 중요한 패러다임은 관점의 분리로 Model과 View의 종속성을 최대한 분리하는 원칙으로 HTML과 jQuery를 따로 관리하는 것이었다고 합니다.
MVVM 아키텍쳐 - angular, react, vue
jquery로 작업을 하다보니 같은 작업이 계속 반복되는 것을 발견합니다. 데이터를 수정하고 이벤트를 연결하고 수정하는 과정이 계속 반복된다는 것입니다.
서버에서는 Template library등을 자주 사용했었는데 {{}}와 같은 치환자가 사용이 가능한 것은 html이 전체적으로 렌더링이 가능하기 때문이었습니다. 이를 통해 선언적으로 개발이 가능했었습니다. 하지만 jQuery를 사용할때는 전체 렌더링을 매번 하는것이 아닌 필요한 곳만 변경을 찾아서 수정해 주어야 했습니다.
이러한 불편함이 계속되던 중 angular 가 등장합니다.
angular에서는 템플릿과 바인딩 이라는 중요한 개념이 등장합니다. (앵귤러를 접해보지 않아 잘 모르겠습니다...)
Model이 변하면 View를 수정하고 View에서 이벤트를 받아서 Model을 변경한다는 Controller의 역할은 그대로 인데 이를 구현하는 방식이 jQuery와 같은 DOM 조작에서 템플릿과 바인딩을 통한 선언적인 방법으로 변하게 됩니다.
이제 코드는 Dom 조작을 직접 하지 않고 A를 B로 바꾸겠다고 선언을 합니다. 그러면 프레임워크에서 이를 처리해줍니다.
이제 View를 그리는 Model만 다루게 되었다는 의미인 ViewModel 이라고 부르며 이 방식을 MVVM이라 합니다.
React, Vue, Angular2, Svelte등 어떤 방식의 템플릿과 바인딩 문법을 쓰느냐 방식만 다를 뿐 MVVM이라는 아키텍처는 그대로 유지 됩니다.
MVC에서 MVVM으로 오면서 달라진 부분
* 컨트롤러의 반복적인 기능이 선언 방식으로 바뀜
* Model과 View의 관점을 분리하려 하지 않고 하나의 템플릿으로 관리하는 방식으로 발전(기존에는 class나 id 등으로 간접적으로 HTML에 접근, 그러나 이제는 직접적으로 HTML에 접근, React의 ref가 HTML에 직접 접근하기 위한 대표적 요소)
컴포넌트 그리고 Container-Presenter 패턴
MVVM은 웹의 DOM API를 알지 못하더라도 비즈니스 로직에만 집중하면 금방 서비스를 만들어 줄 수 있게 해주었습니다. 이는 생산성의 향상을 불러옵니다.
이제는 Page안에 여러가지 모듈이 존재하게 되고 Modal이나 여러 화면들이 하나의 화면을 이룰 수 가 있습니다.
그래서 MVVM이 화면 단위가 아니라 조금 더 작은 컴포넌트로 분리되어 이를 조립하는 방식이 됩니다.(angular의 구현 방식이 궁금하네요)
이 방식이 Component 패턴입니다. 컴포넌트 패턴에 비즈니스 로직이 들어가게 되면 컴포넌트의 재사용은 어렵습니다.
그래서 컴포넌트 에서 비즈니스 로직을 분리해야 했습니다.
이때 등장한 것이 Container/Presetaional 컴포넌트 입니다.
모든 상태/비즈니스 로직은 Container에서 Presentational 컴포넌트는 props를 뿌려주기만 합니다.
이렇게 서로 관심사가 분리되어 있으므로 여러 컴포넌트에서 입맛에 맞는것을 가져다 쓰면 됩니다.
이 방식에서 하나의 문제가 발생했습니다.
상태의 관리와 상태를 보여주는 곳이 거리가 너무 멀면 중간에 위치한 컴포넌트 들이 props로 전달을 해주어야 했습니다. 상태가 변하면 중간에 위치한 컴포넌트 들도 리렌더링이 되어 불필요한 렌더링이 발생하게 되는 것입니다.
FLUX 패턴과 Redux
FLUX 패턴은 mvc 패턴에서 벗어나 단방향 아키텍처를 만들자는 아이디어에서 시작합니다.
제가 기존에 이해했던 mvc 패턴의 문제는 하나의 view에서의 변화는 여러 model의 변화를 일으키고, 하나의 model에서의 변화는 여러 view의 변화를 일으킨 다는 것에 있다고 알고 있었습니다.
이렇게 되면 변화를 예측하기가 힘들어지고 제어하기는 더 어려워진다는 것입니다.
블로그에서 언급된 내용은 기존의 mvc의 경계에 대한 이야기 라고 하였습니다.
같은 데이터를 공유하는 과정에서 props를 통해서 데이터를 전달하는 문제들로 하여금 Model의 관리가 파편화 되는 문제가 발생하였다고 합니다.
위의 경우 count 상태는 루트 컴포넌트에 위치하고 handler와 함께 props drilling을 통해 말미의 컴포넌트 들에 전부 전달해주어야 합니다.
그래서 단일 흐름의 필요성이 대두됩니다.
기존의 컴포넌트인 View에서 action을 발생시키고 이를 감지하던 dispatcher에서는 action의 종류에 맞게 store의 데이터를 변화 시킵니다.
store의 데이터를 참조하고 있던 view에서는 데이터가 변경됬음을 감지하고 관련 컴포넌트는 리렌더링 됩니다.
Redux
reducer + flux 의 합성어 입니다.
상태관리의 대표 적인 선두 주자 입니다.
제가 text로 설명한 dispatcher, action, store의 동작이 위의 그림으로 잘 설명이 됩니다.
다시한번 정리하자면
Flux 패턴은 view를 기존의 MVC 컴포넌트 관점이 아닌 하나의 큰 View를 의미하고 View에서는 Dispatch를 통해 Action을 전달합니다.
Action은 Reducer를 통해서 Data를 변경해주고 Store에 보관합니다.
Store에 있는 데이터는 View에 전달이 되어 사용자에게 보여집니다.
상태관리 => 기존의 컴포넌트 단위의 MVC 개념에서 비즈니스 로직과 View를 분리하면서 이 분리된 개념을 상태관리라고 부르게 됩니다
MVC에서 FLUX로 오면서 달라진 부분
* 공통적으로 사용되는 비즈니스 로직의 Layer와 View의 Layer를 완전히 분리하여 상태관리라는 방식으로 관리합니다.
* 각각의 독립된 컴포넌트가 아닌 하나의 거대한 View영역으로 간주합니다.
* 둘 사이의 관계는 Action과 Reducer라는 인터페이스로 분리하며 Controller는 이제 양방향이 아니라 단방향으로 Cycle을 이루도록 설계합니다.
FLUX 패턴은 본격적으로 상태관리 라이브러리가 등장하게 된 중요한 패턴임을 이 블로그를 읽고 알게 되었습니다.
하지만 Flux 패턴은 쉽지 않습니다.
또한 Redux를 사용하다 보면 action, reducer, saga등의 코드를 작성해야 하는 양이 너무 많았습니다.
redux-toolkit의 createSlice를 이용하면 함수 하나에 action, actionCreator, reducer, saga를 모두 이용할 수 있으므로 코드양이 줄어 드는 것을 확인할 수 있습니다.
Observer-Observable Pattern
초창기
props drilling 문제를 해결하기 위해 Observer-Observable Pattern 방식이 등장합니다.
Flux에서의 Dispatch와 Action을 배제하고 값이 바뀌면 모두에게 전달하는 방식입니다
(이 패턴에 대해서는 따로 공부가 필요할 것 같습니다)
초창기 Mobx에서 이 방식을 기반으로 하였다고합니다.
(graphql을 보면 subscribe를 할 수 있고 만약 eventBus에 event가 발생하면 subscribe 하고 있던 곳으로 effect을 주는 방식을 사용할 수 있는 것으로 알고 있습니다. amplify/dynamodb)
아래는 Angulart에서 Rxjs를 받아들이고 Flux 패턴과 결합한 상태관리 입니다.
3. 현대 웹 프론트엔드의 아키텍쳐 방향성
프론트엔드 프레임워크의 발전 서사는 아래의 순서로 알고 있습니다
jQuery -> 앵귤러js -> React -> 앵귤러2 -> vue, svelte
Context와 hook
React에서 context는 존재했었지만 hook의 등장전까지 공식문서에서 사용을 지양하라고 했었던 걸로 알고 있습니다.
기존에는 사용이 복잡했었고 useContext 훅의 등장으로 그 사용이 간소화 된걸로 알고 있습니다.
이제는 Redux를 대체해서 사용하려는 움직임도 있다고 알고 있습니다.
저는 이러한 원칙을 가지고 있습니다.
전역상태의 추가는 최대한 지양,
상태의 선언 및 관리는 사용되는 컴포넌트에 최대한 가까이 위치
가까운 거리라면 우선 상태 끌어올리기를 시도
상태 끌어올리기로 해결이 안되고 상태의 핸들러가 부모 컴포넌트의 외부에서 사용이 되어지지 않는다면 합성 컴포넌트 사용
부모 컴포넌트의 외부에서도 사용되야 한다면 context API의 사용을 검토합니다.
그 외 상태 간의 거리가 너무 멀거나 로그인 상태, 다국어 처리, 다크 모드등에는 전역 상태로 추가를 하여 관리를 하려 합니다.
또한 서버 상태는 react-query를 사용하여 관리를 맡기고 있습니다.
useState 내부에서 비용이 비싼 함수의 결과물을 상태로 지정해야 한다면 지연 초기화를 사용하여 컴포넌트가 마운트 되었을 시에만 함수가 실행하게 하여 값을 상태에 지정해 줄 수도 있습니다
개선 방향
* View와 Model은 분리한다.
* 프레임워크에서 Props Drilling 문제를 막는 방법을 제공하자
Atomic 패턴 (Recoil, Svelte Store, Vue Composition, jotai)
(아쉽게도 제가 Recoil외에는 사용해보지 않아서 저들은 다음에 직접 사용해봐야 좀 더 와닿을것 같습니다)
View, Store의 분리는 그대로 가져가되 Action~Dispatcher~Store 의 복잡한 구조를 사용하지 않기 위해 만들어진 방법입니다.
Recoil은 훅의 쓰임새와 유사하게 사용이 가능하고 atom, selector등을 이용하여 전역 데이터를 관리, 커스텀 하기 용이합니다.
Recoil 홍보대사는 아니지만 (아직 버전이 0. 대라 사용하지 않는 분도 있는 걸로 알고있습니다) 페이스북에서 관리하는 one-party library로 향후 리액트의 변화도 가장 빠르게 대응할 수 있지 않을까 싶은 개인적인 생각입니다.
개선방향
* View와 Model은 분리한다.
* 중간의 과정은 자율에 맡기고 간단하게 Model에 접근하는 법만 제공하자!!!!!
* 동기화, 동시성 처리가 중요
React-Query - MVC의 확대
서버 상태를 관리하기 위해 나온 대표적인 라이브러리 입니다.
공식문서에도 서버상태를 관리하기 좋다고 나와있습니다.
서버 상태는 다음과 같은 특성을 가지고 있습니다.
1. 사용하는 쪽에서는 서버 상태가 무조건 존재한다는 가정이 있어야 합니다.
2. a시점 에서의 상태와 b시점 에서의 상태가 다를 수 있습니다. 시점에 따라 상태의 stale 상태가 달라집니다.
React-Query 에서는 고전적인 ajax의 데이터를 Model로 간주한다고 합니다.
프론트엔드 개발은 서버 데이터를 CRUD하고 시각으로 그리는 것에 중점이 되어 있는데 FLUX나 Atomic은 너무 복잡한 방법이라는 것입니다.
React Query는
* 서버와의 fetch 영역을 Model로 간주
* View는 React
* Controller는 query와 mutation이라는 2개의 인터페이스를 통해서 서버 데이터의 상태를 관리하고 캐싱, 동기화, refetch등을 관리하는 역할
블로그에서 서버로부터 불러온 데이터를 Model로 간주 한다는 것이 처음엔 정말 이해가 안갔지만 그냥 받아들이자라고 생각한 뒤에는 점차 체화 되는것 같습니다.
기술의 도입과 기반을 MVC, MVVM과 같은 패턴으로 바라보는것이 흥미로웠습니다.
저도 언젠가 저자와 같은 시야를 가질 수 있었으면 좋겠습니다.
아래 내용부터는 저자의 원글 그대로를 가져왔습니다.
제가 하고 싶은 얘기가 아니라 저자의 생각이여서 따로 색칠을 하지 않았습니다
최근 아키텍쳐의 방향성
MVI라는 워딩은 웹 프론트에서는 잘 쓰이지는 않지만 이 이미지가 제가 생각하는 최근 아키텍쳐와 가장 닮아있는 것 같아서 가지고 와봤습니다.
* 단방향 아키텍쳐
* 선언적 프로그래밍을 통한 Controller
* 뷰와 비지니스 로직의 분리(상태 관리)
* 반응형 프로그래밍
* 서버와의 연동을 Controller로 간주하는 움직임
사실 이름은 중요하지 않다. MV*
MVC, MVVM, FLUX, MVI, Atomic, Observable...
현재 쓰고 있는 라이브러리들이 저런 아키텍쳐에 딱 맞게 떨어지지는 않습니다. 아키텍쳐는 비슷한 것들을 묶어서 기존과는 다른 이름을 붙이면 우리가 개념의 범주를 만들기 좋게 하기 위한 일종의 마인드 셋입니다.
어떤 이름들은 라이브러리가 출시가 되고 나서 설명을 하기 위해서 만들어졌고, 어떤 이름들은 전략적으로 이전과는 다른 차별화를 강조하기 위해서 이름을 새로 짓기도 합니다.
앵귤러는 아예 MVW(Model-View-Whatever) 혹은 MV*로 불러달라고 했습니다. 뭐가 되었든 Model과 View 사이에서 무언가를 할테니까요.
각 아키텍쳐의 이름보다는 웹 프론트엔드의 막연히라도 전반적인 아키텍쳐의 구성과 변화의 흐름을 이해해서 아키텍쳐의 본래 이유였던 지속적으로 잘 관리되는 코드를 짜기 위해서 지금 쓰는 아키텍쳐에 맞는 방식으로 더 코드를 잘 작성할 수 있는 기반 지식이 되었으면 합니다.
불편함을 느끼자!
이러한 패러다임의 확장과 개선이 있기 까지는 많은 시간이 걸립니다. 이러한 개선의 시작은 바로 현재의 개발방식에서 불편함을 느끼는 것입니다.
서두에서 언급했던 아키텍쳐의 시작은 불편한것을 찾고 하지 말아야할 규칙을 찾는 것부터 시작합니다. 그리고 그 것을 개선하는 방법을 찾음으로써 새로운 패러다임이 생겨나게 됩니다. -> !!!!!!
내가 당장 새로운 라이브러리를 개발할 수는 없겠지만 언제나 이 불편함을 주시하고 있어야 앞으로 새로운 라이브러리가 나왔을때 그것이 개선이 되었는지 아니면 새롭지 않은 기존 라이브러리의 아류인지 판단할 수 있습니다.
웹은 언제나 무서운 속도로 발전하고 있고 내 코드는 언젠가 레거시가 됩니다. 새로운 것으로 갈아타야할 준비는 언제든지 해야하나 너무 빨리 갈아타도 탈이나고 너무 늦게 갈아타도 문제가 됩니다.
이러한 웹의 발전 역사와 방향성을 이해하면서 그 안에서 아직도 해결되지 않은 여러 불편함들에 대해서 숙지하고 그것들을 해소해주는 라이브러리나 패러다임에 대해서 빨리 깨닫고 공부해야 할 것을 찾고 새로운 기술로 갈아탈 수 있는 눈을 가질 수 있기를 저 역시 그리 되기를 희망합니다.
이 글이 그 과정에 조금이나마 도움이 되었기를 바랍니다.
나의 생각
아키텍쳐의 의미와 앞으로 어떤 마음가짐을 가진채 라이브러리를 대하고 바라봐야 하는지,
생각을 충분히 하지 못한채 라이브러리들을 사용하고자 했던건 아닌가 싶은 생각이 든다
'Frontend > Javascript' 카테고리의 다른 글
비동기 에러처리 🤔 (0) | 2022.06.01 |
---|---|
Javascript Colocation (번역글) 🤔 (0) | 2022.04.26 |
Javascript Don't Use Javascript Variables Without knowing Temporal Dead zone (번역글 + 생각) 💀 (0) | 2022.04.24 |
Javascript undefined/undeclared/null and NaN 🧟 (0) | 2022.04.23 |
Javascript intersection observer api 👀 (0) | 2022.04.20 |