var 키워드

  • 함수레벨 스코프
  • 생략 가능
  • 변수 재선언, 재할당 가능
  • 변수 호이스팅: 선언 이전에 참조할 수 있다.
  • 전역 변수로 선언 시 전역 객체의 프로퍼티로 등록

let 키워드

  • 블록레벨 스코프
1
2
3
4
5
6
7
8
9
let foo = 123; // 전역 변수

{
let foo = 456; // 지역 변수
let bar = 456; // 지역 변수
}

console.log(foo); // 123
console.log(bar); // ReferenceError: bar is not defined
  • 재선언 불가
1
2
3
4
5
var foo = 123;
var foo = 456; // 중복 선언 허용

let bar = 123;
let bar = 456; // Uncaught SyntaxError: Identifier 'bar' has already been declared

let 키워드의 호이스팅

var 키워드로 선언된 변수는 선언단계와 초기화 단계가 동시에 일어난다.

하지만 let, const 키워드로 선언된 변수는 선언단계와 초기화 단계가 분리되어 진행된다.

const 키워드

  • 재할당, 재선언 불가
  • 상수를 주로 사용
  • 변수 자체를 재할당할 순 없지만 객체의 프로퍼티는 변경 가능하다.
1
2
3
4
5
6
7
8
9
const user = { name: "Lee" };

// const 변수는 재할당이 금지된다.
// user = {}; // TypeError: Assignment to constant variable.

// 객체의 내용은 변경할 수 있다.
user.name = "Kim";

console.log(user); // { name: 'Kim' }

댓글 공유

JavaScript의 변수란?

카테고리 CS

컴퓨터는 연산을 담당하는 CPU, 저장을 담당하는 메모리로 각각 역할이 나뉘어져있다.

연산결과를 재사용하기 위해서는 메모리에 저장하고 메모리 주소를 통해 연산결과가 저장된 메모리 공간에 접근이 가능하다.

메모리 주소에 직접 접근하는 것은 위험하다. 가령 운영체제가 사용하고 있는 값을 변경하면 시스템을 멈추게 하는 치명적인 오류가 발생할 수 있기 때문에 자바스크립트는 개발자의 직접적인 메모리 제어를 허용하지 않는다.

변수란, 하나의 값을 저장하기 위해 확보한 메모리 공간 또는 메모리 공간을 식별하기 위해 붙힌 이름이다.

  • 변수는 인터프리터나 컴파일러를 통해 값이 저장된 메모리 주소로 치환되어 실행된다.
  • 자바스크립트 엔진은 변수 이름과 매핑된 메모리 주소를 통해 메모리 공간에 접근하여 저장된 값을 반환한다.

변수 선언

변수는 선언단계에서 변수 이름을 등록하여 자바스크립트 엔진에게 변수의 존재를 알린다.

또한 초기화 단계에서 값을 저장하기 위한 메모리 공간을 확보하고 암묵적으로 undefined를 할당한다.

1
2
console.log(score); // undefined
var score;

모든 선언문은 런타임 이전에 먼저 실행된다.

변수 할당

1
score = 100;

변수 할당단계는 변수에 값을 저장하는 것을 말한다.

  • 선언과 할당이 한줄에 있어도 선언과 할당이 실행되는 시점이 다르다.
  • 값의 할당은 소스코드가 순차적으로 실행되는 시점인 런타임때 실행된다.

변수에 값을 할당할 때, undefined가 있던 메모리 공간을 지우고 값을 저장하는 것이 아닌, 새로운 메모리 공간을 확보한 뒤 그곳에 새로운 값을 저장하고 해당 변수의 메모리 주소를 연결한다.

댓글 공유

저번 포스팅 때, Legend에 hover했을 때, 해당 데이터만 highlight 되도록 구현을 했다.

이번에는 Legend를 커스터마이징하여 색상도 바꿔보도록 하려고한다.

1. CustomLegend 컴포넌트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const CustomLegend = (props: any) => {
const { payload, onMouseEnter, onMouseLeave } = props;

return (
<ul>
{payload.map((entry: any, index: any) => (
<li
key={`item-${index}`}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={{ listStyle: "none", color: colors.GRAPH[`${index + 1}`] }}
>
{entry.value}
</li>
))}
</ul>
);
};
  • 예시를 위해 타입은 any로 설정하였다.
  • Legend의 각 li에 mouse 이벤트를 할당하였다.
  • 마우스 이벤트는 호버된 데이터를 제외한 데이터들의 opacity를 줄여서 해당 데이터만 highlight 되도록 한다.

