node에서의 한글 처리방법

http로 url 값을 전달할 때 오로지 영문자와 숫자만으로 전달한다면 인코딩 디코딩이 필요 없을 것이다. 하지만 다양한 특수문자와 한글이 있게 되고, 이를 제대로 인식하지 못해서 404 에러가 발생하거나 잘못된 값이 발생하는 경우가 있다.

우리나라의 한글 또한 변환한다. 서버가 한글 주소를 이해하지 못하는 경우가 있기 때문이다.

encodeURI,decodeURI는 영 대문자와 소문자, 숫자, 그리고 *-_.를 제외한 모든 문자를 유니코드 형식으로 인코딩(디코딩)한다. 즉 사람이 읽을 수 없지만 컴퓨터가 인식할 수 있는 안전한 유니코드 형식로 변환한다.

encodeURIComponent,decodeURIComponent는 모든 문자를 인코딩(디코딩)하는 함수이다.

스크롤 바로 인한 overflow 해결방법

스크롤바로 인해 발생하는 overflow를 해결한 과정을 정리해보았다.

첫 번째는 CSS를 사용하여 스크롤바 자체를 보이지 않게하는 방법이다.

1
2
3
4
5
6
7
.box {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.box::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera*/
}

하지만 웹접근성을 고려하면 좋은 방법은 아니다.

두 번째는 overflow:overlay를 통해 스크롤바 자체를 띄우는 방법이다.

1
2
3
.box {
overflow: overlay;
}

하지만 이 방법은 webkit기반의 일부 브라우저에만 적용된다.

생각보다 해결 방법은 간단하였다.

react를 사용하여 spa를 만들경우 body 바로 아래에 div 태그를 만들어 거기에 동적으로 html을 삽입하게 된다.

보통 id를 root로 하여 사용하게 되는데, 이 root 아래의 컨텐츠의 overflow를 사용하기 위해 width와 height를 브라우저의 너비와 같도록 설정하게 된다.

너비를 기준으로 할 때엔 다음과 같은 두 가지 방법이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#root {
width: 100vw;
overflow-y: scroll;
}
/* 또는 */

html,
body,
#root {
width: 100%;
}
#root {
overflow-y: scroll;
}

나는 첫 번째 방법을 사용하고 있었는데, 100vw는 스크롤의 너비까지 포함된 값이였고, 100%는 스크롤의 너비를 포함하지 않는 값이기 때문에 아래와 같이 수정 해주었다.

스크롤바로 인해 발생하는 reflow를 없앨 수 있었다.

git fork와 upstream

git은 분산 버전관리 시스템이다.

원격 서버를 거치지 않더라도 버전을 관리할 수 있다.

오픈소스 컨트리뷰톤을 위해 깃허브 수업을 들은 내용을 개인적으로 공부하여 다시 정리해보았다.

remote repository

네트워크 상의 서버에 존재하는 저장소를 remote repository라고 부른다.

local repository

remote에 있는 저장소를 git clone하거나 git init으로 생성해서 로컬에 존재하는 repository를 local repository라고 한다.

push와 pull

local 저장소와 remote 저장소의 작업내용을 반영하기 위해 pushpull이라는 작업을 하게 된다.

  • push: local에서 remote로 commit 이력을 업로드 하는 것
  • pull: remote에서 local로 commit 이력을 다운로드 하는 것

upstream과 downstream

local 기준에서 remote는 upstream, local이 downstream이 된다. remote로부터 local로 흐르는 관계가 되기 때문이다.

초기 저장소에 cli로 push를 할 때 git push -u origin main 명령어를 사용하는데, -u--set-upstream옵션의 줄임말로 upstream을 설정한다는 뜻이다.

upstream을 설정하고 나면 다음부터는 git push 또는 git pull이라고 명령어를 입력하면 자동으로 origin의 main 브랜치로부터 push와 pull을 진행하게 된다.

fork

다른 사람의 저장소를 복사하는 것을 fork라고 한다. 주로 오픈소스 프로젝트에 기여하거나, 협업을 진행하는 경우에 사용한다.

