어색하지 않게 한국어로 소개하고 싶었지만 가능하지 않을 것 같은(위키피디아에서 초본문이라고 소개하는) 하이퍼텍스트.

하이퍼텍스트는 문서 안의 하이퍼링크(참조)를 통해 다른 문서로 즉시 접근할 수 있는 문자를 말한다. html의 ht가 바로 하이퍼텍스트이며 앞서 설명한 내용 보다 html 단어를 봤을 때, 그 의미 파악이 더 쉬웠을 것이다.

1960년대에 테드 넬슨이 하이퍼텍스트라는 개념을 처음 고안했다는 글들이 많이 보이지만 이 개념은 우리가 광복을 맞이하던 해, 1945년, 베네바 부시가 기고한 ’As We Think’에서 메멕스(=MEMory EXtender) 시스템 제안과 함께 시작 되었으며 그것을 테드 넬슨이 하이퍼텍스트라는 단어를 사용하며 정리했다고 보는게 맞을 것 같다. 당연하게도 테드 넬슨은 부시에게서 영감을 얻었다.

테드 넬슨은 미래에는 전 세계가 네트워크로 연결이 되고 사용자 인터페이스를 통해 상호작용이 가능할 것이라 예견하였다. 이와 함께 어느 곳에서나 임의적으로 원하는 정보에 접근할 수 있는, 하이퍼텍스트라는 용어를 만들어 사용했다.

html 외에도 이러한 개념을 찾을 수 있는게 또 있다. 화석들은 당연히 알고 있을 하이퍼픽션(=하이퍼텍스트픽션)이다. 당연히 난 모르던 것이다. (*하이퍼픽션을 소개한 이전 기사)

하이퍼텍스트의 특성을 이용, 여러 갈래를 가진 이야기를 출판할 수 있는 전용 프로그램으로 소설을 제작한다. 하이퍼픽션 최초의 작품으로 인정받고 있는 마이클 조이스의 ‘오후: 이야기’는 1990년 이스트게이트 시스템스(프로그램 이름이 아니라 회사 이름이다)를 통해 쓰여진지 3년만에 퍼블리싱 된다.

  • 주의 1. 이 글은 소속 회사의 입장을 대변하지 않습니다.
  • 주의 2. 이 글에는 늘 그렇듯이 현명함 보다는 멍청함이 묻어 있습니다.
  • 주의 3. 선택한 방법과 주장하는 바가 옳지 않을 수 있습니다.
  • 주의 4. 위의 이유로 예고없이 삭제될 수 있습니다.
  • 주의 5. 수정할 사항이 있다면 댓글에 남겨주세요. 검증 후 본문을 업데이트 하겠습니다.


배경

가끔 개발자들이 완벽한 프로덕트를 위해 무언가를 늦춰야 한다고 이야기합니다. 맞습니다. 가끔은 시간 보다는 품질이 중요할 때가 있습니다. 하지만 대부분의 사업은 충분한 시간이 오히려 적이 되는 경우도 많습니다.(소속 회사의 사정을 말하는게 아닙니다) 프로덕트, 또는 서비스는, 그 가치가 지속 가능하기 위해서 이윤을 남겨야 하거나 그것을 벌어들이는 도구일 수 밖에 없습니다. 그런데 가끔은 그런 것들을 망각하는 경우도 많은 것 같습니다.
 
 
시간이 많지 않은 상황에서 우리는 두개의 센터를 연달아 가동시켜야 할 상황이 되었고 저와 우리 팀, 전체의 조직이 해당 목표를 달성하기 위하여 힘차게 뛰어야 했습니다. 지금은 두개의 센터가 잘 기능하고 있습니다. (너무 감사하게도... 아직 보완할 점은 엄청 많지만...) 그러면 어떻게 동시에 진행할 수 있었을까요?!


섣부른 추상화 대신 아주 작은 교집합 찾아내기

제가 회사에서 수행하는 업무는 물류 도메인에 필요한 프론트엔드 개발을 지원하는 것입니다. 이번 센터 오픈 지원을 진행하면서 새롭게 개발이 필요한 영역은 '피킹 프로세스'였습니다. 피킹 프로세스는 물류 센터에 보관하고 있는 상품을 패킹(출고할 수 있는 상태로 만드는 절차)할 수 있도록 만드는 전처리라고 생각해주시면 됩니다. 할당된 물품들을 물류 창고에 보관되어 있는 곳들을, 동선에 따라 지나가며 토트(상품을 담는 바구니)에 지정된 상품을 담아서 약속된 장소로 이동시켜주는 절차입니다. 이를 위해 작업자들은 스캐너 기능이 있는 PDA 기기를 이용하며, 우리 팀은 해당 기기에 사용할 어플리케이션을 개발하는 업무를 맡게 되었습니다.
 
 
그런데 왜 그런 비슷한 일을 하는 시스템을 별도로 개발하게 됐을까요?! 앞서 설명한 핵심 요건은 같았지만 그 절차가 매우 달랐기 때문입니다. 이럴 때 우리는 많은 고민을 하지만 저와 팀은 한개의 어플리케이션을 만들어 서로 다르게 사용하는 것이 위험할 수 있다는 판단을 했습니다.

  1. 실무에 투입되기 전 섣부른 추상화를 하여 더욱 복잡도를 향상시키는 행위를 하지말자.
  2. 사용성이 비슷해지면 어느 하나를 말라죽이고 이관하는 행위가 더 쉬울 것이다.

위의 두가지가 가장 큰 이유였습니다. 많은 개발자 분들이 효율성을 위해 하나를 만들어 여러 곳에서 사용합니다. 재사용성을 프로그래밍의 미덕으로 생각하고 아직 현장에서 사용하지도 않은 로직을 하나로 개발하여 여러 분기를 만들어냅니다. 물론 이는 중요합니다. 그리고 이것이 나쁘다는 의미는 아닙니다. 하지만 이미 사용중인 어플리케이션에 사용자의 피드백이 어느 정도 예측되는 기능을 넣는것과 아직 사용하지 않은, 앞으로 현장에서 많은 피드백을 수용해야 할 어플리케이션에 개발자들이 예측한 기능을 포함시키는 행위는 많이 다르다는 것을 경험했습니다.
 
 
어플리케이션 사용을 시작하며 개발자들이 예측한 사용자들의 사용 방법이나 프로세스는 늘 예상을 빗나가거나 교묘히 다르기 마련입니다. 두개의 어플리케이션을 따로 개발하면서 저와 팀은 사용자들의 요구사항이 오픈 전과 그 후가 매우 다를것이라 판단한 이유입니다. 만약 우리의 예측이 다르면 적정한 시기에 하나를 닫고 다른 하나를 확장하는 것이, 하나의 어플리케이션을 모든 분기 처리의 다른 부분을 포함한 또 다른 어플리케이션으로 분리하는 것 보다 고통스러운 부분이 덜하다고 판단하였습니다.
 
 
그래서 우리는 더이상 쪼갤 수 없는 단위의 기능들을 열거하고 그 기능들을 만들고 또 로그인 페이지와 같이 공통으로 사용할 수 밖에 없는 단위들을 찾아내며 컴포넌트들의 조합을 만들었습니다. 라이브러리 제작을 하기로 결정했습니다.

아토믹 디자인 패턴