2. CustomLegend의 props

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const handleMouseEnter = (o: any) => {
const dataKey = o.target.innerHTML;

const entries = Object.entries(opacity).map(([key, value]) =>
key === dataKey ? [key, 1] : [key, 0.2]
);

const mappedObj: any = entries.reduce((prev, curr) => {
const [key, value] = curr;
prev = { ...prev, [key]: value };
return prev;
}, {});

setOpacity(mappedObj);
};

그런데, 해당 데이터에 호버를 해도 모든 데이터의 opacity가 줄어드는 문제가 발생했다.

그 이유는 Legend에서의 props와 customLegend의 props가 달라서 mouse 이벤트가 잘못 동작했기 때문이다.

  • 이전 mouse 이벤트에서는 props안에 dataKey 속성으로 호버된 데이터 값을 가져올 수 있었다.
  • 하지만 customLegend에서는 props에 너무나도 많은 속성이 있었고 이 중 나는 target속성의 innerHTML 속성으로 호버된 데이터의 dataKey를 확인하는 로직을 구성하였다.

3. 전체 코드

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { useEffect, useState } from "react";
import {
LineChart,
Line,
Tooltip,
Legend,
ResponsiveContainer,
} from "recharts";
import colors from "styles/colors";

const CustomLegend = (props: any) => {
const { payload, onMouseEnter, onMouseLeave } = props;

return (
<ul>
{payload.map((entry: any, index: any) => (
<li
key={`item-${index}`}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={{ listStyle: "none", color: colors.GRAPH[`${index + 1}`] }}
>
{entry.value}
</li>
))}
</ul>
);
};

function NewPortChart() {
const [opacity, setOpacity] = useState<any>({});

const handleMouseEnter = (o: any) => {
const dataKey = o.target.innerHTML;

const entries = Object.entries(opacity).map(([key, value]) =>
key === dataKey ? [key, 1] : [key, 0.2]
);

const mappedObj: any = entries.reduce((prev, curr) => {
const [key, value] = curr;
prev = { ...prev, [key]: value };
return prev;
}, {});

setOpacity(mappedObj);
};

const handleMouseLeave = () => {
const entries = Object.entries(opacity).map(([key, value]) => [key, 1]);

const mappedObj: any = entries.reduce((prev, curr) => {
const [key, value] = curr;
prev = { ...prev, [key]: value };
return prev;
}, {});

setOpacity(mappedObj);
};

useEffect(() => {
const mappedOpacity = Object.keys(data[0]).reduce((prev, curr) => {
prev = { ...prev, [curr]: 1 };
return prev;
}, {});
setOpacity(mappedOpacity);
}, []);

return (
<ResponsiveContainer width="100%" height="100%">
<LineChart width={857} height={440} data={data}>
<Tooltip />
<Legend
align="right"
verticalAlign="middle"
layout="vertical"
content={
<CustomLegend
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
/>
}
/>
{Object.keys(data[0]).map((key, index) => (
<Line
key={key}
type="linear"
dataKey={key}
strokeOpacity={opacity[key]}
strokeLinecap="round"
stroke={colors.GRAPH[`${index + 1}`]}
activeDot={false}
dot={false}
/>
))}
</LineChart>
</ResponsiveContainer>
);
}

export default NewPortChart;

기본 CustomLegend

기본 customlegend

Hover된 데이터만 highlight

hover1
hover2
hover3

추가로…

색상만 바꿀 것이였다면 왜 CustomLegend까지 쓰면서 복잡하게 시도를 했을까 궁금증이 들 수도 있다.

디자이너 요구사항이 Legend와 해당 Line 데이터 끝 부분을 선으로 연결해달라는 요청이 있었기 때문에 CustomLegend를 사용해보았다.

아직 해당 부분은 좀 더 고민이 필요하기 때문에 추후에 포스팅하도록 하겠다.

댓글 공유

서비스가 주식 관련 서비스이다 보니 차트를 사용할 일이 잦다.

차트를 직접 구현하자니 너무 공수가 많이 들 것 같아 Recharts 라이브러리를 자주 사용하고 있다.

하지만 공식문서에서 모든게 나와있지 않아서 ChatGPT의 도움도 많이 받고 있다. 그래도 꽤 쓸만한 라이브러리이다.

오늘은 Recharts 라이브러리로 legend에 hover했을 때, hover된 데이터만 highlight 되도록 구현해볼 것이다.

chart

우선 기본적인 Line 차트를 렌더링한다.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { useState } from "react";
import {
LineChart,
Line,
XAxis,
YAxis,
Tooltip,
Legend,
ResponsiveContainer,
} from "recharts";

