CarouselSlider 구현중 느낀점 1. 구현 중 필요한 상수 또는 변수는 따로 구분해주자. 1 2 3 4 5 6 7 8 9 10 const duration = 500 ;const direction = { prev : -1 , next : 1 , }; const newImages = [images[images.length - 1 ], ...images, images[0 ]];let currentSlide = 1 ;let transitionComplete = true ;
로직 중간에 재할당이 발생하지 않은 요소는 const 키워드로 선언하여 의도치 않은 재할당을 방지한다.
currentSlide는 DOM API를 사용하여 필요할 때마다 가져왔는데, 값을 데이터 부분에서 한번만 가져와서 그 값을 가지고 사용하는 방법으로 하면 DOM API를 자주 사용하여 발생하는 코드의 복잡성을 해결할 수 있다.
2. transitionend 이벤트와 transitioncancel 이벤트 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const setTransitionCompleteTrue = ( ) => { transitionComplete = true ; }; $container.addEventListener ("transitionend" , setTransitionCompleteTrue); $container.addEventListener ("transitioncancel" , setTransitionCompleteTrue); $container.addEventListener ("click" , (e ) => { if (!e.target .matches (".carousel-control" ) || !transitionComplete) return ; e.target .matches ('.prev' ) ? slideImage (direction.prev , 0 , newImages.length - 2 ) : slideImage (direction.next , newImages.length - 1 , 1 ); });
transitionend 이벤트는 요소 노드의 trnasition 이벤트가 완료될 때, 이벤트 핸들러가 호출된다.
transitioncancel 이벤트는 요소 노드의 trnasition 이벤트가 취소될 때, 이벤트 핸들러가 호출된다.
처음에 캐러셀을 구현할 때, 1-4번 사진에서 next 버튼 클릭시 transition-delay가 유지되면서 다음 사진으로 이동했는데, 연타로 클릭하니 transition-delay가 유지되지 않고 다음 사진으로 이동하여 이를 어떻게 구현할 지 생각해보고 구글링을 하다가 transition 이벤트를 알게되어 사용하였다.
transition이 완료될 때, transitionComplete를 true로 할당하고 클릭 이벤트가 발생하였을 때는 위와 같이 이미지를 슬라이드 해주는 함수를 구현하였다.
slideImage 함수 내부의 로직은 위 부분에서 알 필요가 없고 위 부분에서는 단지 클릭 이벤트가 일어났을 때, 이미지를 슬라이드 해주는 관심사 가 중요하기 때문에 다른 것은 알 필요가 없도록 코드를 구현하는 것이 바로 관심사 의 구분이다.
transitionend 이벤트가 가끔 사라지는 경우
next 버튼, prev 버튼을 연타해서 클릭하거나 예상치 못한 경우에 transition이벤트가 완료되지 않고 갑자기 사라지는 경우가 발생하였다.
위와 같이 갑자기 클릭을 계속하여도 transitionend 이벤트가 동작하지 않아 transitionComplete값이 true로 할당되지 않으므로 정상동작하지 않는 것을 볼 수 있다.
1 $container.addEventListener ("transitioncancel" , setTransitionCompleteTrue);
이러한 예외경우를 위해서 transitioncancel이벤트로 이벤트 핸들러로 다뤄주면 예외 처리가 가능하다.
3. 마지막 캐러셀이 슬라이드 되고 난 후 다시 처음 슬라이드로 이동시키기 (눈속임) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const slideImage = (direction, from , to ) => { transitionComplete = false ; currentSlide += direction; setSlidesProperty ("--duration" , duration); setSlidesProperty ("--currentSlide" , currentSlide); if (currentSlide === from ) { setTimeout (() => { currentSlide = to; setSlidesProperty ("--duration" , 0 ); setSlidesProperty ("--currentSlide" , currentSlide); }, duration); } };
코드를 해석하자면 다음과 같다.
transitionend 이벤트가 발생하면 transitionComplete가 true로 할당이 된다. 즉, transition이벤트가 끝나면 currentSlide를 해당 방향으로 한칸 이동시킨다.
만약 현재 슬라이드가 마지막(from)이라면, transition-delay가 완료되는 시간인 duration 후에 currentSlide에 처음 슬라이드 번호를 할당하고 duration 속성을 0으로 초기화하여 transition 이벤트가 발생하지 않도록 한 후 현재 보이는 슬라이드를 처음 슬라이드 번호로 바뀐 값으로 설정해준다.
이렇게 되면 사용자는 마지막 슬라이드로 이동했을 때, 다시 처음 슬라이드로 돌아온 것으로 무한 루프를 구현한 것처럼 느끼게 된다.
Counter 리팩토링하면서 느낀점 1. 이벤트 핸들러 부분과 함수의 역할에 따라 구분해주자. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const counter = (function Counter ( ) { let counter = 0 ; return function (helper ) { counter = helper (counter); return counter; }; })(); const increaseCount = (counter ) => { counter += 1 ; return counter; }; const decreaseCount = (counter ) => { counter = counter > 0 ? counter - 1 : 0 ; return counter; }; const $counter = document .querySelector (".counter" );document .querySelector (".increase" ).addEventListener ("click" , () => { $counter.textContent = counter (increaseCount); }); document .querySelector (".decrease" ).addEventListener ("click" , () => { $counter.textContent = counter (decreaseCount); });
리팩토링 전에는 헬퍼함수라는 콜백함수를 받아 클로저를 구현해서 increaseCount, decreaseCount 함수가 counter 함수에서만 사용하는데 전역코드에 존재하므로 다른 곳에서도 사용할 수 있는 함수로 오해할 수 있으므로 이들 함수간의 응집도를 눂혀줘야한다.
또한, 이벤트 핸들러 함수가 렌더링을 해주고 있는데 이보다 렌더링 해주는 함수, 카운트 증가시키는 함수, 카운트 감소시키는 함수 등으로 역할에 따라 구분하는 것이 적절하다.
위 두가지 개선사항을 반영하여 나온 코드가 아래 코드이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const counter = (() => { let count = 0 ; const $counter = document .querySelector (".counter" ); const render = ( ) => { $counter.textContent = count; }; return { increase ( ) { count += 1 ; render (); }, decrease ( ) { if (count > 0 ) count -= 1 ; render (); }, }; })(); document .querySelector (".increase" ).addEventListener ("click" , counter.increase );document .querySelector (".decrease" ).addEventListener ("click" , counter.decrease );
counter 식별자에 즉시실행함수의 반환값(객체)를 할당하여 처음 Js 파일이 로드되었을 때, 한번만 호출되거나 실행되는 부분과 여러번 호출되는 부분을 구분하였다.
이벤트 핸들러가 직관적으로 클릭하면 무슨일을 하는 지 알 수 있다.
더 나아가 위 코드를 각 역할에 맞도록 모듈로 구분할 수도 있다.
isPalindrome 리팩토링 중 느낀점 1. 응집도 높이기 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const $input = document .querySelector (".palindrome-input" );const isPalindrome = (string ) => { const newString = string.toUpperCase ().replace (/[^A-Za-z0-9가-힣]/g , "" ); if (newString === "" ) return false ; const reversedString = newString.split ("" ).reverse ().join ("" ); return newString === reversedString; }; document .querySelector (".palindrome-checker" ) .addEventListener ("submit" , (e ) => { e.preventDefault (); const value = $input.value .trim (); if (value === "" ) return ; document .querySelector (".palindrome-result" ).textContent = isPalindrome ( value ) ? `"${value} " is a palindrome` : `"${value} " is not a palindrome` ; $input.value = "" ; });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const checkPalindrome = (() => { const $input = document .querySelector (".palindrome-input" ); const $result = document .querySelector (".palindrome-result" ); const isPalindrome = (string ) => { const newString = string.toUpperCase ().replace (/[^A-Za-z0-9가-힣]/g , "" ); if (newString === "" ) return false ; const reversedString = [...newString].reverse ().join ("" ); return newString === reversedString; }; return (e ) => { e.preventDefault (); const value = $input.value .trim (); if (value === "" ) return ; $result.textContent = `"${value} " is ${ isPalindrome(value) ? "" : "not" } a palindrome` ; $input.value = "" ; }; })(); document .querySelector (".palindrome-checker" ) .addEventListener ("submit" , checkPalindrome);
isPalindrome 함수와 이벤트 핸들러 함수의 응집도를 높히기 위해 즉시실행함수로 감싸주었다.
즉시실행함수는 이벤트 객체를 인수로 받는 함수를 반환한다. 이 함수에다가 리팩토링 전에 이벤트 핸들러 안에 있던 코드들을 담아주었다. 즉시실행함수가 반환하는 함수는 외부함수의 식별자를 참조하고 있고 외부함수보다 생명주기가 더 길어서 클로저 이다.
문자열을 렌더링해주는 부분에서 조건에 따라 ‘not’이 있고 없고 차이가 있으므로 바꿔주었다.
이터러블 요소에는 스프레드 문법을 사용할 수 있다. 문자열은 이터러블하므로 split() 대신에 spread 문법을 사용하여 배열화 해주었다.