CSS transition, 작동 원리 이해하기(왜 작동을 안할까?)

transition의 작동원리와 transition 대체할 수 있는 방법

transition은 애니메이션 효과를 주는 CSS입니다.

transition 하나만으로도 투박한 느낌을 지우고 부드러운 느낌을 줄 수 있는데요.

메뉴가 부드럽게 열리고 닫히거나 화면에 fade-in 효과 등을 줄 수 있습니다.

CSS의 대표적인 애니메이션 처리는 transition와 animation이 있는데요.

transition은 변경이 발생하는 순간 작동하고, animation은 transition보다 더 자유롭고 다양하게 사용이 가능한 친구입니다.

그럼 transition을 작성하는 코드부터 확인해 보겠습니다.

1. transition 사용하기

.setBox{
  background-color:pink;
  width:200px;
  height:200px;
  transition:all 1000ms linear 500ms;
}

위와 같은 방식으로 사용하며, 한 줄 표기의 의미는 다음과 같습니다.

transition: property duration timing delay;

property : color, background-color, border, position, all 등의 속성

duration : 완료까지 걸리는 시간. ms(밀리초) 또는 s(초)로 설정

timing : linear(동일), ease(느림->빠름->느림), ease-in(느림->빠름), ease-out(빠름->느림)

delay : 딜레이 시간(ms 또는 s로 설정)

물론 property나 delay 는 생략도 가능합니다.

이를 풀어 쓰면 다음과 같이 작성할 수 있습니다.

transition-property : property
transition-duration : duration
transition-timing-function : timing
transition-delay: delay

transition을 사용해 마우스를 올리면(hover) 박스가 부드럽게 커졌다가 줄어드는 코드를 확인해 보겠습니다.

.setBox{
  background-color:pink;
  width:100px;
  height:50px;
  transition:all 1000ms linear 500ms;
}

.setBox:hover{
  background-color:gold;
  width:200px;
  height:100px;
  transition:all 300ms ease;
}

<div class='setBox'></div>

박스가 커질 때는 .setBox:hover{}의 transition 설정에 따라 300ms, 줄어들 때는 .setBox{}의 transition 설정에 따라 1000ms동안 움직입니다.

그럼 이제는 요소 숨기기&표시하기 기능을 확인해 보겠습니다.

.setBox{
  background-color:gray;
  color:white;
  width:100px;
  height:50px;
  text-align:center;
  line-height:50px;
}
.showMenu{
  visibility:hidden;
  height:150px;
  width:100px;
  background-color:lightgray;
  opacity:0;
  color:black;
  border-radius:5px;
  transition:linear 500ms;
}
.setBox:hover .showMenu{
  visibility:visible;
  transition:linear 500ms;
  opacity:1;
}

<div class='setBox'>MENU
    <div class='showMenu'>
        <div class='menu'>
          메뉴 A
        </div>
        <div class='menu'>
          메뉴 B
        </div>
        <div class='menu'>
          메뉴 C
        </div>
    </div>
</div>

위 코드의 실행 결과는 아래와 같습니다.

마우스를 올리면 숨겨둔 div를 표시하거나 숨기면서 transition이 작동하는 것을 볼 수 있습니다.

위 코드에서 visibility가 아닌 display를 사용하면 결과는 어떨까요?

display를 사용하면 transition이 작동하지 않는 것을 볼 수 있습니다.

이유는 바로 display와 visiblility의 작동 방식에 차이가 있습니다.

transition은 위에서 이야기한 것처럼 변경이 발생하는 순간 작동합니다.

예를 들어 A 모양에서 B 모양으로 변경될 때 작동을 하는데요.

visibility는 공간은 그대로 두고 설정에 따라 요소를 숨기거나 나타내므로 A에서 B로 변경을 적용할 수 있습니다.

다음 그림과 같은 느낌입니다.


2. transition 작동하지 않는 이유

그렇다면 display가 작동하지 않는 이유도 추측할 수 있습니다.

display:none은 요소의 표시 뿐만 아니라 표소가 있는 공간도 비워버리는 설정입니다.

따라서 ‘A모양 -> B모양’으로의 변형이 아니라 ‘없음 -> B모양’ 이 되므로 transition이 작동하지 않는 것입니다.

그러므로 transition은 ‘모양의 변형‘만 기억하면 됩니다.

그렇다면 같은 효과를 내는 animation은 어떻게 사용할까요?

코드만 봐도 이해하기 쉬운 구조이므로 코드로 확인해 보겠습니다.

.setBox:hover .showMenu{
  visibility:visible;
  animation:setMotion 3s;
}
@keyframes setMotion{
  0%{
      opacity:0;
    }
  100%{
      opacity:1;
    }
}