글의 시작 부분에서 언급했듯이 동시에 하나의 목적을 가진 두개의 어플리케이션을 개발할 수 밖에 없는 상황이었기에 저는 효율적인 방법을 찾아보았습니다. 그래서 별개의 절차에서 아주 작은 단위로 재사용이 될수밖에 없는 컴포넌트들을 제작하여 그것을 별개의 어플리케이션에서 가져다 쓰고 나머지는 어플리케이션의 구현부에서 다름을 장착하기로 결정했습니다.
 
 
완벽하고 또 베스트 프랙티스라고 할만한 수준은 아니지만, 목적에 합당한 수단으로 아토믹 디자인 패턴을 이용하기로 하였고 그것을 결정하고 약속하기 위해 팀원들과 많은 회의를 거쳤습니다.
 
 
여러가지 스캔과 입력상황을 대비하여 많은 수의 input 컴포넌트들을 만들었고 그것들을 각각의 page에 잘 조합하거나 또 꼭 동시에 쓸만한 것들은 조금 더 큰 단위의 컴포넌트를 만들어 개발을 진행했습니다.
 
 
비록 아직 추가해야되거나 지원해야 할 기능들이 많지만 우리는 적기에 어플리케이션을 완성할 수 있었고 각 센터의 요구사항에 충족되도록 수정하거나 확장할 수 있게 되었습니다.

마무리

솔직히 포스팅한지 너무나 오랜 공백이 있었고, 또 기억에 남는 일이었기에 글을 남기는거여서 여러분들이 읽기 재밌거나 유익한 내용이 아닐수는 있습니다. 하지만 간단하지만 복잡해지는 로직들을 보면서 우아한 방법으로 여러 곳에서 사용하게 만드는 것 보다 때로는 단순하지만 안전하게 코드베이스를 관리하는 방법도 있다는 말씀을 전달하고 싶었습니다.
 
 
현장의 피드백을 충분히 적용하고 그 후에 발견된 공통된 규칙으로도, 우리는 우아하게 시스템을 관리할 수 있습니다.(물론 제가 많이 부족하여 내린 결론일 수 있습니다.)

if (a == 0 && a == 1)

며칠 전 꽤 흥미로운 짤을 접했습니다.

그냥 지나가는 짤이라 생각하고 별 생각이 없었지만 해당 게시물에 페친분들의 댓글이 계속해서 달리면서 위의 난해한 코드를 직접 구현해 보게 되었습니다.

 

자바스크립트를 다뤄본 개발자라면 위 구문의 'a'는 '변수'라고 자연스레 가정할 것입니다. 하지만 일반적인 변수로는 위의 평가를 통과할 수 있는 구문을 작성할 수 없습니다. 그렇다면 어떻게 접근해야 할까요?? 이와 비슷한 기능을 하는 것은 무엇일까요?? 만약 a가 일반적으로 프론트엔드 개발자들이 담는 예측할 수 있는 값이 아니라 프로퍼티라면??

 

아마 getter와 setter를 활용하여 해당 값을 담고 반환할 수 있겠죠?! 그렇다면 window의 프로퍼티로 1. 일반적인 변수 형식으로 2. getter에 변화를 주어 반환하게 한다면 가능하지 않을까요??

 

다음은 위의 개념을 증명하기 위해서 테스트를 진행한 코드입니다.

Object.defineProperty(window, 'a', {
    get: function () {
        return window.b++;
    },
    set: function (val) {
        window.b = val;
    }
});
Window { parent ... }                 // 시스템 출력
a = 0;
0;                                    // 시스템 출력
a == 0 && a == 1
true                                // 시스템 출력

const, let과 달리 var를 사용한 변수는 자동적으로 window의 프로퍼티가 되며 window는 생략 가능합니다. 이러한 부분을 고려하여 위에서 정의된 'a'를 'window.a'로 만들면 자연스레 'a'로 접근과 사용이 가능합니다. 그러면 위의 '1. 일반적인 변수 형식'은 만족합니다.

 

동치연산자는 좌변과 우변의 값을 대조하여 평가(이때 여러가지 일이 벌어지지만 해당 부분은 별도로 찾아 보시면 좋을것 같습니다)합니다. 이때 값을 가져오면서 해당 프로퍼티의 값을 평가하게 됩니다. 이 말은 getter가 실행된다는 의미입니다. 위의 '2. getter엔 변화를 주어 반환'한다는 조건에 맞아 떨어집니다.

 

이제 'a'는 위의 두 조건을 모두 만족하여 변수처럼 할당한 값이, 평가되는 순간 변이가 일어나게 되며, 'a == 0 && a == 1'도 true로 평가됩니다.

 

...

...

 

그렇다면 이것은 과연 좋은 코드일까요??

좋은 코드는 무엇인가??

좋은 코드는 무엇일까요?? 클린 코드?? 오류없는 코드?? 요구사항이 잘 반영된, 납기일을 만족하는 코드??

 

좋은 코드는 각자의 상황과 역할에 따라서 정의하는 것이 매우 다를것 같습니다. 좋은 코드를 말하기 위해서, 저는 나쁜 코드는 무엇인가를 매우 한정적으로 정의를 내리고 그것의 반대되는 개념을 찾게 되었습니다.

 

제가 생각하는 나쁜 코드는,

  1. 목적이 불분명한 것들의 조합
  2. 성장하지 않는(리팩토링 되지 않는) 코드
    입니다.

매우 한정적이지만 많은 것을 포함하고 있습니다.

if (a == 0 && a == 1)은 무엇인가??

만약 이 게시물을 만들게 된 평가식을 우리가 관리하고 있는 프로덕트에서 마주하게 된다면 '기 작성된 코드'는 프로퍼티로 식별할 수 있게 반드시 수정되어야 합니다.

 

전역객체를 오염시킬 뿐 아니라 함께 작업을 하고있는 많은 협업자들을 혼란에 빠지게 할 수 있습니다. 구문을 이해하기 위하여 파편화 된 전역 설정을 찾아봐야 할 것이고, 'a'가 도대체 어디서 어떻게 이용되고 있는지 수정 혹은 개선하기 전 모든 스펙과 사용처를 확인해야 합니다.

 

자바스크립트의 전문가 중 한분은 이러한 코드도 '명확한 의도를 가지고 기능하도록 만들었기에' 문제가 없다고 피드백 주시는 분도 계셨습니다. 하지만 우리는 실제 프로덕트를 '그루'로 불리는 전문가들로 구성된 lab에서 개발하지 않습니다. 우리는 함께 일하고 있는 '누구나', '언제든', 해당 코드를 찾고 개선할 때, 명확한 방법과 영향을 알고 사용해야 합니다.

좋은 코드는 나쁜 코드의 반대, 그리고...

사람은 항상 실수를 합니다. 사람이 작성한 코드는 실수를 유발할 수 있습니다. 우리는 이것을 각종 약속과 규약으로 줄이기 위해 노력합니다.

 

코드컨벤션, 현재의 문제점 공유, 코드리뷰, 정적 분석 모두가 실수를 줄이기 위하여 하는 행위(꼭 순수한 목적은 아닙니다)들 입니다.

 

실수를 줄이고, 실수를 할 수 있는 부분을 없애가며, 그 경험을 공유하는 것은 좋은 코드를 향해 나아가는 좋은 자세가 될것이라 믿습니다.

 

조직이나 개인의 여건에 의하여 언제나, 늘, 좋은 품질의 코드를 만들어낼 수는 없을 것입니다. 하지만 그것을 계속해서 묵혀두고 알고만 있다면 훗날 더 큰 어려움을 가져올 수 있다는 것도 인지하여야 합니다.

  • 주의 1. 이 글은 async / await와 아무런 관련이 없습니다.
  • 주의 2. 이 글에는 멍충함이 묻어있을 수 있습니다.
  • 주의 3. 테스트 방법이 잘못됐을 수 있습니다.
  • 주의 4. 위의 이유로 예고없이 삭제될 수 있습니다.
  • 주의 5. 수정할 사항이 있다면 댓글에 남겨주세요. 검증 후 본문을 업데이트 하겠습니다.

 

의심

큰 프로젝트를 앞두고 PoC를 진행하다 기본적인 부분을 돌아보는 시간을 갖게 되었습니다. 어플리케이션에서 전반적으로 사용하는 기능을 개발해야하는데 평소에 알고있다 생각했던 로딩과 실행에 관한 기본적인 것들을 확인하던 중, 제가 알고있던 사실을, 제가 검증한 적이 없다는 것을 깨닫고 스펙을 확인한 후 나름의 환경을 구성하여 테스트를 진행했습니다.

