Skip to content

[22기] 허소영 - 3주차 과제 #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
208 changes: 206 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,206 @@
# study-frontend
프론트엔드 스터디를 위한 레포지토리입니다.
## 컴포넌트를 설계할 때 명확한 기준이 있다면 조금 더 수월한 설계가 가능합니다. 컴포넌트를 설계하고 나누는 기준에는 어떤 것들이 있을까요?

1. 단일 책임 원칙
- 각 컴포넌트는 하나의 명확한 역할(UI조각, 기능)만 수행해야 한다
2. 재사용 가능성
- 다양한 상황에서 재사용할 수 있도록, 인풋을 매개변수로 받아서 컴포넌트의 동작을 제어하도록 설계한다
3. 상위와 하위 컴포넌트 관계 (Parent-Child)
- 계층적으로 설계하여 부모 컴포넌트는 상태를 관리하고, 자식 컴포넌트는 해당 상태를 받아 UI를 렌더링하도록 한다 → 상태가 어디서 관리되고 있는지 명확하게 하여 디버깅이나 수정이 용이하도록
4. 컴포넌트 크기
5. UI와 로직 분리
6. 컨테이너 컴포넌트와 프레젠테이셔널 컴포넌트
- **컨테이너 컴포넌트**는 상태를 관리하고, **프레젠테이셔널 컴포넌트**는 상태를 받아서 UI를 렌더링합니다. → UI와 로직 명확 분리

## `대부분에 상황에서는 함수 컴포넌트를 사용`한다고 언급했습니다. 클래스 컴포넌트를 지양하게 된 이유에는 어떤 것들이 있을까요?

1. 복잡한 코드 구조
- `class` 키워드를 사용하여 정의하며, 함수형 컴포넌트에 비해 코드 구조가 더 복잡하다
- 생명주기 메서드, `render` 함수, `state` 관리 등 다양한 부분을 직접 처리해야 한다
2. 코드 재사용성 저하
- 클래스 내부에서 로직을 처리할 때, 다른 컴포넌트에서 그 로직을 재사용하려면 클래스를 상속하거나, 메서드를 복사해야 할 수 있습니다
- 반면, 함수 컴포넌트는 hook을 통해 로직을 재사용할 수 있어 더 유연하고 간편하다
3. this 바인딩 문제
- 클래스 컴포넌트에서는 메서드 내부에서 `this`를 사용할 때, `this`가 예상과 다르게 동작할 수 있다
- 이를 해결하기 위해, 메서드를 `constructor`에서 바인딩하거나, 화살표 함수를 사용하여 `this`를 명시적으로 바인딩해야 한다. 이 과정은 코드가 복잡하고 실수할 여지가 많다
4. 생명주기 메서드 관리 어려움
- 여러 라이프사이클 메서드(`componentDidMount`, `componentDidUpdate`, `componentWillUnmount` 등)를 사용하여 컴포넌트의 생명주기를 관리해야 한다
- 이 메서드들을 적절히 활용하려면 코드가 길어지고, 관리가 복잡해질 수 있다

## 클래스 컴포넌트를 반드시 사용해야 하는 경우는 언제일까요?

1. 구형 코드베이스와 호환
2. 상속 통한 컴포넌트 확장
- 상속을 통해 기능을 확장 할 수 있는데, 함수 컴포넌트는 상속을 지원하지 않기 때문에, 상속이 필요한 경우 클래스 컴포넌트를 사용해야 한다
3. 고급 라이프사이클 메서드 활용

## 컴포넌트 간에는 부모-자식 관계가 존재할 수 있습니다. 어떨 때 부모-자식 관계가 성립하는 걸까요?

부모-자식 컴포넌트 관계는 **컴포넌트 간 데이터 흐름과 역할**에 따라 성립한다. 자식 컴포넌트는 부모로부터 데이터를 전달받거나, 부모 컴포넌트에게 특정 데이터를 전달하는 관계이다

1. 부모 컴포넌트가 자식 컴포넌트를 포함 시

```jsx
// 부모 컴포넌트
const Parent = () => {
return <Child />;
};

// 자식 컴포넌트
const Child = () => {
return <div>Hello from Child!</div>;
};
```

2. 부모 컴포넌트가 자식 컴포넌트에 `props`를 전달 시
- 부모는 `props`를 통해 데이터를 전달
- 자식은 `props`를 사용하여 UI를 렌더링하거나, 부모로부터 받은 데이터를 기반으로 동작

```jsx
// 부모 컴포넌트
const Parent = () => {
return <Child name="John" />;
};

// 자식 컴포넌트
const Child = ({ name }) => {
return <div>Hello, {name}!</div>;
};
```