이 경우 원본 remote -> 내가 fork한 remote -> 내 local 순으로 upstream, downstream 관계를 만든다.

때문에 github으로 협업을 할 경우 다음과 같은 프로세스를 거치게 된다.

  1. 원본 remote를 github에서 fork
  2. fork한 remote repository(origin)을 깃 클라이언트로 clone
  3. 기능을 완성할 때 까지 반복
    1. clone한 repository(local)에 commit
    2. local에서 origin으로 push
  4. upstream에 반영하기.
    1. PR을 등록하기 전 upstream에 바뀐 내용이 없는 경우
      1. origin에서 upstream으로 PR
    2. PR을 등록하기 전 upstream에 바뀐 내용이 있는 경우
      1. upstream을 local로 pull
      2. local에서 origin으로 push
      3. origin에서 upstream으로 PR

하지만 보통 pull을 바로 해버리는 것은 위험하다.

때문에 upstream의 변경사항을 미리 확인후에 그걸 반영해서 작업하는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// upstream에 원본 remote 등록하기
git remote add upstream url
// upstream의 최신 commit history 반영하기
git fetch upstream main
// 베이스 갱신
git rebase upstream/main
// 충돌 사항 확인하기
git diff
// 작업하고 add하기
git add ....
// rebase 변경사항 저장하기(자신의 커밋을 반영)
git rebase --continue
// 지저분한 것들 지워주기
git reset --soft
// 컨플릭트 할 수 있는 상태가 됨
git push origin develop -f
// PR하기

Ref

https://backlog.com/git-tutorial/kr/stepup/stepup2_8.html
https://ihp001.tistory.com/229

d3 기본도형 path

path는 경로라는 의미로 종이 위에 펜을 두고, 펜을 움직여 그 경로를 모양으로 한다고 생각하면 된다.

핵심적인 속성은 d가 있다.

이 d속성을 이용하여 경로를 설정하게 된다.

d

d의 속성은 다음과 같다.

  1. Move To: M, m
  2. Line To: L, l, H, h, V, v
  3. Cubic Bézier Curve: C, c, S, s
  4. Quadratic Bézier Curve: Q, q, T, t
  5. Elliptical Arc Curve: A, a
  6. ClosePath: Z, z

사실 d3를 다루며 다른 도형에 대해선 이해할 수 있어도, 이 path 도형을 이해하는데에는 좀 오랜 시간이 걸렸다. 그래서 다시 하나씩 정리해보았다.

1. Move To

펜을 들어 지정된 곳에 놓는 것으로 생각할 수 있다. 즉 현재 지점을 이동하는 거을 의미한다.

CommandParametersNotes
M(x,y)+현재 지점을 x,y로 옮긴다.
m(dx,dy)+현재 경로를 (curX,curY)라고 할 때 현재 지점을 (curX+dx,curY+dy)로 옮긴다.

2. Line To

선을 그리는 정의이다.

CommandParametersNotes
L(x,y)+현재 지점에서 x,y까지 선을 그린다.
l(dx,dy)+현재 경로를 (curX,curY)라고 할 때 현재 지점에서 (curX+dx,curY+dy)까지 선을 그린다.
Hx+현재 지점에서 x,y까지 수평선을 그린다.
h(dx,dy)+현재 경로를 (curX,curY)라고 할 때 현재 지점에서 (curX+dx,curY+dy)까지 수평선을 그린다.
V(x,y)+현재 지점에서 x,y까지 수직선을 그린다.
v(dx,dy)+현재 경로를 (curX,curY)라고 할 때 현재 지점에서 (curX+dx,curY+dy)까지 수직선을 그린다.

3. Cubic Bézier Curve

4개의 점을 사용하는 부드러운 곡선 정의이다.

시작점 (current point)
(Po = {xo, yo})

끝점
(Pn = {xn, yn})

시작 쪽 제어 점
(Pcs = {xcs, ycs}) (controls curvature near the start of the curve)

