이벤트란 웹 페이지에서 일어나는 사용자의 행위로, 버튼 클릭, 페이지 스크롤, 새로고침 등이 있다. 예를 들면, 사용자가 버튼을 클릭한 경우 버튼 클릭 이벤트, 텍스트를 입력하면 텍스트 변경 이벤트가 발생했다고 표현한다.
[ 이벤트 핸들링과 이벤트 핸들러 ]
이벤트 핸들링은 이벤트가 발생하면 특정 코드가 동작하도록 만드는 작업을 말한다. 다음 코드는 리액트를 사용하지 않고 HTML과 자바스크립트만트로 이벤트를 핸들링한 예시이다.
<script>
function handleOnClick() {
alert("button clicked!");
}
</script>
<button onclick="handleOnClick()">
Click Me!
</button>
이 코드는 페이지에 버튼을 클릭하는 이벤트가 발생하면 handleOnClick 함수가 실행되어 "button clicked!"라는 경고 대화상자를 페이지에 표시한다. 이때, 함수 handleOnClick을 이벤트를 처리하는 함수라는 의미에서 '이벤트 핸들러'라고 한다.
[ 리액트의 이벤트 핸들링 ]
다음은 리액트에서의 이벤트 핸들링의 예시이다.
function Body() {
function handleOnClick() {
alert("버튼을 클릭하셨군요!");
}
return (
<div className="body">
<button onClick={handleOnClick}>클릭하세요</button>
</div>
);
}
export default Body;
scr/component/Body.js
위 코드는 Body 컴포넌트에서 생성된 버튼을 클릭하면 클릭 이벤트를 처리하는 이벤트 핸들러 함수 handleOnClick을 호출한다.
리액트의 이벤트 핸들링은 HTML의 이벤트 핸들링과 비슷해 보이지만, 몇 가지 차이점이 있다.
[ 이벤트 객체 사용하기 ]
리액트에서는 이벤트가 발생하면 이벤트 핸들러에게 이벤트 객체를 매개변수로 전달한다. 이벤트 객체는 이벤트가 어떤 요소에서 어떻게 발생했는지에 관한 정보를 가지고 있다. 다음은 Body 컴포넌트에 버튼 2개를 만들고, 이벤트가 발생하면 클릭한 버튼의 이름을 콘솔에 출력하도록 하는 코드이다.
function Body() {
function handleOnClick(e) {
console.log(e);
console.log(e.target.name);
}
return (
<div className="body">
<button name="A버튼" onClick={handleOnClick}>
A 버튼
</button>
<button name="B버튼" onClick={handleOnClick}>
B 버튼
</button>
</div>
);
}
export default Body;
src/component/Body.js
버튼 이벤트 핸들러 함수 handleOnClick은 이벤트 객체(e)를 매개변수에 저장하고, 이벤트 객체 e와 해당 객체의 target.name 프로퍼티 값을 콘솔에 출력한다. 또한 버튼 A와 B를 생성하고 각 버튼의 속성을 'A버튼', 'B버튼'으로 지정한 후 클릭 이벤트 핸들러로 handleOnClick을 설정한다. 이렇게 되면 A버튼을 클릭할 경우 e.target에는 A버튼이 저장되고, B버튼을 클릭할 경우 e.target에는 B버튼이 저장된다. 따라서 e.target.name을 콘솔에 출력하면 현재 이벤트가 발생한 요소의 name 속성값을 출력한다.
다음은 페이지에서 A버튼과 B버튼을 번갈아 한 번씩 출력한 결과이다.
버튼을 클릭하면 클릭한 버튼의 이름이 콘솔에 출력되는 것을 볼 수 있다. 이때, 작은 삼각형 버튼을 누르면 이벤트 객체에 관한 프로퍼티들이 출력되는데, 아주 복잡한 이벤트 처리가 아니면 대체로 1~2개의 값만 활용하게 된다.
지금까지는 값이 변하지 않는 정적인 리액트 컴포넌트를 만들었다면, 이제부터는 사용자의 행위나 시간 변동에 따라 값이 변하는 동적인 리액트 컴포넌트를 만들어 볼 차례이다. 이를 위해 리액트의 핵심 기능 중 하나인 State에 대한 이해가 필요하다.
[ State 이해하기 ]
State는 상태라는 뜻으로, 전구와 스위치에 빗대어 생각하면 쉽게 이해할 수 있다. 전구의 상태는 소등과 점등으로 나눌 수 있으며, 소등 상태일 때 스위치를 켜면 '점등'으로 상태 변화가 일어난다. 반대로 점등 상태일 떄 스위치를 끄면 '소등' 으로 상태 변화가 일어난다. 이를 상태가 아닌 State라는 표현을 통해 변경하면 다음과 같다.
위 예시와 같이 상태 변화에 따라 전구의 상태가 점등 또는 소등으로 변하는 것처럼 리액트 컴포넌트 또한 State 값에 따라 다른 결과를 랜더링한다.
[ State의 기본 사용법 ]
▷ useState로 State 생성하기
리액트에서는 함수 useState로 State를 생성한다.
const [light, setLight] = useState('off');
State 변수 set 함수 생성자(초깃값)
useState를 호출하면 2개의 요소가 있는 배열을 반환한다. 이때 배열의 첫 번째 요소 'light'는 현재 상태의 값을 저장하고 있는 변수로, 이 변수를 'State' 변수라고 부른다. 두 번째 요소인 'setLight'는 State 변수의 상태를 업데이트하는 함수이며, 이 함수를 'set 함수' 라고 부른다. useState를 호출할 때 인수로 값을 전달하면 이 값이 State의 초깃값이 되므로, 위 코드의 경우 State 변수 light의 초깃값은 off이다.
다음은 Body 컴포넌트에서 숫자를 카운트하는 State 변수 count를 생성한 예시이다.
import { useState } from "react";
function Body() {
const [count, setCount] = useState(0);
return (
<div>
<h2>{count}</h2>
</div>
);
}
export default Body;
src/component/Body.js
위 코드의 경우, State의 초깃값으로 0을 전달하였으므로, 페이지에서는 0을 렌더링한다.
▷ set 함수로 State 값 변경하기
컴포넌트에서 버튼을 하나 만들고, 버튼을 클릭할 때마다 State(count) 값을 1씩 늘린다.
import { useState } from "react";
function Body() {
console.log("Update!");
const [count, setCount] = useState(0);
const onIncrease = () => {
setCount(count + 1);
};
return (
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>+</button>
</div>
);
}
export default Body;
src/component/Body.js
페이지에서 ' + ' 버튼을 클릭하면 onIncrease 이벤트 핸들러가 실행되고, 함수 onIncrease는 setCount를 호출하여 연재의 count 값에 1 더한 값을 전달한다. 다음은 ' + ' 버튼을 클릭해 컴포넌트를 렌더링한 결과이다.
이렇게 set 함수를 호출하여 State 값을 변경하면, 변경값을 페이지에 반영하기 위해 컴포넌트를 다시 렌더링한다. 이것을 '컴포넌트의 업데이트'라고 표현한다.
컴포넌트가 페이지에 렌더링하는 값은 컴포넌트 함수를 다시 호출한다는 의미와 같다. 따라서 위 코드는 Body를 호출할 때마다 문자열 'Update!'를 출력하게 된다.
다음은 페이지에서 ' + '버튼은 12번 클릭한 다음 콘솔을 확인한 것이다. 처음 나온 Update!는 컴포넌트를 처음 렌더링할 때 출력된 것이고, 나머지 12번의 Update!는 버튼을 클릭할 때마다 Body 컴포넌트를 다시 호출하기 때문에 출력된 것이다.
컴포넌트는 자신이 관리하는 State 값이 변하면 다시 호출되고, 변경된 State 값을 자동으로 페이지에 렌더링한다.
State 값이 변해 컴포넌트를 다시 렌더링하는 것을 '리렌더' 또는 '리렌더링'이라고 한다.
[ State로 사용자 입력 관리하기 ]
웹 사이트는 입력 폼을 통해 사용자로부터 입력을 받는다. HTML은 다양한 형식의 정보를 입력할 수 있는 <Input>, 여러 옵션에서 하나를 선택하도록 DropDown 목록을 보여주는 <Select>, 여러 줄의 텍스트를 입력할 수 있는 <Textarea> 등을 사용한다.
▷ <input> 태그로 텍스트 입력
<input> 태그를 사용하여 만들 수 있는 입력 폼은 매우 다양한데, <input> 태그의 type 속성에서 text를 지정하면 텍스트 폼, date를 지정하면 날짜 형식의 폼, tel을 지정하면 전화번호 형식의 폼을 만들 수 있다. 라디오 버튼이나 체크 박스 등도 <input> 태그를 이용해 만들 수 있으며, type 속성에서 아무것도 지정하지 않으면 텍스트 폼으로 설정된다.
import { useState } from "react";
function Body() {
const [text, setText] = useState("");
const handleOnChange = (e) => {
setText(e.target.value);
};
return (
<div>
<input value={text} onChange={handleOnChange} />
<div>{text}</div>
</div>
);
}
export default Body;
src/component/Body.js
위 코드는 빈 문자열을 초깃값으로 하는 State 변수 text를 생성하여 폼에 입력한 텍스트를 변경할 때마다 set 함수인 setText를 호출해 text 값을 현재 입력한 텍스트로 변경한다. 또한 <input> 태그의 value 속성에 변수 text를 설정하고, 변수 text의 값을 페이지에 렌더링한다. 입력 폼에서 사용자가 텍스트를 입력하면 onChange 이벤트가 발생해 이벤트 핸들러 handleOnChange가 호출되며, handleOnChange 함수 내부에서 set 함수를 호출한다.
▷ <input> 태그로 날짜 입력
<input> 태그에서 type 속성을 'date'로 설정하면 날짜 형식의 데이터를 입력할 수 있다.
import { useState } from "react";
function Body() {
const [date, setDate] = useState("");
const handleOnChange = (e) => {
console.log("변경된 값: ", e.target.value);
setDate(e.target.value);
};
return (
<div>
<input type="date" value={date} onChange={handleOnChange} />
</div>
);
}
export default Body;
src/component/Body.js
위 코드와 같이 type을 date로 설정하면 onChange 이벤트가 발생했을 때, 이벤트 객체의 e.target.value에는 문자열 형식의 yyyy-mm-dd 이 저장된다.
입력 폼에서 날짜를 변경하면 콘솔에서도 변경된 날짜가 바로 출력되는 것을 볼 수 있다.
▷ 드롭다운 상자로 여러 옵션 중 하나 선택하기
<select> 태그는 <option>과 함께 사용한다. 이 태그를 사용하면 드롭다운(DropDown) 메뉴로 여러 목록을 나열해 보여주는 입력 폼을 만들 수 있다. 다음은 드롭다운 입력 폼에서 입력한 값을 State로 처리한 예시이다.
import { useState } from "react";
function Body() {
const [option, setOption] = useState("");
const handleOnChange = (e) => {
console.log("변경된 값: ", e.target.value);
setOption(e.target.value);
};
return (
<div>
<select value={option} onChange={handleOnChange}>
<option key={"1번"}>1번</option>
<option key={"2번"}>2번</option>
<option key={"3번"}>3번</option>
</select>
</div>
);
}
export default Body;
src/component/Body.js
드롭다운 입력 폼에서 사용자가 옵션을 변경하면 onChange 이벤트가 발생한다. 이벤트 핸들러에 제공되는 이벤트 객체 e.target.value에는 현재 사용자가 선택한 옵션의 key 속성이 저장된다.
위의 경우, 2번 옵션 선택 후 3번 옵션을 선택한 모습이다. 콘솔에는 2번과 3번이 차례로 출력되었음을 볼 수 있다.
▷ 글상자로 여러 줄의 텍스트 입력
<textarea> 태그는 사용자가 여러 줄의 텍스트를 입력할 때 사용하는 폼을 만든다. 이 폼은 편의상 '글상자'라고 부른다. 다음은 리액트에서 글 상자에 입력한 내용을 State로 처리한 예시이다.
import { useState } from "react";
function Body() {
const [text, setText] = useState("");
const handleOnChange = (e) => {
console.log("변경된 값 : ", e.target.value);
setText(e.target.value);
};
return (
<div>
<textarea value={text} onChange={handleOnChange} />
</div>
);
}
export default Body;
src/component/Body.js
글자를 입력할 때마다 콘솔에서는 입력한 텍스트가 바로 표시된다.
▷ 여러 개의 사용자 입력 관리
여러 개의 사용자 입력을 한 번에 입력할 수 있도록 다음과 같이 Body 컴포넌트를 수정한다. 이때 사용자로부터 여러 입력 정보를 받아 State로 처리하는 경우, 관리할 State의 개수가 많아지면 코드의 길이가 길어지게 된다. 따라서 객체 자료형을 이용하여 입력 내용이 여러 가지라도 하나의 State에서 관리할 수 있도록 코드를 작성한다.
import { useState } from "react";
function Body() {
const [state, setState] = useState({
name: "",
gender: "",
birth: "",
bio: "",
});
const handleOnChange = (e) => {
console.log("현재 수정 대상:", e.target.name);
console.log("수정값:", e.target.value);
setState({
...state,
[e.target.name]: e.target.value,
});
};
return (
<div>
<div>
<input
name="name"
value={state.name}
onChange={handleOnChange}
placeholder="이름"
/>
</div>
<div>
<select name="gender" value={state.gender} onChange={handleOnChange}>
<option key={""}></option>
<option key={"남성"}>남성</option>
<option key={"여성"}>여성</option>
</select>
</div>
<div>
<input
name="birth"
type="date"
value={state.birth}
onChange={handleOnChange}
/>
</div>
<div>
<textarea name="bio" value={state.bio} onChange={handleOnChange} />
</div>
</div>
);
}
export default Body;
src/component/Body.js
객체 자료형으로 State를 하나 생성하고 공백 문자열(" ")을 초깃값으로 설정한다. 모든 입력 폼에서 name 속성을 지정하여 각 입력 정보를 구분할 수 있도록 하고, 모든 입력 폼의 value를 객체 State의 프로퍼티 중 하나로 설정한다. 또한 각 태그에 사용자 입력을 처리할 이벤트 핸들러 handleOnChange를 생성한다. 이벤트핸들러는 이벤트 객체 e를 매개변수로 저장하고 다음과 같이 setState를 호출한다.
setState({
...state,
[e.target.name]: e.target.value,
});
함수 setState는 새로운 객체를 생성해 전달하는데, 스프레드 연산자를 이용해 기존 객체 state의 값을 나열한다. 그리고 객체의 괄호 표기법을 사용하여 입력 폼의 name 속성 e.target.name을 key, 입력된 값 e.target.value를 value로 저장한다.
e.target.name은 현재 이벤트가 발생한 요소의 name 속성으로, 객체 state의 4가지 프로퍼티 중 현재 이벤트가 발생한 요소에 해당하는 프로퍼티의 value 값을 변경한다.
[ Props와 State ]
리액트의 State 역시 일종의 값이기 때문에 Props로 전달할 수 있다.
import "./Body.css";
import { useState } from "react";
function Viewer({ number }) {
return <div>{number % 2 === 0 ? <h3>짝수</h3> : <h3>홀수</h3>}</div>;
}
function Body() {
const [number, setNumber] = useState(0);
const onIncrease = () => {
setNumber(number + 1);
};
const onDecrease = () => {
setNumber(number - 1);
};
return (
<div>
<h2>{number}</h2>
<Viewer number={number} />
<div>
<button onClick={onDecrease}>-</button>
<button onClick={onIncrease}>+</button>
</div>
</div>
);
}
export default Body;
src/component/Body.js
위 코드에서 Viewer 컴포넌트에는 Props로 Body 컴포넌트에 있는 State 변수 number가 전달된다. Viewer 컴포넌트는 조건부 렌더링을 이용해 number의 값에 따라 짝수 또는 홀수 값을 페이지에 렌더링한다. 또한 Body에서 Viewer를 자식 컴포넌트로 사용하고 있음을 볼 수 있다.
State 값이 1일 때 |
State 값이 2일 때 |
위 결과를 보면 자식 컴포넌트는 Props로 전달된 State 값이 변하면 자신도 리렌더된다는 사실을 알 수 있다. 다시말해, 부모에 속해 있는 State(number) 값이 변하면 자식 컴포넌트에서 구현한 값(여기서 '짝수', '홀수')도 따라서 변한다는 것이다.
[ State와 자식 컴포넌트 ]
앞에서도 알아봤듯이, 부모의 State 값이 변하면 해당 State를 Props로 받은 자식 컴포넌트 역시 리렌더된다. 그런데 부모 컴포넌트가 자식에게 State를 Props로 전달하지 않는 경우는 어떤 일이 일어날까?
import { useState } from "react";
function Viewer() {
console.log("viewer component update!");
return <div>Viewer</div>;
}
function Body() {
const [number, setNumber] = useState(0);
const onIncrease = () => {
setNumber(number + 1);
};
const onDecrease = () => {
setNumber(number - 1);
};
return (
<div>
<h2>{number}</h2>
<Viewer />
<div>
<button onClick={onDecrease}>-</button>
<button onClick={onIncrease}>+</button>
</div>
</div>
);
}
export default Body;
src/component/Body.js
위 코드는 Viewer 컴포넌트가 Props를 받지 않는다. 대신에 console.log를 이용해 리렌더가 일어날 때마다 메시지를 콘솔에 출력한다.
콘솔을 보면 총 9번의 'viewer component update!'가 출력되었다. 첫 번째 출력은 Viewer 컴포넌트를 처음 렌더링할 때 출력된 것이다. 나머지 8번은 부모인 Body 컴포넌트의 State가 변할 때마다 출력되었다.
그런데 사실 지금의 Viewer는 Body 컴포넌트의 State가 변한다고 해서 리렌더할 이유가 없다. Viewer 컴포넌트의 내용은 변하지 않았기 때문이다. 이렇게 의미없는 리렌더가 자주 발생하면 웹 브라우저의 성능이 떨어지게 된다. 따라서 부모 - 자식 관계에서 State를 사용할 경우 주의해야 할 필요가 있다.
Ref는 Reference의 줄임말로, 리액트의 Ref를 이용하면 돔(DOM) 요소들을 직접 조작할 수 있다.
[ useRef 사용하기]
리액트에서는 useRef라는 리액트 함수를 이용해 Ref 객체를 생성한다. 다음은 돔 요소 중 하나인 <input> 태그의 입력 폼에 접근하는 Ref를 만든 예시이다.
import { useRef, useState } from "react";
function Body() {
const [text, setText] = useState("");
const textRef = useRef();
const handleOnChange = (e) => {
setText(e.target.value);
};
const handleOnClick = () => {
alert(text);
};
return (
<div>
<input ref={textRef} value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성 완료</button>
</div>
);
}
export default Body;
src/component/Body.js
useRef는 리액트가 제공하는 기능이므로 import를 사용해 react 라이브러리에서 불러와야 한다. 함수 useRef는 인수로 전달한 값을 초깃값으로 하는 Ref 객체를 생성하고, 생성한 Ref를 상수 textRef에 저장한다.
또한 <input> 태그에서 ref = {textRef} 명령으로 textRef가 돔 입력 폼에 접근하도록 설정하여 입력 폼을 직접 조작할 수 있게 된다.
▷ useRef로 입력 초기화
useRef를 이용해 입력폼에서 작성한 값을 초기화하는 방법은 다음과 같다.
import { useRef, useState } from "react";
function Body() {
const [text, setText] = useState("");
const textRef = useRef();
const handleOnChange = (e) => {
setText(e.target.value);
};
const handleOnClick = () => {
alert(text);
textRef.current.value = ""; //(추가) textRef.current의 value 값을 공백 문자열로 초기화
};
return (
<div>
<input ref={textRef} value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성 완료</button>
</div>
);
}
export default Body;
src/component/Body.js
텍스트 입력 후 작성 완료 클릭 |
초기화 |
▷ useRef로 포커스하기
웹 서비스에서는 사용자가 특정 폼에 내용을 입력하지 않거나 내용이 정한 길이보다 짧으면 해당 폼을 포커스(focus)하여 추가 입력을 유도한다. 다음은 리액트의 Ref 기능을 이용하여 텍스트 입력 폼에서 사용자가 다섯 글자 미만으로 입력할 시, 요소에 포커스한 상태 입력이 추가될 때까지 대기하는 코드이다.
import { useRef, useState } from "react";
function Body() {
const [text, setText] = useState("");
const textRef = useRef();
const handleOnChange = (e) => {
setText(e.target.value);
};
const handleOnClick = () => {
if (text.length < 5) {
textRef.current.focus(); //텍스트의 길이가 5보다 작다면 focus
} else {
alert(text);
setText("");
}
};
return (
<div>
<input ref={textRef} value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성 완료</button>
</div>
);
}
export default Body;
src/component/Body.js
조건문을 사용하여 현재 <input> 태그로 지정한 폼에 입력한 텍스트가 다섯 글자보다 적다면 textRef.current.focus()를 통해 입력 폼에 포커스를 지정한다. focus()는 현재 돔 요소에 포커스를 지정하는 메서드이다.
위와 같이 입력된 글자가 다섯 글자 미만(띄어쓰기 포함)인 경우, 작성 완료 버튼을 클릭해도 초기화되지 않고 포커스 기능이 동작하는 것을 알 수 있다.
+) 리액트 훅(React Hook)
함수로 만든 리액트 컴포넌트에서 클래스로 만든 리액트 컴포넌트의 기능을 이용하도록 도와주는 함수들.
앞에서 배웠던 useState와 useRef이 이에 해당한다. 두 함수의 이름은 모두 use로 시작하는데, 리액트 훅은 이름 앞에 항상 use를 붙인다. State와 Ref는 원래 함수로 만든 컴포넌트에서는 사용할 수 없는 기능이지만, 훅 기능 덕분에 사용할 수 있었던 것이다.
리액트 훅이 발표되기 전까지는 대부분 컴포넌트를 클래스로 만들었다. 그러나 클래스로 컴포넌트를 만들 경우, 기본적으로 작성해야 할 코드가 너무 많고 문법이 간결하지 못한 문제가 발생하였다. 이러한 점을 개선하기 위해 리액트 개발팀은 함수로 만든 컴포넌트에서도 클래스로 만든 컴포넌트 기능을 사용할 수 있게 하였다.
다음은 앞으로 배우게 될 훅들이다.
※ 코드 작성 문제
1. 아래 예시와 같이, 입력 폼에 숫자를 입력하고 버튼을 클릭하면 짝수와 홀수를 판별하는 경고창을 띄우는 기능을 만들어보시오.
숫자 입력 전 |
3을 입력한 결과 |
2. 아래 이미지와 같은 화면이 나오도록 코드를 작성하시오. (단, useRef를 사용하여 5글자 이상 입력이 되지 않을 경우 해당 폼을 포커스하는 기능을 지정하세요.)
답) 1. 이벤트 핸들링, 이벤트 핸들러 2. X (함수 그 자체를 전달) 3. useState 4. set 함수 5. <select>, <option>
6. 객체 자료형 7. O 8. useRef
1.
import { useRef, useState } from "react";
function Body() {
const [num, setNum] = useState("");
const numRef = useRef();
const handleOnChange = (e) => {
setNum(e.target.value);
};
const handleOnClick = () => {
if (num.parseInt % 2 === 0){
alert(num + "은(는) 짝수입니다!");
}
else{
alert(num + "은(는) 홀수입니다!");
}
};
return (
<div>
<input ref={numRef} value={num} onChange={handleOnChange} />
<button onClick={handleOnClick}>짝수/홀수 확인</button>
</div>
);
}
export default Body;
2.
import { useRef, useState } from "react";
function Body() {
const [text, setText] = useState("");
const textRef = useRef();
const handleOnChange = (e) => {
setText(e.target.value);
};
const handleOnClick = () => {
if (text.length < 5){
textRef.current.focus();
}
else{
alert(text);
textRef.current.value = "";
}
};
return (
<div> 5글자 이상을 입력하세요 <br />
<input ref={textRef} value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성 완료</button>
</div>
);
}
export default Body;
출처 : 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023), https://reactjs.winterlood.com
Corner React.js 2
Editor: 엔초_
[React.js 2팀] 8장. Hooks (0) | 2024.11.29 |
---|---|
[React.js 2팀] project 1 [카운터] 앱 만들기 ~ 6장. 라이프 사이클과 리액트 개발자 도구 (0) | 2024.11.22 |
[React.js 2팀] 5장. 리액트의 기본 기능 다루기 (1) (0) | 2024.11.08 |
[React.js 2팀] 3장. Node.js ~ 4장. 리액트 시작하기 (10) | 2024.10.11 |
[React.js 2팀] 2장. 자바스크립트 실전 - 구조 분해 할당 ~ 비동기 처리 (3) | 2024.10.04 |