script의 async / defer 요소

제가 검증하려던 부분은 스크립트의 로딩과 실행이었습니다. 모듈, 의존되는 기능 실행, DOM 핸들링에 관하여 테스트를 실행하다 보니 자연스럽게 async와 defer를 테스트하게 되었습니다. 우선 script의 async와 defer는 무엇인지, 어떤 특징과 차이를 가지는지 알아보겠습니다.

 

출처 / MDN

 

async | HTML5

For classic scripts, if the async attribute is present, then the classic script will be fetched in parallel to parsing and evaluated as soon as it is available.
For module scripts, if the async attribute is present then the scripts and all their dependencies will be executed in the defer queue, therefore they will get fetched in parallel to parsing and evaluated as soon as they are available.
This attribute allows the elimination of parser-blocking JavaScript where the browser would have to load and evaluate scripts before continuing to parse. defer has a similar effect in this case.
This is a boolean attribute: the presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value.
See Browser compatibility for notes on browser support. See also Async scripts for asm.js.

 

defer

This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded.
Scripts with the defer attribute will prevent the DOMContentLoaded event from firing until the script has loaded and finished evaluating.
This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this case it would have no effect.
The defer attribute has no effect on module scripts — they defer by default.
Scripts with the defer attribute will execute in the order in which they appear in the document.
This attribute allows the elimination of parser-blocking JavaScript where the browser would have to load and evaluate scripts before continuing to parse. async has a similar effect in this case.

(*한글 번역이 매끄럽지 않아 영문으로 가져왔습니다.)

 

MDN과 '노란책'으로 통용되는 '프론트엔드 개발자를 위한 자바스크립트 프로그래밍'에서 async와 defer에 관하여 언급한 내용을 요약하면 async는 HTML5에 등장, 즉시 다운로드 후 사용이 가능한 시점에 바로 실행, defer는 HTML 4.01에서 소개, 즉시 다운로드하지만 문서가 준비되었을 때 실행한다 입니다.

 

출처 / async vs defer attributes

 

 

 

위의 설명과 이미지를 보면 스크립트의 async와 defer 다운로드와 실행 순서는 async는 다운로드 된 이후, 다큐먼트가 준비된 것과는 별개로 실행 가능한 시점에 바로 실행이 될것이고 defer는 다운로드 후 문서의 콘텐트가 모두 완료된 후, DOMContentLoaded 전에 실행되리라 짐작할 수 있습니다. 그런데 꼭 그렇지만은 않았습니다.

환경 구축

예제파일

 

위의 링크에서 예제파일을 다운로드 및 설치 후 npm run start를 입력하시면 간단한 테스트베드에서 실제로 동작하는 모습을 콘솔창을 통해 확인할 수 있습니다. index.html을 확인하면 head의 script 구문들과 body 안에서 inline으로 작동하는 스크립트 확인이 가능하며, head의 script 작성 순서는 다음과 같습니다.

 

  • index.js / global function definition
  • async.js / async option
  • defer.js / defer option
  • default / no options

 

우리가 앞서 살펴본 내용으로 추론하자면,

1) index.js는 다큐먼트 구문 분석을 멈추고 실행

2) async.js는 다운로드 즉시 실행되므로 async.js에서 정의한 색상(red)의 div는 페이지에서 확인이 불가

3) defer.js는 다큐먼트가 준비된 후 실행되므로 마지막에 실행

4) default.js는 문서 작업을 멈춘 후 바로 실행될 것이므로 async.js와 동일하게 페이지에서 확인이 불가

 

할 것으로 예상했습니다.

검증

파이어폭스, 사파리, 크롬으로 해당 테스트를 진행했습니다. (IE는 여건상 할 수 없었습니다.) 아래는 테스트를 진행한 결과의 스크린샷 입니다.

 

 

예상했던 대로 테스트 결과, 브라우저 별로 작동하는 방식이 달랐습니다. 하지만 너무나 다릅니다. index.js를 제외한 스크립트의 실행 순서는 다음과 같았습니다.

 

파이어폭스
ASYNC -> DEFAULT -> INLINE / head -> INLINE / body -> INLINE / after header -> INLINE / after section -> INLINE / after footer -> DEFER -> DOMContentLoaded -> load

 

사파리

DEFAULT -> INLINE / head -> INLINE / body –> INLINE / after header -> INLINE / after section –> INLINE / after footer -> DEFER -> DOMContentLoaded –> ASYNC -> load

 

크롬

DEFAULT -> INLINE / head -> INLINE / body -> INLINE / after header -> INLINE / after section -> INLINE / after footer -> ASYNC -> DEFER -> DOMContentLoaded -> load

 

추가적으로 모든 브라우저에서 계속 동일한 결과를 주지는 않았습니다. 가끔은 실행 순서가 바뀌고 캐시를 삭제하지 않은 페이지 재진입은 크롬에서 꽤 높은 확률로 async가 defer 보다 뒤에서 실행되었습니다. 이것은 단어를 어떻게 이해했느냐와 관련된 문제일 수도 있겠지만 너무나 당연시 여기던 저의 '상식'이 잘못되었음을 깨닫게 되었습니다. 여기서 확실히 다짐한 점은, '절대 스크립트 로딩과 실행을 option 값으로만(async, defer) 판단하고 사용하지 말자'입니다.

결론

'알고있는 내용이 알고있던 것과 다를 수 있음을 기억하자'

 

파이어폭스 조차 페이지 리로드를 계속해서 하다보면 '빨간 박스'가 페이지에 노출될 때가 많습니다. 물론 스크립트 파일을 내려받고 실행하는 것이 동일한 기능을 한다해도 늘 같은 속도를 보장하지는 않겠지만 이를 의식하지 않고 암기한 내용을 사용하면 큰 낭패를 볼 가능성이 보입니다. 해당 문제에 관한 내용은 추후 포스팅을 통해서 그 이유를 추적하고자 합니다. (물론, 언제할지는 미정입니다만...)

 

무엇이든 신뢰하지 말자는게 아니라 확실히 알고 사용해야 한다에 초점을 두고 알고있던 기능도 한번쯤은 이렇게 테스트 해보는 계기가 되었으면 합니다.

! 시니어는 구간을 의미하지도 기술을 대변하지도 않는다. 기간과 실력 모두에서 멀어질 수 없지만 또 절대적이지 않다는 의미다.

 

@ 어딘가에 해결하지 못한 문제가 있다. 그 문제에 관하여 누군가는 시간이 부족하다고, 아니면 또 다른이는 우리 중 해결할 사람이 없다고 말한다.

 

# 정말 그럴 수 있다. 내가, 혹은 우리가 가진 자산과 환경이 그렇지 않을 수 있다. 급격하게 팽창하는 조직은 더 그렇다.

 

$ 그러면 포기해야 하는가?! 아니다, 그렇지 않다. 누군가를 다독이고 또 어떠한 솔루션을 함께 찾아주고 미리 경험했던 것, 혹은 알고 있던것을 전파하고 공유할 수 있다.

 

% 미리 경험한다는 것은 단순히 경력이 길어서가 아니다. 연차가 쌓인다고 우리가 세상을 위해, 또는 무언가를 이루려고 했던 모든 행위의 폭이 더 넓어지는 것은 아니다. 누군가 생각만으로 해결책을 찾을 때 다른 누군가는 코드스니펫을 만들어 직접 해결을 시도한다.

 

^ 흔히 말하는 '레거시'를 대하는 태도는 시니어와 주니어를 보다 거시적으로 구분해준다. 전임자가 작성한 코드를 보면서 그 이유를 되새기고 그럴 수 밖에 없었던 치열함을 인정하는 사람과 알수없는 코드를 욕하기만 하는 사람.

 

