티스토리 뷰
2024년 5월 7일 프론트엔드 강의 요약 정리
취업/이직을 하기 위해서 해야하는 일
이력서 수정? 프로젝트 구현? 강의 학습? 교육기관에서 코스 교육 수료?
→ X. 신입/주니어는 '계속 자문하며, 문제를 풀어나가는 능력'을 기르기
취업 전략을 요구하는 시장
- 계속 자문하기
- 문제를 풀어나가는 능력
개발자는 '기술적 문제를 해결하는 사람이고, 그 노동의 댓가로 가치가 생기는 직군'
다만, '기술적인' 문제를 해결하는 것이며, '기술로만' 문제를 해결하는 것이 아니다.
세 단계의 입문 ~ 주니어
- 기초적 프로그래밍 개념(변수, 함수, 조건문, 반복문 등)과 프레임워크를 익히는 단계
- 프로젝트의 특정 기능을 구현할 수 있는 단계
- 제품을 실제 운영 환경에서 사용자에게 제공하고 대처하며 운영해나갈 수 있는 단계
2 ~ 3 사이의 시점에 취업이 가능하며, 3단계는 보통 직접 제품을 운영해보지 않으면 힘든 영역
특히, 보통 'Localhost' 환경에서 경험하며, 여기서 벗어나야 배우는 것들이 크게 증가
언어, *네트워크*, 컴퓨터 공학적인 개념을 추가로 공부, 알고리즘 풀이를 많이 하지만, 가장 중요한 것은 '회사에 어떻게든 들어가서 몸으로 부딪히거나', '나만의 서비스(혹은 도구)를 운영해보는 방법'이 필요
나를 뽑아야 할 이유 만들기
경쟁이 심화된다면, '나를 뽑아야 할 이유'를 만드는 게 필요
'나'를 뽑을 이유가 아닌, '나'에 포커스를 너무 맞추는 것이 아닌 '회사 입장에서는 일을 해야 하고, 그 일을 할 사람이 필요', '지금 회사에 필요한 사람'에 집중하기
그러나, 그 기준을 맞추기에는 어려움이 크게 존재
그렇다면, 취업 컨설팅이 답인가? 아니다.
되돌아보기
가장 중요한 가치는 '문제 해결'
강의를 듣는다고 문제를 잘 해결되는 것도, 프로젝트를 하나 더 한다고 문제가 잘 해결되는 것도, 교육과정을 수료한다고 문제를 잘 해결하는 것이 아니다.
외부적인 요인보다, 그것을 마주하는 나 자신이 '진정으로', '정말' '문제 해결'의 프레임으로 바라보는가?
목적이 어떠한가에 대한 큰 차이의 예시
- Next.js 사용 경험을 위해 프로젝트 하나 만들기
- 써보기 → 프로젝트 완료 또는 실패
- 무엇을 만들지 모르지만, Next.js를 사용하기만 하면 되기 때문에 기술 스택 변경 등의 선택이 어려움
- 블로그를 만들고 싶지만, 정적인 사이트를 만드는 도구 필요
- Next.js 써보기 → 사용해봤지만, 목적(블로그 제작)에 생각보다 부합하지 않아 Gatsby로 변경
- 목적 달성을 위해 문제를 해결해나가는 과정 자체가 중요하기 때문에 기술 스택의 변경은 큰 문제가 아니다
- 그러나, "블로그를 왜 직접 만들어야 했을까?" 하는 생각 가능성 존재
전자의 경우에는, Next.js에 대한 기본적인 지식에 대한 질문 정도만 가능하다.
후자의 경우에는, 프로젝트를 중심으로 여러 가지 이야기를 풀어나가는 것이 가능하다.
즉, 단순 기술 질문이 아니라 '이 사람은 어떤 사람인가'라는 의문이 자연스럽게 해소
Gatsby로 변경한 것이 실수였다고 해도, 그 속에 '문제 해결'을 위한 '판단'과 '선택'의 과정이 담긴 스토리가 존재
결과적으로, 감점이 없었다고 좋아할 것이 아닌, 득점을 하고 있는지를 조금 더 눈여겨 봐야 한다.
- 자주 사용하는 기술을 사용해야 하는가?
- 새로운 기술을 꼭 사용해야 하는가?
회사에 가면 '일'을 해야 한다. 내가 '일'을 할 수 있는 사람, '무언가를 보여줄 수 있는 사람'이라는 것을 증명해야 한다.
'계속 자문하고', '문제를 해결해 나가는 사람'임을 보여줘야 한다.
다짐하기
구직자들의 단점은 '방법이 효과적이지 않거나', '방법론 자체에 수동적인 측면이 있어 타 구직자들 대비 결정적인 차별점을 만들지 못한다.'
막연히, 핵심적인 차별점을 만드는 지점에서 효괒거인 노력을 기울이지 못하게 되는 '해야 한다'보다 '피드백 루프를 빠르게 돌리고 루프 당 개선 폭이 크게 만들기'를 해야 취업 가능성이 높아진다. 즉, 시장에서 원하는 능력치에 근접하기 위한 노력을 기울여야 한다.
자신이 아닌 선에서 출발한 채 막연히 이어가는 공부보다, 경력자의 부합할 수 있도록 구체적인 목표와 방향성을 위해 필요한 도구로 문제 해결을 위한 '왜?'를 물어보는 자세를 갖춰야 한다.
내가 알고 있다고 생각했던 것들도 '왜?'라는 질문을 통해 새로운 시각에서 바라볼 수 있다.
왜 함수는 나눠도 계속 복잡할까?
왜 하는지 중요하지 않지만, 조건문(IF문) 대신 삼항 연산자(?, !), 논리 연산자(&&, || 등), 구조 분해 할당 등 문법에 대한 애착이 샘솟으면서 '무엇을 분리해볼까?'하는 마음이 크게 생긴다.
// 1. 문과 식의 차이를 모르고 연산자로만 조건을 표현하는 경우
function printAdultStatus(age: number) {
age >= 18 && console.log("You are an adult."); // 문과 식의 차이를 혼동하고 있는 예
}
// 올바른 사용은 if 문을 사용하는 것입니다.
- 식(Expression): 식은 값을 생성하며 이를 반환함. 또한 다른 식의 일부가 될 수 있다는 특징 존재
- 문(Statement): 문은 프로그램의 행동을 지시하며 값을 반환하지 않음. 의미론적으로는 코드의 흐름을 제어하거나 변
수를 선언하는 등의 역할을 수행
위 코드의 경우, '식'일까? '문'일까?
조건문이 있어 '식'으로 생각할 수 있지만, '문'에 가깝다.
// 2. 하나의 함수에 과도하게 많은 파라미터를 추가하는 경우
function createUser(
name: string,
age: number,
email: string,
address: string,
phone: string,
isAdmin: boolean,
isVerified: boolean,
hasLicense: boolean
) {
// ...
}
위 처럼, 너무 많은 파라미터를 가지고 있는 함수는 유지보수, 가독성 측면에서 큰 단점
일반적인 상황에서는 2~3개 정도의 변수가 권장
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
function ExampleComponent() {
const [data, setData] = useState(null);
const navigate = useNavigate();
useEffect(() => {
async function fetchData() {
const response = await fetch('https://some-api.com/data');
const result = await response.json();
setData(result);
if (result.needsRedirect) {
navigate('/redirect-page');
}
}
fetchData();
}, [navigate]);
return (
<div>
{/* 여기에 컴포넌트의 UI 렌더링 로직이 있을 것입니다. */}
</div>
);
}
export default ExampleComponent;
위 함수는 데이터를 호출하는 역할을 함수가 컴포넌트 외부로 나누고 싶게 작성되어 있다. 그렇지만, 함수 안에 정의되어 있는 'setData' 와 'navigate'가 문제가 된다. 만약, 그럼에도 불구하고 분리한다면?
function ExampleComponent() {
// ...
}
async function fetchData(setData, navigate) {
const response = await fetch('https://some-api.com/data');
const result = await response.json();
setData(result);
if (result.needsRedirect) {
navigate('/redirect-page');
}
}
수정된 코드가 좋다고 생각한다면, 잘못된 것이다. 오히려 나빠졌다고 볼 수 있다.
그 이유는 'fetchData' 함수의 이름을 초과하는 동작을 하고 있다. 그래서 동작을 예상하기 어렵다.
- 서버로부터 데이터 호출
- 응답값 React 상태로 저장
- 결과 값에 따른 페이지 이동
'setData'와 'navigate'를 인자로 받았지만, 사실 상 재활용이 어려우며, 함수에 대한 테스트 로직 작성을 하더라도 단순 자바스크립트 로직만으로 처리가 어려우며, 'useState'와 'useNavigate'를 반드시 의존성으로 추가해야 한다.
즉, 로직만을 따로 테스트할 수 없고, React 렌더링이 함께 테스트에 포함되어야 하므로 번거롭다.
→ 렌더링이 포함되면, '액션'이 된다. 렌더링 '시점'을 생각한다는 것은 로직 파악 시, 입출력에 더해 '시간' 축을 고려해야 하며, 같은 로직이더라도 '언제' 호출하냐에 따라 결과가 달라진다.
즉, 함수를 나누는 것만으로는 문제를 해결할 수 없다.
순수 함수와 부수 효과
순수 함수(Pure Function): 입력 값에 대해 항상 동일한 출력값을 반환하며, 부수 효과가 없는 함수
const add = (a, b) => a + b
부수 효과(Side Effect): 함수의 핵심 목적에서 벗어나 외부 세계에 영향을 주는 행위가 포함된 함수. 외부 세계와 영향을 주고 받기 때문에 항상 같은 결과를 반환할 것이라는 확신이 없는 경우
const asyncCall = async () => {
return fetch(/** 비동기 호출 */)
}
위의 코드는 서버로 비동기 호출을 하여 결과물을 반환하는 함수로, 서버가 다운되어 있거나 요청이 몰려 timeout이 되는 경우 값 대신 'Promise.reject()'를 반환한다.
정리하자면, 외부 세계에 영향을 주며, 같은 응답을 보장할 수 없는 경우
const App = ({ name }) => {
const [todos, setTodos] = useState([])
useEffect(() => {
const todos = fetch(/** */).then((res) => res.json())
setTodos(todos)
}, [])
return <div>{name}</div>
}
위의 함수는 API call을 하여 결과물을 State에 담는 예시로, 외부 세계와 소통하는 함수로 부수 효과 발생한다.
이 때, React는 부수 효과를 일으키는 장소로 'useEffect'를 제공한다.
만약, 부수 효과가 없는 함수 컴포넌트는 순수 함수로 몇 번을 실행(리렌더링) 하더라도 같은 결과값(UI) 보장
const ui = render(props, state) // y = f(x)
const jsx = App({ name })
리렌더링을 할 때마다 UI가 확률적으로 고장난다면, 매우 비합리적으로, 이것이 바로 React의 렌더링이 순수해야 하는 이유이다.
데이터 / 계산 / 액션만 구분해도 중간!
함수 분리로 다시 돌아와서, 함수를 제대로 분리하지 않았다고 생각이 들어도 유심히 들여다보면 아마 '액션'에 해당하는 로직이 섞여 있는 경우가 많아서 일 것이다.
- 데이터: 이벤트에 대한 사실. 문자열, 객체 등 단순한 값 그 자체
- 예. 사용자가 입력한 이메일 주소, 은행 API로 읽은 달러 수량
- 계산: 입력으로 얻은 출력. (순수 함수, 수학 함수)
- 예. 최댓값 찾기, 이메일 주소 검사
- 액션: 외부 세계와 소통하므로 실행 시점과 횟수에 의존. 부수 효과 발생
- 예. 이메일 전송, 데이터 베이스 읽기
데이터, 계산으로부터 가급적 액션을 분리하고, 가급적 계산을 사용하며 액션으로부터 계산을 추출하는 것이 효율적
계산 - 외부에 영향을 주지 않아 쉽게 테스트 가능하고, 여러 번 테스트해도 문제 발생 없음
액션 - 실행 시점과 횟수에 따라 결과가 다르게 나올 수 있으며, 외부 세계에 영향을 주기 때문에 재현하기 위해서 많은 노력 필요. (테스트하고자 하는 로직 뿐만 아니라 외부 세계를 함께 재현 필요) → 꼭 필요하지만, 가급적 접촉면 작게 하기
type Email = string;
const validateAndSendEmail = async (email: Email, content: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (emailRegex.test(email)) {
// 이메일 유효성 검사 통과 후, 이메일 전송 로직
console.log(`Sending email to ${email}: ${content}`);
// 예시를 위한 가상의 비동기 처리
await new Promise(resolve => setTimeout(resolve, 1000));
} else {
console.error("Invalid email address");
}
}
위의 코드의 문제점
- 이메일 유효성 검사 로직을 독립적으로 테스트하기 어려움 → 이메일 전송 로직과 결합되어 있어 유효성 검사만 대상하는 단위 테스트 작성 어려움
- 이메일 유효성 검사 로직을 다른 곳에서 재사용하기 어려움 → 다른 상황에서 이메일 유효성만을 검사하고 싶을 때, 이 로직을 그대로 사용 불가
type Email = string;
// 계산: 이메일 유효성 검사
function isValidEmail(email: Email): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// 액션: 이메일 보내기
async function sendEmail(email: Email, content: string): Promise<void> {
console.log(`Sending email to ${email}: ${content}`);
// 예시를 위한 가상의 비동기 처리
await new Promise(resolve => setTimeout(resolve, 1000));
}
// 별도의 함수를 사용하여 계산과 액션을 조합
async function validateAndSendEmail(email: Email, content: string): Promise<voi
if (isValidEmail(email)) {
await sendEmail(email, content);
} else {
console.error("Invalid email address");
}
}
큰 차이가 없어 보이지만, '계산' 로직과 '액션'이 각기 함수로 분리된 것을 확인할 수 있다.
그리고 새로운 사용처에서 조합되고 있음을 볼 수 있다. 즉, 이메일 유효성 검사의 계산 함수로 별도의 유닛 테스트가 가능해졌다.
또한, 새로운 사용처 'validateAndSendEmail' 함수가 액션인 이유는 내부에 'sendMail'의 액션을 가지고 있기 때문이다. 즉, 액션은 이런 식으로 연결된 코드 전체로 '전염'된다.
정리
- 순수 함수에 속하는 '계산'인 함수들은 컴포넌트 외부로 분리해도 상관없음
- '액션'에 속하는 함수들은 부수 효과를 일으키며, 컴포넌트 외부로 분리하기 어렵고, 만약 다른 계산 로직들과 합쳐진다면 그들 또한 액션으로 만든다는 사실
최종적으로, 컴포넌트 외부로 빼는 것을 목적으로 삼지 않더라도, 리팩토링의 중간 과정으로 활용할 수 있다. 이 과정에서 자연스럽게 계산과 액션이 분리된다.
쿼리스트링
'상태 폭발(State Explosion)': 소프트웨어의 상태가 증가할 때 각 구성요소가 결합 가능한 상태가 기하급수적으로 증가하여 관리하기 어려울 정도로 복잡도가 높아지는 현상
흔히, UI 작성 시 발생하며, Form 기능을 만들면서 입력 타입(일반 텍스트, 숫자, 날짜, 휴대폰 번호 등), 필드별 유효성 검사, 에러 상태에 대한 라벨 표기, 로딩 상태 등에서 볼 수 있다.
사용자 입력에 따라 변해야 하는 화면
대부분 웹 프론트엔드 제품들은 'CRUD' 기능을 핵심적인 기반으로 하며, 거의 모든 'Web Information Serving'을 하는 제품의 본질을 가지고 있다.
프론트엔드 개발자의 역할은 서비스 전체적인 관점에서 사용자와 서버/DB 사이에 존재하는 '입출력 인터페이스'. 즉, 입력에 따라 출력(화면 변경)을 잘 보여주는 일.
예시. 쇼핑 필터
필터 기능을 구현하면서 여러가지 요구사항을 추가 구현하게 되면, 무한히 증식하게 될 것이다.
특히, URL 공유 시에 필터 적용된 상태로 보여주고 싶다는 요구사항일 경우에는 로컬 스토리지에 저장하거나 서버에 받아서 사용하는 등 많은 고민을 하게 된다.
이 때, 우리는 'URL'이 전역 상태임을 알고 있어야 한다.
새로고침을 해도, 다른 사용자에게 공유를 해도 값을 유지하기 위해서는 URL이 최적이다.
이 때, 'URL Scheme' 개념을 알고, 'path parameter', 'query parameter' 등을 구분하여 사용하게 된다.
이를 쉽게 사용하기 위해서 'URLSearchParams' 생성자를 사용하면 손쉽게 쿼리스트링을 다룰 수 있다. 또한, 'window.location.search'를 사용하면 쿼리스트링을 쉽게 따올 수 있다는 것은 덤이다.
(React에서는 useSearchParams Hook)
배워야 할 자세
- 실패에서 배우기: 실패는 피할 수 없고, 중요한 학습 기회 → 문제에 직면했을 때, 그 원인을 분석하고 해결책을 찾는 과정에서 많은 것을 배운다.
- 학습은 지속적인 과정: 기술은 끊임없이 변하고 발전하여, 새로운 도구, 기술, 라이브러리에 대해 계속 학습 필요 → 단순한 CRUD 기능 구현에서 시작해도, 시간이 지남에 따라 필요한 기능과 요구사항이 증가하며 학습의 폭도 넓혀야 한다.
- 실제 문제 해결을 통해 학습: 이론적 지식도 중요하지만, 실제 문제를 해결하면서 배우는 것이 값진 경험 → 문제에 직면하고 이를 해결하는 과정에서 얻는 실질적인 지식은 이론을 통해 얻는 지식보다 훨씬 깊고 오래 지속된다.
- 문제 해결을 위한 도구와 기술의 적절한 선택: 간단한 도구로 시작하여, 문제의 복잡도가 증가함에 따라 더 나은 도구나 기술 선택 필요 → 'URLSearchParams'와 같은 빌트인 API 사용은 복잡한 로직을 간소화하고 코드의 안정성을 높이는 데 도움을 준다.
- 전부 내 머리로만 해결하지 않기: 모든 문제가 새로운 것은 아니며, 대부분의 기술적 도전은 대부분 이미 해결되었을 높은 가능성 → 다른 사람들이 만든 결과물을 잘 활용하는 것은 효율적으로 문제를 해결하는 데 큰 도움이 된다.
가장 중요한 것은 "왜?"를 질문하는 순간
예를 들어, 위 필터 기능에서 "왜?"가 등장하는 순간들은 주로 개발자가 새로운 문제에 직면하거나 기존 접근 방식이 효과적이지 않을 때!
필터 기능의 복잡성 증가
단순한 필터 기능을 확장하면서 왜 추가적인 복잡성이 발생하는지, 왜 단순한 'if-else 조건문'만으로 충분하지 않은가 고민
→ 더 나은 구조의 필요성을 인식하게 하고, 확장 가능하고 유지보수가 용이한 코드를 작성하기 위해 고민
URL을 통한 상태 관리
새로고침 후에도 필터 상태가 유지되지 않는 문제에 직면했을 때, 왜 이러한 상태 유지가 중요한지, 그리고 왜 기존의 로컬 스토리지 방식만으로는 부족한지 질문
→ URL 자체를 상태 저장 매체로 사용하는 방법을 고안하게 되며, URLSearchParams와 같은 새로운 도구의 도입을 이끌어내는 계기
'여러가지 활동 > 프리온보딩 프론트엔드 챌린지' 카테고리의 다른 글
Rendering과 Isomorphic Components (0) | 2024.05.16 |
---|---|
타입스크립트와 Tanstack-Query (0) | 2024.05.14 |
UI/UX와 반응형 웹 (0) | 2024.04.15 |
사용자 시나리오와 UX 전략 (0) | 2024.04.12 |
Rest API 간략하게 (0) | 2024.04.11 |
- Total
- Today
- Yesterday
- 조코딩과함께
- javascript
- 개발 이력서 지원 팁
- 원티드 프리온보딩
- 원티드 프리온보딩 챌린지
- 깃허브 Merge
- Express
- Frontend
- #포스텍애플디벨로퍼아카데미
- 고민한 부분
- Default Branch
- 자바스크립트
- node
- React
- PostechAppleDeveloperAcademy
- 포스텍애플디벨로퍼아카데미
- 그룹인터뷰후기
- 코딩테스트 대비
- 신입개발자가 준비해야 할 것들
- LottieFiles
- 최종추가합격
- 개발자이력서꿀팁
- 프론트엔드 챌린지
- 설명회느낌점
- 스프링
- DB Error MongooseServerSelectionError
- 싱글톤
- if(kakao)dev2022
- 포스텍애플아카데미
- Singleton
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |