ul태그가 있고 얘가 li태그들을 자식들로 갖고 있다고 하자. 이 li태그들에 어떤 이벤트를 등록하려면 지금까지는 다음과 같이 해왔었다. 

<ul id="some-list">
    <li>가계부 정리하기</li>
    <li>자바스크립트 강의듣기</li>
    <li>리액트 공부하기</li>
    <li>코딩테스트 문제 풀기</li>
</ul>
const someList = document.getElementById("some-list");

for (let item of someList.children){
    item.addEventListener("click", function(e){
        e.target.ClassList.toggle("done");
    };
}

즉 자식들로 잡든 처음부터 getElementsByClassName으로 잡던 li태그들 하나하나에 직접 이벤트 리스너들을 때려박는 형태였다. 그러나 하나하나 때려박는다는 것이 성능상 안 좋기도 하고, 나중에 js코드로 some-list에 새로운 li태그를 넣을 경우 새로 넣어진 li태그에도 별도로 다시 저 이벤트를 때려박아야 하는 번거로운 일이 생긴다.

 

이런 상황에서 쓸 수 있는 것이 이벤트 위임.

간단하게 말해서, 자식들의 이벤트 리스너를 부모에서 다루는 것이다. 

이벤트 버블링을 통해서.

 

알다시피 이벤트 버블링은 자식에서 발생한 이벤트가 부모로 퍼져가는 것을 말한다. 즉 위와 같은 클릭 이벤트를 등록하는 상황에서, 자식(li태그)에서 발생한 click이벤트가 부모(ul태그 등)으로 퍼져나가는 것을 활용한다는 것이다. 그리고, e.target을 활용하면 이벤트를 발생시킨 녀석에 대한 특정한 동작 등이 가능하다!! 즉 다음과 같이 할 수 있다.

const someList = document.getElementById("some-list");

someList.addEventListener("click", function(e){
    if (e.target.tagName == "li"){
        e.target.ClassList.toggle("done");
    }
});

if (e.target.tagName == "li")를 넣은 이유는, ul태그의 영역에서 li태그가 없는 공간을 클릭하는 상황에도 저 이벤트 리스너가 수행되는 상황을 막는 것이다. 해석하자면 ul태그의 영역에 대해 클릭 이벤트가 발생했을 때 이벤트 리스너를 실행하는데 그 중에서도 ul 태그 내의 li태그가 클릭돼서 수행되는 경우에만 if문 내의 코드를 실행한다는 것. 

 

기존에 쓰던 반복문을 통해 하나하나 이벤트 리스너를 때려박는 방법이 비효율적이라곤 생각했으나 이렇게 쉬운 방법이 있을 줄 몰랐다. 강의를 듣던 중 관련된 질문에 "아니 이렇게 하면 부모에 등록되는 건데, 이 경우엔 자식에도 등록이 되어지는 거냐?"란 질문이 있던데, 내 생각은 "No. 아닙니다. 자식인 li태그를 클릭하면 캡처링 단계에서 수수숙 내려와 li태그 즉 사건을 불러일으킨 놈을 찍고, 다시 부모 요소들로 올라가는 버블링이 생기게 되는데 그 때 저 이벤트 리스너가 수행되면서 target을 통해 불러일으킨 놈이 내 자식 중에서 li일 때만 수행하는 방식입니다"이다.

 

참고로 이 이벤트 위임이란 방법은 버블링을 이용하는 방법이므로, 당연한 소리지만 자식 태그에 버블링을 막도록 코드를 짜놨으면 무용지물이다. 이 점 알아두고 사용하자.

js에서 sort()메소드를 사용하면 배열을 정렬할 수 있다. sort는 정렬한 배열을 리턴해주는데 주의할 점은 정렬한 배열은 새 배열이 아니라 원본 배열과 같은 녀석이다(즉 같은 참조이다).

 

sort()메소드에 아무런 인자도 전달해주지 않으면 기본적으로 유니코드에 정의된 문자열 순서에 따라 정렬해준다.

즉,

const letters = ['A', 'C', 'E', 'B'];
const nums = [111, 13, 20, 10000];

letters.sort();
nums.sort();

console.log(letters); // ['A', 'B', 'C', 'E'];
console.log(nums);    // [10000, 111, 13, 20]

숫자들조차도 문자들 정렬할 때처럼 사전순으로 정렬된 모습이다ㅋㅋ 즉 문자열을 정렬할 경우엔 그냥 sort()를 써도 되지만, 숫자들을 정렬할 때는 sort()만 한다고 되지 않는 모습. 파이썬은 그냥 해주던데..에휴

 

암튼 이렇게 숫자를 정렬하고 싶은 상황 등에선 sort()에 콜백함수를 전달해야 한다. 

const nums = [111, 13, 20, 10000];

nums.sort((a, b) => a - b); // 오름차순 정렬
console.log(nums);          // [13, 20, 111, 10000]

nums.sort((a, b) => b - a); // 내림차순 정렬
console.log(nums);          // [10000, 111, 20, 13]

급하면 여기까지만 보고 나가도 됨.

근데 이제 이 이유가 궁금한 사람들이 있잖아? 나처럼. 

다른 데 가지 말고 여기서 저렇게 해야 하는 이유를 같이 보자구요 :) ㅋㅋㅋㅋㅋㅋ

 

