본문으로 건너뛰기

"issue" 태그로 연결된 5개 게시물개의 게시물이 있습니다.

모든 태그 보기

Local Storage 용량 초과

· 약 5분
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

프로젝트에서 LocalStorage를 사용했을 때 용량을 고려한 적이 있나요?

localStorage의 용도에 따라 다르겠지만, 대부분의 경우는 용량을 고려하지 않고 사용하게 됩니다. 하지만, 데이터의 성격이 계속해서 쌓이는 구조의 데이터를 저장한다면 데이터는 localStorage의 용량을 초과할 수 있습니다.

초과하면 과연 어떻게 동작할까요?

정답은 Throw 에러 입니다.

Figure 1Local Storage Exceed Error

Solution

위 에러가 발생하면, setStorage 구문은 throw로 인해 종료가 되고 에러 전파를 진행하게 됩니다.

프로젝트에 에러 전파를 잘 처리하고 있다면, 에러 메세지와 함께 에러 로그가 수집 될 것이고 전파를 잘 처리 하지 않았다면 프로젝트가 멈출 수 있습니다.

이러한 상황을 방지하기 위해서는 try-catch 구문을 사용해서 에러를 잡아내고, 에러를 처리하는 로직을 추가해야 합니다.

const setStorage = (key, value) => {
try {
window.localStorage.setItem(key, value);
} catch (e) {
// Stop Throw Error
console.error('Local Storage Exceed Error', e);
}
}

물론 에러가 발생했다는 것은 LocalStorage의 용량을 초과했다는 것이므로 앞으로 사용자는 계속해서 Local Storage Exceed Error가 발생하고, 데이터를 저장하지 못할 것 입니다.

따라서 에러처리 뿐 아니라 데이터를 지워주는 작업까지 추가해주어야합니다.

const setStorage = (key, value) => {
try {
window.localStorage.setItem(key, value);
} catch (e) {
// Stop Throw Error
console.error('Local Storage Exceed Error', e);
window.localStorage.clear();
}
}

React의 이벤트 전파

· 약 13분
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

React 이벤트 핸들러 방식과 vanilla Javascript의 이벤트 핸들러 방식을 혼용해서 사용할 때 이벤트 전파가 의도한대로 동작하지 않을 수 있다. 예를 들면 아래와 같이 button1, button2 구조에서 button2를 클릭했을 때 button1의 이벤트 핸들러도 실행되는 문제가 발생한다.

const buttonEl = document.getElementById('button1');
buttonEl.addEventListener('click', () => {
console.log('button1 clicked');
});

const handleClick = (e) => {
e.stopPropagation();
console.log('button2 clicked');
};

return (
<button id='button1'>
<button id='button2' onClick={handleClick}>Click me</button>
</button>
);
// When button2 is clicked
button2 clicked
button1 clicked

Solution

React에서 이벤트 전파는 vanilla Javascript에서 사용하는 이벤트 전파와 다르다. React에서 이벤트 전파를 delegation 방식으로 처리하기 때문이다. (comment)

React에서는 (React 17 이후로) 이벤트 리스너를 rootDOM에 등록해서 사용한다. 따라서 React끼리의 event listener는 생각한 대로 DOM 구조에 따라 이벤트가 전파가 전달되지만 vanilla Javascript의 이벤트 전파는 생각대로 동작하지 않는다.

가능하다면 두가지 이벤트를 섞어서 쓰지 않는 방향이 좋겠지만, 3rd party library를 사용하거나 제어하지 못하는 부분의 이벤트는 제어하기 힘들기 때문에 동작 방식에 맞춰 이벤트 전파를 막아야한다.

// Unify event listener to vanilla Javascript
const buttonEl = document.getElementById('button1');
buttonEl.addEventListener('click', () => {
console.log('button1 clicked');
});

const button2El = document.getElementById('button2');
button2El.addEventListener('click', (e) => {
e.stopPropagation();
console.log('button2 clicked');
});

return (
<button id='button1'>
<button id='button2'>Click me</button>
</button>
);

// Or
// Unify event listener to React
const handleClick1 = (e) => {
console.log('button1 clicked');
};

const handleClick = (e) => {
e.stopPropagation();
console.log('button2 clicked');
};

return (
<button id='button1' onClick={handleClick1}>
<button id='button2' onClick={handleClick}>Click me</button>
</button>
);

Nextjs에서 server action은 순차적으로 실행된다

· 약 8분
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