CSS3는 아주 다재다능한 친구라 기존에는 jquery를 사용해 복잡하게 구현할 수 있던 애니메이션 효과들을 이제는 한 행의 코드만으로 해결할 수 있게 되면서 CSS3는 조금만 배워 둬도 아주 유용하게 사용할 수 있는 친구가 되었네요. CSS에 조금 더 관심을 갖고 포스팅을 늘려 가야겠습니다.

does not have a commit checked out 에러(Git)

git add 시 발생하는 에러를 해결하자

git add 명령어 실행 시 does not have a commit checked out 에러가 발생하는 경우가 있습니다.

이는 레포지토리에 있는 폴더 내 .git 파일이 존재하기 때문이며 이 파일을 모두 제거해주면 문제 없이 git add 실행이 가능합니다.

파일이 숨겨져 있기 때문에 간단하게 폴더나 탐색기를 열고 해당 폴더 내 숨겨져 있는 .git 파일을 찾아 모두 삭제해주고 다시 git add를 실행하면 됩니다.

위와 같이 폴더에서 ‘숨긴 항목’을 체크하여 표시한 다음 .git으로 된 폴더를 제거해주면 됩니다.

  1. git add를 하려는 폴더로 이동
  2. 숨긴 항목 해제 후 .git 폴더 삭제
  3. git add <파일> 실행

간단하게 에러가 해결된 것을 볼 수 있습니다.



기록의 힘을 믿고 간단한 것이라도 기록하는 습관을 기르도록 노력 중입니다!

target으로 부모 요소, 자식 요소 접근하기(자바스크립트,target)

event와 target으로 부모 요소와 자식 요소에 접근하는 방법

자바스크립트는 보통 이벤트와 target을 통해 요소에 접근합니다.

그리고 <div>와 같은 요소를 중첩해서 사용하다 보면 이벤트 발생 시 부모 요소 또는 자식 요소에 접근이 필요한 경우가 있습니다.

예를 들면 다음과 같이 id=2인 div 요소의 이벤트가 발생했을 때 부모 div 요소 id=1에 접근하고 싶은 경우가 있습니다.

const check = (event) => {
    console.log(event.target.id);
}

<body>
  <div id=1>    
        <div id=2 onClick=(check(event))>click
            <div id=3></div>
            <div id=4></div>
        </div>
  </div>
</body>

그렇다면 어떻게 접근을 해야 할까요?

1. 이벤트를 통해 부모 요소 속성 접근하기

현재 이벤트가 발생한 요소를 감싸고 있는 부모 요소에 접근하기 위해서는 target과 parentElement를 사용하여 접근할 수 있습니다.

부모 요소의 id 속성에 접근하는 코드는 다음과 같습니다.

const check = (event) => {
    console.log(event.target.parentElement.id);    // 1
}

<body>
  <div id=1>    
        <div id=2 onClick=(check(event))>click
            <div id=3></div>
            <div id=4></div>
        </div>
  </div>
</body>

실행해보면 콘솔에 1을 프린트합니다.

만약 id가 아닌 다른 속성에 접근하고 싶은 경우에는 id 대신 해당 속성명을 그대로 사용하면 됩니다.

구조를 확인하고 싶은 경우에는 console.log(event.target.parentElement)을 입력하여 확인할 수 있습니다.

console.log(event.target.parentElement)결과

현재 요소를 기점으로 접근을 진행하므로 target을 사용하며, 이벤트가 기점이 되는 경우 currentTarget를 사용합니다.

관련 포스트 : target, currentTarget 차이가 뭘까?


2. 이벤트를 통해 자식 요소 속성 접근하기

위 코드에서 보면 <div id=2>의 자식 요소는 <div id=3>과 <div id=4>입니다.

자식 요소는 target과 children을 통해 접근하는데요.

부모 요소는 하나밖에 없지만 자식 요소는 여럿 존재할 수 있습니다.

따라서 자식 요소는 인덱스를 통해 접근해야 하며, 리액트 코드를 통해 자식 요소의 구조를 살펴보겠습니다.

const accessChildren = () => {

const check = (e) => {
    console.log(e.target.children)
}

    return (
        <div id={1}>    
            <div id={2} onClick={check}>click
                <div id={3}>I'm a first child</div>
                <div id={4}></div>
            </div>
        </div>
    )
}

export default accessChildren;

콘솔에 찍히는 children의 구조는 다음과 같이 두 개의 object를 갖습니다.

따라서 첫 번째 자식 요소의 id에 접근하기 위해서는 다음과 같이 사용하면 됩니다.

const check = (e) => {
    console.log(typeof(e.target.children[0].id)) // 3
}

그렇다면 첫 번째 자식 요소의 텍스트인 I’m a first child는 어떻게 접근할까요?

간단하게 위 children 구조를 열어서 살펴보면 답이 나옵니다.

textContent를 사용해서 접근할 수 있습니다. (innerHTML은 XSS에 취약)

const check = (e) => {
    console.log(e.target.children[0].textContent) //I'm a first child
}


