diff --git a/frontend/lecture/src/components/Callout/index.tsx b/frontend/lecture/src/components/Callout/index.tsx new file mode 100644 index 0000000..7e6d2d5 --- /dev/null +++ b/frontend/lecture/src/components/Callout/index.tsx @@ -0,0 +1,10 @@ +import { PropsWithChildren } from 'react'; + +export const Callout = (props: PropsWithChildren<{ title: string }>) => { + return ( +
+

{props.title}

+ {props.children} +
+ ); +}; diff --git a/frontend/lecture/src/lectures/React/lecture.tsx b/frontend/lecture/src/lectures/React/lecture.tsx deleted file mode 100644 index 1ac1099..0000000 --- a/frontend/lecture/src/lectures/React/lecture.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { CodeSnippet } from '@/components/CodeSnippet'; -import { AssetDescriptionLayout } from '@/components/SlideLayout'; -import { Slides } from '@/components/Slides'; -import { getLectureItem } from '@/lectures'; - -export const reactLecture = getLectureItem({ - title: '리액트', - description: 'JSX, state, props, hooks, context, 합성', - date: new Date('2024-09-11'), - element: ( - -

리액트 없이

- {`, - ` const countElement = document.getElementById('count');`, - ` const currentCount = parseInt(countElement.innerText, 10);`, - ` countElement.innerText = (currentCount + 1).toString();`, - ` });`, - ]} - /> - `, - ` `, - `
0
`, - ``, - ]} - /> -

리액트로

- `, - ` `, - `
{count}
`, - ` `, - `);`, - ]} - /> - - } - /> - ), - }, - ]} - /> - ), -}); diff --git a/frontend/lecture/src/lectures/ReactApis/lecture.tsx b/frontend/lecture/src/lectures/ReactApis/lecture.tsx new file mode 100644 index 0000000..b7db4a1 --- /dev/null +++ b/frontend/lecture/src/lectures/ReactApis/lecture.tsx @@ -0,0 +1,22 @@ +import { Slides } from '@/components/Slides'; +import { getLectureItem } from '@/lectures'; + +export const reactApisLecture = getLectureItem({ + title: '리액트 기능들', + description: + 'Context API, useState, useEffect, useMemo, memo, useCallback, Custom Hooks', + date: new Date('2024-09-11'), + element: ( + }, + { title: 'hooks: useState', content:
}, + { title: 'hooks: useEffect', content:
}, + { title: 'hooks: useMemo', content:
}, + { title: 'memo', content:
}, + { title: 'hooks: useCallback', content:
}, + { title: 'Custom Hooks', content:
}, + ]} + /> + ), +}); diff --git a/frontend/lecture/src/lectures/ReactBasic/lecture.tsx b/frontend/lecture/src/lectures/ReactBasic/lecture.tsx new file mode 100644 index 0000000..b69bdaa --- /dev/null +++ b/frontend/lecture/src/lectures/ReactBasic/lecture.tsx @@ -0,0 +1,589 @@ +import { ArrowRightIcon } from '@radix-ui/react-icons'; +import { useReducer } from 'react'; + +import { Callout } from '@/components/Callout'; +import { CodeSnippet } from '@/components/CodeSnippet'; +import { ExternalLink } from '@/components/ExternalLink'; +import { InlineCode } from '@/components/InlineCode'; +import { AssetDescriptionLayout } from '@/components/SlideLayout'; +import { Slides } from '@/components/Slides'; +import { Button } from '@/designsystem/ui/button'; +import { Separator } from '@/designsystem/ui/separator'; +import { getLectureItem } from '@/lectures'; + +const IncrementButton = () => { + const [count, increment] = useReducer((c: number) => c + 1, 0); + + return ( + + ); +}; + +export const reactBasicLecture = getLectureItem({ + title: '리액트 기본', + description: 'JSX, state, props, hooks, Virtual DOM', + date: new Date('2024-09-11'), + element: ( + +

+ 브라우저는 이미 나{' '} + 같은 DOM API를 제공 +

+

+ 그런데, 얘들만으로 웹 어플리케이션을 만들기에는 무리가 있습니다. +

+

EX) 투두 리스트

+ {`, + ` const newTodo = document.createElement('li');`, + ` newTodo.textContent = todoInput.value;`, + ` `, + ` const deleteButton = document.createElement('button');`, + ` deleteButton.textContent = 'Delete';`, + ` deleteButton.addEventListener('click', () => {`, + ` todoList.removeChild(newTodo);`, + ` });`, + ` `, + ` newTodo.appendChild(deleteButton);`, + ` todoList.appendChild(newTodo);`, + ` todoInput.value = '';`, + `});`, + ]} + /> +