3. 자식 컴포넌트가 부모 컴포넌트에 이벤트를 전달 시
- 자식은 이벤트(이벤트 핸들러)를 부모 컴포넌트에 전달
- 부모는 그 이벤트를 처리(이벤트 핸들러 실행)

```jsx
// 부모 컴포넌트
const Parent = () => {
const handleClick = () => {
alert('Button clicked!');
};
return <Child onButtonClick={handleClick} />;
};

// 자식 컴포넌트
const Child = ({ onButtonClick }) => {
return <button onClick={onButtonClick}>Click Me</button>;
};
```

4. 상태 관리에서, 부모 컴포넌트가 상태를 관리하고 자식 컴포넌트가 이를 참조하는 경우
- 부모는 상태를 관리, `props`로 전달
- 자식은 그 상태를 사용

```jsx
// 부모 컴포넌트
const Parent = () => {
const [count, setCount] = useState(0);

return (
<div>
<Child count={count} />
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};

// 자식 컴포넌트
const Child = ({ count }) => {
return <div>Current count: {count}</div>;
};
```

5. 부모 컴포넌트가 자식 컴포넌트에게 콜백 함수를 전달하는 경우
- 부모는 **콜백 함수**를 `props`로 전달하여, 자식이 특정 행동을 실행할 때 부모에게 알린

```jsx
// 부모 컴포넌트
const Parent = () => {
const handleAction = (data) => {
console.log("Received from child:", data);
};

return <Child onAction={handleAction} />;
};

// 자식 컴포넌트
const Child = ({ onAction }) => {
return <button onClick={() => onAction('Some data')}>Send Data to Parent</button>;
};

```


## 배열을 렌더링할 때, 각 컴포넌트에 key값을 만들어 전달해줘야 합니다. key는 왜 필요한 걸까요? key값을 정하는 기준은 무엇인가요?

key값은 각 항목을 고유하게 식별할 수 있는 값을 말한다

- key가 필요한 이유
1. 리액트 효율적 랜더링
- 배열의 아이템이 변경될 때 리액트는 각 아이템을 식별할 수 있는 고유한 값을 필요로 한다
- key 값은 **리액트가 배열 아이템을 추적**하고, 배열의 **변경 사항을 빠르게 파악**하여 최소한의 DOM 변경만을 수행하게 해준다
2. 배열의 항목 재정렬, 추가, 삭제 시 성능 최적화
- key 값이 없으면, 리액트는 배열을 렌더링할 때 모든 항목을 새로 렌더링하려고 시도할 수 있다
- 하지만 key 값이 있으면 리액트는 각 항목을 비교하여 최소한의 변경만을 수행할 수 있다
- key값 정하는 기준
- 항목이 고유한 ID를 갖는 경우
- 그 ID를 key 로 사용

```jsx
const items = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' }
];

const List = () => {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
};
```

- 항목에 고유한 ID가 없고, 배열이 고정되어 있고 순서가 중요한 경우
- index를 key로 사용
- 배열이 동적으로 변경될 가능성이 있는 경우, 리액트는 최적화된 방식으로 렌더링을 하지 못하고 모든 항목을 다시 렌더링할 수 있어 성능 저하

```jsx
const items = ['Apple', 'Banana', 'Cherry'];

const List = () => {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
};
```


## 조건부 렌더링이란 무엇일까요? 어떨 때 사용할 수 있을까요?

- 조건부 렌더링
- **특정 조건에 따라 UI를 다르게 렌더링**하는 기법
- 리액트에서는 **조건문**(`if`, `ternary operator`, `&&`)을 사용하여 어떤 요소를 렌더링할지 결정할 수 있다
- 즉, 조건이 맞을 때 만 UI의 일부가 표시되거나 변경된다
- 조건부 렌더링을 사용할 수 있는 경우
1. **사용자 인증 여부**에 따라, 화면을 다르게 보여야 할 때
2. 버튼, 링크 등을 조건에 맞게 표시하거나 숨기고 싶을 때
3. **사용자가 선택**한 옵션에 따라 콘텐츠를 다르게 렌더링할 때
4. **특정 조건에서 에러**를 표시해야 할 때
5. **데이터 로딩 중**인 상태 표시 - 데이터 로딩 중에 로딩 스피너나 메시지를 표시하고, 데이터가 로드된 후 실제 데이터를 보여주는 경우

```jsx
const MyComponent = ({ isLoggedIn }) => {
if (isLoggedIn) {
return <div>Welcome!</div>;
} else {
return <div>Please log in.</div>;
}
};
```
33 changes: 33 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'

export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Loading