CSS, ::before, ::after 간단하게 알아보기

가상 요소(pseudo element) ::before, ::after의 역할

pseudo(슈도)는 [가짜의, 허위의]라는 뜻을 갖고 있습니다.

그렇다면 이 가상 요소가 존재하는 의미는 무엇일까요?

가장 큰 목적은 HTML에 존재하지 않는 요소를 CSS가 임의로 생성해서 스타일링하는 것입니다.

모양은 ::before 또는 ::after이며, 콜론이 두 개(!)가 붙는 것이 가상 요소, 콜론 하나가 가상 클래스입니다(최신 명세).

before와 after의 의미에서 알 수 있듯이 요소의 앞과 뒤에 가상 요소를 생성하여 HTML에 존재하지 않는 요소를 CSS로 생성합니다.

예를 들면 다음과 같습니다.

<h3> - 오늘 할 일</h3> 

위 태그는 다음과 같은 화면을 렌더링합니다.

텍스트에 ‘-‘가 포함되어 있으므로 마우스로 드래그하면 ‘-‘도 함께 텍스트로 선택이 됩니다.

그러나 가상 요소를 사용하면 다음과 같습니다.

‘-‘는 텍스트가 아닌 가상 요소에서 다루고 있으므로 텍스트로 선택되지 않습니다.

// css
.list::before{
  content:'-'
}


// html
<h3 class="list">오늘 할 일</h3>

‘-‘가 h3 태그에는 텍스트로 존재하지 않지만 ::before를 통해 content로 추가해 주었으므로 외형은 위와 같지만 속은 다른 방식이 됩니다.

이 가상 요소를 사용하면 세부적인 스타일링이 가능해집니다.

// css
.list::before{
  content:'';
  display:inline-block;
  width:20px;
  height:20px;
  background:gold;
}

.list::after{
  content:'😋'
}


// html
<h3 class="list">
 오늘 할 일
</h3>

<h3 class="list">
내일 할 일
</h3>

before로 요소의 앞, after로 요소의 뒤를 설정했으며 content에는 문자 이외에도 이모지나 이미지(url(주소)) 등을 사용할 수도 있습니다.

이외에도 ::first-line, ::selection, ::placeholder 등이 있습니다.

추가로 form과 이미지에는 가상 요소가 적용되지 않습니다.

타입스크립트와 제네릭의 이해(Typescript and Generics)

클래스, 함수, 인터페이스, 타입의 재활용을 가능하게 만드는 제네릭

타입스크립트는 변수나 파라미터 등에 데이터 타입을 명시하여 코드 작성 단계에서 오류를 확인할 수 있고 미리 타입을 결정하여 실행 속도가 빠르다는 장점이 있습니다.

그러나 매번 타입을 지정하는 것은 분명 번거로운 일이므로 타입스크립트의 단점으로 꼽힙니다.

더욱이 똑같은 기능을 하는 함수지만 상황에 따라 다른 데이터 타입을 전달받는 함수는 매번 타입에 맞는 함수를 생성해야 합니다.

그렇다면 데이터를 전달할 때 데이터 타입 정보도 함께 전달해서 함수에게 알려주면 어떨까요?

전달하는 데이터의 타입 정보를 함께 전달하여 선언 시점이 아닌 생성 시점에 타입 정보도 변수처럼 변경할 수 있도록 하는 기능이 바로 제네릭입니다.

제네릭은 오래 전 C++부터 사용되어 왔는데요.

제네릭 함수의 모양새는 다음과 같습니다.

//함수형
const thisGeneric = <T>(value: T):T => {
     return value;
}

//클래스형
function thisGeneric2<T>(value: T) : T {
     return value;
}

const myVariable = thisGeneric<string>('my generic');
const myVariable2 = thisGeneric2('my generic2');  //< >생략 시 타입 추론

< >를 사용해 제네릭을 표현하고 안의 T는 정해진 것이 아니므로 다른 글자를 사용해도 되지만 관습적으로 T(type의 t)를 많이 사용합니다. 만약 두 개의 제네릭 정보를 받는 경우에는 <T, U>와 같이 표기합니다.