& 클린 코드는 '리얼 월드'에서, 또는 '프로덕션'에서 그렇게 쉽게 찾아볼 수 있는것이 아니다. 그렇다고 상상 속에만 있는 것은 아니다. 우리가 지향해야 하는 일은 클린 코드 그 자체가 아니라 상황을 인지하고 하나씩 하나씩, 점진적으로 약속을 늘려가고 그것을 공표하는 것, 클린 코드는 볼 수 없지만 좋은 코드베이스를 향하는 것을 멈추지 않는 일이다.

 

* 그렇다면 그러한 것을 누가 하는가?? 힘이 들어 자신을 믿지 못할 때 본인을 믿게하고 답이 보이지 않는 일을 그래도 찾을 수 있을거라며 먼저 앞장서는 일, 코드 분석이 안될 정도로 처참하게 파편화된 코드를 보며 정리할 수 있는 부분을 캐치하는 역할, 그게 바로 시니어다. 시니어는 경력으로 인해 자동적으로 획득되는게 아니다.

 

좋은 시니어는 여지껏 없었다는 말을 듣거나 접할 때마다 그렇다면 넌 좋은 주니어였는지 물어보고 싶었다. 물론 꼰대소리가 싫어 피했지만...

 

어차피 나하고는 상관없는 이야기, 난 좋은 시니어도, 좋았던 주니어도 아니었다. 그냥 모니터만 바라보며 욕받이만 했을 뿐...

리액트를 다루는 기술을 보면서 학습하던 중 중요한 부분들은 블로그에 정리 중에 있습니다. 아직 정리가 덜 끝나 간단한 부분 몇개만 공개를 하고 추후(언제 될지는 몰라요...) 포스팅 하도록 하겠습니다. 오늘은 어플리케이션 개발에 많은 혼란을 가져오는 부분 중 하나인 라이프 사이클에 대한 정리 부분을 공개합니다.

라이프 사이클

모든 리액트 컴포넌트에느 라이프사이클이 존재. 컴포넌트의 수명은 페이지에 렌더링되기 전인 '준비 과정'에서 시작하여 페이지에서 사라질 때 '끝'남.

메서드

  1. Will 접두사 - 어떤 작업을 작동하기 전에 실행되는 메서드
  2. Did 접두사 - 어떤 작업을 작동한 후 실행되는 메서드

컴포넌트의 라이프 사이클

1. 마운트 - 페이지에 컴포넌트가 나타남

  • 마운트 과정에서 호출하는 메서드

    1. constructor: 컴포넌트를 새롭게 만들 때 호출되는 클래스 생성자
    2. getDerivedStateFromProps: props의 값을 state에 넣을 때 사용하는 메서드
    3. render: UI를 렌더링하는 메서드
    4. componentDidMount: 컴포넌트가 웹 브라우저상에 나타난 후 호출하는 메서드

2. 업데이트 - 컴포넌트 정보를 업데이트 (리렌더링)

  • 업데이트가 일어나는 경우

    1. props가 바뀔 때
    2. state가 바뀔 때
    3. 부모 컴포넌트가 리렌더링될 때
    4. this.forceUpdate로 강제 렌더링을 트리거할 때
  • 업데이트가 일어나는 경우 호출하는 메서드

    1. 업데이트를 발생시키는 경우가 실행된 경우 (위의 원인)
    2. getDerivedStateFromProps: (마운트 과정 및 업데이트가 시작하기 전 호출) props의 값을 state에 넣을 때 사용하는 메서드
    3. shouldComponentUpdate: 컴포넌트 리렌더링을 결정하는 flag. true 반환 시 라이프 사이클의 다음 메서드 실행 / false 반환 시 작업중지. this.forceUpdate() 함수를 호출하면 이 과정을 생략하고 바로 render 함수 호출
    4. render: 컴포넌트 리렌더링
    5. getSnapshotBeforeUpdate: 업데이트 반영 전 snapshot을 가져오는 메서드
    6. componentDidUpdate: 컴포넌트의 업데이트 작업이 끝난 후 호출하는 메서드

3. 언마운트 - 페이지에서 컴포넌트가 사라짐

  • 언마운트 과정에서 호출하는 메서드

    1. componentWillUnmount: 컴포넌트가 브라우저에서 사라지기 전 호출하는 메서드

출처: 리액트를 다루는 기술

'Notes' 카테고리의 다른 글

[React / What is react.js??] 리액트  (0) 2020.07.14

리액트 컨셉


불변성

복잡한 특징들을 구현하기 쉽게 만든다. 직접적인 데이터 변이는 이전 이력을 유지 및 재사용할 수 없다.

변화감지

직접적인 변이는 변경을 감지하는 것이 어렵다. 참조하고 있는 불변 객체가 이전 객체와 다르면 객체는 변한것이라 판단한다.

재(再)렌더링

불변성과 변화감지를 이용하여 순수 컴포넌트를 만들고 이를 이용하여 데이터가 변화할 때 즉각적으로 재렌더링을 실행한다.

render 함수

render() { /\* pass /\* };

render 함수는 컴포넌트(레이아웃, 기능 등)를 정의한다. 해당 함수는 html 형식의 문자열이 아닌 뷰의 shape과 behave를 정의한 객체를 반환한다. 최상위 컴포넌트의 렌더링이 끝나면 해당 객체들을 이용하여 DOM 안에 mount한다. (1. 문자열 형태의 HTML 코드를 생성 2. DOM에 해당 내용을 주입)

reconciliation (조화 과정)

리액트의 뷰는 '변형' 되는것이 아니라 새로운 요소로 '교체'된다. 해당 작업 역시 render 함수가 담당한다. 컴포넌트가 데이터를 업데이트했을 때 해당 과정에서 생성된 새로운 데이터를 활용하여 render 함수가 또 다시 호출되며 그러한 과정에서 새로운 view가 생성된다.

Virtual DOM

Virtual DOM을 사용하면 실제 DOM에 접근하여 조작하는 대신, 이를 추상화한 자바스크립트 객체를 구성하여 사용한다. Virtual DOM은 DOM(이미 추상화 된)의 추상적 개념이다. 데이터가 변경되면 1. View 영역을 Virtual DOM에 리렌더링 2. 이전 Virtual DOM과 현재의 것을 비교 3. 바뀌 부분만 실제 DOM에 적용

클래스 컴포넌트


import React, { Component } from  'react';

class App extends Component {
    render() {
        ...
        return ...
    }
}

export default App;

특징 및 차이점

함수 컴포넌트는 컴포넌트의 default가 함수의 형태이며 반환할 값에 template을 클래스형 컴포넌트에는 render 메서드에 사용할 데이터와 return문을 함께 명시하여 준다. 클래스형 컴포넌트의 경우 state 기능 및 라이프사이클 기능을 사용할 수 있으며 임의 메서드를 정의하여 사용할 수 있다.

state vs props

리액트의 state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다. (vue.js에서는 store의 프로퍼티, 즉 상태 값을 의미) props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며 컴포넌트에서는 읽기 전용으로만 사용한다. (vue.js에서는 props는 전달되는 데이터 값이며 읽기전용이 아닌 초기화 값으로 사용)

state vs useState

두가지의 state 값이 존재하며 클래스형 컴포넌트에서는 state를, 함수 컴포넌트에서는 useState를 사용한다.

Property Initializer Syntax

클래서형 컴포넌트의 메서드 바인딩은 생성자 안에서 처리하는 것이 Best Practice이지만 불편하고 코드량이 많아지며 구현부가 파편화되고 관리 이슈가 생길 수 있다. transform-class-properties를 사용하면 이를 회피할 수 있다.

ref

ref(=reference)는 id를 대체하는 리액트의 선택자이다. 특정 엘리먼트를 직접 핸들링할 경우에는 id 대신에 ref를 사용하여 처리한다. ref를 설정하는 가장 기본적인 방법은 1.콜백 함수를 활용하는 방법이며 2. createRef 함수를 사용하는 방법이 있다.