nextjs에서 server action은 client 환경에서도 server를 통해 함수를 호출하기 위한 방법으로 사용된다. 이 때, server action은 순차적으로 실행되어 여러 함수를 호출하더라도 하나씩 실행되므로 사용에 유의해야한다.

Behavior

간략하게 요약 된 예시를 보자. 예시는 두가지 함수가 존재한다.

'use server';
import { format } from 'date-fns';

export const callServerFn = async (count)=> {
return new Promise((resolve) => setTimeout(() => {
const time = format(new Date(), 'HH:mm:SS.sss');
console.log(`Server Call #${count}: `, time);
resolve(count)
}, 1000));
}

callServerFn 함수는 응답에 1초가 걸리는 함수이고, 'use server'를 사용하며 server action으로 사용된다. 해당 로직은 next server 에서 이루어진다. 서버 응답 타이밍을 확인하기 위해 log를 찍고있다.

// client.tsx
'use client';

export const CallButton = () => {
const handleClick = async () => {
const result = await Promise.all([
callServer(1),
callServer(2),
callServer(3),
])
}

return (<button onClick={handleClick}>Hello World</button>);
}

CallButton 컴포넌트는 버튼을 클릭하면 callServer 함수를 3번 호출하고, 모든 함수가 완료되면 결과를 반환한다.

Server Call #1:  01:23:22.010
Server Call #2: 01:23:24.011
Server Call #3: 01:23:25.012

버튼을 눌렀을 때 분명 3개의 호출이 동시에 이루어졌지만, log 결과는 하나씩 순차적으로 1초에 한번씩 호출되는 것을 확인 할 수 있다.

CSS로 line-spacing을 구현하는 방법

· 약 4분
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

CSS에서는 줄 간격을 조정하는데 line-height를 사용한다. 하지만 내가 경험했던 프로젝트에서 line-spacing을 지원해야하는 요구사항이 있었다. 이에 대해 CSS로 line-spacing을 구현하는 방법에 대해서 설명하고자 한다.

Line Spacing

Line Gap

Line Gap

Line Gap

Line Spacing (5px)

Line Height

Line Gap

Line Gap

Line Gap

Line Height (fontSize + 5px)

Cheat Sheet

여백이 위, 아래로 나눠서 적용되는 line-height와 달리, line-spacing은 줄 사이에만 여백을 적용한다. 이 때, line-height와의 차이는 줄의 처음과 끝의 여백이 들어가지 않는다는 점이다.

이를 이용해서 margin을 이용하여 line-spacing을 구현할 수 있다.

const lineHeight = 20; // origin line-height
const lineSpacing = 10;
const margin = - (lineSpacing / 2);

const LineSpacingText = () => {
return (
<p style={{
marginTop: `${margin}px`,
marginBottom: `${margin}px`,
lineHeight: `${lineHeight + lineSpacing}px`,
}}>
Hello, World!
</p>
);
};

Playground

Line Spacing 0px

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet.

Line Spacing (0px)

Line Height (0px + fontSize)

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet.

Line Height (0px + fontSize)

nested component를 피해야 하는 이유

· 약 5분
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

프로젝트를 진행하면서 불필요하게 re-render가 발생하는 문제를 만났다.

단순히 render를 하는 것 뿐 아니라 매 render시마다 DOM을 새롭게 다시 그리고 있던 것이 문제였는데, 원인은 nested component로 사용한 코드였다.

따라서 이 글에서는 nested component를 사용하는 것을 피해야 하는 이유를 설명하고자 한다.

Cheat Sheet

// Bad
const List = ({ hasWrapper, borderStyle, children }) => {
const Border = () => {
return <p style={borderStyle}>{children}</p>;
}

if (hasWrapper) {
return (
<Wrapper>
<Border />
</Wrapper>
);
}

return <Border/>;
};
// Good
const List = ({ hasWrapper, borderStyle, children }) => {
if (hasWrapper) {
return (
<Wrapper>
<Border borderStyle={borderStyle}>
{children}
</Border>
</Wrapper>
);
}

return (
<Border borderStyle={borderStyle}>
{children}
</Border>
);
};

const Border = ({ borderStyle, children }) => {
return <p style={borderStyle}>{children}</p>;
}

위와 같이 Border의 코드를 재사용하고, List의 props를 재사용하기 위해 nested component를 사용하게 될 수 있다. 하지만 이런 경우 children은 불필요하게 매 render시마다 새롭게 DOM이 그려지게 된다.