특정 요소가 속한 속성 전체(예를 들면 e.target)를 콘솔에 출력하면 다양한 하위 속성을 확인할 수 있으므로 원하는 속성을 찾거나 에러를 해결할 때 유용하게 사용할 수 있습니다.

빅오 표기법(Big O notation)을 알아보자

알고리즘의 효율성을 나타내는 빅오 표기법

빅오(Big O) 표기법은 점근적 표기법(Asymptotic Notation)의 하나로 Landau symbol이라고 부르기도 하며, 알고리즘의 효율성(시간 복잡도)를 나타낼 때 사용합니다.

그렇다면 점근적 표기법이란 무엇일까?

점근적 표기법은 중요하지 않은 상수와 계수를 제거하여 알고리즘의 복잡도를 단순화하여 나타낼 때 사용하는 표기법입니다.

점근적 표기법은 빅 세타(Big-θ), 빅 오(Big-O), 빅 오메가(Big-Ω)가 있으며 대략적인 의미는 다음과 같습니다.

빅 오(Big-O)점근적 상한에 대한 표기ex) O(n²)
빅 세타(Big-θ)상한과 하한의 평균에 대한 표기ex) θ(n²)
빅 오메가(Big-Ω)점근적 하한에 대한 표기ex) Ω(n²)

빅 세타와 빅 오메가는 원하는 효율성을 정확하게 나타내지 못하는 관계로 빅 오 표기법이 주로 사용됩니다.

알고리즘의 대략적인 실행 시간만 알면 되므로 빅 오 표기법은 차수가 가장 높은 항만 사용해 수행 시간의 상한(최악의 경우)을 나타냅니다.

예를 들어 f(n)=3n²+2n+1일 때, 빅 오 표기법을 사용하면 O(n²)로 나타낼 수 있습니다.

입력값이 작을 때는 문제가 없지만 입력값이 무한대에 가까워지는 경우에는 기하 급수적으로 복잡도가 증가하며 주요 증가율은 다음과 같습니다.

시간 복잡도n이 2배가 될 경우
O(1)변함 X
O(log n)약간 증가
O(n)2배 증가
O(n log n)2배보다 약간 증가
O(n2)4배 증가
O(n3)8배 증가
O(nk)2k배 증가
O(kn)kn배 증가
O(n!)nn배 증가

증가율을 그래프로 보면 다음과 같습니다.

그래프 기울기가 완만할수록 효율이 좋은 것을 나타내며 기울기가 가파를수록 효율성이 나쁜 것을 의미합니다.

출처 : https://en.wikipedia.org/wiki/Big_O_notation

위 그래프를 통해 상수->로그->선형->다항->지수->팩토리얼의 순으로 효율이 떨어지고 있는 것을 알 수 있습니다.

ジャバスクリプト、文字列操作(JAVASCRIPT & STRING)

Javascript文字列のメソッド : slice(), substring(), includes(), startsWith(), trim(), replace()…….

フロントエンド(React, Vue, Angular)もバックエンド(Nodejs)もジャバスクリプト(Javascript)が使われる時代になって、だんだんジャバスクリプトの文法が重要となりますね。

その中、文字列(String)関連のメソッドはよく使うのに、あまり覚えてはいないやつですね。😂

文字列関連のメソッドは一度覚えておくと自動に覚えられるぐらい使う頻度が高いので最初だけ頑張ればですね!

では、頻繁に使うメソッドの中slice(), substring(), includes(), startsWith(), trim(), replace(), indexOf(), test()をご紹介します。


sliceメソッドはインデックスをパラメータで渡し、必要な文字列を抽出します。

スタートインデックス(start index)と終了インデックス(end index、省略可)を渡し、終了インデックスを省略する場合は文字列の最後までになります。

パラメータが0の場合は頭から一番目、-1は逆で後ろから一番目の意味になります。

const myWord = 'primavera';

myWord.slice(0,5);   // 'prima'
myWord.slice(-4);    // 'vera'
myWord.slice(3);     // 'mavera'

myWord.slice(3,1);    // '' -> slice()はスタートが終了より大きいと空を返す

substringメソッドもsliceと同じくインデックスを渡し、文字列を抽出します。

基本的な操作は同様ですがsubstringはスタートが終了より大きいの場合自動に位置を交換してメソッドを実施します。

また、substringに負数を入れると0と認識します。

例えば(3, -1)を渡すと(3, 0)として認識します。それに(3, 0)はスタートが大きいので自動交換し、(0, 3)を実施します。

メソッド名が似たようなsubstrは’位置’と’文字数’をパラメータに渡すメソッドです。

const myWord = 'invierno';

myWord.substring(3);     // 'ierno'
myWord.substring(1,3);   // 'nv'

myWord.substring(3,1);   // 'nv' -> 自動にスタートと終了位置交換

myWord.substring(3,-1);  // 'inv' -> 負数は0, 自動位置交換

myWord.substring(-4);    // 'invierno' -> 負数は0, スタート(0)から最後まで