많이 사용되는 알파벳의 의미는 다음과 같습니다.

T : type
E : element
K : key
V : value

어떤 타입을 전달해도 사용이 가능한 any도 제네릭으로 볼 수 있지만 any는 타입을 체크하지 않으므로 전달받은 데이터의 타입을 알 수 없고 리턴 시에도 타입 정보를 반환하지 않습니다. 그러나 제네릭은 전달받은 타입을 확인하고 정보를 함께 반환할 수 있으며 세부적인 제한을 둘 수도 있습니다.

제네릭 인터페이스도 사용 방법은 제네릭 함수와 동일합니다.

interface ValueInterface<T> { 
   value : T
}

let myValue : ValueInterface<string> {
  value : 'data'
}

그렇다면 or의 연산자 | 를 사용하여 여러 타입을 지정할 수 있는 유니온 타입과는 어떤 차이가 있을까?

유니온은 | 로 지정한 여러 타입의 공통 메서드만 사용할 수 있으며 리턴 데이터의 타입도 하나가 아닌 유니온 타입이 반환됩니다.

function myUnion(val:number|string){
   return val;
}

const test = myUnion('string');
console.log(test.length); //에러 (length는 number와 string의 공통 메서드가 아니므로)

유사한 기능이 존재하지만 제네릭이 갖는 장점이 명확합니다.

제네릭을 사용하면 깔끔하게 코드를 줄이면서도 코드의 재활용이 가능해 매우 유용합니다.

제네릭을 사용한 오픈 소스들이 매우 많으므로 제네릭을 익혀두면 다른 고급 개발자들의 코드를 살펴볼 때도 큰 도움이 될 것 같습니다.

옵셔널 체이닝(optional chaining)의 유용함(javascript)

ES2020, typescript3.7, Babel 7.8.0, CRA 3.3.0 Node.js 14 지원

옵셔널 체이닝의 연산자는 ?. 입니다.

최신 버전(ES2020)에서 사용 가능한 기능으로 객체 체인 속성의 존재 여부를 확인합니다.

애플워치 구매를 고민하는 친구에게 하던 말처럼 ‘있으면 모르겠지만 없으면 너무 불편한’ (결론은 익숙해지면 아주 유용한) 기능의 연산자입니다.

자바스크립트로 객체 내부의 속성에 접근할 때, 존재하지 않는 객체(undefined)의 내부 속성에 접근하면 에러가 발생합니다.

const myObject = {
   myOne:{
      name:'one'
   }
}

console.log(myObject.myTwo); // undefined
console.log(myObject.myTwo.name); // error

위와 같이 존재하는 객체의 하부 속성이 존재하지 않으면 undefined를 반환하지만 존재하지 않는 객체의 하부 속성에 접근하면 에러가 발생합니다.

객체와 속성이 존재하지 않는 상황을 가정하여 코드를 보수적으로 짜는 것은 습관화되어 있지 않으면 놓치기 쉽습니다.


1. 옵셔널 체이닝 사용

앞의 에러를 방지하기 위해서는 다음과 같이 먼저 객체의 존재 여부를 확인 후 존재하는 경우에만 속성에 접근하는 방법을 사용할텐데요.

const myObject = {
   myOne:{
      name:'one'
   }
}

console.log(myObject.myTwo&&myObject.myTwo.name); // undefined

&&를 사용해 존재 여부를 먼저 체크합니다.

A && B를 예로 들어,

A = true면 return B,
A = false면 return A

와 같습니다.

false의 조건은 다음과 같습니다.

  • undefined
  • null
  • NaN
  • 0
  • empty string
  • “”

위 코드는 존재 시 name 속성을 반환하지만 속성이 존재하지 않더라도 undefined가 반환되므로 에러가 발생하지 않습니다.

이 체크 부분의 코드를 간략하게 만들어 코드의 가독성을 높일 수 있는 것이 옵셔널 체이닝입니다.

const myObject = {
   myOne:{
      name:'one'
   }
}
console.log(myObject.myTwo?.name); // undefined

앞의 코드와 완전히 동일한 결과가 발생합니다.

굉장히 단순하고 직관적인 것이 매력적입니다.