끝 쪽 제어 점
(Pce = {xce, yce}) (controls curvature near the end of the curve)

같은 시간동안 시작 점에서 시작 쪽 제어 점으로 움직이는 점, 시작 쪽 제어 점에서 끝 쪽 제어 점으로 움직이는 점, 끝 쪽 제어 점에서 끝 점으로 움직이는 점을 각각 Pstarttocontrol, Pcontroltocontrol, Pcontroltoend으로 정의할 수 있다.

또한 PstarttoControl과 Pcontroltocontrol, Pcontrol과 Pcontroltoend을 연결하고 같은 시간동안 각각의 선의 시작점에서부터 끝 점까지 움직이는 점을 정의할 수 있다.

이러한 점을 각각 Pbezierstart, Pbezierend라고 하면 같은 시간동안 이 점의 움직임을 연결한 곡선이 Bezier 곡선이다.

말로 표현하면 좀 어려운데, Ref를 참조하면 이해가 더 쉽다.

CommandParametersNotes
C(x1,y1,x2,y2,x,y)+현재 지점에서 x,y까지 3차 베지어 곡선을 그린다. 끝 점은 x,y 시작 제어 점은 x1,y1, 끝 제어 점은 x2,y2로 정의된다.
c(dx1,dy1,dx2,dy2,dx,dy)+현재 지점에서 끝점까지 3차 베지어 곡선을 그린다. 현재 지점을 (curX,curY) 라고 할 때 끝 점은 (curX+dx,curY+dy), 시작 제어 점은 (curX+dx1,curY+y1), 끝 제어 점은 (curX+dx2,curY+dy2)로 정의된다.
S( x2, y2, x, y)+C와 똑같이 동작한다. 다만 시작점을 이전 베지어곡선의 끝점으로 한다.
s( dx2, dy2, dx, dy)+c와 똑같이 동작한다. 다만 시작점을 이전 베지어곡선의 끝점으로 한다.

4. Quadratic Bézier Curve

세 점을 사용하는 2차 베지어 곡선이다.

시작 점
Po = {xo, yo}

끝 점
Pn = {xn, yn}

제어 점
Pc = {xc, yc} (controls curvature)

CommandParametersNotes
Q(x1,y1,x,y)+현재 지점에서 x,y까지 2차 베지어 곡선을 그린다.
q(dx1,dy1,dx,dy)+현재 지점에서 끝점까지 3차 베지어 곡선을 그린다. 현재 지점을 (curX,curY) 라고 할 때 끝 점은 (curX+dx,curY+dy)으로 정의된다.
T(x, y)+Q와 똑같이 동작한다. 다만 시작점을 이전 베지어곡선의 끝점으로 한다.
t(dx, dy)+q와 똑같이 동작한다. 다만 시작점을 이전 베지어곡선의 끝점으로 한다.

5. Elliptical Arc Curve

타원의 일부로 정의된 곡선이다. 즉 타원의 호이다.

CommandParametersNotes
A(rx, ry, angle, large-arc-flag, sweep-flag, x, y)+현재 지점에서 x,y까지 호를 그린다.
rx,ry는 타원의 두 반지름이다.
angle은 x축에 대한 타원의 회전 각도이다.
large-arc-flag, sweep-flag는 4개의 호가 가능하므로 어떤 호를 그릴지 선택한다.
large-arc-flog는 큰 호(1) 작은호(0)을 선택할 수 있다.
sweep-flag는 시계 방향 호(1) 반시계 방향 호(0)을 선택할 수 있다.
좌표 x,y는 다음 명령의 새 현재 점이 된다.
a(rx, ry, angle, large-arc-flag, sweep-flag, dx, dy)+현재 지점에서 끝점까지 호를 그린다. 현재 지점을 (curX,curY) 라고 할 때 끝 점은 (curX+dx,curY+dy)으로 정의된다.

6. Close Path

현재 위치에서 경로의 첫 번째 점까지 직선을 그린다.