myWord.substr(1,3);      // 'nvi' -> スタート1から三つ

includesは文字列の中、指定した文字列が含まれているかをチェックします。

パラメータは文字列(必須)とインデックス(省略可)を渡し、返すのはtrue或はfalseです。

このメソッドは指定文字はもちろん、配列の中で空の値を確認するためには使えます。

const myWord = 'alegria';

myWord.includes('le');       // true
myWord.includes('i');        // true
myWord.includes('rio');      // false

const myArray = ['dream','','king'];
myArray.includes('');       // true   -> myArray[1]が空なのでtrue
myArray.includes('dream');  // true   -> myArray[0]と一致
myArray.includes('drea');   // false  -> 要素は完全一致する場合のみtrue

startsWithメソッドも文字列が含まれているかをチェックします。

文字列のみ渡すと0インデックスから始まる指定文字の存在を確認し、BOOLEANを返します。

endsWithは指定文字で終わることをチェックします。

const mySentence = 'cafe con leche';

mySentence.startsWith('cafe');     // true
mySentence.startsWith('fe');       // false
mySentence.startsWith('fe',2);     // true

mySentence.endsWith('eche');      // true

trimは文字列両断の空白を除くメソッドです。

文字列変換の過程で入られた空白またはタイプミスで入った文字列は文字比較の時falseを返し、エラーになりがちです。

なのでtrimで空白を事前に除き、エラーを予防することができます。

文字列両断の空白、タップ、改行(/n)文字を除くことができますが、両断ではなく文字列中に入っているのは作動しません。

const mySentence = '  la casa de papel   ';

mySentence.trim();        // 'la casa de papel'

const mySentence = ' el amor \n';

mySentence.trim();       // 'el amor'

replaceは文字列から指定した文字を探して変換文字に変換します。

文字列の中に指定文字が一つ以上存在するとき、最初に見当たるのだけが変更対象になります。

例えば ‘my_name_is_Ron’からアンダーバーを全部空白にするためreplace(‘_’,’ ‘)にしても結果は’my name_is_Ron’になります。

なので、文字列の中で存在する全ても指定文字を変換したい時は正規表現を使うと行けます。

const mySentence = 'my name is:Ron';

mySentence.replace(':',' ');        //'my name is Ron'

const mySentence = 'my_name_is_Ron';

mySentence.replace('_',' ');      // 'my name_is_Ron'

//正規表現ですべての文字を変換
mySentence.replace(/_/g, ' ');    // 'my name is Ron'

indexOfは指定文字の位置を返します。

このメソッドは指定文字の前または後で文字列を切り取るとき有用なものです。

たとえ、’Z001-03’から後ろの’03’だけを抽出したいとき、indexOfで’-‘文字を探し、返しのindex +1位置から最後までカットすると’03’を切り取ることができます。

もし文字列の中指定文字が一つ以上の場合、頭から探し始め最初に見当たる文字位置を返します。

でもパラメータにindexを渡すと指定された位置から指定文字を探します。

注意点はstart indexを渡しても返す値は全体の文字列から何番目であるという数字を返します。

後ろから検索するのはlastIndexOfで、後ろから最初の指定文字の位置を返します。

この時も返す値は頭から何番目であるという数字です。

指定文字がなければ-1を返します。

const mySentence = 'Z001-03';

mySentence.indexOf('-');     // 4

mySentence.substring(mySentence.indexOf('-')+1);    // '03'

mySentence.indexOf('D');    // -1

const mySentence = 'Z001-03-02';

mySentence.indexOf('-');    // 4
mySentence.indexOf('-', 5);   // 7

mySentence.lastIndexOf('-')   // 7

testは一般的に正規表現と一緒に使い、渡す文字列と比較し有効性を確認します。

パスワード、アルファベット、特殊文字、生年月日などの形式検査に使えます。

const mySentence = /a/;
const myWord = 'baby';

mySentence.test(myWord);   // true

/\d/.test('cookie');       // false \dは数字を意味(正規表現)
/\d/.test('cookie1');      // true

上の例以外にも山ほどメソッドが多いし、また新しく作られるメソッドもたくさんありますね。

また必要な機能のメソッドがあれば確認していきましょ!

あざーす!

Moment.js, A Simple Tool For Calculating Date And Time(feat.Nodejs)

Easy way to calculate date and time way better

Sometimes we need to calculate date and time.

Actually more than sometimes.

And this library, moment.js, is so powerful for your purpose.

Getting date, setting a date format whatsoever you want, subtracting date from date, counting days, and others.

Below listed some functions for your quick use.

Let’s dig it.


Install moment.js

Install library with below command.

npm install moment

Import the library with ‘require’ function.

var moment = require('moment');
moment();

//ES6 syntax
import moment from 'moment';
moment();

Let’s get started from parsing date.

const date = moment(""); //2022-01-23T11:15:00+09:00

We can display date using format() method with below tokens.