물론 속성 이외에 메서드나 함수 호출에도 사용할 수 있습니다.

const myObject = {
   show(){
     console.log('show');
   }
}
console.log(myObject.show?.()); // show

console.log(myObject.exist?.()); // undefined
console.log(myObject.exist()); // error

호출한 메서드가 존재하지 않으면 옵셔널 체이닝은 undefined를 반환하지만 사용하지 않으면 에러가 발생합니다.

이와 같이 옵셔널 체이닝을 사용하면 null, undefined로 발생하는 에러를 많이 때려 잡을 수 있습니다.

그러나 과유불급을 잊지 말아야 합니다.

여기 저기 남발하면 에러 발생은 현저하게 줄어들겠지만 정작 문제가 발생해야 하는 부분에서도 문제가 발생하지 않아

디버깅이 어려워지는 문제가 발생하므로 꼭 필요한 부분에만 사용하는 것이 좋습니다.

map, reduce, filter 서로 대체가 가능할까 (javascript)

자바스크립트 배열을 처리하는 메서드 map, reduce, filter

자바스크립트에서 배열의 요소에 접근하는 대표 메서드인 map, reduce, filter는 개발자라면 필수적으로 그리고 좀 더 상세히 알고 가면 좋을 것 같습니다.

각각의 특징에 대해 이야기하면 대부분은 아마도

  • map -> 모든 요소에 한 번씩 접근하기
  • reduce -> 모든 요소를 하나로 합치기
  • filter -> 모든 요소에서 원하는 것만 골라내기

정도의 개념을 갖고 있을 것 같습니다.

굳이 위 메서드가 아니라 forEach 문 등 다른 방식을 사용해도 같은 로직의 구현이 가능한데요.

그렇다면 위 메서드끼리도 서로 변경하여 같은 결과를 반환하도록 사용이 가능할까요?

각 메서드의 기능을 살펴보면서 알아보도록 하겠습니다.


1. map

먼저 메서드는 위와 같으며 *가 붙어있는 파라미터는 생략할 수 있습니다.

인덱스는 현재값의 인덱스, 배열은 map이 순회하는 배열 array를 의미합니다.

이제 map을 사용하는 가장 기본적인 코드를 확인해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.map((value) => { return value });

console.log(newArray);  // [10, 20, 30, 40, 50]
console.log(myArray===newArray); //false

map은 해당 배열 자체를 바꾸는 것이 아니므로 리턴값을 사용하기 위해서는 다른 변수에 넣어 주어야 합니다.

파라미터에서 index는 현재 접근한 요소의 index이며 array는 순회하는 배열 전체를 전달합니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.map((value, index, array) => {
   if(index%2){
     return value-array[1];   
   }else{
     return value-array[0];
   }                              
});

console.log(newArray);

다음 코드는 자주 접하는 실수 중 하나로 코드의 동작 여부를 생각해 볼 필요가 있습니다.

const myData = {10, 20, 30, 40, 50};

const newData = myData.map((value) => {return value+1});

console.log(newData);

위 코드는 에러가 발생합니다.

바로 map의 특성 때문인데요. map은 배열에서 동작하는 메서드이며 객체 object에서는 동작하지 않습니다.

하지만 다음과 같이 배열 내부의 object는 접근 가능합니다.

const myData = [{name:'chamchi', age:3},{name:'ggongchi', age:2},{name:'myulchi', age:1}];

const newData = myData.map((value) => value.name );

console.log(newData); //['chamchi', 'ggongchi', 'myulchi']

그렇다면 map을 사용해 reduce나 filter 메서드와 같은 로직을 구현할 수 있을까요?

다음 코드를 통해 확인할 수 있을 것 같습니다.

const myArray = [1, 2, 3, 4, 5];

const newArray = myArray.map((value) => {
   if(value%2){
     return value;   
   }                            
});

console.log(newArray); //[1, undefined, 3, undefined, 5]

홀수의 요소만 반환하고자 위와 같이 작성하더라도 map은 모든 요소를 반환합니다.

짝수 부분은 리턴값이 정의되지 않아 undefined로 반환된 것을 확인할 수 있습니다.