SyntheticEvent (합성 이벤트)

합성 이벤트는 네이티브 이벤트와 다르게 이벤트가 끝나고 나면 이벤트가 초기화되므로 정보를 참조할 수 없다. 비동기적으로 이벤트 객체를 참조하려면 e.persist() 함수를 호출한다.

'Notes' 카테고리의 다른 글

[React / 라이프 사이클] 생명주기  (0) 2020.07.15
사무실 밖으로 보이는 나무들이 너무 추워보입니다. 녹색으로 푸르게 꽉차서 가지 조차도 보이지 않던 유리창이 이제는 휑해져 도로위의 자동차 불빛이 보일 정도로 겨울은 빠르게 다가왔고 벌써 겨울임을 알리고 있습니다.

매년 1월이면 희망에 가득 차서 신년에 할일과 목표를 설정하지만 정작 한해의 마무리는 술, 모임으로만 가득 차있어서 그런지 취기 어린 기억들만 남는 것 같습니다.

올 한해를 살짝 뒤돌아보면 2018년 보다 더 바쁜 한해였습니다. 개인적으로는 이사와 외부활동, 그 외의 일들을 조금 더 늘린 한해였습니다. 오늘은 한해를 마무리하며 개인적인 삶, 회사에서의 삶, 그리고 앞으로 다가올 한해를 또 어떻게 보낼지 계획해 보는 시간을 가지려 합니다.

 

고양이를 키우는 독거노인의 삶

폭등하는 집값에 원하는 구조의 집을 찾기 위해서는 몇 가지를 포기할 수 밖에 없었습니다. 고양이를 모시고 사는 입장이라 고양이가 조금 활발히 움직이고 또 분리된 공간을 보장하기 위해서 복층으로 이사를 준비했습니다. 이렇게나 많은 집을 단기간에 본적은 없었는데 그래도 출 / 퇴근 시간을 '완전히' 포기하니 고양이와 제가 알콩달콩 지낼 수 있는 거처를 마련할 수 있었습니다.

 

출 / 퇴근 시간을 포기한다는 것은 그만큼 멀리 떠났다는 의미이고 멀리 떠난다는 것은 앞으로 더 고독하게 지내야 한다는 말로 바꿀 수 있었습니다. 처음에는 사놓고 읽지 못했던 책 읽으면 되겠지라고 생각했지만 이런저런 핑계로 책을 넘기는 시간은 줄어들고 혼자서 TV 앞에 앉아 라면에 소주, 먹태에 맥주를 먹는 시간이 늘어나게 되었습니다.

 

취미가 없어서 그런건가 하는 생각에 10월 즈음에 평소 배우고 싶었던 일렉 기타를 장만하여 열심히, 술을 마시고 있습니다. -_-a 물론, 술친구가 없으니 조금만 마시게 되더군요. (혼술을 더 자주 즐기게 되었다 / 희한하게도 총량은 줄었다)

 

다른 회사는 어떻게 일하는지 / 또 다른 이들은 어떤 생각을 하는지

19년도 초 즈음에 이사님께서 다른 회사는 어떻게 일하는지도 좀 알아보고 그것을 흡수해 보는것도 좋을 것 같다는 말씀을 해주셔서 올 한해에는 정말 많은 컨퍼런스, 밋업, 모임 등에 참석하였습니다. 혹자는 저를 보고 이직 중이면 이력서를 달라고 연락을 주신 분도 계셨고, 컨퍼런스만 참석한다고 일을 잘하는 것도 배우는 것도 아니라며 핀잔을 주신 분들도 계셨습니다. 일일이 계획을 설명드리기 애매해서 그냥 웃어 넘겼지만 저에게는 꽤나 도움이 되는 부분이 많았습니다.

 

카카오페이 / 페이톡
NHN FORWARD / 가장 인상적
LINE / Extremely Pratical!!!

 

지금 우리 조직이 가진 문제점, 혹은 우리 조직의 구성원들이 극복해야할 고민거리를 해결하고 극복한 사례나 그것을 방지하기 위해서 행하는 것들을 직접 듣고 그자리에서 질문하며 그들의 좋은 선례를 참고하고 도입하기 위해서 많은 노력을 할 수 있었습니다.

 

견고한 코드를 만드는 방법과 자신이 작성한 코드에 자신감과 확신을 갖는 방법을 배웠고 공통 모듈을 어떻게 다른 팀 / 파트와 함께 관리하고 개발해 나아갈지 방법을 논의하는 자리를 마련할 수 있었습니다. 대부분의 조직에서 버스팩터를 높게 유지하기 위해서 어떠한 일들을 하는지에 대해서도 고민하고 그것을 위한 첫 단계를 실천에 옮길 수도 있었습니다.

 

조직을 구성하는 구성원들의 다양한 생각도 알고 싶었고 단순한 QnA 게시판 보다는 개발 문화나 다양한 분야의 개발자들이 현재 하고있는 일과 그들의 고민, 취업 (이직) 등의 정보도 공유할 목적으로 페이스북에 Developer Square라는 그룹도 만들고 운영하게 되었습니다. 물론 활발히 교류하시는 분들은 아직까지는 많이 없지만 그래도 좋은 정보들이 생기면 그에 대해 한번이라도 읽어보고 적합한 정보들은 전파를 위해 공유를 하고 있으며, 제가 예상했던 분들보다 더 많은 분들이 가입을 하고 계셔서 너무 감사할 뿐입니다.

 

자연스레 다른 조직에 계시는 분들과도 소통할 기회가 생기게 되어 개인적으로는 좋은 개발자 분들을 조금이나마 많이 알게 된 계기가 되었습니다.

 

프로젝트 / 프로젝트 / 프로젝트

제가 담당했던 프로젝트를 모두 열거하기는 힘듭니다만 지금 그 프로젝트들을 되돌아보면 1. 허둥지둥 했고, 2. 시간이 없었고, 3. 여전히 시간이 부족했습니다. 명백한 면피용 발언이겠지만 조직에서 원하는 완료 시점, 품질, 요구사항을 모두 만족시킬 시간이 많이 부족했습니다. 물론 지금은 다른 이유로 시간이 없지만 세가지의 커다란 프로젝트를 하면서 느꼈던 가장 아쉬운 점은 생산성을 위한 개발은 다시 시간을 갉아먹는 결과물을 가져올 수 밖에 없다는 점이었습니다. 첫번째는 코드의 품질을 전혀 생각하지 않고 개발을 진행했고, 두번째 프로젝트는 기획을 무시하고 할수있는 것에 치중한채 업무를 진행했습니다. 마지막 프로젝트는 타 부서와의 연계와 소통의 부재로 원하던 목표에 누락된 기능들을 포함한 채 릴리즈 되었습니다.

 

반면에, TDD나 코드리뷰, 적극적인 소통을 통한 경계를 허문 업무 진행은 3가지의 프로젝트를 거치며, 그것들을 점진적으로 도입하면서, (위의 활동과 함께 개인적인 스터디 혹은 도움으로) 많은 부분을 개선할 수 있었습니다. 비록 프론트엔드를 담당하는 사람이 저 뿐이지만, 파트의 구성원 모두 자바스크립트 기반의 개발을 하고 있기에 적극적인 코드리뷰를 진행하며 서로가 놓쳤던 부분을, 혹은 잘이해하지 못했던 부분을 메울 수 있었고 전반적인 컨벤션 혹은 구조, 동작을 어떠한 관점에서 바라보고 제어하는지를 공유할 수 있었습니다. 시간이 부족하였지만 자바스크립트 동작의 원리를 조금 더 서로가 잘 알수 있도록 한가지의 책을 일주일간 업무 외 시간에 스터디하고 그것을 리뷰하는 시간을 마련했습니다. 기본기를 학습하기 원했던 이유는 아직도 많은 개발자들이 for loop 안의 setTimeout의 var와 let이 왜 다른 결과를 반환하는지를 모르기 때문과도 같습니다. (꼭 블록과 함수 스코핑 때문만이 아닙니다. 더 중요한 부분도 들어있습니다.)

 