tokenmeanexample
YY(YY) year ex) YYYY -> 2022, YY -> 22
MM(MM) monthex) MMMM -> January, MM -> 01
DD dateex) DD -> 25
dd(dd) dayex) dd -> Tu, dddd -> Tuesday
hhhour(12)ex) hh -> 01
HH hour(24)ex) HH -> 13
mm minuteex) mm -> 07
sssecondex) ss -> 03
a am, pmex) a -> pm
Do ordinalex) Do -> 25th
//January Tu 2022, 01:07:21 pm
moment.format('MMMM dd YYYY, hh:mm:ss a'); 

//January Tuesday 2022, pm 01:17
moment.format('MMMM dddd YYYY, a hh:mm') 

// 01/25/2022
moment.format('MM/DD/YYYY'); 

isValid() method works well on it.

moment('2022-01-25','YYYY-MM-DD').isValid(); //true

moment('2022-02-30','YYYY-MM-DD').isValid(); //false

moment('My birth is 2022-1-25','YYYY-MM-DD').isValid(); //true

Also parsing every detail date is available by intuitive methods.

hour()get hour
minute()get minute
second()get second
millisecond()get millisecond
date()get date (1~31)
day()get day of week (0~6)
week()get week of year (1~53)
month()get month (0~11)
year()get year
moment(new Date).hour(); //13

moment().millisecond(); //331

moment().week(); //5

To get the difference in date, use diff() method.

const theDate = moment('2021-01-20', 'YYYY-MM-DD');
const myDate = moment('2022-01-25', 'YYYY-MM-DD');

myDate.diff(theDate, 'days');     // 5
myDate.diff(theDate, 'year');    // 1
myDate.diff(theDate, 'month');  // 12

We can use this method for counting expire date or calculate D-day.

And there is a bunch of methods this library thanks to the last long maintenance till today.

Please check official web for further infomation.

momentjs.com

피보나치 수열, 자바스크립트 재귀로 함수 만들기(feat.메모이제이션)

메모이제이션을 사용한 피보나치 수열의 재귀 함수
피보나치(Fibonacci) 수는 이탈리아 수학자인 레오나르도 보나치(Leonardo Bonacci)에 의해 연구되어 알려진 것으로, 토끼가 새끼를 낳아 매달 증가하는 토끼의 수에 관해 계산하는 것으로 유명합니다.

0부터 시작하는 수를 순서대로 나열하면,

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181…….와 같이 수가 증가하는데요.

바로 앞(n-1)의 수와 그 바로 앞(n-2)의 수를 더하면 현재의 수(n)가 됩니다.

다음 그림은 네모칸 채우기로 한 면의 길이가 피보나치 수와 같이 증가를 보이는 것을 알 수 있습니다.

출처:https://en.wikipedia.org/wiki/Fibonacci_number

사각형이 1/4씩 회전하며 그리는 나선형은 황금 비율(1.618)에 수렴하여 과학적이며 예술적인 비율을 만들어 낸다고 합니다.

https://en.wikipedia.org/wiki/Fibonacci_number

그럼 이 피보나치 수열을 재귀를 사용해 자바스크립트 함수로 만들어 보겠습니다.

const fibonacci = (n) => {

  if ( n <= 1 ) {
    return n;
  }
  
  return fibonacci(n-1) + fibonacci(n-2);
}

위 함수는 피보나치 수열을 재귀적으로 수행하지만 계산 양이 늘어날 때마다 시간 복잡도는 가파르게 증가합니다.

이는 계산의 중복 문제 때문인데요.

예를 들어 fibonacci(10)을 실행하면 fibonacci(9), fibonacci(8), fibonacci(7)……. 순서로 실행을 하게 되는데요.

fibonacci(9)는 다시 fibonacci(8), fibonacci(7), fibonacci(6)…. 을 실행하게 됩니다.

결국 이미 한 계산을 하고 또 하고 또 하고… 합니다.

하지만 이미 수행한 계산 결과를 배열로 저장해두고 거기서 값을 꺼내쓰면 계산의 중복은 발생하지 않으므로 효율적이고 아름다운 함수가 됩니다.

이를 메모이제이션(Memoization)이라고 하는데요.

메모이제이션을 사용하는 자바스크립트 코드는 다음과 같습니다.

const fibo = (num, memo) => {

      memo = memo || {}

      if(memo[num]){
          return memo[num]
      }

      if(num<=1){
          return num;
      }

      return fibo(num-1, memo) + fibo(num-2, memo);

}

간단하면서도 효율적으로 피보나치를 계산하는 자바스크립트 함수를 사용할 수 있습니다.


setState & useState, 왜 비동기일까?(탐구일기, 리액트React)

setState, useState를 동기로 사용하는 방법

리액트(React)의 state는 컴포넌트 내부의 변경 가능한 값입니다.

클래스형 컴포넌트는 state를 사용하고, 함수형 컴포넌트는 useState 훅(클래스 내부에서는 동작 X)을 사용합니다.

