시작하는 마음
안녕하세요 초원입니다. 👩🏻💻
작년 초부터 개발 공부를 시작해서 국비 교육, 부트캠프, 외주 프로젝트를 거치고
드디어 올해 6월부터 일을 하기 시작했어요! 👏🏻👏🏻
처음 시작할 땐 vscode의 파이썬 실행 버튼조차 찾지 못해 낙담하고,
유치원생처럼 하나 하나 물어가며 배웠었는데 ...
자신이 없어질 때쯤 그때를 떠올리면 '장족의 발전이네! 아주 잘 하고 있어. 계속, 즐겁게 개발하자'
하며 마음을 다잡게 되는 것 같아요 ✊🏻
그래서 이번엔 어떤 도전을 ?
이번엔 주니어 개발자를 위한 역량 강화 프로그램인 항해 플러스 프론트엔드 과정에 참여했습니다-
때때로 '무슨 교육을 그렇게 많이 듣냐?’ 라는 말을 듣곤 하지만 ..
평생을 인문학도로 살아온 제가 이공계 마인드를 온전히 가지려면
4년 대학 교육만큼은 더 받아도 되지 않을까 하는 생각이에요 헤헤
선배 개발자 분들이나 기업 차원에서 마련한 좋은 커리큘럼이 있다면
지출이 있더라도 적극적으로 참여하려는 편입니다.
세상엔 고민할 게 너무도 많은데, 전문가 또한 많으니까
도움 받을 수 있는 부분은 최대한 받고
저는 또 다른 전문가가 되어 다른 이들을 돕는 구조를 만드는거죠? ✌🏻
1주차 참여 후기