+ 상태가 복잡해지면 복잡해질수록 이런 코드는 유지보수하기 어렵다 +

+ + ), + }, + { + title: 'jQuery도 마찬가지', + content: ( +
+

2006년에 등장한 jQuery를 사용하면 조금 더 간단해진다:

+ {`, + ` const todoText = $('#todo-input').val();`, + ` const $newTodo = $('
  • ').text(todoText);`, + ` const $deleteButton = $('
  • + ), + }, + { + title: '프론트엔드 프레임워크의 등장', + content: ( +
    +

    + 복잡한 웹 애플리케이션 개발의 필요성이 증가하면서 프론트엔드가 + 분야로서 분리되고 다양한 프레임워크가 등장 +

    +
    +
    + Angular + Angular +
    +
    + Vue + Vue +
    +
    + React + React +
    +
    + Svelte + Svelte +
    +
    +

    + 이들 모두 컴포넌트 기반의 선언적인 방식으로 UI를 구성할 수 있게 + 해줌 +

    +

    그 중에서도 리액트가 압도적으로 인기가 많다

    +
    + ), + }, + { + title: 'React를 씁시다', + content: ( +
    +

    + 최근 트렌디한 프론트엔드 개발을 하는 곳이라면 대부분 React + 기반의 프레임워크를 사용합니다 +

    +
    +
    + Next.js + Next.js +
    +
    + Remix + Remix +
    +
    + Vite + + Vite (우리 세미나에서 쓰는 거!) + +
    +
    +

    + 하지만 리액트도 하나의 트렌드일 뿐, 언제든 저물 수 있다 +
    + 언제나 프론트엔드의 근본은 HTML, CSS, JavaScript +
    + 세미나 이름을 React {' '} + Frontend 로 변경한 것도 이 이유 +

    +
    + ), + }, + { + title: 'React란?', + content: ( + `, + ` `, + `
    {count}
    `, + ` `, + `);`, + ]} + /> + } + /> + ), + }, + { + title: '컴포넌트', + content: ( +
    +

    리액트의 기본 구성 요소

    +

    독립적이고 재사용 가능한 UI 조각

    +

    + 리액트 말고 다른 프레임워크들도, 대부분 컴포넌트 기반으로 + 개발됩니다 +

    + {`, + ` const [count, increment] = useReducer((c: number) => c + 1, 0);`, + ``, + ` return ;`, + `};`, + ]} + /> +
    + + + +
    +
    + ), + }, + { + title: 'state', + content: ( + + 변경하려면 반드시 를 통해야 한다 + , + ]} + asset={ +
    + {`, + ` const [count, setCount] = useState(0);`, + ` return (`, + `
    `, + `

    You clicked {count} times

    `, + ` `, + `
    `, + ` );`, + `};`, + ]} + /> + {`, + ` const [count, setCount] = useState(0);`, + ` return (`, + `
    `, + `

    You clicked {count} times

    `, + ` `, + `
    `, + ` );`, + `};`, + ]} + /> +
    + } + /> + ), + }, + { + title: '상태에 대한 오해와 상태를 설계하는 법', + content: ( +
    +

    + 상태 === 값을 저장하는 곳이라고 생각하기 쉬운데, React 의 상태는 + 그렇게 간단한 개념이 아닙니다 +

    +

    + 리액트 개발 경험을 기반으로, 정말 이 컴포넌트의{' '} + 상태가 무엇이어야 할지 진지하게 고민해야 합니다 +

    +

    + 그러면 자연스럽게 컴포넌트는 버그가 없어지고 재사용하기 + 좋아집니다 +

    + +
    + ), + }, + { + title: 'JSX', + content: ( +
    +

    리액트 코드를 보면 이렇게 HTML같이 생긴 코드가 있음

    + Hello, world!
    ;`, + ]} + /> + +
    + 하지만 묘하게 다른데, 가령 +
      +
    • + 대신{' '} + 사용 +
    • +
    • + 대신 {' '} + 사용 +
    • +
    • + 에 객체를 넣음 +
    • +
    +
    + +

    위 코드는 빌드 시 아래와 같이 변환됩니다:

    + + +

    JSX는 HTML과 비슷한 개발 경험을 주기 위해 고안된 문법

    +

    따라서 그냥 JavaScript 값일 뿐

    +

    + 즉 아래와 같이 변수에 할당할 수 있고, 함수에 인자로 넘길 수 + 있고, 반환값으로 사용할 수 있음 +

    + Hello;`, + `return
    {element}
    ;`, + ]} + /> + {'{ }'}는 JSX에서 JavaScript 표현식을 사용할 때 사용 + + ), + }, + { + title: 'props', + content: ( +
    +

    + 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방법. 마치 + html에 속성 주듯이 +

    + {`, + ` return

    Hello, {props.name}

    ;`, + `};`, + ``, + `const App = () => {`, + ` const [name, setName] = useState('Sara');`, + ` return ;`, + `};`, + ]} + /> +

    수정을 위해서는 onChange 콜백을 넘긴다

    + void }) => {`, + ` return (`, + `
    `, + `

    Hello, {name}

    `, + ` onChange(e.target.value)}`, + ` />`, + `
    `, + ` );`, + `};`, + ``, + `const App = () => {`, + ` const [name, setName] = useState('Sara');`, + ` return ;`, + `};`, + ]} + /> +
    + ), + }, + { + title: 'render', + content: ( +
    +

    + 좁은 의미에서, 컴포넌트라는 함수가 실행되어 JSX를 반환하는 것 +

    + {`, + ` const greeting = "Hello";`, + ` const currentTime = new Date().toLocaleTimeString();`, + ` const randomNumber = Math.floor(Math.random() * 100);`, + ` console.log("Rendering Welcome component");`, + ` return (`, + `
    `, + `

    {greeting}, {props.name}

    `, + `

    Current time: {currentTime}

    `, + `

    Random number: {randomNumber}

    `, + `
    `, + ` );`, + `};`, + ]} + /> +

    리렌더링이 발생하는 경우:

    +
      +
    • 상태(state)가 변경될 때
    • +
    • 부모 컴포넌트가 리렌더링될 때
    • +
    • context로 구독한 값이 변경될 때
    • +
    +
    +

    주의:

    +

    + 리렌더링이 발생한다고 해서 항상 DOM이 업데이트되는 것은 아님 +

    +

    + 리액트는 Virtual DOM을 사용하여 실제로 변경된 부분만 실제 + DOM에 반영 +

    +
    +
    + ), + }, + { + title: 'hooks', + content: ( +
    +

    + 함수 컴포넌트에서 상태와 생명주기 기능을 사용할 수 있게 해주는 + 기능 +

    +

    + 리액트가 제공하는 대표적인 훅: +

    +

    + Custom Hook도 만들 수 있다. 이 경우 상태 업데이트 로직을 + 재사용할 수 있음 +

    + void) => {`, + ` const ref = useRef(null);`, + ``, + ` useEffect(() => {`, + ` const handleClickOutside = (event: MouseEvent) => {`, + ` if (ref.current && !ref.current.contains(event.target as Node)) {`, + ` callback();`, + ` }`, + ` };`, + ``, + ` document.addEventListener('mousedown', handleClickOutside);`, + ` return () => {`, + ` document.removeEventListener('mousedown', handleClickOutside);`, + ` };`, + ` }, [callback]);`, + ``, + ` return ref;`, + `};`, + ]} + /> +

    Rules of Hooks

    + +
      +
    • 최상위에서만 호출
    • +
    • 함수 컴포넌트 내에서만 호출
    • +
    • 조건문, 반복문, 중첩 함수 내에서 호출하지 않음
    • +
    • + 이름이 로 시작 +
    • +
    +
    + ), + }, + { + title: '오랜만에 이론적인 이야기: Virtual DOM', + content: ( +
    +

    + 앞서, 리액트는{' '} + 상태가 변경되면 모두 날리고 다시 그린다고 했음 +

    +

    + 그러면 리액트를 만든 사람 입장에서, 최상단 컴포넌트의 상태가 + 바뀌면 모든 element 를 날리고 다시 만들어야 하나? +

    + +

    이런 식으로 하면 성능이 매우매우 나쁠 것이다

    + +

    + DOM API는 실제 브라우저의 렌더링 엔진과 직접 상호작용하기 + 때문에 상대적으로 느린 작업 +

    +

    따라서 DOM API를 너무 자주 많이 호출하면 매우 느리다

    +
    + +

    그래서 리액트는 Virtual DOM이라는 개념을 도입

    +
      +
    • 실제 DOM의 가벼운 복사본
    • +
    • 메모리에만 존재하므로 실제 DOM보다 빠르게 조작 가능
    • +
    • 변경사항을 일괄 처리하여 실제 DOM에 한 번에 적용
    • +
    + +

    흔한 오해: Virtual DOM을 이용하면 성능이 높다

    +

    + React 가 워낙 무식하게 다 부수려고 하니 Virtual DOM을 통해 겨우 + 최적화해 둔 것이지, +
    + Virtual DOM을 쓰면 성능이 높아진다는 것이 아님 +

    +

    + 당연히 Virtual DOM은 JavaScript를 한 번 더 거치는 것이기 때문에, + 더 느리면 느렸지 더 빠를 수가 없다 +

    + + +
    + ), + }, + ]} + /> + ), +}); diff --git a/frontend/lecture/src/lectures/WebBasic/lecture.tsx b/frontend/lecture/src/lectures/WebBasic/lecture.tsx index 43050ba..4e5488b 100644 --- a/frontend/lecture/src/lectures/WebBasic/lecture.tsx +++ b/frontend/lecture/src/lectures/WebBasic/lecture.tsx @@ -1,4 +1,5 @@ import { CodeSnippet } from '@/components/CodeSnippet'; +import { ExternalLink } from '@/components/ExternalLink'; import { InlineCode } from '@/components/InlineCode'; import { SlideContent } from '@/components/SlideContent'; import { AssetDescriptionLayout } from '@/components/SlideLayout'; @@ -82,11 +83,25 @@ export const webBasicLecture = getLectureItem({ { title: '역사에서 알 수 있는 포인트들', content: ( -
      -
    • 웹은 처음에 문서를 보는 도구로 만들어졌다
    • -
    • 표준 없이 꽤 오랜 기간을 지나왔다
    • -
    • 계속해서 새로운 기술이 등장하고 발전하고 있다
    • -
    + + 웹은 처음에 문서를 보는 도구로 만들어졌다{' '} + + , + '표준 없이 브라우저간 경쟁 상태에서 꽤 오랜 기간을 지나왔다', + '계속해서 새로운 기술이 등장하고 발전하고 있다', + ]} + asset={ + + } + /> ), }, { @@ -136,14 +151,14 @@ export const webBasicLecture = getLectureItem({ { title: 'HTML이란?', content: ( -
    -

    Hyper Text Markup Language

    -
    -
      -
    • 웹 문서의 구조를 정의하는 언어
    • -
    • 태그를 사용하여 문서의 구조를 정의
    • -
    • 태그는 속성을 가질 수 있음
    • -
    + ', @@ -159,8 +174,8 @@ export const webBasicLecture = getLectureItem({ ]} language="html" /> -
    -
    + } + /> ), }, { @@ -250,13 +265,13 @@ export const webBasicLecture = getLectureItem({ { title: 'CSS란?', content: ( -
    -

    Cascading Style Sheets

    -
    -
      -
    • 웹 문서의 스타일을 정의하는 언어
    • -
    • 선택자와 속성을 통해 문서의 구조를 정의
    • -
    + -
    -
    + } + /> ), }, { @@ -394,7 +409,7 @@ export const webBasicLecture = getLectureItem({ title: 'JavaScript란?', content: (
    -

    JavaScript (이름 잘못 지음)

    +

    [오피셜] 이름 잘못 지음

    • 웹 문서의 동적인 기능을 담당하는 언어
    • @@ -418,22 +433,14 @@ export const webBasicLecture = getLectureItem({ content: (
      -

      - 우리는 TypeScript로 시작할 거고, 그것도 React 에서 자주 쓰는 - TypeScript 문법에 익숙해져야 하는 것이기에 JavaScript를 너무 - 깊게 보진 않겠습니다 -

      +

      JavaScript 문법은 대부분 안다고 가정합니다.

      이미 JavaScript 를 할 줄 아시는 분들을 위해 조금 스포하자면,
      이나 , - , 등 은 거의/아예 사용하지 않습니다 -

      -

      - 물론 JavaScript를 잘 하는 것은 프론트엔드 개발자에게 너무나도 - 중요하고 기본적인 소양입니다. 세미나 시간이 너무 짧아서 - 이번에만 포기했을 뿐 + , , 등 + 은 거의/아예 사용하지 않습니다

      @@ -472,7 +479,7 @@ export const webBasicLecture = getLectureItem({ 'JavaScript 에 정적 타입 검사 기능을 더한 Superset 언어', '컴파일(트랜스파일) 시 JavaScript로 변환됨', 'JavaScript vs TypeScript 에서 이제는 TypeScript가 압승', - '오히려 처음 배우는 입장에서도, 뭘 할 수 있고 하면 안 되는지 더 빨리 알 수 있는 TypeScript가 더 유리', + '오히려 처음 배우는 입장에서도, 뭘 할 수 있고 하면 안 되는지 더 빨리 알 수 있는 TypeScript가 더 쉬운 언어이다', ]} asset={ a + b;`, + ``, + `// 물론 얘도 나중 가면 이런 복잡한 타입을 쓸 수 있게 되지만, 세미나 수준에서는 이런 건 몰라도 됩니다`, + `type ComplexType = T extends Array`, + ` ? U extends { [K in keyof U]: U[K] extends (...args: any[]) => any ? U[K] : never }`, + ` ? { [K in keyof U]: ReturnType }`, + ` : never`, + ` : never;`, ]} /> } @@ -490,7 +504,12 @@ export const webBasicLecture = getLectureItem({ title: 'TypeScript와 JavaScript의 차이', content: (