따라서 map은 reduce, filter의 기능은 구현할 수 없을 것 같습니다.


2. reduce

먼저 메서드는 위와 같으며 *가 붙어있는 파라미터는 생략할 수 있습니다.

누적값은 배열을 순회하면서 작업의 처리 결과를 누적하는 값으로 순회가 종료되면 최종 리턴값이 됩니다.

reduce의 예시로 모든 요소를 더하는 예시가 많다보니 용도는 전체 요소 더하거나 빼서 하나로 만들기라고

알고 있을 수 있지만 누적값 설정을 통해 다양한 방식으로 활용이 가능합니다.

인덱스는 현재값의 인덱스, 배열은 reduce가 순회하는 array 배열을 의미합니다.

마지막에 있는 시작값은 누적값의 처음 기본값을 전달합니다.

생략하면 0이 기본값이 됩니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { return acc + value });

console.log(newArray); // 150

시작값이 생략되었으므로 acc의 기본값은 0이 됩니다.

그럼 초기값을 설정한 샘플을 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { return acc + value },100);

console.log(newArray); // 250

이를 응용하여 초기값을 설정하면 다양한 결과를 만들 수 있습니다.

먼저 배열을 다른 배열로 복사하는 기능을 구현해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { 
     acc.push(value);
     return acc; 
 },[]);

console.log(newArray); // [10, 20, 30, 40, 50]

이것으로 map과 똑같은 로직의 구현이 가능합니다.

그렇다면 filter를 구현해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value, index) => { 
     if(index%2){
       acc.push(value);
     }
     return acc; 
 },[]);

console.log(newArray); // [20, 40]

코드를 통해 확인해보니 reduce는 초기값 설정을 통해 map이나 filter와 같은 로직의 구현이 가능합니다.


3. filter

filter는 말 그대로 배열의 요소를 필터링하여 조건이 참이 되는 요소만 반환합니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.filter((value) => { 
    return value%20 === 0
 });

console.log(newArray); // [20, 40]

filter는 필터링 기능만 갖고 있어 map이나 reduce의 로직을 만들긴 어려울 것 같습니다.



map과 filter는 용도가 조금 더 특정되어 있다보니 다른 메서드의 로직을 그대로 구현하기는 어렵지만 reduce는 초기값을 통해 다양한 로직의 구현이 가능해 다재다능한 메서드인 것 같습니다.

맥 OS(M1)에서 mongoDB 설치(zsh: command not found: mongo 해결)

zsh에 환경변수 설정하기

brew가 설치되어 있는 상황에서 진행합니다.

혹시 설치가 되어 있지 않은 경우에는 ‘M1 homebrew’를 검색하면 많은 안내 자료가 나오므로 참고하시면 됩니다.

먼저 설치 가능한 리스트를 확인합니다.

$ brew search mongodb

위 명령어를 실행하면 다음과 같은 리스트를 확인할 수 있습니다.

tap 명령어를 사용하면 더 많은 리스트를 확인할 수 있습니다.

$ brew tap mongodb/brew

그리고 다시 brew search mongodb를 실행하면 더 확장된 것을 볼 수 있습니다.

여기서 원하는 버전을 선택해서 설치를 진행하면 됩니다.

4.2 버전을 설치하도록 해보겠습니다.

$ brew install mongodb-community@4.2

설치가 완료되었으니 먼저 start로 몽고DB를 시작해 보겠습니다.

$ brew services start mongodb/brew/mongodb-community@4.2

Successfully started 메시지가 나오면 성공입니다.

이제 DB에 접속하기 위해 mongo를 입력해 보겠습니다.

커맨드를 찾을 수 없다는 에러가 나오는데요.

위를 살펴보면 If you need to have~~~~~ 부분에서 처음 시작하는 자는 다음 명령어를 실행하라고 나옵니다.

$ echo 'export PATH="/opt/homebrew/opt/mongodb-community@4.2/bin:$PATH"' >> ~/.zshrc

실행하고 다시 mongo를 실행해도 같은 에러가 발생하는 경우가 있습니다.