올 한해를 보내면서 수행했던 큰 프로젝트들을 뒤돌아보면서 향후의 목표와 자세를 정하자면, 앞으로도 1. 'Collective Ownership을 위한 다양한 활동을 해야겠다'와 2. '우리가 안다고 넘어간 것은 결코 전부가 아니다'를 실천하자 입니다. AST와 ASI를 들어만 보고 이게 무엇인지, 비슷한건지, 헷갈려 하지말고 코드수를 줄이는 것 보다 그것이 실행되는 연산의 횟수를 줄이는 것에 목표를 둔다면, 더 나아가서 그러한 반복된 훈련과 연습을 계속하게 되면, 결국에는, 생산성 역시도 높여줄 것이라는 기대를 하게 되었습니다.

 

무언가 새로운 것을 시도하기 보다는 벌여놓은 일을 잘하자

나이가 들고 개발자로 살아가는 시간이 점차 늘어나면서 제가 해왔던 일들을 한해를 넘어서 뒤돌아 보면 많은 것을 했지만 제대로 마친 것은 별로 없구나를 느끼게 되었습니다. 물론 괄목할만한 성장과 결과도 있었지만 그만큼 다양한 시도나 속된말로 뻘짓 역시도 적지 않았습니다. (예를 들어 번역공모...) 뭐 시도한 것에 후회는 단 한번도 해본적 없지만, 이제는, 새로운 것에 대해서 갈증을 갖기 보다 내가 하고있는 일에 더 집중하고 더 잘하는 시간을 마련하기 위한 한 해를 계획하고 있습니다. 그동안 미루웠던 웹개발의 전반을 이해해 보는 것과 UI / UX에 대해서 (둘은 다른 것이지만 함께 언급하기에 저도 관습적으로 사용합니다) 조금 더 깊이있게 다가서는 것입니다.

 

정말 개인적이지만 아직도 프론트엔드 개발자를 백엔드 개발자 보다 아래로 보는 시선들이 있어서 올 한해의 계획 안에는 그러한 잘못된 시각을 바로잡는 역할을 하려고도 합니다. 

 

참 많은 일들과 아직도 끝나지 않은 '도전'이 남아있지만 올 한해는 50% 이상의 목표는 충족한, 이제껏 경험해 보지 못한, 2019년도 였습니다.

 

자... 여러분들의 한해는 어떠셨나요?

리액트를 배워 보자!!!

 

프론트엔드 개발자로서의 삶을 살아가면서 아직도 접해보지 않았던 이름이 있었습니다. 10에 4명 이상은 리액트로 프로젝트를 진행하는 요즘에도 조직과 개인적 이유로 리액트 스터디를 미뤄오다 우연치 않은 기회에 [리액트를 다루는 기술]을 획득(?!)하여 간단한 후기를 남기게 되었습니다.

 

* 현재까지의 스터디 분량으로 리뷰를 진행하며 책의 전체 리뷰는 완독 후 해당 포스트에 업데이트 할 예정입니다.

 

저는 마크업과 UI를 개발하다 AngularJS를 접하며 프론트엔드 개발자로 성장 하였습니다. 처음 접하게 된 프레임워크와 프론트엔드 개발이 이유가 된건지 AngularJS (지금은 사용하지 않습니다... 다 까먹었어요...) / Angular에 대한 애착이 있었고 토이 프로젝트 혹은 프로토타이핑은 Angular를 주로 이용했지만 지금의 회사에서는 프레임워크를 통일하여 사용하고 있기에 Vue.js로만 모든 개발업무를 진행하고 있습니다.

 

리액트에 대한 개발자로서의 호기심과 좋은 평판을 들으며 리액트를 스터디 하려고 계획만 세워놓고 스터디 모임, 컨퍼런스, 밋업, 취미생활, 게임, 음주... 등 계속해서 업무와 개인적인 일련의 일들을 진행하다 보니 자연스레 계획했던 공부는 늘 '내일하자'로 마음 속 한 구석에 처박아 놓고 잊어버리고 있었습니다.

 

개발자 리뷰어를 모신다는 '길벗' 출판사의 광고를 보고나서, 구매해 보고 싶었던 책을 얻게되면 그걸 (반강제적이지만 지극히 자발적인) 동기부여의 기회로 삼아 공부를 할 수 있겠다는 생각이 들어 신청을 했는데, 왠걸!!! 됐습니다!!! 선정되었습니다!!! 그리하여 오늘은 [리액트를 다루는 기술] 그것도 무려 300페이지 쯤 더 두꺼워진 개정판의 스터디(still ...ing) 후기를 남겨보려 합니다.

(자세한 책의 정보는 아래의 링크에서 얻으실 수 있습니다.)

 

* 길벗, 리액트를 다루는 기술

 

리액트를 다루는 기술(개정판)

입문부터 대규모 애플리케이션까지 한 권으로!

www.gilbut.co.kr

 

이 책은 친절하지만은 않다.

리액트에 관심이 있는 프론트엔드 개발자라면 김민준(이라 적고 벨로퍼트라고 읽음) 저자를 한번쯤은 들어보셨을 겁니다. 여러 커뮤니티에서 저자의 게시물과 작업들로 도움을 받으신 분들이 많이 계실겁니다. 여러 경로를 통해 개발자들에게 많은 도움을 주셨던 분이지만 이 책이 한없이 친절한 것은 아닙니다.

 

* 김민준 저자 블로그

 

책의 초반은 리액트의 간단한 개념으로 배경지식을 조금씩 설명하고 JSX가 무엇이며 장점은 무엇인지, 어떻게 코드를 작성하는지 린트를 소개하고 적용하는 방법 등을 소개하며 리액트의 구성요소를 하나씩 차근차근 설명해줍니다.

 

하지만 자바스크립트를 어느 한 라이브러리만 사용해서 개발을 진행했던, 예를 들어(아니, 콕 짚어) jQuery에 의존하여 개발을 진행했거나 JS의 객체지향 패턴 비구조화 할당, 화살표 함수, this의 바인딩과 스코프 (함수 / 블록 스코핑) 등의 전반적인 지식이 부족하거나 문법이 낯설다면 저자의 친절한 설명 글들이 그리 친절하게만 보이지 않을 수도 있습니다.

 

만약 마크업 개발만을 담당하다 프론트엔드 개발자로서 발돋움하기 위해서 책을 펼친다면 이 책은 종국에는 굉장히 큰 도움이 될 거라 확신하지만 다른 얇은 기본서와 ES6에 대한 서적을 빠르게 읽어보시는게 좋을 것 같습니다. (물론, 제 입장에서 바라보는 이야기입니다.)

 

그럼에도 불구하고 이 책은 정말 친절하다.

하지만 이 책은 굉장히 친절하게도 몇가지의 프로젝트를 진행하며 실무에서 고민하고 맞닥뜨려야 하는 여러가지 문제를 자연스럽게 익힐 수 있도록 도와줍니다. 비록 챕터가 지날수록 조금씩 난이도가 높아지는 느낌은 있지만 (현재 기준으로는) 친절하게 설명을 이어가고 독자가 잘 이해할 수 있는 가능한 많은 방법 (노트, 패턴별 코드 비교 및 분석, 개념 설명)을 동원하여 독자가 이해할 수 있는 길로 가도록 잘 이끌어 줍니다.

 

한쪽만 다루지는 않는다.