우선, sort()는 파라미터로 compareFunction을 옵셔널로 받음. 옵셔널이란 말은 이 놈을 받을 수도 있고 안 받을 수도 있다는 얘긴데 받는다면 이 compareFunction을 기준으로 정렬이 되고, 안 받는다면 유니코드에 의한 문자열 순서대로 정렬한다는 것.

 

암튼 이 compareFunction은 파라미터를 두 개 받도록 만들어야 함. (a, b)로 받는다고 해봅시다. 그러면 이 함수의 리턴값에 의해 어떻게 정렬할지가 결정되는데, 그건 다음과 같음.

 

1. 리턴값이 음수 : a가 앞으로 오도록 정렬

2. 리턴값이 0 : a, b를 바꾸지 않음

3. 리턴값이 양수 :  b가 앞으로 오도록 정렬

 

따라서 리턴값을 a - b로 하면 오름차순으로, b - a로 하면 내림차순으로 정렬할 수 있는 것임!

 

※ 참고링크

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

 

Array.prototype.sort() - JavaScript | MDN

sort() 메서드는 배열의 요소를 적절한 위치에 정렬한 후 그 배열을 반환합니다. 정렬은 stable sort가 아닐 수 있습니다. 기본 정렬 순서는 문자열의 유니코드 코드 포인트를 따릅니다.

developer.mozilla.org

 

일반적인 화살표 함수의 사용법은 다음과 같다.

그러나 여기서 반환값이 한 줄로 표현가능하면 return과 중괄호를 생략가능! 즉 다음처럼 쓸 수 있다.

그러나 리턴값이 객체(Object)이면, 저렇게 중괄호와 return을 모두 생략한 형태로 작성하면 error가 난다. 소괄호를 감싸줘야 한다. 즉 다음처럼 써야 함.

중괄호를 그대로 쓰면서 return을 생략 가능한 줄 알았는데 아니었다. 다시는 이 사실을 잊지 말고자 기록.

 

정리

여러 줄이면 중괄호를 쓰고, return을 써야 한다.

내용이 return문 한 줄이면 중괄호와 return 생략 가능, 그러나 return되는 것이 '객체'라면 소괄호로 감쌀 것(객체임을 명시적으로 알려주는 역할을 한다)

기존에 배열에서 for loop를 돌 때 사용가능한 방법으로 알고 있던 것은 for-of문을 사용하는 것이었다.

그러나 js에선 기본적으로 배열에서 루프를 도는 것고 관련해  forEach와 map이란 메소드를 제공한다. 이들에 대해 간단하게 살펴보자.

 

1. forEach

파라미터로 콜백함수를 작성하며, 이 콜백함수의 파라미터로 배열의 각각 요소들이 들어가서 콜백함수의 내용을 수행한다. 즉 콜백함수에는 파라미터 하나가 반드시 작성되어야 한다.

당연히 화살표 함수(Arrow function)으로도 작성가능

근데 이 콜백함수에 작성하는 파라미터를 여러 개 쓸 수도 있다. 일단 첫 번째 파라미터는 배열의 각각의 요소들이 들어가는 자리고, 두 번째 파라미터를 주면 요소의 인덱스를 관리할 수 있게 된다. 당연히 이 녀석은 처음엔 0부터 시작해서 각 요소들이 순회될 때마다 1씩 더해지는 값이다. 주로 다음과 같이 많이 쓴다.