이 때는 환경변수 설정만 하고 적용이 안된 상태라서 발생하는 문제인데요.

환경변수를 적용하기 위해 다음 명령어를 실행하면 해결됩니다.

$ source ~/.zshrc

그리고 실행하면 다음과 같이 접속되는 것을 확인할 수 있습니다.

만약 이 환경변수를 직접 수정하고 싶은 경우에는 에디터를 사용해 ~/.zshrc를 열면 됩니다.

vi 에디터를 사용해 zsh(Z shell)의 환경변수 파일을 열어보겠습니다.

$ vi ~/.zshrc

앞에서 설정한 path가 들어있는 것을 볼 수 있습니다.

여기에 원하는 환경변수를 입력하거나 수정하고 저장(ESC + :wq + ENTER)해주시면 됩니다.

그리고 꼭! source ~/.zshrc를 사용해 변경을 적용을 해줘야 환경변수가 작동하므로 참고해주세요!

자바스크립트 배열+반복문의 속도 테스트(map, for, while, for…in, for…of)

반복문의 배열 작업 속도를 확인하기

배열 관련하여 각 요소별로 순회하는 반복문 작업을 할 때 가장 익숙한 방법을 사용하거나 때로는 가장 먼저 떠오르는 방법을 사용하기도 합니다.

배열의 크기가 작으면 상관이 없지만 배열의 크기가 커지면 작은 차이가 큰 차이를 만들어낼 수 있습니다.

문득 연산 속도가 가장 빠른 반복문이 무엇일까라는 궁금증이 생겨 간단하게 하나의 배열 데이터를 다른 배열로 옮기는 연산을 통해 속도를 테스트해보고자 합니다.

크기에 상관없이 반복문 별로 같은 효율을 보여주리라 생각했지만 배열의 크기에 따라 각각 다른 속도를 보여주는 점이 흥미로웠습니다.


1. 테스트 방법

먼저 일정 크기를 갖는 배열 array을 생성합니다.

console.time 메서드를 사용해 이 배열의 데이터를 다른 배열로 복사하는 작업을 진행하고 이 작업 시간을 측정합니다.

테스트는 map, for, while, for…in, for…of의 다섯 가지 반복문을 사용합니다.

// 배열 데이터 생성
const array = [];
for(let i=0;i<10000;i++){
  array.push(i);
}

// 데이터가 복사될 배열
let arraymap = [];

console.time('map');  //타이머 시작
arraymap = array.map(list=>list);
console.timeEnd('map'); //타이머 종료

위와 같은 방식으로 3회씩 테스트를 진행하며 배열의 크기를 조절하여 반복문 별로 상대적인 작업 속도의 변화를 확인합니다.


2. 테스트

먼저 배열의 크기가 아주 작을 때는 모두 큰 차이를 보이지 않습니다.

다음은 배열의 크기가 100일 때 결과입니다.

작업을 세 번 진행한 결과 map과 for…of가 엎치락뒤치락하지만 항상 가장 빠른 속도를 보여줍니다.

for, while, for…in도 세번 모두 결과가 엎치락뒤치락하지만 위의 두 반복문보단 느린 속도를 보여줍니다.

그럼 배열의 크기를 1000으로 올려보겠습니다.

크기가 1000일 때는 대체적으로 비슷한 속도를 보여줍니다.

for…in만 다른 반복문보다 느린 것이 확인됩니다.

이제 확연하게 유의미한 차이를 느낄 수 있는 크기인 10000으로 올려보겠습니다.

10000에서는 map이 가장 우수한 속도를 보여줍니다.

눈에 띄는 부분은 작은 사이즈에서는 map과 대등한 속도를 보이던 for…of가 여기서는 가장 느린 속도를 기록합니다.

또한 for…in은 오히려 for…of보다 빠른 속도를 보여줍니다.

그럼 1000000으로 확인해 보겠습니다.

배열이 매우 커지니 작업 속도는 for문이 가장 준수합니다.

map, while도 큰 차이를 보이지 않고 준수한 성능을 보여줍니다.

for…of는 확연하게 조금 느린 속도를 보여주고 for…in은 이제 인사를 해야할 것 같습니다.