그렇다면 일반적으로 사용하는 변수를 두고 왜 state를 사용해서 값을 관리할까요?

이는 state가 갖는 특성 때문인데요. 바로 값이 변경되면 리렌더링(Re-rendering)이 발생하기 때문입니다.

따라서 값이 변화함에 따라 실시간(!!!)으로 화면이 렌더링되고 변화된 값이 화면에 바로 반영됩니다.

값의 변화를 리액트도 알아차릴 수 있게 해주어야 하므로 값의 변경은 리액트가 제공하는 함수를 통해서만 이루어져야 합니다.

//클래스형 컴포넌트
class MyClass extends React.Component {
  constructor(props) {
    super(props);
    //state
    this.state = {
      cnt: 0  //초기화 0
    };
  }

updateState = () => {
  this.setState({ cnt: this.state.cnt + 1 });
}

render() {
    return (
      <div>
        <p>값:{this.state.cnt}</p> 
        <button onClick={this.updateState}>plus</button>
      </div>
    );
  }
}
//함수형 컴포넌트
import React, { useState } from 'react';

const MyFunc = () => {
  //useState 훅을 통해 state 사용
  const [cnt, setCnt] = useState(0); //초기화 0

const plusNum = () => {
   setCnt( cnt+1 );
}

return(
    <div>
       <p>값:{cnt}</p>
       {/* 훅이 반환하는 함수를 통해서만 값을 변경해야 함 */}
       <button onClick={plusNum}>plus</button>
    </div>
  )
}

클래스형 또는 함수형의 코드를 실행하면 다음과 같은 친구가 뜹니다.

plus를 누르면 값이 1씩 플러스 되는 것을 볼 수 있습니다.

하지만 다음과 같이 사용할 때는 결과가 어떨까요?

const plusNum = () => {
   setCnt(cnt+1);
   console.log('result:'+cnt);
}

의도한 것은 state에 cnt+1의 값을 설정하고 새로 설정된 값을 바로 콘솔창에 출력하고 싶은 것인데요. 결과는 다음과 같습니다.

콘솔창의 결과는 한 걸음 늦습니다.

왜 이런 결과가 발생하는 것일까요?

바로 비동기(Asynchronous)의 특성 때문인데요.


1. 동기와 비동기

동기(Synchronous) – 순서대로 하나씩 처리.

비동기(Asynchronous) – 순서가 아닌 이벤트에 따라 처리.

짱구의 일상을 통해 동기 작업을 확인해 보겠습니다.

엄마 : “짱구야! 액션분식 가서 떡볶이 3인분만 포장해 오겠니? 집에 가져와서 그릇에 옮겨 담고 엄마를 불러!”

짱구 프로세스는 1.떡볶이를 사러 나가서 2.주문을 하고 3.집에 들고 와서 4.다시 그릇에 담고 5.엄마를 부르는 과정이 모두 순서대로 동기로 진행됩니다.

호기심 많은 짱구는 엄마 말을 잘 들을리가 없겠지만요..

그렇다면 비동기 작업을 시키려면 어떻게 할까요?

비동기 엄마 : “짱구야! 액션분식 떡볶이 2인분이랑 쵸코비반점 짬뽕 두개를 배달 시켜서 도착하면 각자 그릇에 옮겨 담아줘! 배달이 오기 전까지는 방에 장난감 좀 치워주면 초코비를 줄지도 몰라~!”

작업을 각각 요청하고, 요청한 작업이 가게에서 진행되는 동안 짱구는 다른 작업을 할 수 있습니다.

짱구가 배달 주문(작업 요청)을 하고 도착하기 전까지(요청 처리중)는 장난감 치우기(다른 작업)를 진행하다가 배달이 완료(요청 작업 완료)되면 다시 그릇에 옮겨 담는(요청 관련 작업 진행) 식입니다.


2. 동기로 처리하기

그렇다면 위 샘플에서 콘솔 출력이 업데이트된 값으로 변경되지 않는 이유는 무엇일까요?

여러 글과 문답을 참고하여 내린 결론은 다음과 같습니다.

비동기 특성을 갖는 이벤트 루프에 의해 setCnt 작업은 뒤로 밀리고 console.log작업이 먼저 실행되기 때문입니다.

다음 코드와 같습니다.

const printSequence = () => {
    console.log('first Call');
    setTimeout(()=>{secondCall()},0)
    console.log('third Call');
}

const secondCall = () => {
    console.log('second Call');
}

콘솔창의 결과는요.

비동기 처리를 하는 함수들이 있지만 기본적으로 자바스크립트는 단일 프로세스이므로 동기로 작업을 합니다.

하지만 위와 같이 작업 순서가 뒤바뀌는 이유는 이벤트 루프라는 보이지 않는 손에 의해 비동기로 업무를 처리하기 때문입니다.