CommandParametersNotes
Z,z경로의 마지막 지점을 시작 지점과 연결하여 현재 경로를 닫는다. 두 점이 다른 좌표에 있으면 두 점 사이에 직선이 그려진다.

interpolate

d3.interpolate를 사용하여 선을 보간할 수 있다.

아래의 링크를 참조하면 된다.(혹은 공식문서)

https://web.archive.org/web/20201029202235/https://www.dashingd3js.com/svg-paths-and-d3js

Ref

https://ko.javascript.info/bezier-curve
https://blog.coderifleman.com/2016/12/30/bezier-curves/

d3 데이터를 dom에 바인딩하기

d3의 가장 기초가 되는 메소드에 대해 알아보자

select

조작하고자 하는 DOM 요소를 선택할 수 있다.

반환되는 값은 selection 객체이다.

해당하는 결과가 없을 경우 빈 객체를 반환한다.

부모의 데이터와 인덱스등의 상태를 보존한 상태로 선택한다.

selectAll

선택자와 함께 DOM 요소를 여러 개 선택한다.

반환되는 값은 selection 객체 배열이다.

해당하는 결과가 없을경우 빈 객체 배열을 반환한다.

부모의 데이터와 인덱스등의 상태를 보존하지 않은 상태로 새 그룹을 만든다.

즉 새로운 데이터를 바인딩하기 위해선 select가 아닌 selectAll을 사용해야한다.

datum

요소에 단일 데이터를 바인딩 할 때 사용한다.

실제 DOM요소와의 연결을 계산하지 않는다.(enter,update,exit)

단순히 모든 요소에 할당한다.

1
2
3
4
5
6
const data = 1;
d3.select("body").datam(data).enter().append("div");

const datas = [1, 2, 3];
d3.select("body").datam(datas).enter().append("div");
// 모든 요소에 1,2,3을 할당

parameter를 제공하지 않을경우, 선택된 첫번째 요소의 데이터만을 반환한다.

위의 예시에서는 1을 반환한다.

data

데이터 배열을 selection 객체와 연결한다.

실제 DOM 요소와 연결하기 위해 이를 계산한다.

때문에 data를 수행할 경우 selection 객체는 enter,exit,update의 메소드를 갖게된다.

즉 데이터 각각을 실제 DOM 요소와 연결할 수 있다.

data를 수행할 경우 index를 key로 갖게 되는데, data함수 내부의 두 번째 parameter에 key function을 제공하여 고유한 키를 갖도록 할 수 있다.

1
2
3
4
5
6
7
const data = 1;
d3.select("body").data([data]).enter().append("div");
// 배열로 제공하였다.

const datas = [1, 2, 3];
d3.select("body").data(datas).enter().append("div");
// 실제 DOM 요소와의 연결이 존재하지 않는 2,3번째 요소에 2,3을 할당하여 enter

이중 enter()는 selection 객체에 바인드된 데이터 중에 실제 DOM을 가지지 못하는 요소를 찾아내 가상의 객체로 만들어 반환한다.

즉 데이터의 수가 DOM요소보다 많을 경우 하는 작업이다.

1
2
const data = [1, 2, 3];
d3.select("body").data(data).enter().append("div");

update는 업데이트된 데이터에 따라 어떻게 작업할지를 명시한다.

data 자체가 update이다.

1
2
const data = [1, 2, 3];
d3.select("body").data(data).attr("update");

exit은 업데이트된 데이터에 따라 요소를 제거할 때에 사용된다.

즉 데이터의 수가 DOM요소보다 적을 경우 하는 작업이다.

1
2
const data = [1, 2, 3];
d3.select("body").data(data).exit().remove();

parameter를 제공하지 않을경우, 선택된 요소에 대해 바인딩 된 데이터를 배열로 반환한다.

위의 예시에서는 [1,2,3] 을 반환한다.

join

enter,exit,update를 함수 하나에 정의할 수 있다.

즉 예전 general update pattern에 대한 대안이다.