세 번째 파라미터는 주로 arr이란 이름으로 많이들 작성하며, forEach메소드를 쓰고 있는 배열 자체(위 예시에선 myArr이란 배열)이 arr로 들어온다. myArr처럼 변수이름에 담아서 배열을 쓸 경우엔 굳이 쓸 필요 없긴 하지만, [1, 2, 3, 4]처럼 리터럴 배열을 사용할 경우엔 요긴하게 쓸 수 있을 것이다.

 

2. map

forEach와 동작하는 방식은 거의 비슷하다. 파라미터로 콜백함수를 받으며, 콜백함수의 첫 번째 파라미터로는 배열의 각각 요소들이, 두 번째 파라미터로는 인덱스 값이 전달된다. 그러나 map이 forEach와 다른 점은 메소드의 호출 결과로 또 다른 배열을 리턴한다는 것이다(forEach메소드는 리턴값이 없다). 즉 콜백함수 내에서 return문을 작성하게 되면 각각의 리턴값으로 구성된 새로운 배열이 map메소드의 결과로 리턴된다는 것이다!

 

즉 단순 반복작업이 필요하면 forEach, 새로운 배열이 필요하면 map메소드를 사용한다는 것.

주의할 점이 있는데, 두 메소드의 최대 반복 횟수는 메소드를 처음 호출할 때의 요소 개수이다. 즉 중간에 배열의 길이가 늘어나도 처음의 배열 원소 개수만큼만 동작한다는 점. 그러나 형광펜으로 강조했듯이 최대 반복 횟수가 처음 호출할 때의 요소 개수란 것이지 언제나 반복 횟수가 그렇다는 게 아니다. 배열의 길이가 줄어들면(pop을 한다던지), 그 만큼 반복횟수가 줄어든다. 이 점 알아두고 사용하도록 하자.

 

리액트는 state와 props를 통해 데이터마다 다른 화면을 보여줄 수 있었다. 이번엔 state가 바뀔 때마다 리액트가 렌더링하는 방식을 살펴보겠다.

 

우선 결론적으로 말하자면, state가 바뀔 때마다 그 state를 갖고 있는 컴포넌트 자체를 새로 렌더링한다. 즉 그 컴포넌트를 통째로 화면에  다시그린다는 것. 이 때 쉽게 생각할 수 있는 문제점은 "아니 그럼 컴포넌트를 통째로 다시 그린다면 컴포넌트 내에서 안 바뀌는 부분도 다시 그린다는 건데 이건 좀 낭비 아냐?"이다.

 

이런 문제를 해결하기 위해, React에선 Virtual DOM(가상 돔)이란 것을 활용한다. 리액트 내에서 DOM Tree를 본따서 만든 것인데, 이를 통해 Element를 새로 렌더링할 때 실제 DOM Tree에 바로 반영하는 게 아니라 일단은 Virtual DOM에 적용한다. 이후 state 변경 전의 Virtual DOM과 변경 후의 Virtual DOM을 비교해, 실제로 바뀐 부분만 찾아낸 다음에 비로소 그 부분들만 실제 DOM Tree에 적용한다. 똑똑하게도 바로 화면을 바꾸는 게 아니라 화면을 바꿀 준비를 하고 어디어디가 바뀌는지 파악한 다음 그 때 바꾸는 것.

 

이런 방식으로 렌더링하는 것은 크게 2가지의 좋은 점이 있다.

 

1. 개발자가 직접 DOM을 신경쓸 필요가 없으니 단순하고 깔끔한 코드를 작성할 수 있다는 것. 무슨 데이터를 어떻게 보여줄 지만 신경쓰면 된다.

2. 변경사항들을 리액트가 적당히 모아 처리할 수 있다는 것. 브라우저의 한정적인 자원을 효율적으로 이용할 수 있다는 것이다.

 

참고로 이 Vitrual DOM은 리액트에만 있는 개념은 아니다. 그냥 여기선 Virtual DOM을 사용하면 효율적인 화면 처리가 가능하다는 것만 알아두면 될 것 같다. 

 

 

+ Recent posts