그렇다면 setTimeout을 통해 console.log 작업 시간을 뒤로 연기하면 작업이 끝난 뒤 로그를 찍을 테니 값이 제대로 표시되지 않을까요?

  const showAlert = () => {
    setCnt(cnt+1);
    setTimeout(()=>{
      console.log('result(3sec):'+cnt)}
    ,3000);
 }

plus를 다섯번 누른 각각의 결과는요.

값을 5까지 찍었으니 콘솔도 5까지 찍혀야 하지만 위와 같이 업데이트 되기 전의 값이 나옵니다.

이유가 무엇일까요?

바로 setTimeout 함수의 특성 때문인데요.

setTimeout 함수는 실행할 때마다 새로운 함수가 만들어지며 전달되는 함수 내부의 값은 변수가 아닌 전달하는 시점의 변수의 값(상수)이 전달되기 때문이라고 보면 됩니다.

따라서 3초가 아닌 1000초 뒤에 실행하더라도 실행되는 값은 전달하는 시점의 변수의 값입니다.

그렇다면 위 코드를 동기처럼 사용하는 방법은 무엇일까요?

state 설정 시 클래스형은 함수를 전달하고 함수형은 useEffect를 사용하면 됩니다.

 //클래스형
 updateState = () => {
    this.setState(
      { cnt: this.state.cnt + 1 },
      ()=>{ console.log(this.state.cnt) }
    )
 }


 //함수형
 const plusNum = () => {
    setCnt(cnt+1);
 }

 useEffect(()=>{
  console.log(cnt);
 },[cnt])

이제 의도한 대로 결과가 출력됩니다.


3. 비동기로 작동하는 이유

그렇다면 state 설정은 왜 비동기로 작동할까요?

위에서 설명한대로 state는 값이 변경되면 리렌더링이 발생하는데요.

변경이 하나라면 리렌더링이 한번만 발생하지만 수십 개, 수백 개의 값이 계속 변경된다면 리액트는 매번 렌더링만 하다가 생을 마감하고 말 것입니다. 속도는 말할 것도 없구요.

따라서 변경된 값들을 모아 한번에 업데이트를 진행하여 렌더링을 줄이고자 배치(Batch) 기능을 사용해 비동기로 작동한다고 볼 수 있습니다.

참고로 배치 업데이트는 16ms 주기라고 합니다!


18 버전에서 추가될 자동배치(Automatic Batching)는 기존에 이벤트 핸들러에서만 실행되던 배치가 이제는 setTimeout, Promise 등의 이벤트에서도 동작될 예정이며, flushSync() 등을 사용해 예외를 둘 수도 있다고 하여 많은 기대를 받고 있는 것 같아 미리 알아두면 좋을 것 같습니다.

자동배치 관련한 상세 설명(영어)

쉽고 빠르게 커링(Currying) 기법 이해하기(feat.자바스크립트)

커링(Currying) 기법의 이해와 사용, 그리고 장단점

커링(Currying)은 카레와 같은 스펠링을 갖고 있지만 그 유명한 하스켈(Haskell)이라는 이름의 출처인 수학&논리학자 하스켈 브룩스 커리(Haskell Brooks Curry)의 성(Family Name)입니다.

커링은 하나 이상의 매개변수(Parameter)를 갖는 함수를 부분적으로 나누어 각각 단일 매개변수를 갖는 함수로 설정하는 기법입니다.

식으로 설명하면 다음과 같습니다.

func(a, b, c) -> f(a)(b)(c)

위 식을 코드로 변경하면 함수를 리턴하면서 다음 매개변수를 받아 또 함수를 리턴합니다.

//non-curried
function plusFunc(a, b, c){
  console.log(a + b + c);
}

plusFunc(1, 2, 3);   // 6

//curried
function plusFunc(a){
    return function(b){
       return function(c){
          console.log(a + b + c);
       }
    }
}

plusFunc(1)(2)(4);  // 7

논커리와 커리의 차이를 확인해 보겠습니다.

non-curried : 함수 실행 시 파라미터가 모자라도 문제 없이 실행이 가능함
👉 함수 정의 : func(a, b, c)
👉 함수 실행 : func(a)
👉 실행 결과 : func(a, undefined, undefined)

curried : 함수가 인수를 전부 받을 때까지 실행을 보류함.
👉 함수 정의 : func(a, b, c)
👉 함수 실행 : func(a)
👉 실행 결과 : func(a)상태에서 b 함수 입력 대기

위 특징에 따라 다음과 같이 사용할 수 있습니다.

function setAppell(appell){
    return function(name) {
       console.log(appell + name);   
    }
}

const setName = setAppell('Mr.');
setName('Right');  // Mr.Right
setAppell('Mr.')('Baker'); //Mr.Baker

//ES6 화살표 함수
const setAppell = appell => name => console.log(appell + name);

const setName = setAppell('Miss.');
setName('Dior');  // Miss.Dior

또한 다음과 같이 이벤트와 파라미터를 동시에 전달할 때도 유용합니다.

const setNumber = (num) => (event) => {
     console.log(event.target.id+':'+num);
}

<div onClick="setNumber(2)(event)" id='myNum'>
  click
</div>

// myNum:2

그럼 커링의 장점을 요약하면 무엇이 있을까요?

장점👍
재사용 가능
– 생산성 향상(코드 양 감소)
– 가독성 향상

그렇다면 커링의 단점은 무엇일까요?

단점👎
– 함수가 깊이 깊이 중첩되면 메모리를 과다하게 점유할 가능성이 있는 것과 같은 맥락에서 커링을 과용하면
메모리와 속도에 문제점 발생 가능성이 있음

커링의 수학적인 설명이 필요하신 분을 위한 링크



커링을 사용하지 않아도 원하는 기능을 대부분 구현할 수 있지만 재사용성이나 가독성에 따른 생산성 향상을 위해 커링을 사용하면 좋은 효과를 기대할 수 있습니다. 커링을 직접 구현하지 않더라도 커링의 개념과 사용법에 대해 알아두면 다른 사람의 코드를 읽거나 로직을 이해하는데 큰 도움이 될 것이라 생각합니다.

target과 currentTarget, 차이가 뭘까?(feat.자바스크립트)

target과 currentTarget을 상황에 맞게 처리하기

자바스크립트에서 이벤트를 다룰 때 targetcurrentTarget의 프로퍼티를 사용해 요소에 접근이 가능합니다.

그럼 targetcurrentTarget의 차이는 무엇일까요?

1. target과 currentTarget

간단하게 설명하면 target은 이벤트가 발생한 바로 그 요소를 직접 가리키고 currentTarget이벤트 리스너(EventListener)를 가진 요소를 가리킵니다.

‘백문불여일코드(百聞不如一code)’이므로 코드를 통해 무엇을 의미하는지 확인해 보겠습니다.

<style>
  .upper{
    background:gold;
    width:80px;
    text-align:center;
  } 
  .lower{
    background:pink;
    width:50px;
  }
</style>

<script>
  function clicked(event){
    alert(event.target.id+" clicked");
  }
</script>

<div class="upper" onClick="clicked(event)" id="div">
  <span class="lower" id="span">                  
    span
  </span>
</div>

위의 코드를 통해 각각이 의미하는 바를 확인해 보겠습니다.

먼저 위 코드를 실행하면 다음 그림과 같은 결과가 나타납니다.

여기서 노랑은 div, 핑크는 span이며, 핑크가 노란색 위에 앉아있는 것이라고 볼 수 있습니다.

그런데 onClick 이벤트는 div에서 설정했지만 노랑을 눌러도, 핑크를 눌러도 모두 이벤트가 발생합니다.

div에만 이벤트를 설정했는데 왜 자식인 span도 이벤트를 상속 받는 것일까요?

이는 이벤트 버블링이벤트 캡쳐와 관련이 있으며, 관련 내용은 아래 포스트에서 확인해 보겠습니다.

이벤트 버블링과 이벤트 캡쳐 포스트

이벤트 발생에 따른 target은 다음과 같습니다.

– 핑크 부분을 클릭
target -> 핑크 (핑크를 눌렀으므로 핑크가 이벤트 발생 시점이 됨)
currentTarget -> 노랑 (onClick 이벤트는 노랑이 갖고 있음)

노란 부분을 클릭
target -> 노랑(노랑을 눌렀으므로 노랑이 이벤트 발생 시점이 됨)
currentTarget -> 노랑(onClick 이벤트는 노랑이 갖고 있음)

따라서 노랑을 누르면 ‘div clicked’, 핑크를 누르면 ‘span clicked’ 알림창을 띄웁니다.

만약 핑크를 눌러도 이벤트를 가진 노랑의 속성에 접근하고 싶다면 currentTarget과 getAttribute를 사용하면 됩니다.

<style>
  .upper{
    background:gold;
    width:80px;
    text-align:center;
  } 
  .lower{
    background:pink;
    width:50px;
  }
</style>

<script>
  function clicked(event){
    alert(event.currentTarget.getAttribute('id')+" clicked");
  }
</script>

<div class="upper" onClick="clicked(event)" id="div">
  <span class="lower" id="span">                  
    span
  </span>
</div>

위 코드는 핑크, 노랑 둘 다 이벤트를 가진 노랑 속성에만 접근합니다.

target은 누른 바로 그 것, currentTarget은 이벤트를 실행하는 바로 그 것으로 이해하면 될 것 같습니다.



targetcurrentTarget이벤트핸들링이벤트 캡쳐와 함께 이벤트 발생 관련하여 중요한 속성과 개념입니다.

백문불여일코드(code)이므로 코드를 통해 확인하고 연습하는 습관을 기르면 어제의 정보가 내일의 나의 무기가 될 수 있을 것이라고 생각합니다.