5개월 간 합숙하며 전산학 핵심 내용을 공부하는 과정인 정글사관학교를 수료할 때까지만 해도,
커뮤니티의 힘이 이렇게 큰지 몰랐습니다.
회사를 들어오고 다른 개발자 분들의 이야기를 들으니 함께 고민할 수 있는 동료가 있다는 게 참 든든하더라구요!
( 너무 당연하게 생각했던 .. 우리 정글러들 아주 소중해 🫶🏻 )
항해플러스가 이런 네트워킹을 통해 소통의 자리를 제공하고 지속적인 연결을 구축하는데
아주 탁월하다고 느꼈어요.
10주 간 최선을 다해 참여하면 수료 이후에도 도움을 주고 받을 수 있는
좋은 관계로 이어질 거라고 기대중입니다. 🤟🏻
휘핑크림 성장곡선
제가 방금 지어낸 정의입니다만? 🤷🏻♀️ 저는 휘핑크림 성장곡선을 추구하고 가지고 있다고 생각해요.
처음 한 바퀴 돌 땐 녹기도 하고, 쌓이지 않는 것 같지만
계속 해서 나아가면 '같은 길 아닌가 ..' 싶을 때 쯤 어느새 목표 지점까지 도달해 있는거죠! (와우)
저는 혼자 이런 거 생각하고 오 .. 좋은데? 하며 기분 좋아하는 사람이랍니다. 히히
최종 목표
목표를 향해 달리는 것은 별로 안 좋아하지만 .. 한 번 정해보자면
1. 과제 매주 제출하기 : 최선을 다해 고민할 것
2. 너무 욕심 부리다 퍼지지 않게 나를 잘 돌아보기 : 조급해 하지 않고, 한 가지를 깊게 학습하려고 노력할 것
제가 10주 간 해낼 것은 이 두 가지 입니다.
완주 해서 블랙 배지 받고 싶어요 🥋
그럼 이제 한 주 간 과제를 통해 어떤 문제를 해결했는지 소개드릴게요! 🌱
1주차 과제
본 게시글은 신입 개발자가 항해플러스 프론트엔드 2기 _리액트 파헤치기 과정을 거치며 고민한 내용을 담았습니다. 과제와 테스트 코드를 제공해주신 준일 코치님께 감사 인사 드립니다 🙇🏻♀️
총 3주 간의 리액트 과정에서 우리는 다음과 같은 목표를 가집니다.
1주차 - useState, useCallback, useMemo, PureComponent 등 리액트의 기본 개념을 확실하게 이해합니다. 또한 useRef를 직접 구현하면서 react의 lifecycle에 대해 이해해 봅니다.
2주차 - (작성중)
3주차 - (작성중)
목표를 달성하기 위한 방법은 다양하지만, 저의 경우 코드를 통해 학습하는 것을 좋아합니다.
문제 해결을 위한 고민과 과정, 그리고 구현을 위해 학습한 내용을 정리하며 과정을 복습해보려고 합니다.
useState
import { useState } from "react";
// NOTE: state의 값이 정상적으로 변경이 되도록 만들어주세요.
export default function UseStateTest() {
const [state, setState] = useState({ bar: { count: 1 } });
const increment = () => {
const newState = { ...state };
newState.bar.count += 1;
setState(newState);
}
return (
<div>
count: {state.bar.count}
<button onClick={increment}>증가</button>
</div>
);
}
증가 버튼을 눌러도 count 값이 증가하지 않는 문제를 해결하기 위해,
전개 연산자를 사용하여 기존 state를 깊은복사한 newState를 생성했습니다.
`const newState = { ...state };`
객체는 참조 타입이므로 불변성을 유지하지 않습니다.
즉, 기존 state에 변경 사항이 생기더라도 참조하는 메모리 주소는 바뀌지 않고 값만 변경됩니다.
이로 인해 리액트는 state에 변경이 없다고 판단하고 리렌더링을 수행하지 않습니다.
이 문제를 해결하기 위해 setState 함수에 newState를 전달했습니다.
`setState(newState);`
이를 통해 리액트는 새로운 객체가 생성되었음을 인식하고, 리렌더링을 수행하여 count 값을 올바르게 반영합니다.
useMemo
import { useMemo, useState } from "react";
import { repeatBarked, repeatMeow } from "./UseMemoTest.utils.ts";
export default function UseMemoTest() {
const [meowCount, setMeowCount] = useState(1);
const [barkedCount, setBarkedCount] = useState(1);
const meow = useMemo(() => repeatMeow(meowCount), [meowCount]);
const bark = useMemo(() => repeatBarked(barkedCount), [barkedCount]);
return (
<div>
<p data-testid="cat">고양이 "{meow}"</p>
<p data-testid="dog">강아지 "{bark}"</p>
<button data-testid="meow" onClick={() => setMeowCount(n => n + 1)}>야옹</button>
<button data-testid="bark" onClick={() => setBarkedCount(n => n + 1)}>멍멍</button>
</div>
);
}
UseMemoTest 컴포넌트가 리렌더링될 때마다 내부 함수인 meow와 bark가 호출되는 문제를 해결하기 위해, 내부 함수인 meow, bark가 필요할 때만 작동하도록 useMemo 훅을 사용했습니다.
useMemo는 첫 번째 인자로 함수를 받고, 두 번째 인자로 의존성 배열을 받습니다.
의존성 배열의 값이 변하지 않으면, useMemo는 이전에 계산된 값을 반환하여 불필요한 함수 호출을 방지합니다.
이렇게 함으로써, meowCount나 barkedCount가 변경될 때만 meow와 bark 함수가 재생성되어 불필요한 함수 호출을 줄일 수 있습니다.
useCallback
import { useCallback, useState } from "react";
import { BarkButton, MeowButton } from "./UseCallbackTest.components.tsx";
export default function UseCallbackTest() {
const [meowCount, setMeowCount] = useState(0);
const [barkedCount, setBarkedCount] = useState(0);
const handleMeow = useCallback(() => {
setMeowCount(n => n + 1);
}, [setMeowCount]);
const handleBark = useCallback(() => {
setBarkedCount(n => n + 1);
}, [setBarkedCount]);
return (
<div>
<p data-testid="cat">meowCount {meowCount}</p>
<p data-testid="dog">barkedCount {barkedCount}</p>
<MeowButton onClick={handleMeow}/>
<BarkButton onClick={handleBark}/>
</div>
);
}
UseCallbackTest 컴포넌트가 리렌더링될 때마다 handleMeow와 handleBark 함수가 매번 재생성됩니다.
그로 인해 이미 memo로 최적화된 MeowButton과 BarkButton 컴포넌트가
새로운 props를 받은 것으로 인지하고 자식 컴포넌트까지 불필요하게 리렌더링되는 문제가 발생합니다.
이 문제를 해결하기 위해,
handleMeow와 handleBark 함수에 각각 useCallback을 적용하였습니다.
useCallback은 첫 번째 인자로 함수를, 두 번째 인자로 의존성 배열을 받습니다.
의존성 배열에 있는 값들에 변경사항이 있을 때만 새로운 함수를 생성하고, 그렇지 않으면 이전에 생성된 함수를 반환합니다.
handleMeow와 handleBark 함수가 의존성 배열의 값들이 변경되지 않는 한 동일한 함수 참조를 유지하게 함으로써, MeowButton과 BarkButton 컴포넌트의 불필요한 리렌더링을 방지할 수 있습니다.
PureComponent (React.memo)
import { memo, useState } from "react";
import { Cat, Dog } from "./PureComponentTest.components.tsx";
const MemoizedCat = memo(Cat);
const MemoizedDog = memo(Dog);
// NOTE: 다른 파일은 수정하지 않고, 현재 파일만 수정하여 문제를 해결해주세요.
export default function PureComponentTest() {
const [meowCount, setMeowCount] = useState(1);
const [barkedCount, setBarkedCount] = useState(1);
return (
<div>
<MemoizedCat crying={meowCount}/>
<MemoizedDog crying={barkedCount}/>
<button data-testid="meow" onClick={() => setMeowCount(n => n + 1)}>야옹</button>
<button data-testid="bark" onClick={() => setBarkedCount(n => n + 1)}>멍멍</button>
</div>
);
}
기존 meow 또는 bark 버튼이 클릭되었을 때 onClick 핸들러가 실행되어 setState를 통해 부모 컴포넌트인 PureComponentTest의 상태가 변경되면 리액트는 리렌더링을 수행합니다. 이때, 자식 컴포넌트인 Cat과 Dog 역시 리렌더링이 됩니다.
PureComponentTest 컴포넌트가 리렌더링될 때마다 동일한 props를 받는 자식 컴포넌트도 리렌더링되는 문제를 해결하기 위해, 자식 컴포넌트인 Cat과 Dog에 memo를 적용하여 불필요한 리렌더링을 방지했습니다.
memo는 컴포넌트를 메모이제이션하여, props가 변경되지 않으면 이전에 렌더링된 결과를 재사용합니다.
RequireRefactoring
import { ComponentProps, memo, PropsWithChildren } from "react";
type Props = {
countRendering?: () => void;
}
const PureComponent = memo(({ children, countRendering, ...props }: PropsWithChildren<ComponentProps<'div'> & Props>) => {
countRendering?.();
return <div {...props}>{children}</div>
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let outerCount = 1;
const styleProps = { width: '100px', height: '100px' };
const onClickProps = () => {outerCount += 1;};
// useMemo, useCallback 등을 사용하지 않고 이 컴포넌트를 개선해보세요.
export default function RequireRefactoring({ countRendering }: Props) {
return (
<PureComponent
style={styleProps}
onClick={onClickProps}
countRendering={countRendering}
>
test component
</PureComponent>
);
}
RequireRefactoring 컴포넌트가 props로 받는 countRendering 함수가 불필요하게 여러 번 호출되는 문제를 해결하기 위해, 먼저 함수 호출 조건을 분석해보았습니다.
countRendering 함수 자체는 `TestComponent` 컴포넌트 외부에 선언되어 있기 때문에, 항상 같은 메모리 주소를 참조합니다.
`RequireRefactoring` 컴포넌트에서는 props로 전달받고, 자식 컴포넌트인 `PureComponent`로 다시 전달하는 역할만 합니다.
이때 countRendering 외에 style 객체와 onClick 함수를 추가로 전달합니다.
`PureComponent`는 memo로 컴포넌트 최적화가 되어있지만,
동일하지 않은 props가 들어왔다고 판단되면 컴포넌트가 리렌더링되고, 이때 countRendering 함수가 함께 호출됩니다.
이를 해결하기 위해, style 객체와 onClick 함수 역시 컴포넌트 외부에 선언하여 동일한 메모리 주소를 참조하게 함으로써 `PureComponent`의 리렌더링을 방지하고 불필요한 함수 호출을 막도록 했습니다.
useRef
import { useMemo } from "react";
export function useMyRef<T>(initValue: T | null) {
const refObj = useMemo(() => {return {current: initValue}},[]);
return refObj;
}
Set 객체는 원시값이나 객체 참조 값 등 모든 유형의 고유 값을 저장할 때 사용되며, 중복을 허용하지 않습니다.
`UseMyRefTest` 컴포넌트의 초기 실행 코드에서 useMyRef의 반환값으로 ref를 선언하고, 이후 전역에 저장된 Set 객체(refs)에 ref 객체를 저장하는 코드가 실행됩니다.
만약 `useMyRef`가 React의 useRef처럼 동작한다면 React 컴포넌트의 라이프사이클 동안(즉, 마운트부터 언마운트까지) 동일한 객체를 반환해야 합니다.
Set 객체에는 동일한 ref가 중복 저장되지 않아 최종적으로 두 개의 ref만 담겨야 하는데, 실제로는 총 네 개의 ref가 담기는 문제가 발생했습니다.
이 문제를 해결하기 위해, useMemo를 사용하여 useMyRef를 구현했습니다.
useMemo를 통해 useMyRef가 리렌더링 시에도 동일한 ref를 반환하도록 했고 Set 객체에는 고유한 ref만 저장되도록 했습니다.
1주차 과제 제출 - 김초원 by kimfield98 · Pull Request #43 · hanghae-plus/front_2nd
github.com
네트워킹 파티 후기
새삼 개발자가 진짜 많구나 .. 생각했다.
각자의 고민과 기대가 고스란히 느껴졌고, 벅차오를 정도였다!
처음엔 나를 알리고 나의 생각을 입 밖으로 꺼내는 것이 많이 부끄러웠다.
내가 외주 프로젝트에 참여하며 멋있다고 생각했던 스파르타 개발자 분들도 과정에 함께 참여하고 있었기 때문이다.
아 .. 난 얼마나 채워야 하지? 그래서 숨고 싶다는 생각이 조금 들었다.
팀원들과의 시간에서 이 생각이 많이 깨졌다.
나는 팀 활동에서 많은 장점을 가지고 있다. 분위기를 이끌고 기록을 남기는 것을 잘 한다.
또 하나 뿌듯했던 점은 코치님께서 vitest를 사용하시는데 팀원분이 테스트를 하나씩만 돌리는 방법을 찾으시길래
얼른 찾아서 방법을 공유드린 것 ✌🏻
함께 하다 보면 도움을 받기도, 주기도 한다. 그래서 함께 라는 것이 가치가 있는 것 같다.
네트워킹 파티에서는 부끄럽다는 생각이 완전히 깨진 것이,
술을 마셔서 인지(?) 앉아서도 이야기를 나누고 화장실 다녀오다 길에서 만난 코치님들께도 질문 드리고
많은 분들과 시간을 보내다보니 '그래 뭐가 부끄럽냐. 다 선배님들인데 많이 배워가자' 싶었다.
그들이 나에게 기대하는 건 함께 열심히 참여해줬으면 하는 태도이지,
'잘했으면 좋겠다. 잘 해서 다 나 알려줬으면 좋겠다' 이런 거 아니잖아?
ㅋㅋㅋ
즐길 때 최선을 다해서 즐겼으니 이제 다시 집중모드 👩🏻💻 달린다 ~~
2주차 후기가 궁금하다면?
[항해플러스 프론트엔드 2기] 2주차 후기 - 리액트 파헤치기
들어가며 안녕하세요 초원입니다. 👩🏻💻오늘은 발제 시간에 들었던 인상 깊었던 문장을 공유하며 시작해보려고 해요. 시간과 돈을 가장 덜 쓰게 만드는 것, 개발자가 해야 할 일. 충분히
kimfield.tistory.com
'항해플러스 프론트엔드 과정' 카테고리의 다른 글
[항해플러스 프론트엔드 2기] 중간점검, 솔직 후기! 과연 정말 실력이 늘었는지? (0) | 2024.07.22 |
---|---|
[항해플러스 프론트엔드 2기] 5주차 후기 - 디자인 패턴 (0) | 2024.07.22 |
[항해플러스 프론트엔드 2기] 4주차 후기 - 클린코드 (0) | 2024.07.16 |
[항해플러스 프론트엔드 2기] 3주차 후기 - 리액트 파헤치기 (0) | 2024.07.08 |
[항해플러스 프론트엔드 2기] 2주차 후기 - 리액트 파헤치기 (0) | 2024.07.08 |
시작하는 마음
안녕하세요 초원입니다. 👩🏻💻
작년 초부터 개발 공부를 시작해서 국비 교육, 부트캠프, 외주 프로젝트를 거치고
드디어 올해 6월부터 일을 하기 시작했어요! 👏🏻👏🏻
처음 시작할 땐 vscode의 파이썬 실행 버튼조차 찾지 못해 낙담하고,
유치원생처럼 하나 하나 물어가며 배웠었는데 ...
자신이 없어질 때쯤 그때를 떠올리면 '장족의 발전이네! 아주 잘 하고 있어. 계속, 즐겁게 개발하자'
하며 마음을 다잡게 되는 것 같아요 ✊🏻
그래서 이번엔 어떤 도전을 ?
이번엔 주니어 개발자를 위한 역량 강화 프로그램인 항해 플러스 프론트엔드 과정에 참여했습니다-
때때로 '무슨 교육을 그렇게 많이 듣냐?’ 라는 말을 듣곤 하지만 ..
평생을 인문학도로 살아온 제가 이공계 마인드를 온전히 가지려면
4년 대학 교육만큼은 더 받아도 되지 않을까 하는 생각이에요 헤헤
선배 개발자 분들이나 기업 차원에서 마련한 좋은 커리큘럼이 있다면
지출이 있더라도 적극적으로 참여하려는 편입니다.
세상엔 고민할 게 너무도 많은데, 전문가 또한 많으니까
도움 받을 수 있는 부분은 최대한 받고
저는 또 다른 전문가가 되어 다른 이들을 돕는 구조를 만드는거죠? ✌🏻
1주차 참여 후기

5개월 간 합숙하며 전산학 핵심 내용을 공부하는 과정인 정글사관학교를 수료할 때까지만 해도,
커뮤니티의 힘이 이렇게 큰지 몰랐습니다.
회사를 들어오고 다른 개발자 분들의 이야기를 들으니 함께 고민할 수 있는 동료가 있다는 게 참 든든하더라구요!
( 너무 당연하게 생각했던 .. 우리 정글러들 아주 소중해 🫶🏻 )
항해플러스가 이런 네트워킹을 통해 소통의 자리를 제공하고 지속적인 연결을 구축하는데
아주 탁월하다고 느꼈어요.
10주 간 최선을 다해 참여하면 수료 이후에도 도움을 주고 받을 수 있는
좋은 관계로 이어질 거라고 기대중입니다. 🤟🏻
휘핑크림 성장곡선
제가 방금 지어낸 정의입니다만? 🤷🏻♀️ 저는 휘핑크림 성장곡선을 추구하고 가지고 있다고 생각해요.
처음 한 바퀴 돌 땐 녹기도 하고, 쌓이지 않는 것 같지만
계속 해서 나아가면 '같은 길 아닌가 ..' 싶을 때 쯤 어느새 목표 지점까지 도달해 있는거죠! (와우)
저는 혼자 이런 거 생각하고 오 .. 좋은데? 하며 기분 좋아하는 사람이랍니다. 히히
최종 목표
목표를 향해 달리는 것은 별로 안 좋아하지만 .. 한 번 정해보자면
1. 과제 매주 제출하기 : 최선을 다해 고민할 것
2. 너무 욕심 부리다 퍼지지 않게 나를 잘 돌아보기 : 조급해 하지 않고, 한 가지를 깊게 학습하려고 노력할 것
제가 10주 간 해낼 것은 이 두 가지 입니다.
완주 해서 블랙 배지 받고 싶어요 🥋
그럼 이제 한 주 간 과제를 통해 어떤 문제를 해결했는지 소개드릴게요! 🌱
1주차 과제
본 게시글은 신입 개발자가 항해플러스 프론트엔드 2기 _리액트 파헤치기 과정을 거치며 고민한 내용을 담았습니다. 과제와 테스트 코드를 제공해주신 준일 코치님께 감사 인사 드립니다 🙇🏻♀️
총 3주 간의 리액트 과정에서 우리는 다음과 같은 목표를 가집니다.
1주차 - useState, useCallback, useMemo, PureComponent 등 리액트의 기본 개념을 확실하게 이해합니다. 또한 useRef를 직접 구현하면서 react의 lifecycle에 대해 이해해 봅니다.
2주차 - (작성중)
3주차 - (작성중)
목표를 달성하기 위한 방법은 다양하지만, 저의 경우 코드를 통해 학습하는 것을 좋아합니다.
문제 해결을 위한 고민과 과정, 그리고 구현을 위해 학습한 내용을 정리하며 과정을 복습해보려고 합니다.
useState
import { useState } from "react";
// NOTE: state의 값이 정상적으로 변경이 되도록 만들어주세요.
export default function UseStateTest() {
const [state, setState] = useState({ bar: { count: 1 } });
const increment = () => {
const newState = { ...state };
newState.bar.count += 1;
setState(newState);
}
return (
<div>
count: {state.bar.count}
<button onClick={increment}>증가</button>
</div>
);
}
증가 버튼을 눌러도 count 값이 증가하지 않는 문제를 해결하기 위해,
전개 연산자를 사용하여 기존 state를 깊은복사한 newState를 생성했습니다.
`const newState = { ...state };`
객체는 참조 타입이므로 불변성을 유지하지 않습니다.
즉, 기존 state에 변경 사항이 생기더라도 참조하는 메모리 주소는 바뀌지 않고 값만 변경됩니다.
이로 인해 리액트는 state에 변경이 없다고 판단하고 리렌더링을 수행하지 않습니다.
이 문제를 해결하기 위해 setState 함수에 newState를 전달했습니다.
`setState(newState);`
이를 통해 리액트는 새로운 객체가 생성되었음을 인식하고, 리렌더링을 수행하여 count 값을 올바르게 반영합니다.
useMemo
import { useMemo, useState } from "react";
import { repeatBarked, repeatMeow } from "./UseMemoTest.utils.ts";
export default function UseMemoTest() {
const [meowCount, setMeowCount] = useState(1);
const [barkedCount, setBarkedCount] = useState(1);
const meow = useMemo(() => repeatMeow(meowCount), [meowCount]);
const bark = useMemo(() => repeatBarked(barkedCount), [barkedCount]);
return (
<div>
<p data-testid="cat">고양이 "{meow}"</p>
<p data-testid="dog">강아지 "{bark}"</p>
<button data-testid="meow" onClick={() => setMeowCount(n => n + 1)}>야옹</button>
<button data-testid="bark" onClick={() => setBarkedCount(n => n + 1)}>멍멍</button>
</div>
);
}
UseMemoTest 컴포넌트가 리렌더링될 때마다 내부 함수인 meow와 bark가 호출되는 문제를 해결하기 위해, 내부 함수인 meow, bark가 필요할 때만 작동하도록 useMemo 훅을 사용했습니다.
useMemo는 첫 번째 인자로 함수를 받고, 두 번째 인자로 의존성 배열을 받습니다.
의존성 배열의 값이 변하지 않으면, useMemo는 이전에 계산된 값을 반환하여 불필요한 함수 호출을 방지합니다.
이렇게 함으로써, meowCount나 barkedCount가 변경될 때만 meow와 bark 함수가 재생성되어 불필요한 함수 호출을 줄일 수 있습니다.
useCallback
import { useCallback, useState } from "react";
import { BarkButton, MeowButton } from "./UseCallbackTest.components.tsx";
export default function UseCallbackTest() {
const [meowCount, setMeowCount] = useState(0);
const [barkedCount, setBarkedCount] = useState(0);
const handleMeow = useCallback(() => {
setMeowCount(n => n + 1);
}, [setMeowCount]);
const handleBark = useCallback(() => {
setBarkedCount(n => n + 1);
}, [setBarkedCount]);
return (
<div>
<p data-testid="cat">meowCount {meowCount}</p>
<p data-testid="dog">barkedCount {barkedCount}</p>
<MeowButton onClick={handleMeow}/>
<BarkButton onClick={handleBark}/>
</div>
);
}
UseCallbackTest 컴포넌트가 리렌더링될 때마다 handleMeow와 handleBark 함수가 매번 재생성됩니다.
그로 인해 이미 memo로 최적화된 MeowButton과 BarkButton 컴포넌트가
새로운 props를 받은 것으로 인지하고 자식 컴포넌트까지 불필요하게 리렌더링되는 문제가 발생합니다.
이 문제를 해결하기 위해,
handleMeow와 handleBark 함수에 각각 useCallback을 적용하였습니다.
useCallback은 첫 번째 인자로 함수를, 두 번째 인자로 의존성 배열을 받습니다.
의존성 배열에 있는 값들에 변경사항이 있을 때만 새로운 함수를 생성하고, 그렇지 않으면 이전에 생성된 함수를 반환합니다.
handleMeow와 handleBark 함수가 의존성 배열의 값들이 변경되지 않는 한 동일한 함수 참조를 유지하게 함으로써, MeowButton과 BarkButton 컴포넌트의 불필요한 리렌더링을 방지할 수 있습니다.
PureComponent (React.memo)
import { memo, useState } from "react";
import { Cat, Dog } from "./PureComponentTest.components.tsx";
const MemoizedCat = memo(Cat);
const MemoizedDog = memo(Dog);
// NOTE: 다른 파일은 수정하지 않고, 현재 파일만 수정하여 문제를 해결해주세요.
export default function PureComponentTest() {
const [meowCount, setMeowCount] = useState(1);
const [barkedCount, setBarkedCount] = useState(1);
return (
<div>
<MemoizedCat crying={meowCount}/>
<MemoizedDog crying={barkedCount}/>
<button data-testid="meow" onClick={() => setMeowCount(n => n + 1)}>야옹</button>
<button data-testid="bark" onClick={() => setBarkedCount(n => n + 1)}>멍멍</button>
</div>
);
}
기존 meow 또는 bark 버튼이 클릭되었을 때 onClick 핸들러가 실행되어 setState를 통해 부모 컴포넌트인 PureComponentTest의 상태가 변경되면 리액트는 리렌더링을 수행합니다. 이때, 자식 컴포넌트인 Cat과 Dog 역시 리렌더링이 됩니다.
PureComponentTest 컴포넌트가 리렌더링될 때마다 동일한 props를 받는 자식 컴포넌트도 리렌더링되는 문제를 해결하기 위해, 자식 컴포넌트인 Cat과 Dog에 memo를 적용하여 불필요한 리렌더링을 방지했습니다.
memo는 컴포넌트를 메모이제이션하여, props가 변경되지 않으면 이전에 렌더링된 결과를 재사용합니다.
RequireRefactoring
import { ComponentProps, memo, PropsWithChildren } from "react";
type Props = {
countRendering?: () => void;
}
const PureComponent = memo(({ children, countRendering, ...props }: PropsWithChildren<ComponentProps<'div'> & Props>) => {
countRendering?.();
return <div {...props}>{children}</div>
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let outerCount = 1;
const styleProps = { width: '100px', height: '100px' };
const onClickProps = () => {outerCount += 1;};
// useMemo, useCallback 등을 사용하지 않고 이 컴포넌트를 개선해보세요.
export default function RequireRefactoring({ countRendering }: Props) {
return (
<PureComponent
style={styleProps}
onClick={onClickProps}
countRendering={countRendering}
>
test component
</PureComponent>
);
}
RequireRefactoring 컴포넌트가 props로 받는 countRendering 함수가 불필요하게 여러 번 호출되는 문제를 해결하기 위해, 먼저 함수 호출 조건을 분석해보았습니다.
countRendering 함수 자체는 `TestComponent` 컴포넌트 외부에 선언되어 있기 때문에, 항상 같은 메모리 주소를 참조합니다.
`RequireRefactoring` 컴포넌트에서는 props로 전달받고, 자식 컴포넌트인 `PureComponent`로 다시 전달하는 역할만 합니다.
이때 countRendering 외에 style 객체와 onClick 함수를 추가로 전달합니다.
`PureComponent`는 memo로 컴포넌트 최적화가 되어있지만,
동일하지 않은 props가 들어왔다고 판단되면 컴포넌트가 리렌더링되고, 이때 countRendering 함수가 함께 호출됩니다.
이를 해결하기 위해, style 객체와 onClick 함수 역시 컴포넌트 외부에 선언하여 동일한 메모리 주소를 참조하게 함으로써 `PureComponent`의 리렌더링을 방지하고 불필요한 함수 호출을 막도록 했습니다.
useRef
import { useMemo } from "react";
export function useMyRef<T>(initValue: T | null) {
const refObj = useMemo(() => {return {current: initValue}},[]);
return refObj;
}
Set 객체는 원시값이나 객체 참조 값 등 모든 유형의 고유 값을 저장할 때 사용되며, 중복을 허용하지 않습니다.
`UseMyRefTest` 컴포넌트의 초기 실행 코드에서 useMyRef의 반환값으로 ref를 선언하고, 이후 전역에 저장된 Set 객체(refs)에 ref 객체를 저장하는 코드가 실행됩니다.
만약 `useMyRef`가 React의 useRef처럼 동작한다면 React 컴포넌트의 라이프사이클 동안(즉, 마운트부터 언마운트까지) 동일한 객체를 반환해야 합니다.
Set 객체에는 동일한 ref가 중복 저장되지 않아 최종적으로 두 개의 ref만 담겨야 하는데, 실제로는 총 네 개의 ref가 담기는 문제가 발생했습니다.
이 문제를 해결하기 위해, useMemo를 사용하여 useMyRef를 구현했습니다.
useMemo를 통해 useMyRef가 리렌더링 시에도 동일한 ref를 반환하도록 했고 Set 객체에는 고유한 ref만 저장되도록 했습니다.
1주차 과제 제출 - 김초원 by kimfield98 · Pull Request #43 · hanghae-plus/front_2nd
github.com
네트워킹 파티 후기
새삼 개발자가 진짜 많구나 .. 생각했다.
각자의 고민과 기대가 고스란히 느껴졌고, 벅차오를 정도였다!
처음엔 나를 알리고 나의 생각을 입 밖으로 꺼내는 것이 많이 부끄러웠다.
내가 외주 프로젝트에 참여하며 멋있다고 생각했던 스파르타 개발자 분들도 과정에 함께 참여하고 있었기 때문이다.
아 .. 난 얼마나 채워야 하지? 그래서 숨고 싶다는 생각이 조금 들었다.
팀원들과의 시간에서 이 생각이 많이 깨졌다.
나는 팀 활동에서 많은 장점을 가지고 있다. 분위기를 이끌고 기록을 남기는 것을 잘 한다.
또 하나 뿌듯했던 점은 코치님께서 vitest를 사용하시는데 팀원분이 테스트를 하나씩만 돌리는 방법을 찾으시길래
얼른 찾아서 방법을 공유드린 것 ✌🏻
함께 하다 보면 도움을 받기도, 주기도 한다. 그래서 함께 라는 것이 가치가 있는 것 같다.
네트워킹 파티에서는 부끄럽다는 생각이 완전히 깨진 것이,
술을 마셔서 인지(?) 앉아서도 이야기를 나누고 화장실 다녀오다 길에서 만난 코치님들께도 질문 드리고
많은 분들과 시간을 보내다보니 '그래 뭐가 부끄럽냐. 다 선배님들인데 많이 배워가자' 싶었다.
그들이 나에게 기대하는 건 함께 열심히 참여해줬으면 하는 태도이지,
'잘했으면 좋겠다. 잘 해서 다 나 알려줬으면 좋겠다' 이런 거 아니잖아?
ㅋㅋㅋ
즐길 때 최선을 다해서 즐겼으니 이제 다시 집중모드 👩🏻💻 달린다 ~~
2주차 후기가 궁금하다면?
[항해플러스 프론트엔드 2기] 2주차 후기 - 리액트 파헤치기
들어가며 안녕하세요 초원입니다. 👩🏻💻오늘은 발제 시간에 들었던 인상 깊었던 문장을 공유하며 시작해보려고 해요. 시간과 돈을 가장 덜 쓰게 만드는 것, 개발자가 해야 할 일. 충분히
kimfield.tistory.com
'항해플러스 프론트엔드 과정' 카테고리의 다른 글
[항해플러스 프론트엔드 2기] 중간점검, 솔직 후기! 과연 정말 실력이 늘었는지? (0) | 2024.07.22 |
---|---|
[항해플러스 프론트엔드 2기] 5주차 후기 - 디자인 패턴 (0) | 2024.07.22 |
[항해플러스 프론트엔드 2기] 4주차 후기 - 클린코드 (0) | 2024.07.16 |
[항해플러스 프론트엔드 2기] 3주차 후기 - 리액트 파헤치기 (0) | 2024.07.08 |
[항해플러스 프론트엔드 2기] 2주차 후기 - 리액트 파헤치기 (0) | 2024.07.08 |