많은 기술 서적은 본연의 책임과 역할을 잘 수행하기 위하여 한가지만을 중점적으로 설명합니다. 이 이야기는 반대로 한가지만 설명하고 그것을 지탱하고 수행하는 부분의 어쩌면 핵심이 될 수도 있는 부분은 버린다는 이야기가 될것입니다. 하지만 해당 서적은 SPA만 집중적으로 설명하는 것을 넘어서 SSR(서버사이드 렌더링), Node.js, Koa, 거기다 JWT를 통한 인증 시스템 구축까지 실무에서 당연히 마주할 부분을 커버합니다. 그렇다고 많은 부분을 보여주기 위해 소흘한 부분이 있는 것은 아닌 것 같습니다. (아직 완독하기 전이라 섣불리 뭐가 부족하다는 말은 삼가하겠습니다.) 상태관리나 새롭게 추가된 핵심 기능, 함께 사용할 법한 라이브러리 등의 소개 역시도 꼼꼼히 소개해줍니다.

 

대상독자라면 충분히 기본을 넘어설 수 있다.

포스팅을 하면서 앞서 언급한 것과 같이 이 책은 새롭게 프론트엔드 개발을 배우기 위한 입문서적은 될 수 없습니다. 역시나 자바스크립트에 대한 아주 기초적인 지식만으로는 병행해야 하는 별도의 '공부'가 필요할 수 있습니다. 하지만 이 책과 저자가 염두해 둔 [대상독자]라면 이 책은 아주 훌륭한 입문서이자 레퍼런스가 될 수 있을 것 같습니다.

 

* 책에대한 리뷰는 읽어 나가는대로 이 곳에 업데이트할 예정입니다.

** 이 책의 대상독자가 아닌 분들을 위해 입문서로 활용할 아주 얇은서적이 있다면 다른 분들을 위해 댓글로 정보공유 부탁드립니다.

*** 해당 리뷰는 서적 자체를 제외한 별도의 금전적 지원은 받지 않았으며 어떠한 가이드나 지침으로 작성된 글이 아님을 밝힙니다.

 

 

 

프로그레시브 웹 앱은 이미 수년전부터 큰 화두였습니다.

오늘은 PWA를 적용하며 느끼고 고민했던 것들 + 약간의 팁(...이라고 말하기 민망한 주의사항)을 공유하고자 합니다.

 

* 글에 앞서 해당 내용은 주관적인 생각이 20,000% 포함되어 있으며 이 글의 내용이 개발 가이드는 절대 아님을 밝힙니다. 수정 보완해야 할 내용이 있다면 댓글에 공유 부탁드립니다. (_ _)

 

* 이어서 간단한 소개를 할 것이지만, 혹시나 PWA를 처음 들어보신다면, 아래의 링크들을 참고 해보세요. 많은 도움이 되실 것 같습니다.

 

Progressive Web Apps

 

- 웹 펀더멘털

 

여러분의 첫 Progressive Web App  |  Web Fundamentals  |  Google Developers

Pete is a Developer Advocate 소개 웹 앱, 프로그레시브 웹 앱은 무엇입니까? 프로그레시브 웹 앱은 웹을 통해 직접 구축 및 제공되는 데스크톱 및 모바일에서 설치 가능하고 앱과 유사한 환경을 제공합니다. 빠르고 신뢰할 수있는 웹 앱입니다. 가장 중요한 것은 모든 브라우저에서 작동하는 웹 앱입니다. 오늘 웹 앱을 제작한다면, 이미 프로 그레시브 웹 앱을 구축하는 길에 서 있습니다. 신속하고 신뢰할 수있는 모든 웹 경험은 빠르며, 특히

developers.google.com

 

- 조은님의 PWA 소개 포스팅

 

PWA를 구성하는 기술들

지난 글 만으로 코드랩을 진행하고자 하였으나 막상 준비를 하다보니 사람들이 PWA는 어떤 기술들로 구성되어있는 지 궁금해할까 하여 이 글을 작성한다. 우선 명확하게 말하자면 그 어떤 기술도 PWA를 위해 반드시 사용할 필요는 없다.

medium.com

- MDN 프로그레시브 웹 앱 소개

 

프로그레시브 웹 앱 소개

이 문서는 프로그레시브 웹 앱(PWA)의 소개입니다. PWA가 무엇이고 일반 웹 앱에 어떤 이점을 가져다주는지 설명합니다.

developer.mozilla.org

 

PWA는??

처음 PWA를 접하는 분들은 이 용어에 많은 혼란을 느낍니다. 이것은 어떠한 언어 혹은 프레임워크일까요? 정확히 말하자면 PWA는 일반적인 웹 앱을 네이티브 앱 수준에 가깝게 점진적으로 발전시키려는 목표를 가지고 2015년 '알렉스 러셀'이 고안한 '개념'입니다. 그렇기 때문에 PWA는 그 개념에 관한 이해가 상당히 중요하며 어떠한 플러그인 혹은 기술로 PWA가 certificated(인증)되는 것은 아닙니다.

 

그렇다면 PWA의 개념을 만족시켜주기 위해서는 무엇이 필요할까요?? (여기서는 4가지의 핵심 키워드가 아닌 3가지 키워드로만 설명합니다.)

 

Progressive Web Apps are user experiences that have the reach of the web, and are:

  • Reliable - Load instantly and never show the downasaur, even in uncertain network conditions.
  • Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling.
  • Engaging - Feel like a natural app on the device, with an immersive user experience.

This new level of quality allows Progressive Web Apps to earn a place on the user's home screen.

 

위의 글은 구글 개발자 사이트의 Progressive Web Apps 메뉴의 소개입니다. 바로 이 세가지(혹은 네가지)가 PWA의 핵심 개념이며 이러한 노력과 기술들이 들어있어야 PWA로 인정받을 수 있습니다.

 

신뢰성

'불안정한 네트워크 조건에서도 페이지 로딩이 즉각적이고 공룡(downasaur: 크롬 에러페이지 주인공입니다.)을 보여서는 안된다'

어떠한 형태로든 네트워크 의존일 수 밖에없는 웹 앱을 네트워크 상황에 맞게 사용자가 사용할 수 있도록 만들어야 함을 뜻합니다.

 

신속성

'사용자 상호작용에 부드러운 애니메이션 효과와 버벅임 없는 스크롤로 즉각적으로 반응해야한다.'

사용자가 스크롤이나 터치, 클릭 등의 상호작용을 하였을 경우 그에 대한 응답이 빠르게 처리되어야 함을 의미합니다.

 

참여성

'몰입형 사용자 경험(immersive user experience)으로 디바이스의 원래 앱처럼 느끼게 한다.'

참여성(Engaging)이라는 말이 어렵게 느껴질지 모르지만 현재의 웹 앱이 단순히 웹의 어느 곳에서 서비스되는 것처럼 느끼게 하는 것 보다 마치 실제 데스크탑의 프로그램처럼, 혹은 스마트폰에 직접 설치한 앱처럼 느껴지게 해야함을 표현합니다.

 

위의 세가지를 지향하고 이를 적극적으로 반영한 웹 앱이 바로 PWA입니다.

 

Vue.js + PWA + Prerender

이번 프로젝트 역시도 웹뷰에서 서비스 되기 때문에, 조금더 네이티브 앱에 가깝게 구현하기 위한 노력들을 하게 되었고 자연스럽게 PWA를 도입하기로 결정하였습니다. 이와 함께 SEO, 공유기능 등의 이유로 Prerender역시 함께 도입했습니다.

 

Vue-cli를 사용하여 PWA 플러그인을 추가하였고  Prerender SPA Plugin을 사용하여 해당 프로젝트를 진행하였습니다. PWA를 프로젝트에 추가하면 일반적인 Vue.js  프로젝트에 추가적으로 manifest.jsonregisterServiceWorker.js 파일이 설치됩니다.

 

manifest.json은 일반적인 웹 앱을 확장하여 설치가능한 네이티브 앱처럼 만들게 도와줍니다. 흔히 이 파일에는 앱의 이름과 디스플레이 모드, 방향(orientation), 스플래시 스크린의 설정 값들이 들어가게됩니다. 물론 브라우저에 따라서 제대로 동작되지 않는 것들이 많습니다. 이와 함께 설치된 registerServiceWorker.js는 Service worker를 등록하게 도와주는 파일입니다.

 