그럼 마지막으로 10000000(천만)으로 테스트를 해보겠습니다.

세 번 테스트한 결과 모두 for 문이 가장 빠른 속도를 보여주었으며, map, while도 의미있는 속도를 보여줍니다.

확실히 for…of는 위의 세 반복문보다 느린 속도를 보여주었으며 for…in은 10배가 넘는 느린 속도의 차이를 보여줍니다.


3. 결과 정리

배열 테이터의 크기가 작을 때는 의미있는 차이가 없지만 배열이 커질수록 map, for, while의 속도가 우수하고 for…in, for…of는 속도 저하를 보이는 결과를 나타냅니다.

어디까지나 단순한 작업인 배열 데이터 복사 기능만을 사용한 테스트이므로 작업 내용에 따라 다른 결과를 보일 가능성이 있어 단순 참고용으로 삼으면 좋을 것 같습니다.

다음은 테스트에 사용한 코드 전체입니다.

console.log('-------------start----------------')

const array = [];

for(let i=0;i<10000;i++){  //크기 변경
	array.push(i);
}

console.log("arraysize:"+array.length);
console.log('--------------------------------');

let arraymap = [];

console.log('arraymap start:'+arraymap.length);
console.time('map');

arraymap = array.map(list=>list);

console.log('arraymap end:'+arraymap.length);
console.timeEnd('map');
console.log('--------------------------------');


let arrayfor = [];
console.log('arrayfor start:'+arrayfor.length);
console.time('for');

for(let i=0; i<array.length;i++){
	arrayfor.push(array[i]);
};

console.log('arrayfor end:'+arrayfor.length);
console.timeEnd('for');
console.log('--------------------------------');

let arraywhile = [];
let whilenum = 0;
console.log('arraywhile start:'+arraywhile.length);
console.time('while');

while(whilenum<array.length){
	arraywhile.push(array[whilenum]);
	whilenum++;
};

console.log('arraywhile end:'+arraywhile.length);
console.timeEnd('while');
console.log('--------------------------------');

const arrayforof = [];
console.log('arrayforof start:'+arrayforof.length);
console.time('forof');

for(const num of array){
	arrayforof.push(num)
};

console.log('arrayfor end:'+arrayforof.length);
console.timeEnd('forof');
console.log('--------------------------------');

let arrayforin = [];
console.log('arrayforin start:'+arrayforin.length);
console.time('forin');

for(const num in array){
	arrayforin.push(array[num]);
};

console.log('arrayforin end:'+arrayforin.length);
console.timeEnd('forin');


console.log('---------------end-----------------');

MongoDB, lean을 사용한 속도 개선(mongoose)

쿼리에 lean() 추가를 통한 성능 개선

몽구스(mongoose) 쿼리의 리턴값은 Document 클래스의 인스턴스입니다.

이 인스턴스는 많은 state를 갖고 있어 다양한 작업이 가능하게 합니다.

.get(), .set(), .save(), toObject(), toJSON() 등 리턴값에 대해 여러 메서드 사용이 가능하고 이 결과로 다시 쿼리를 진행할 수 있습니다.

하지만 단지 결과 데이터만 목적으로 하는 find() 같은 작업은 다른 정보나 메서드를 사용하지 않습니다.

이 때 lean()을 유용하게 사용할 수 있습니다.

쿼리에 lean()을 추가하면 인스턴스가 아닌 POJO(Plain Old Javascript Object)를 리턴합니다.

따라서 필요 없는 데이터를 함께 반환하지 않으니 속도와 메모리 부분에서 큰 장점을 발휘합니다.

샘플 코드를 통해 결과를 확인해 보겠습니다.

import sizeof from 'object-sizeof';

const query = {'status':1};
const lean = await Product.find(query).lean();
const normal = await Product.find(query).exec();

console.log('lean: '+sizeof(lean));
console.log('-----------');
console.log('normal: '+sizeof(normal));

위에서 lean과 normal의 크기를 비교한 결과는 다음과 같습니다.

lean 하나로 객체의 사이즈가 약 10배가 넘게 차이 나는 결과가 발생합니다.