const data = [
{
name: "Page A",
uv: 4000,
pv: 2400,
amt: 2400,
},
{
name: "Page B",
uv: 3000,
pv: 1398,
amt: 2210,
},
{
name: "Page C",
uv: 2000,
pv: 9800,
amt: 2290,
},
{
name: "Page D",
uv: 2780,
pv: 3908,
amt: 2000,
},
{
name: "Page E",
uv: 1890,
pv: 4800,
amt: 2181,
},
{
name: "Page F",
uv: 2390,
pv: 3800,
amt: 2500,
},
{
name: "Page G",
uv: 3490,
pv: 4300,
amt: 2100,
},
];

function MultiLineCharts() {
const [opacity, setOpacity] = useState({ uv: 1, pv: 1 });

const handleMouseEnter = (o) => {
const { dataKey } = o;

const entries = Object.entries(opacity).map(([key, value]) =>
key === dataKey ? [key, 1] : [key, 0.2]
);

const mappedObj = entries.reduce((prev, curr) => {
const [key, value] = curr;
prev = { ...prev, [key]: value };
return prev;
}, {});

setOpacity(mappedObj);
};

const handleMouseLeave = (o) => {
const { dataKey } = o;

const entries = Object.entries(opacity).map(([key, value]) => [key, 1]);

const mappedObj = entries.reduce((prev, curr) => {
const [key, value] = curr;
prev = { ...prev, [key]: value };
return prev;
}, {});

setOpacity(mappedObj);
};
return (
<ResponsiveContainer width="100%" height="100%">
<ResponsiveContainer width="100%" height={300}>
<LineChart
width={500}
height={300}
data={data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
/>
<Line
type="monotone"
dataKey="pv"
strokeOpacity={opacity.pv}
stroke="#8884d8"
activeDot={{ r: 8 }}
/>
<Line
type="monotone"
dataKey="uv"
strokeOpacity={opacity.uv}
stroke="#82ca9d"
/>
</LineChart>
</ResponsiveContainer>
</ResponsiveContainer>
);
}

export default MultiLineCharts;
  • Line 그래프의 opacityuseState로 관리한다.
  • Legend에 onMouseEnter, onMouseLeave 이벤트를 할당한다.
  • 여기서 유저가 이벤트를 발생시킨 요소만 opacity를 두고 나머지 데이터들의 opacity를 줄여주기 위해 opacity 객체를 재구성했다.
  • Object.entries()reduce()를 사용하여 편리하게 객체를 재구성할 수 있다.

결과

hover chart

  • 보라색 Legend Hover 시

hover_chart2

  • 연두색 Legend Hover 시

댓글 공유

input에 입력값을 입력하기 전까지 placeholder는 사라지지 않는다.

이는 input 창이 포커싱되어있는지 헷갈리게 할 수 있어 UX를 떨어뜨릴 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
input:focus::-webkit-input-placeholder,
textarea:focus::-webkit-input-placeholder {
/* WebKit browsers */
color: transparent;
}

input:focus:-moz-placeholder,
textarea:focus:-moz-placeholder {
/* Mozilla Firefox 4 to 18 */
color: transparent;
}

input:focus::-moz-placeholder,
textarea:focus::-moz-placeholder {
/* Mozilla Firefox 19+ */
color: transparent;
}

input:focus:-ms-input-placeholder,
textarea:focus:-ms-input-placeholder {
/* Internet Explorer 10+ */
color: transparent;
}
  • 크로스 브라우징을 고려하여 CSS를 추가해주었다.
  • 포커스가 되었을 때, placeholder 색상을 투명하게 설정하도록 해준다.

댓글 공유

객체의 key 값에 유니온 타입 선언하기

1
2
3
4
5
type userInfoType = "name" | "age" | "address";

interface User {
[key: userInfoType]: string;
}

유니온 타입 에러

  • “인덱스 시그니처 매개변수는 리터럴 타입이나 제네릭 타입이 될 수 없다.”는 에러가 발생했다.
  • 매핑된 객체를 대신 사용하라고 알려준다.

해결방법

1
2
3
4
5
6
7
8
9
10
11
type userInfoType = "name" | "age" | "address";

type userType = {
[key in userInfoType]: string;
};

let user: userType = {
name: "홍길동",
age: "20",
address: "서울",
};

댓글 공유

Queue 클래스로 구현하기

enqueue와 dequeue

  • Queue는 입구가 rear이고 출구가 front이다.
  • 입구쪽에서 데이터를 추가하는 것을 enqueue라고 한다.
  • 출구쪽에서 데이터를 제거하는 것을 dequeue라고 한다.

노드 삽입할 때,

  • 빈 Queue이면 front, rear가 모두 첫 노드를 가리킨다.
  • 빈 Queue가 아니면, rear의 next가 새 노드를 가리키고 rear를 새 노드로 옮긴다.

노드 꺼낼 때,

  • 빈 Queue가 되면, front, rear는 모두 None을 가리킨다.
  • Queue에 노드가 남아있으면 front를 front의 next로 옮긴다.

코드

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
31
32
class Node:
def __init__(self,data):
self.data = data
self.next = None

class Queue:
def __init__(self):
self.front = None
self.rear = None

def enqueue(self,data):
node = Node(data)
if self.front == None:
self.front = node
self.rear = node
else:
self.rear.next = node
self.rear = node

def dequeue(self,data):
if self.front == None:
return None
node = self.front
if self.front == self.rear:
self.front = None
self.rear = None
else:
self.front = self.front.next
return node.data

def is_empty(self):
return self.front == None

댓글 공유

Queue(큐)란?

큐는 대기행렬(줄)이다.

우리가 무언가를 사거나 장소에 들어갈 때 줄을 선 순서를 생각하면 된다.

많은 양의 자료를 프린터로 출력한다 했을 때, 프린터 상태창을 보면 출력할 자료가 순서대로 들어가있고 들어간 순서대로 출력되는 것을 알 수 있다.

queue

스택이 한쪽입구가 막힌 상자에 자료를 차곡차곡 쌓는 것이라면, 큐는 입구와 출구가 따로 있는 통로로서 한쪽에서 밀어 넣으면 반대쪽에서 나오는 것이다.

  • 큐에서는 head 대신 front, tail 대신 rear(or back) 이라 한다.
  • enqueue: 가장 마지막에 자료를 넣는 것으로 위 그림에서 연결리스트의 append()와 같다.
  • dequeue: 가장 먼저 들어간 자료를 꺼내는 것으로 연결리스트의 popLeft()와 같다.

댓글 공유

내부 경계(border) 효과 주기

문제

문제사진

CSS로 작업을 하다가 경계가 검정색 바탕 부분과 border에 약간의 단차가 생기는 문제가 있었다.

마크업 구조는 다음과 같다.

1
2
3
4
5
6
<div>
<h2>나의 자산 목표 금액 계산기</h2>
<div>
<h3>목표 금액 계산기란?</h3>
</div>
</div>
  • h2 태그에 배경색이 검정색이다.
  • h2 태그와 형제태그인 div 태그에 보더를 주었더니 단차가 생겼다.

해결방법

1
box-shadow: 0 0 0 1px gray inset;
  • box-shadow 효과를 inset으로 주어서 보더 처럼 보이도록 구현했다.

댓글 공유

문제

참조: https://www.geeksforgeeks.org/next-greater-element/

음이 아닌 정수 배열이 주어졌을 때, 각 원소의 오른쪽에 있는 원소 중에서 현재 원소보다 큰 값을 출력하되, 가장 근접한 원소를 출력하라. 현재 원소보다 큰 값이 없으면 -1을 출력하라.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
예시 1

입력: [4, 5, 2, 25]
출력:
4 --> 5
5 --> 25
2 --> 25
25 --> -1
예시 2

입력: [13, 7, 6, 12]
출력:
13 --> -1
7 --> 12
6 --> 12
12 --> -1

풀이

이중반복문

1
2
3
4
5
6
7
8
9
def solution(int_arr):
for i in range(len(int_arr)):
int = int_arr[i]
result = -1
for j in range(i,len(int_arr)):
if int < int_arr[j]:
result = int_arr[j]
break
print(f"{int} --> {result}")

스택

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def solution*by_stack(int_arr):
n = len(int_arr)
s = []
res = [-1 for * in range(len(int_arr))]

for i in range(n-1,-1,-1):
while s:
if s[-1] > int_arr[i]:
res[i] = s[-1]
break
else:
s.pop()
s.append(int_arr[i])

for i in range(n):
print(f"{int_arr[i]} --> {res[i]}")
  • 현재 원소와 오른쪽 원소를 비교하므로, 오른쪽에서 왼쪽으로 이동하면서 비교하면 수월하다.
  • 문제에서 요구하는 것은 현재 원소의 오른쪽 값 중 가장 가까운 값이므로, 오른쪽 부터 왼쪽으로 가면서 원소를 저장했다면 꺼낼 때는 역순으로 꺼내서 비교한다.
  1. stack을 빈 상태로 초기화
  2. res 배열을 -1로 배열 길이 만큼 초기화
  3. 역순으로 순회를 하면서 stack이 비어있으면, 원소를 추가한다.
  4. 만약 stack에 값이 있다면, 스택의 값들과 현재 원소를 비교한다.
  5. 스택의 값이 크다면 해당 원소의 index에 위치하는 res 배열에 스택의 값을 할당하고 해당 원소를 stack 저장
  6. 다음 순회
  7. 만약 스택의 값이 작다면 stack에 마지막 값을 pop한다.

댓글 공유

loco9939

author.bio


author.job