일일히 정의해줄 수도 있다.

1
2
3
4
5
6
7
8
const data = [1, 2, 3];
d3.select("body")
.data(data)
.join(
(enter) => enter.append("div"),
(update) => update.attr("class", "update"),
(exit) => exit.remove()
);

예시

1
2
3
<div>hi</div>
<div>hi</div>
<div>hi</div>
1
2
3
4
const data = [1, 2, 3];
let dom = d3.select("body").selectAll("div").data(data).enter();

dom.append("div").text("hello");

즉 위의 코드의 결과는 다음과 같다.(enter)

1
2
3
4
5
<div>hi</div>
<div>hi</div>
<div>hi</div>
<div>hello</div>
<div>hello</div>
1
2
3
<div>hi</div>
<div>hi</div>
<div>hi</div>
1
2
const data = [1, 2, 3];
d3.select("body").selectAll("div").data(data).text("hello");

다음과 같이 수정하면 hello 5개로 구성된 HTML을 얻을 수 있다.(update)

1
2
3
4
5
<div>hello</div>
<div>hello</div>
<div>hello</div>
<div>hello</div>
<div>hello</div>

Ref

https://github.com/d3/d3-selection#joining-data
https://bost.ocks.org/mike/join/
https://bost.ocks.org/mike/nest/
https://stackoverflow.com/questions/13728402/what-is-the-difference-d3-datum-vs-data
https://www.intothevoid.io/data-visualization/understanding-d3-data-vs-datum/

데이터 시각화의 기본적인 프로세스

오렐리를 통해 데이터 시각화 책을 쓴 Ben Fry는 그의 책 첫 번째 장에서 데이터 시각화 프로세스의 일련의 단계를 정의하였다.

d3를 다루다보면 약간 막히는 부분이 생기는데 어느 프로세스에 있는지 항상 생각하면 더 수월하게 개발할 수 있을 것 같아서 메모한다.

  1. 데이터 수집

데이터를 수집한다.

  1. 데이터 구문분석

데이터를 이루고 있는 구성 성분으로 분해하고 “데이터” 로서 정의한다.

d3의 경우 이름, 형식, 태그등의 데이터 속성을 key,value 형태의 객체로 정의하는 과정이다.

  1. 데이터 필터링

데이터 시각화에 필요하지 않은 데이터를 필터링 해야한다.

  1. 데이터 마이닝

데이터 안에서 통계적 규칙이나 패턴을 분석하여 가치있는 정보로 만든다.

데이터를 표현하기 전 데이터에 대한 기본적인 이해를 돕는 과정이다.

  1. 데이터 표현방법 정의

막대 그래프, 트리등의 시각적 모델을 선택한다.

  1. 데이터의 표현을 정제

css3,html5,svg등의 마크업을 개선한다. 또한 여러 색상이론과 그래픽 디자인 이론에 따라 데이터의 표현을 개선한다.

  1. 상호작용 추가

데이터를 조작하거나 표시되는 기능을 제어하는 방법을 추가한다.

HTML5 pre태그

HTML <pre> 요소는 미리 서식을 지정한 텍스트를 나타내며, HTML에 작성한 내용 그대로 표현합니다. 텍스트는 보통 고정폭 글꼴을 사용해 렌더링하고, 요소 내 공백문자를 그대로 유지합니다.

  L          TE
    A       A
      C    V
       R A
       DOU
       LOU
      REUSE
      QUE TU
      PORTES
    ET QUI T'
    ORNE O CI
     VILISÉ
    OTE-  TU VEUX
     LA    BIEN
    SI      RESPI
            RER       - Apollinaire

HTML5 이후의 a태그

인라인요소 안에 블럭요소를 넣으면 웹접근성에 위배된다.

HTML5 이전에는 a태그는 오직 인라인 요소만 포함할 수 있었지만 HTML5 이후로는 a 태그는 transparent인 태그라 인라인 요소와 블록 요소 둘 다 포함할 수 있다.