서비스 워커를 모두 소개하기에는 무리가 있지만 간단히 설명하자면 브라우저의 백그라운드에서 작동하는 워커오프라인에서 웹 페이지를 보여줄 수 있는 방법을 고민하다 나오게 되었습니다. 기본적인 registerServiceWorker.js 외에도 서비스워커를 등록할 수 있는 방법은 여럿이며 서비스워커는 '캐싱전략'과 아주 밀접한 관계를 가지고 있으므로 이는 직접 찾아보시면서 학습하셔야 할 필요가 있습니다.

 

추가적으로 전달드리고 싶은 점은 서비스워커를 도입하면 디바이스의 푸시 알람을 보낼 수 있다는 부분과 업데이트가 일반적인 웹 앱과는 다르다는 것입니다. (iOS는 지원하지 않으며 웹뷰에서는, 저희 내부적인 보안상 이유 때문인지는 몰라도, 작동이 안됩니다.) 무작정 PWA를 도입했다가 캐싱과 업데이트 이슈 때문에 빠르게 삭제해 본 경험이 있어서 그런지(이번이 두번째 PWA 도입) 우선은 어떻게 사용자에게 앱에 새로운 변화를 알릴지 (iOS 포함) 그리고 어떻게 해야 엔드유저의 이탈을 막을 수 있을지 여러가지를 다양한 부분에서 신경써야 된다고 말씀드리고 싶습니다.

 

위의 두가지에 대한 전략과 셋팅이 어느 정도 진행되었다면 이제 PWA 본연의 철학을 위해 내부적으로 진행해야 할 리스트들이 필요할 것입니다. 저 같은 경우에는 혼자서 모든 마크업과 프론트엔드 개발을 진행하기에 바이크쉐딩이 없었지만 어떠한 전략도 완벽한 것은 없으니 가장 적게 잃는 것 (성능, 심미적 아름다움, 사용성 등)을 선택하시길 바랍니다.

 

저의 최우선 과제는 성능과 앱과 같은 UI/UX였습니다.

 

처음 디자인을 받았을 때에는 위와 같은 UI가 아니였습니다. 조금 더 모바일 웹 같은 모습이였고 단순한 스크롤 이벤트로 각각의 리스트 아이템들을 살펴볼 수 있었기 때문에 제가 원하던 형태의 앱과 같은 UI/UX는 아니었습니다. 이를 해소하기 위하여 여러 앱을 벤치마킹하였고 디자인팀과 회의를 통해 현재의 Card 형태의 디자인과 스와이프 제스쳐를 사용한 디자인이 완성되었습니다.

 

디자인이 완료된 후에는 최우선 과제였던 성능적인 부분이 걱정되었습니다. 기존의 스와이퍼 라이브러리들은 제가 원하던 효과를 지원하지 않았고 이를 해결하기 위해서 외부 라이브러리를 수정하기에는 예측과 방어가 어려운 side-effect가 우려되었기 때문에 저희만의 UI/UX를 위한 기능을 직접 제작하기로 결정하였습니다.

 

마우스 커서를 이용한 시연이기에 샘플로 보여지는 gif에서는 그 느낌이 잘 살진 않지만 제법 앱과 같은 부드러운 터치 제스쳐 느낌을 줄 수 있었고 필요한 기능만 간단하게, 예를 들어 물리적인 느낌을 주기 위해서 자바스크립트로 ease-in 등을 구현하지 않고(사실 삼각함수 바보인 문과 출신인지라...) 최대한 CSS를 활용하여, 작은 사이즈로 원하던 효과를 얻을 수 있었습니다.

 

이렇게 직접 구현이 필요한 부분과 외부 라이브러리 사용을 검토하는 것은 개발 및 운영상 큰 이점이 있습니다. 커뮤니티가 활발한 라이브러리를 사용한다면 1. 에러가 발생할 경우 집단 지성을 통해 이슈 대응이 수월하고 2. 그러므로 비교적 안정적인 서비스 운영을 할 수 있습니다. 직접 제작할 경우에도 1, 2의 장점을 모두 가질 수 있겠지만 경험상으로는 잘 만들어진 라이브러리를 적절하게 활용하는 것이 개발 생산성을 높이는데 도움이 많이 되었습니다.

 

직접 구현이 필요하다는 판단은 개발자들 마다 다른 관점의 다양한 이유가 존재하겠지만 저에게는 크게 1. 구현하고자 하는 효과 / 연산 / 퍼포먼스가 기대치 이하일 때, 2. 근사치에 가까운 라이브러리가 존재하지만 수정하는 것이 위험하다고 판단될 때 입니다. 물론 선택과 도입은 개발자 본인의 몫입니다.

 

 

프리렌더링은 처음 시도해 보는 것이기도 했고 레퍼런스가 적었기 때문에 개발 초기부터 꾸준히 이터레이션 하면서 배포 과정 중에 확인 및 수정하며 작업을 진행했습니다. 프리렌더링은 미리 정적인 파일을 만들어 deploy하는 것으로 routing 룰과 많은 관계가 있습니다. 예를 들어 /some_dir을 사용하는 path에서 보여지는 페이지를 프리렌더링 하고 싶다면 해당 파일을 번들링 시에 만들어 내도록 vue.config.js에 routes를 등록하고 해당 routes 룰을 직접 수정 및 보완해야 합니다. 여기서 페이지에 어떠한 사용자 상호작용 혹은 일정 시간이 필요하다면 renderer 프로퍼티에 별도의 옵션들을 설정하여 스냅샷을 찍을 때 원본에 가깝도록 만들어줘야 합니다. renderAfterTime 같이 물리적인 시간을 설정하여 기다린 후 파일을 생성하거나 혹은 어떠한 이벤트가 발생한 후 캡쳐하도록 하는 방법들이 Github에 자세히 설명되어있으며 이를 적극 활용하시기 바랍니다.

 

이렇게 개발 막바지에 들어서고 웹뷰가 아닌 '홈 스크린에 추가'된 웹 앱으로 스플래시 스크린, 아이콘 사이즈 등을 테스트 하다 중요한 사실을 하나 확인할 수 있었습니다. 네이티브 앱과 달리 사용자의 방향(orientation) 전환 이슈가 발생하였고 이를 방지하기 위한 추가적인 조치가 필요하다는 것이었습니다.

 

UI/UX적으로도 해당 orientation을 대응할 수 없었기에 방향전환 이벤트가 발생하면 정상적인 사용을 유도하기 위한 기능을 넣게 되었습니다. 물론 웹에서 접근하는 경우가 극히 적지만 이를 방어하고 불필요한 에러 혹은 그와 같은 경험을 제거하기 위하여 꼭 필요한 조치였습니다.

 

아직 오프라인에 대한 대응을 어떻게 해야할지, 그리고 PC 대응 등의 여러 태스크들이 존재하지만 PWA가 지향하는 바와 근접하게 개발하기 위하여 여러가지를 다양하게 고민해 본 프로젝트였습니다.

 

도입후

확실히, 글에서 언급한 것 외에도, 여러가지 고민을 한 결과 만족할만한 결과물을 얻을 수 있었습니다. 다만 모든 선택이 다 장점만 얻을 수 없듯이 프리렌더링에 대한 아쉬움은 조금 큰 편입니다. 모든 데이터를 자동적으로 반영하여 물리적인 document를 생성하는 것은 불가능하며 이를 통해 검색엔진에서 비교적 정확, 신속한 정보를 얻어내기에는 무리가 있을 것 같습니다. 만약 포스팅한 내용과 비슷하게 서비스를 개발하시려고 한다면 잘 참고하시기 바랍니다.

+ Recent posts