하지만 lean을 사용한 결과값은 .save(), .get() 등의 사용이 불가하니 필요에 따라서 사용해야 하는 점을 유의해야 합니다.

Faster Mongoose Queries With Lean



성능 개선은 뛰어난 안목과 분석력이 있어야만 가능한 것이 아니라 작은 부분 하나하나가 만들어내는 차이를 쌓아가는 부분이라고 생각합니다.

OnKeyPress는 왜 ESC가 인식이 안될까?(React.키 이벤트 처리)

Key 입력을 처리하는 속성

리액트 input에서 키 입력 이벤트를 처리할 때 onKeyPress, onKeyDown, onKeyUp 이벤트를 사용합니다.

자바스크립트와 같은 명칭의 속성들을 리액트는 camel case로 표기합니다.

다음과 같이 사용합니다.

const onKeyPress= e => {
    if(e.key==='Enter'){    
        findExecute();
    }   
}

........

<div onKeyPress={onKeyPress}>click</div>

이벤트가 발생하는 시점이 조금씩 다를 뿐 사용 방법은 같습니다.

그리고 각 이벤트 별 특징은 다음과 같습니다.

onKeyDown 👉 이벤트가 먼저 실행
onKeyUp 👉 text가 입력되면 실행
onKeyPress 👉 text 입력이 완료되면 실행 (Deprecated)

MDN의 공식 문서를 보면 이제 onKeyPress는 더 이상 사용되지 않는다고 하니 거의 비슷하게 동작하는 onKeyDown을 사용하는 것이 좋습니다.

MDN – keypress event


왜 onKeyPress에서 ESC가 동작하지 않을까?

onKeyPress는 기본적으로 ESC가 눌려졌을 때 이벤트가 생성되지 않기 때문입니다.

onKeyPress는 ESC, CTRL, ALT 등 function 기능을 갖는 키를 제외하고 알파벳과 숫자 키에서만 이벤트가 생성됩니다.

하지만 onKeyDown, onKeyUp은 onKeyPress에서 인식되지 기능 키들도 인식이 됩니다.

또한 onKeyPress는 이제 더 이상 지원되지 않는다고 하니 기본적으로는 onKeyDown을 사용하고 상황에 따라 onKeyUp을 사용하면 큰 문제 없이 원하는 방식으로 구현할 수 있을 것입니다.

각 키 값과 이슈 관련 페이지를 링크로 남기겠습니다.


키 코드를 직접 입력해보면서 알 수 있는 사이트 -> https://keycode.info/

관련 이슈 -> https://github.com/Leaflet/Leaflet/issues/5234

이진 탐색(binary search), 자바스크립트로 구현하기

이진 탐색과 구현방법

이진 탐색(binary search)은 데이터 집합에서 원하는 데이터를 찾을 때까지 집합을 이분(二分)하여 탐색하는 방법입니다.

데이터 집합을 둘로 나누고 찾는 데이터가 있는 집합을 선택하여 다시 반으로 나누고 다시 데이터가 있는 집합에 같은 과정을 계속 반복합니다.

따라서 아무리 큰 데이터라도 몇 번의 연산만으로 원하는 데이터를 찾을 수 있습니다.

하지만 이진 탐색은 조건이 있는데요.

데이터가 반드시 순서대로 정렬된 상태여야 합니다.

그림을 통해 탐색의 과정을 확인해 보겠습니다.

위와 같은 데이터 집합에서 2를 찾으려면 먼저 집합을 반으로 나누고 찾는 데이터가 속한 집합을 선택합니다.

그럼 첫 번째로 선택한 집합은 다음과 같습니다.

또 반을 나누고 2가 속한 집합만을 선택합니다.

여기서 반을 나누고 2가 속한 집합을 선택하면 다음과 같습니다.

이제 둘 중 하나를 확인하여 원하는 데이터를 선택하면 됩니다.

이진 탐색 코드는 다음과 같습니다.

const binarySearch=(target, data)=>{

  let low = 0;
  let high = data.length-1;
  
  while(low<=high){
    
    let mid = Math.floor((low+high)/2);    
  
    if(target===data[mid]){
       return mid;
    }else if(target>data[mid]){
      low = mid+1 
    }else if(target<data[mid]){
      high = mid-1
    }
  }

  return undefined;
}