Although previous versions of HTML restricted the a element to only containing phrasing content (essentially, what was in previous versions referred to as “inline” content), the a element is now transparent; that is, an instance of the a element is now allowed to also contain flow content (essentially, what was in previous versions referred to as “block” content)—if the parent element of that instance of the a element is an element that is allowed to contain flow content.

특정 요소 foo가 transparent면 다음과 같은 속성을 가진다.

  • foo 요소가 콘텐츠 모델이 flow 요소를 포함하도록 허용 된 상위 요소 baz 의 자식인 경우 foo 요소의 콘텐츠도 flow 요소 를 포함할 수 있습니다 .

  • foo 요소가 콘텐츠 모델이 phrasing 요소를 포함하도록 허용 된 상위 요소 baz 의 자식인 경우 foo 요소의 콘텐츠 도 phrasing 요소를 포함할 수 있습니다 .

즉 부모요소의 콘텐츠 모델을 따르게 된다.

콘텐츠 모델에 대한 설명은 여기서 볼 수 있다.

history api 알아보기

history API는 html5에 도입되었다.

SPA의 라우팅 로직에 주로 쓰인다.

histroy API 는 history객체(window.history)를 활용하여 주소를 바꾼다.

여러 메소드들이 있지만 SPA에서 쓰이는 메소드는 다음과 같다.

history.pushState(state,title,url)

history.pushState() 메서드는 브라우저의 세션 기록 스택에 상태를 추가하는 함수이다.

state

state는 바뀐 주소와 저장할 데이터 객체이다.
history의 상태가 바뀔 때마다 popstate 이벤트가 발생하는데, 이때 이벤트 객체의 state 속성에 해당 상태의 복제본이 담기게 된다.

주소와 함께 데이터를 history.state에 저장할 수 있기 때문에 유용하다.

바뀔 페이지의 정보들을 담아두고 history.state에 접근하여 새로운 페이지를 렌더링하면 된다.

title

제목이지만 대부분의 브라우저에서 title을 지원하지 않는다. 빈 문자열을 제공하면 된다.

url

바뀔 주소이다.

절대경로를 제공하려면 /url 상대경로를 제공하려면 ‘url’의 형태로 제공하면 된다.

history.replaceState(state,title,obj)

history.replaceState()는 현재 history를 수정해 stateObj,title,URL로 대체한다. history.pushState()는 추가를 하지만 history.replaceState는 업데이트한다.

popstate

popstate이벤트는 pushStatereplaceState후에 뒤로가기나 앞으로가기를 할 경우에 발생한다.

Ref

Single Page Application & Routing

함수형 컴포넌트의 생명주기

lifecycle

함수형 컴포넌트의 생명주기는 다음과 같다.

별건 아닌데 react-redux 내부의 useEffect에서 ref와 useEffect를 통해 이전 상태를 저장하는 트릭이 여러 라이브러리나 커스텀 훅에 많이 쓰여서 작성한다.

1
2
3
4
5
6
7
8
9
function usePreviousState(value) {
const ref = useRef(value);

useEffect(() => {
ref.current = value;
}, [value]);

return ref.current;
}

대충 간단한 커스텀 훅을 작성해보았다.

이 커스텀 훅은 이렇게 사용된다.

1
2
3
4
const Component = () => {
const [value, setValue] = useState(1);
const prev = usePreviousState(value);
};

커스텀 훅을 풀어보면 다음과 같다.

1
2
3
4
5
6
7
8
const Component = () => {
const [value, setValue] = useState(1);
const ref = useRef(value);

useEffect(() => {
ref.current = value;
}, [value]);
};

value가 바뀌었을 때 위 코드는 다음과 같은 순서로 동작한다.

  1. setValue가 렌더링을 트리거
  2. 가상돔의 변경사항을 DOM에 업데이트
  3. useEffect가 실행되며 ref의 값을 업데이트(ref 값의 변경은 렌더링을 트리거하지 않는다.)
  4. 다음 렌더링시에는 ref는 이전 value를 참조