이 탐색 방법은 데이터의 양이 많아지면 엄청난 효율을 자랑합니다.

데이터가 10000까지 있을 때 8000을 찾기 위해서 무차별 대입은 7999번의 연산을 진행해야 하지만 이진 탐색 방법은 13번의 연산이면 원하는 데이터를 찾을 수 있습니다.

자바스크립트를 이용한 BFS, DFS 구현하기(javascript)

데이터는 선형 구조(배열, 연결리스트, 스택, 큐) 또는 비선형 구조(트리, 그래프)로 이루어져 있으며, 순차적으로 나열된 선형 구조에 비해 비선형 구조의 데이터는 탐색이 어렵습니다.

하지만 비선형 구조의 대표적인 탐색 방법인 BFS, DFS를 사용하면 깔끔하게 탐색이 가능합니다.

두 방법 모두 무차별 탐색(Brute Force Search, 모든 데이터를 하나씩 탐색) 방법을 사용합니다.


DFS는 깊이 우선 탐색 방법으로 트리 구조의 데이터에서 노드마다 가장 깊이까지 탐색한 뒤 다음 노드로 이동하는 방법입니다.

위와 같은 트리 구조의 데이터가 있을 때, DFS는 한번 선택한 길은 끝까지 가본 뒤 다음 길을 탐색하는 방식과 같습니다.

그림으로 나타내면 다음과 같습니다.

검색 속도는 BFS에 비해서 느리지만 조금 더 간단합니다.

경로의 특징이 필요한 문제를 풀 때 DFS를 사용합니다.

const graph = {
  A: ["B", "C"],
  B: ["A", "D"],
  C: ["A", "E"],
  D: ["B", "F"],
  E: ["C","G"],
  F: ["D","H","I"],
  G: ["E","J","K"],
  H: ["F","L"],
  I: ["F", "M"],
  J: ["G","N"],
  K: ["G","O"],
  L: ["H"],
  M: ["I","P"],
  N: ["J"],
  O: ["K"],
  P: ["M"]
};

const bfs = (graph, start) => {

    const checked = [];    // 탐색 완료 데이터
    const willCheck = [];  // 탐색 예정 데이터
    
    willCheck.push(start);
    
    while(willCheck.length!==0){
      const node = willCheck.pop();  // 스택(Last In First Out)
      if(!checked.includes(node)){
       	 checked.push(node);
         //reverse() 제거 시 그림의 4,3,2,1 순서로 탐색     
      	 willCheck.push(...graph[node].reverse());  
        
      }
   }
	return checked;
}

console.log(bfs(graph, "A"));
// ['A', 'B', 'D', 'F', 'H', 'L', 'I', 'M', 'P', 'C', 'E', 'G', 'J', 'N', 'K', 'O']

BFS는 너비 우선 탐색 방법으로 트리 구조 데이터에서 노드의 인접 데이터를 모두 탐색한 뒤 다음 데이터로 이동하는 방법입니다.

그림으로 나타내면 다음과 같습니다.

탐색 속도는 DFS보다 빠르며 최단 거리를 구하는 문제에서 사용할 수 있습니다.

const graph = {
  A: ["B", "C"],
  B: ["A", "D"],
  C: ["A", "E"],
  D: ["B", "F"],
  E: ["C","G"],
  F: ["D","H","I"],
  G: ["E","J","K"],
  H: ["F","L"],
  I: ["F", "M"],
  J: ["G","N"],
  K: ["G","O"],
  L: ["H"],
  M: ["I","P"],
  N: ["J"],
  O: ["K"],
  P: ["M"]
};

const bfs = (graph, start) => {

    const checked = [];
    const willCheck = [];
    
    willCheck.push(start);
    
    while(willCheck.length!==0){
      const node = willCheck.shift(); // 큐(First In First Out)
      if(!checked.includes(node)){
       	 checked.push(node);
      	 willCheck.push(...graph[node]);       
      }
   }
	return checked;
}

console.log(bfs(graph, "A"));
// ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P']