2025.04.07 ~ 04.09
요소 노드(<>부분) , 어트리뷰트 노드(속성) , 텍스트 노드로 만들어지는 가상 DOM 개념
const element = <h1 align=”center”>엘리먼트!!</h1>
엘리먼트를 반환하는 개념을 가진 화면의 구성 단위
function App() {
return (
<h1>
엘리먼트!!!!!!!!!!
</h1>
);
}
HTML Parsing → CSS Parsing → Render → Layout → Paint
Virtual DOM 사용 시 흐름
React를 사용할 때 앱으로 만드는 방식과, CDN 링크를 이용하는 방식이 있다.
먼저 CDN 링크 방식으로 사용한다.
<!-- 개발용으로 적합, 배포용 버전에는 적합하지 않음 -->
<!-- <head> 안에 작성한다.-->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
HTML 파일 안에 < div id="root">< /div>와 같은 엘리먼트를 두고, < script> 안에 JavaScript 코드를 작성하여 ReactDOM을 사용해 렌더링
h1 태그와 같은 HTML 요소를 JavaScript 객체로 생성할 수 있다.
<div id="root"></div>
<script>
const greetingElement = React.createElement('h1', {className: 'greeting', id: 'greeting'}, 'hello world!!!');
const textElement = React.createElement('h3', {id: 'name'}, '안녕하세요. 홍길동입니다.');
ReactDOM.createRoot(document.getElementById('root')).render([greetingElement, textElement]);
</script>
엘리먼트들의 집합
클래스형 컴포넌트
class HelloWorld extends React.Component {
render() {
return React.createElement(
'h1',
{className: 'greeting'},
'Hello World'
);
}
}
함수형 컴포넌트
function HelloWorld() {
return React.createElement('h1', {className: 'greeting', id : 'test'}, 'Hello World');
}
자바스크립트에서 HTML처럼 보이는 문법을 사용할 수 있도록 만든 확장 문법 표현식


<div id="root"></div>
<script type="text/babel">
const helloworld = <h1>Hello World!</h1>;
ReactDOM.createRoot(document.getElementById('root')).render(helloworld);
</script>
JSX 문법 내에 중괄호를 이용하여 모든 javascript 표현식(문법)을 넣을 수 있다
<div id="root"></div>
<script type="text/babel">
function formatName(user) {
return `${user.lastName} ${user.firstName}`;
}
const user = {
firstName: 'Gildong',
lastName: 'Hong'
};
const element = <h1>Hello, {formatName(user)}</h1>;
ReactDOM.createRoot(document.getElementById('root')).render(element);
</script>
동일 레벨의 노드들이 연달아 정의되는 경우 문법적인 에러가 발생
const element = (
<h1>Hello</h1>
<h3>phone</h3>
);
const element = (
<div>
<h1>Hello, {user.name}</h1>
<h3>phone: {user.phone}</h3>
</div>
);
const element = (
<React.Fragment>
<h1>Hello, {user.name}</h1>
<h3>phone: {user.phone}</h3>
</React.Fragment>
);
const {Fragment} = React;
<!-- const Fragment = React.Fragment; -->
const element = (
<Fragment>
<h1>Hello, {user.name}</h1>
<h3>phone: {user.phone}</h3>
</Fragment>
);
const element = (
<>
<h1>Hello, {user.name}</h1>
<h3>phone: {user.phone}</h3>
</>
);
class라는 속성은 className이라는 속성으로 사용
const element = <h1 id="title" className="highlight" onClick="test">Hello World!</h1>;
const id = "title";
const className = "highlight";
const element = <h1 id={id} className={className} onClick={test}>Hello World!</h1>;
ReactDOM.createRoot(document.getElementById('root')).render(element);
function test(){
console.log('클릭했네');
}
<script type="text/babel">
// 속성 값은 문자열 혹은 숫자 형태로 작성해야 한다.(객체 X)
const style = {
backgroundColor: 'black',
color: 'white',
cursor: 'pointer',
textAlign: 'center',
padding: 20
// 단위를 작성하려면 문자열로 사용하지만 단위를 생략하면 숫자만 사용 가능(생략 시 px를 붙인다.)
}
const element = (
<>
<h1 style={style}>Hello World!</h1>
<h3>inline styling test</h3>
</>
);
ReactDOM.createRoot(document.getElementById('root')).render(element);
</script>
const element = (
<>
<h1>Event Attribute</h1>
<button onClick={() => alert('Hello World!')}>클릭하세요</button>
</>
);
const onClickHandler = () => {
alert('Hello World!');
};
const element = (
<>
<h1>Event Attribute</h1>
<button onClick={onClickHandler}>클릭하세요</button>
</>
);
ReactDOM.createRoot(document.getElementById('root')).render(element);
{} 안에 JavaScript 표현식처럼 주석을 작성
const element = (
<>
<h1>Comment in JSX</h1>
{/* JSX 내에서의 주석은 이렇게 작성합니다. */}
{
// JSX 내의 한줄주석은 표현식을 한 줄이 아닌 여러 줄로 작성하면 적용할 수 있다.
}
<h3
className="text" // 시작 태그에도 주석을 작성할 수 있다.
>
JSX 내의 주석 사용하는 방법
{
// 한 줄 주석
}
</h3> {/* 닫기 태그는 태그 내부에 주석을 일반적으로 하지 않는다. */}
/* 이런 주석이나 */
// 이런 주석은 그대로 화면에 나타나게 된다.
</>
);
< div id="root"> 요소 내부에 포함되는 모든 React 엘리먼트는 ReactDOM이 관리하므로, 이 요소를 루트(root) DOM, 루트 노드(Node)라고 부른다
React 엘리먼트는 불변객체(immutable)
엘리먼트를 생성한 이후에는 해당 엘리먼트의 자식이나 속성을 변경할 수 없다
엘리먼트를 업데이트 하기 위해서는 완전히 새로운 엘리먼트를 만들고 ReactDOM.render로 다시 렌더링을 하는 방식을 사용
Virtual DOM은 메모리에 있는 가상의 트리, 실제 DOM 구조와 동일한 형태로 관리
React에서 UI가 바뀌면,
DOM 전체를 다시 렌더링하지 않고도, 마치 UI가 동적으로 수정되는 것처럼 효율적으로 동작
리액트의 컨셉은 변화가 있는 엘리먼트를 찾아서 어떻게 바꿀 것인지를 고민하는 것이 아닌 기존 가상돔의 엘리먼트를 지우고 새롭게 엘리먼트를 생성하는 방식으로 고안
여러 개의 엘리먼트 중 특정 조건에 따라 하나의 엘리먼트만 렌더링
<div id="root"></div>
<script type="text/babel">
const root = ReactDOM.createRoot(document.getElementById('root'));
const answer = parseInt(prompt('리액트가 재미있으신가요?\n1. 재미있다.\n2. 어렵다'));
const positiveElement = <h1>앞으로 점점 더 재미있어 질 거예요^^</h1>;
const negativeElement = <h1>천천히 앞의 내용을 복습해 보세요^^</h1>;
root.render(
(answer === 1)? positiveElement: negativeElement
);
</script>
<div id="root"></div>
<script type="text/babel">
const root = ReactDOM.createRoot(document.getElementById('root'));
const answer = parseInt(prompt('리액트가 재미있으신가요?\n1. 재미있다.\n2. 어렵다'));
let element;
if(answer === 1) {
element = <h1>앞으로 점점 더 재미있어 질 거예요^^</h1>;
} else {
element = <h1>천천히 앞의 내용을 복습해 보세요^^</h1>;
}
</script>
<div id="root"></div>
<script type="text/babel">
const root = ReactDOM.createRoot(document.getElementById('root'));
const answer = parseInt(prompt('리액트가 재미있으신가요?\n1. 재미있다.\n2. 어렵다'));
const element = (answer === 1)? (
<h1>앞으로 점점 더 재미있어 질 거예요^^</h1>
): (
<h1>천천히 앞의 내용을 복습해 보세요^^</h1>
);
</script>
<div id="root"></div>
<script type="text/babel">
const root = ReactDOM.createRoot(document.getElementById('root'));
const answer = parseInt(prompt('리액트가 재미있으신가요?\n1. 재미있다.\n2. 어렵다'));
const element = answer === 1 && <h1>앞으로 점점 더 재미있어 질 거예요^^</h1>
console.log(element);
</script>
<div id="root"></div>
<script type="text/babel">
const root = ReactDOM.createRoot(document.getElementById('root'));
const answer = parseInt(prompt('리액트가 재미있으신가요?\n1. 재미있다.\n2. 어렵다'));
const number = 0;
console.log(!!number); // false
const element = number && <h1>truthy일 때 나오는 태그</h1>;
root.render(element); // falsy 값이지만 element로 취급 됨
</script>
state 사용 및 라이프 사이클 기능, 임의의 메소드 정의 등은 클래스 컴포넌트에서만 사용 가능한 방식
<div id="root"></div>
<script type="text/babel">
class Title extends React.Component {
render() {
return (
<h1>Class Componenet</h1>
);
}
}
// 클래스형 컴포넌트를 만들게 되면 사용자 정의 컴포넌트를 엘리먼트 형태로 사용할 수 있다.(사용자 정의 태그처럼)
ReactDOM.createRoot(document.getElementById('root')).render(<Title/>);
/* 1. 앞글자 대문자, 2. 닫기 태그 존재, 3. 클래스나 함수 이름과 동일해야 함 */
</script>
<!-- JSX 문법을 쓰지 않을 때는 React.createElement()를 직접 사용해야 한다 -->
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(Title));
함수형 컴포넌트는 ReactElement를 반환하는 함수
사용자 정의 엘리먼트를 이용할 시 JSX 태그 형태로 사용
함수 이름의 첫 글자는 반드시 대문자
함수형 컴포넌트의 단점 : state와 라이프사이클 API 사용이 불가능
<div id="root"></div>
<script type="text/babel">
function Title() {
return (
<h1>Function Component</h1>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<Title/>);
</script>
여러 개의 작은 컴포넌트를 조합(합성)하여 하나의 큰 컴포넌트를 만드는 방식
<div id="root"></div>
<script type="text/babel">
let name = '홍길동';
let age = 20;
let phone = '010-1234-5678';
let email = 'hong123@gmail.com';
const user = {name, age, phone, email}; // 프로퍼티 단축 구문
function NameCard() {
return <h1>{user.name}</h1>;
}
function AgeCard() {
return <h2 style={{color: 'orangered'}}>{user.age}</h2>;
}
function PhoneCard() {
return <h3 style={{color: 'red'}}>{user.phone}</h3>;
}
function EmailCard() {
return <h3 style={{backgroundColor: 'yellow'}}>{user.email}</h3>;
}
// 상위 컴포넌트 : 여러 하위 컴포넌트를 조합하여 구성
function UserInfo() {
return (
<div style={{width: 300, border: '1px solid black'}}>
<NameCard/>
<AgeCard/>
<PhoneCard/>
<EmailCard/>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<UserInfo/>);
</script>
properties의 약어
컴포넌트에 외부로부터 전달받은 값(속성), 부모 컴포넌트가 자식 컴포넌트에 값을 전달할 때 사용하는 읽기 전용 객체
- 순수 함수 : 동일한 입력값에 대해 동일한 결과를 반환하는 것
컴포넌트에 데이터를 직접 전달하여 내부적으로 표현할 수 있다.
<div id="root"></div>
<script type="text/babel">
const name1 = '홍길동';
const name2 = '유관순';
/* 1. 컴포넌트에 props를 전체 객체로 받아서 내부에서 접근하는 방법 */
function Title({props}){
return <h1>안녕하세요 {props.name}님 반갑습니다.</h1>;
}
/* 2. 구조 분해 할당 방법 */
const name = props.name;
const {name} = props; -->
function Title({name}){
return <h1>안녕하세요 {name}님 반갑습니다.</h1>;
}
/* props.name이 존재하지 않는 경우 기본값 설정 */
Title.defaultProps = {
name: '기본이름'
}
ReactDOM.createRoot(document.getElementById('root')).render(
[
<Title name={name1}/>,
<Title name={name2}/>,
<Title name='이순신'/>,
<Title/>
]
);
</script>
자식 노드를 전달하는 props.children
<div id="root"></div>
<script type="text/babel">
function ChildNodePrinter(props) {
return (
<>
<h1>자식 노드가 가지고 있는 값은?</h1>
<h3>children:<font style={{color: 'orangered'}}>{props.children}</font></h3>
</>
)
}
ReactDOM.createRoot(document.getElementById('root')).render(
[
<ChildNodePrinter>텍스트 노드</ChildNodePrinter>,
<ChildNodePrinter><div>텍스트 노드</div></ChildNodePrinter>,
<ChildNodePrinter><div>1</div><div>2</div><div>3</div></ChildNodePrinter>
]
);
</script>
<div id="root"></div>
<script type="text/babel">
function PropsPrinter(props){
/* props 객체에서 name과 children이라는 key를 가진 프로퍼티 값을 꺼내 동일한 이름의 변수에 할당한다.(객체 구조 분해 할당) */
const {name, children} = props;
return (
<>
<h1>제 이름은 {name}입니다.</h1>
<h3>제가 가지고 있는 children은 {children}입니다.</h3>
</>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<PropsPrinter name="홍길동">텍스트노드</PropsPrinter>);
</script>
<div id="root"></div>
<script type="text/babel">
function PropsPrinter({name, children}){
return (
<>
<h1>제 이름은 {name}입니다.</h1>
<h3>제가 가지고 있는 children은 {children}입니다.</h3>
</>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<PropsPrinter name="홍길동">텍스트노드</PropsPrinter>);
</script>
React 컴포넌트에 전달되는 props의 자료형(Type)과 존재 여부(Required)를 검사(검증)해주는 도구
<div id="root"></div>
<script type="text/babel">
// attribute형 값 두 개와 child 노드
function PropsVerify({name, favoriteNumber, children}){
return (
<>
<h1>제 이름은 {name}입니다.</h1>
<h2>제가 가장 좋아하는 숫자는 {favoriteNumber}입니다.</h2>
<h3>제가 가지고 있는 children은 {children}입니다.</h3>
</>
);
}
PropsVerify.propTypes = {
// name props의 속성은 string 형이어야 한다.
name: PropTypes.string,
// favoriteNumber prop의 속성은 number 형이면서 반드시 주어져야 한다.
favoriteNumber: PropTypes.number.isRequired
};
ReactDOM.createRoot(document.getElementById('root')).render(
[
<PropsVerify name="홍길동" favoriteNumber={5}>텍스트노드</PropsVerify>,
// 콘솔 경고 발생 (name은 string인데 number 전달)
<PropsVerify name={3}>텍스트노드</PropsVerify>
]
);
</script>
컴포넌트 내부에서 바꿀 수 있는(바뀔 수 있는) 값
컴포넌트가 자체적으로 관리하는 동적인 데이터
props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하고 주는 읽기 전용 값
리액트에서 클래스형 컴포넌트는 state를 내부에서 직접 선언하고 사용할 수 있음
함수형 컴포넌트에서는 기본적으로 직접 state를 사용하는 것이 불가능
- useState라는 Hook을 통해 state 관리 가능
클래스형 컴포넌트에서 state를 다루는 방법
<div id="root"></div>
<script type="text/babel">
// 클래스형 컴포넌트를 만들 때는 React.Component를 상속
class Counter extends React.Component{
// 컴포넌트를 생성할 때 가장 먼저 호출되는 생성자 함수이다.
constructor(props){
/* super(props)는 부모 클래스(React.Component)의 생성자에 props를 넘겨주는 것 (필수!) */
super(props);
/* 컴포넌트 내부에서 관리할 값들을 저장하는 객체
state는 반드시 this.state 형태로 정의 */
this.state = {
// 변경 될 관리할 값의 초기값 설정
number: 3
};
}
render() {
const {number} = this.state;
return (
<>
<h1>{this.state.number}</h1>
<button onClick={() => this.setState({number: number - 1})}>- 1</button>
<button onClick={() => this.setState({number: number + 1})}>+ 1</button>
</>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<Counter/>);
</script>
- state를 직접 변경하면 안 됨(예: this.state.number = 10 ❌)
- state의 setter 메소드(setState())를 호출하고 관리할 값에 변화를 준 새로운 객체를 인수로 전달
- 반드시 this.setState({...})로 변경
- 변경할 값을 포함한 새로운 객체를 전달
prevState를 활용한 setState 함수의 사용
constructor(props) 생성자 함수는 생략이 가능
setState의 비동기성 문제
<div id="root"></div>
<script type="text/babel">
// 클래스형 컴포넌트를 만들 때는 React.Component를 상속
class Counter extends React.Component{
// 클래스의 속성으로 state를 추가하는 것도 가능하다.
// (이 때 this.은 사용 불가능하다.)
state = {
number: 0
};
render() {
const {number} = this.state;
return (
<>
<h1
style={(number < 0)? {color:'red'} : {color: 'blue'}}
>
Count : {number}
</h1>
<button onClick={ () => this.setState({number: number - 1})}>- 1</button>
<button onClick={ () => {
console.log(`before setState number: ${this.state.number}`);
this.setState({number: number + 1})
console.log(`after setState number: ${this.state.number}`);
this.setState({number: this.state.number + 1})
console.log(`final setState number: ${this.state.number}`);
}}
>
+ 1
</button>
</>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<Counter/>);
</script>
동기식 누적 업데이트 방식 (prevState 사용)
<div id="root"></div>
<script type="text/babel">
// 클래스형 컴포넌트를 만들 때는 React.Component를 상속
class Counter extends React.Component{
// 클래스의 속성으로 state를 추가하는 것도 가능하다.
// (이 때 this.은 사용 불가능하다.)
state = {
number: 0
};
render() {
const {number} = this.state;
return (
<>
<h1
style={(number < 0)? {color:'red'} : {color: 'blue'}}
>
Count : {number}
</h1>
<button onClick={ () => this.setState({number: number - 1})}>- 1</button>
<button onClick={ () => {
this.setState((prevState, props) => {
console.log('prevState' , prevState);
console.log('props' , props);
return {
number: prevState.number + 1
};
});
// 전달받는 props가 없으면 생략해서 사용해도 된다.
this.setState((prevState, props) => {
console.log('prevState' , prevState);
return {
number: prevState.number + 1
};
});
this.setState(prevState => {return {number: prevState.number + 1}});
// 화살표 함수에서 중괄호({}) 생략 및 return 구문 생략 시 반환값이 리터럴 객체라면 소괄호(())를 씌워야 리터럴 객체를 반환
this.setState(prevState => ({number: prevState.number + 1}));
}}
>
+ 1
</button>
</>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<Counter/>);
</script>
setState는 비동기적으로 작동하기 때문에, 상태 변경 후 특정 작업을 하고 싶을 때는 두 번째 인자로 콜백 함수를 전달하면 된다
this.setState(updatedState, () => {
// 상태 업데이트가 완료된 후 실행될 작업
});
<div id="root"></div>
<script type="text/babel">
class Light extends React.Component {
state = {
isOn: false
};
render() {
const {isOn} = this.state;
const style = {
width: 200,
height: 200,
backgroundColor: isOn? 'green': 'red',
transition: '2s'
}
return (
<>
<div style={style}></div>
<button onClick={() => {
this.setState(
/* 업데이트할 state 객체 */
{isOn: !isOn},
/* 상태가 반영된 후 실행할 콜백 함수 */
() => console.log(!isOn? '불이 켜졌습니다': '불이 꺼졌습니다')
)
}}>
{isOn? 'ON': 'OFF'}
</button>
</>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<Light/>);
</script>
함수형 컴포넌트에서 상태(state)를 관리할 수 있게 해주는 React Hook
React객체에서 내부에 존재하는 함수형 속성
<div id="root"></div>
<script type="text/babel">
const {useState} = React;
function Say() {
const [message, setMessage] = useState('기본상태');
/* 여러 개의 상태들을 한 컴포넌트 내에서 사용해도 된다. */
const [color, setColor] = useState('black');
const [backgroundColor, setBackgroundColor] = useState('white');
const onClickEnter = () => setMessage('안녕하세요!');
const onClickLeave = () => setMessage('안녕히 가세요!');
return (
<>
<h1 style={{color, backgroundColor}}>{message}</h1>
<div>
<button onClick={onClickEnter}>입장</button>
<button onClick={onClickLeave}>퇴장</button>
</div>
<div>
<button onClick={() => setColor('red')}>빨간색</button>
<button onClick={() => setColor('purple')}>보라색</button>
<button onClick={() => setColor('green')}>초록색</button>
</div>
<div>
<button onClick={() => setBackgroundColor('white')}>기본 배경</button>
<button onClick={() => setBackgroundColor('black')}>반전 배경</button>
</div>
</>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<Say/>);
</script>
JSX attribute 내부에서 함수를 정의할 수 있지만 attribute 방식으로 이벤트를 할당하게 되면 코드가 길어진다는 단점이 존재한다.
<button onClick={() => alert('클릭됨!')}>클릭</button>
-> 외부에 핸들러 함수를 정의하고 이벤트가 발생했을 때 해당하는 함수가 참조만 하도록 작성
function handleClick() {
alert('클릭됨!');
}
<button onClick={handleClick}>클릭</button>
함수의 호출과 참조
onClick={handleClick}
onClick={handleClick()}
html -> <button onclick="alert('hello world')">클릭</button>
react -> <button onClick={() => alert('hello world')}>클릭</button>
DOM 요소에만 이벤트를 설정할 수 있다
컴포넌트에 onClick이라고 해서 전달하면 props 객체에 담기는 값일 뿐이다
function CustomButton(props) {
return <button onClick={props.onClick}>클릭</button>;
}
// 사용 예시
<CustomButton onClick={handleClick} />
```
컴포넌트 내부에서 props에 전달되어 온 함수를 이벤트 핸들러로 추가할 수 있다
<button onClick={this.props.onClick}>클릭</button>
```
<div id="root"></div>
<script type="text/babel">
class EventButton extends React.Component {
/* constructor를 생략하면 React가 자동으로 아래 구문을 적어준다. */
constructor(props) {
super(props);
}
/* 1. 직접 이벤트 속성에 이벤트 핸들러 함수 정의하며 이벤트 연결 */
render() {
console.log(this.props);
return (
<button onClick={() => alert('인라인 함수 이벤트 동작 확인')}>{this.props.children}</button>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(
<EventButton>이벤트버튼</EventButton>
);
</script>
<div id="root"></div>
<script type="text/babel">
class EventButton extends React.Component {
/* constructor를 생략하면 React가 자동으로 아래 구문을 적어준다. */
constructor(props) {
super(props);
}
/* 2. render 함수 외부에 이벤트 핸들러 함수 정의 후 함수 전달하여 이벤트 연결 */
onClickHandler = () => alert('외부 함수 이벤트 동작 확인');
/* 클래스 내부의 함수(메소드)를 호출 할 때는 this.을 반드시 붙여줘야 한다. */
render() {
return (
<button onClick={this.onClickHandler}>{this.props.children}</button>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(
<EventButton>이벤트버튼</EventButton>
);
</script>
<div id="root"></div>
<script type="text/babel">
class EventButton extends React.Component {
/* constructor를 생략하면 React가 자동으로 아래 구문을 적어준다. */
constructor(props) {
super(props);
}
/* 3. component에 이벤트를 props로 전달 후 연결 */
render() {
console.log(this.props);
const {onClick, children} = this.props;
return (
<button onClick={onClick}>{children}</button>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(
<EventButton onClick={() => alert('props로 이벤트 전달 후 연결 확인')}>이벤트 버튼</EventButton>
);
</script>
onChange 이벤트
입력 필드의 값이 변경될 때마다 호출되는 이벤트
onChange 이벤트의 event.target은 해당 input 요소
대부분의 onChange 이벤트 핸들러는 내부적으로 이벤트에 대한 전반적인 정보를 관리하는 이벤트 객체(event / e)를 자동으로 첫번째 매개변수로 전달받는다
이벤트 객체로 값을 꺼내고 전달할 수 있다.
- e.target.value
<div id="root"></div>
<script type="text/babel">
class EventComponent extends React.Component {
state = {
message: ''
};
render() {
return (
<>
<h1>이벤트 핸들링</h1>
<input
type="text"
name="message"
placeholder="텍스트를 입력해주세요"
// 입력을 하면 글이 계속 변화하므로 변화 시에 발생하는 이벤트라고 보면 된다.
// 입력할 때마다 변화를 감지
onChange={
// event의 target은 input이다
(e) => {
console.log(e);
this.setState({
message: e.target.value
});
}
}
value={this.state.message}
/>
<button
onClick={
() => {
alert(`${this.state.message} 지워짐!`);
this.setState({
message: ''
});
}
}
>
확인
</button>
</>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<EventComponent/>);
</script>
함수가 호출될 때 this는 호출부에 따라 결정
해결방법
1. constructor에서 bind(this) 사용
<div id="root"></div>
<script type="text/babel">
class EventComponent extends React.Component {
state = {
message: ''
};
constructor(props) {
super(props);
/* constructor에서 bind, this 고정 */
this.onChangeHandler = this.onChangeHandler.bind(this);
this.onClickHandler = this.onClickHandler.bind(this);
}
onChangeHandler(e) {
this.setState({
message: e.target.value
});
}
onClickHandler(e) {
alert(this.state.message);
this.setState({
message: ''
});
}
render() {
return (
<>
<h1>이벤트 핸들링 - 핸들러 메소드 분리</h1>
<input
type="text"
name="message"
placeholder="텍스트를 입력해주세요"
onChange={this.onChangeHandler}
value={this.state.message}
/>
<button onClick={this.onClickHandler}>확인</button>
</>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<EventComponent/>);
</script>
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this); // this 고정
}
handleClick() {
console.log(this); // this는 컴포넌트 인스턴스
}
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
2. 클래스 필드에서 화살표 함수 사용
<div id="root"></div>
<script type="text/babel">
class EventComponent extends React.Component {
state = {
message: ''
};
// 화살표 함수로 this 자동 바인딩
onChangeHandler = (e) => {
this.setState({
message: e.target.value
});
};
onClickHandler = () => {
alert(this.state.message);
this.setState({
message: ''
});
};
render() {
return (
<>
<h1>이벤트 핸들링 - 화살표 함수 방식</h1>
<input
type="text"
name="message"
placeholder="텍스트를 입력해주세요"
onChange={this.onChangeHandler}
value={this.state.message}
/>
<button onClick={this.onClickHandler}>확인</button>
</>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<EventComponent />);
</script>
한 state의 valuse를 각각 별로의 이벤트 핸들러로 작성하여 관리하게 되면 이벤트가 많아질수록 외부함수도 많아지게 되어 코드가 복잡해진다.
1. 클래스형 컴포넌트(class component) 이벤트 핸들링 방식
<div id="root"></div>
<script type="text/babel">
class LoginComponent extends React.Component {
state = {
username: '',
password: ''
}
/* 이벤트가 발생한 input 태그의 name 속성 값을 활용하여 하나의 핸들러 메소드로 처리할 수 있다. */
onChangeHandler = e => {
this.setState({
/* e.target.name : 입력된 input의 name 속성 값
e.target.value : input에 실제로 사용자가 입력한 값
[e.target.name] : 'username' 또는 'password'를 키로 사용
하나의 함수로 두 input을 모두 처리
e.target.name에 대괄호를 씌우는 이유는 해당 변수 안에 있는 값을 프로퍼티 키로 적용하기 위함 */
[e.target.name]: e.target.value
});
}
onClickHandler = e => {
alert(`username: ${this.state.username} \n password: ${this.state.password}`);
this.setState({
username: '',
password: ''
});
}
render() {
return (
<>
<h1>로그인</h1>
<label>아이디: </label>
<input
type="text"
name="username"
placeholder="아이디를 입력하세요"
value={this.state.username}
onChange={this.onChangeHandler}
/>
<br/>
<label>비밀번호: </label>
<input
type="password"
name="password"
placeholder="비밀번호를 입력하세요"
value={this.state.password}
onChange={this.onChangeHandler}
/>
<br/>
<button onClick={this.onClickHandler}>로그인</button>
</>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<LoginComponent/>);
</script>
2. 함수형 컴포넌트(function component) 이벤트 핸들링 방식
useState hooks을 사용해 하나의 객체 state로 여러 input 값을 통합 관리
<div id="root"></div>
<script type="text/babel">
/* React.useState */
const {useState} = React;
function LoginComponent(){
/* form이라는 상태 선언 */
const [form, setForm] = useState({
username: '',
password: ''
});
const {username, password} = form;
/* 클래스형 컴포넌트와 다른 점 */
const onChangeHandler = e => {
const changedForm = {
/* 기존 form 내용 복사
새로운 객체를 생성하기 때문에 기존 값들을 유지하려면 먼저 복사해야 한다.*/
...form,
/* 이벤트가 발생한 name과 일치하는 프로퍼티 값은 value값으로 수정 */
[e.target.name]: e.target.value
}
/* 통째로 수정한 객체를 setForm에 던져준다. */
setForm(changedForm);
};
const onClickHandler = () => {
alert(`username: ${username} \n password: ${password}`);
setForm({
username: '',
password: ''
});
}
return (
<>
<h1>로그인</h1>
<label>아이디: </label>
<input
type="text"
name="username"
placeholder="아이디를 입력하세요"
value={username}
onChange={onChangeHandler}
/>
<br/>
<label>비밀번호: </label>
<input
type="password"
name="password"
placeholder="비밀번호를 입력하세요"
value={password}
onChange={onChangeHandler}
/>
<br/>
<button onClick={onClickHandler}>로그인</button>
</>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<LoginComponent/>);
</script>
arr.map(callback);
리액트에서 컴포넌트 배열을 렌더링 했을 때 어떤 원소에 변동이 있는지를 알아내기 위해 사용하는 식별자 역할
key 없이 렌더링(경고 발생)
props 객체의 배열을 map을 활용하여 각각의 요소를 가지는 li요소 배열을 반환
<div id="root"></div>
<script type="text/babel">
function Items({names}) {
console.log(names);
return (
<ul>
{names.map(name => <li>{name}</li>)}
<br/>
/* 배열로 만든 요소들 */
{[<li>{'홍길동'}</li>, <li>{'유관순'}</li>, <li>{'윤봉길'}</li>, <li>{'이순신'}</li>, <li>{'임꺽정'}</li>]}
<br/>
<li>{'홍길동'}</li>
<li>{'유관순'}</li>
<li>{'윤봉길'}</li>
<li>{'이순신'}</li>
<li>{'임꺽정'}</li>
</ul>
);
}
const names = ['홍길동', '유관순', '윤봉길', '이순신', '임꺽정'];
ReactDOM.createRoot(document.getElementById('root')).render(<Items names={names}/>);
</script>
key를 지정한 경우 (정상 렌더링, 경고 없음)
<div id="root"></div>
<script type="text/babel">
function Items({names}) {
console.log(names);
return (
<ul>
{names.map((name, index) => <li key={index}>{name}</li>)}
</ul>
);
}
const names = ['홍길동', '유관순', '윤봉길', '이순신', '임꺽정'];
ReactDOM.createRoot(document.getElementById('root')).render(<Items names={names}/>);
</script>
DB에서 값을 가져오는 경우 key 지정 방식
return (
<ul>
{names.map((name) => <li key={name.id}>{name.name}</li>)}
</ul>
);
}
const names = [{id:1, name: '값1'}, {id:2, name: '값2'}];
ReactDOM.createRoot(document.getElementById('root')).render(<Items names={names}/>);

컴포넌트의 라이프 사이클 메소드는 총 9가지가 있다.
마운트 시
constructor -> getDerivedStateFromProps -> render -> componentDidMount 순으로 호출
<div id="root"></div>
<script type="text/babel">
class Greeting extends React.Component {
/* 1. 컴포넌트를 새로 만들 때마다 호출되는 클래스 생성자 메소드이다. */
constructor(props) {
/*
컴포넌트를 만들 때 처음으로 실행된다.
생성자 내에서는 state를 초기화 할 수 있다.
(getDerivedStateFromProps를 쓸려면 반드시 state 초기화를 해야 한다.)
*/
super(props);
console.log('constructor');
this.state = {
text: '초기값'
};
}
/* 2. props에 있는 값을 state에 넣을 때 사용하는 메소드이다. */
static getDerivedStateFromProps(nextProps, nextState) {
/*
리액트 16.3 이후에 새로 만든 라이프 사이클 메소드이다.
props로 받아온 값을 state에 동기화 시키는 용도로 사용하며, 마운트와 업데이트 시 호출된다.
*/
console.log('getDerivedStateFromProps');
return null; // state를 변경할 필요가 없다면 null을 반환한다.
}
/* 3. 리액트 엘리먼트를 가상돔으로 구성하고 실제 렌더트리까지 구성하는 시점에 호출되는 메소드이다. */
render() {
/*
랜더링 될 컴포넌트의 형태를 리액트 컴포넌트로 반환한다.
라이프 사이클 메소드 중 유일한 필수 메소드이다.
render 메소드는 this.props와 this.state에 접근할 수 있다.
아무런 컴포넌트도 보여주고 싶지 않다면 null 혹은 falsy 값을 반환하도록 한다.
주의 사항
1. 이 메소드 내에서는 이벤트 설정이 아닌 곳에서 setState를 사용하면 안된다.
this.setState({
text: '수정'
});
2. 브라우저의 DOM에 접근해서도 안된다.
DOM 정보를 가지고 오거나 state에 변화를 줄 때 componentDidMount에서 처리해야 한다.
*/
console.log('render');
return (
<>
<h1>현재 상태값, {this.state.text}</h1>
</>
)
}
/* 4. 컴포넌트가 웹 브라우저 상에 나타난 후 호출되는 메소드이다. */
componentDidMount() {
/*
컴포넌트를 다 만든 후에 첫 렌더링을 마치고 나면 호출된다.(DOM이 그려지고 난 후, Mount 이후)
다른 자바스크립트 라이브러리 또는 프레임워크의 함수를 호출하거나
이벤트 등록, setTimeout, setInterval, 네트워크 요청 같은 비동기 작업을 처리하면 된다.
*/
console.log('componentDidMount');
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<Greeting/>);
</script>
컴포넌트 업데이트 생명주기에 대한 업데이트 기준은 크게 4가지로 나뉜다
1. props 변경
2. state 변경
3. 부모 컴포넌트 리렌더링
4. this.forceUpdate로 강제 렌더링 트리거(render 내에서는 가급적 사용하지 않아야 한다. 사실상 쓸 일은 거의 없음)
<div id="root"></div>
<script type="text/babel">
class TimePrinter extends React.Component {
constructor(props) {
super(props);
this.state = {
now: new Date().toLocaleTimeString()
};
}
/* 1. props에 있는 값을 state에 넣을 때 사용하는 메소드이다. */
static getDerivedStateFromProps(nextProps, nextState) {
console.log('getDerivedStateFromProps');
console.log(nextProps); // 리렌더링 시 변경된 props값
console.log(nextState); // 리렌더링 시 변경된 state값
/* 아래와 같은 조건문으로 인수들을 활용해 prop에 변화가 생기면 state에도 변화를 줄 수 있는 코드를 작성할 수도 있다. */
// if(nextProps.text !== nextState.prevProp) {
// return {now: nextProps.text};
// }
return null; // state에 변화를 주고 싶지 않다면 null을 반환
}
/* 2. 컴포넌트가 리렌더링을 할 것인지 말 것인지를 결정하는 메소드이다.(render() 메소드 호출 유무 결정) */
shouldComponentUpdate(nextProps, nextState) {
/*
자주 사용되지는 않는 생명주기 메소드이다.
주로 성능 최적화를 목적으로 하며, 상황에 따라 리렌더링을 방지할 목적으로 사용한다.
*/
console.log('shouldComponentUpdate');
console.log(nextProps);
console.log(nextState);
/* 반드시 boolean을 반환해야 하며, false를 반환할 시 render부터 이후 생명주기 메소드 호출은 여기서 중단된다. */
return true;
}
/* 3. 컴포넌트를 렌더링 하는 메소드이다. */
render() {
console.log('render');
return (
<>
<button
onClick={() => this.setState({now: new Date().toLocaleTimeString()})}
>
현재 시간 확인하기
</button>
<h1>{this.state.now}</h1>
</>
);
}
/* 4. 컴포넌트의 변화를 실제 DOM에 반영하기 직전에 호출하는 메소드이다. */
getSnapshotBeforeUpdate(prevProps, prevState) {
/*
리액트 16.3 이후 만들어진 메소드이다.
render에서 만들어진 결과물이 브라우저에 실제 반영되기 직전에 호출되며,
해당 메소드의 반환값은 componentDidUpdate에서 전달받을 수 있다.
주로 업데이트 하기 직전의 값을 참고할 일이 있을 때 활용한다.(대표적으로 스크롤바 위치 유지)
*/
console.log('getSnapshotBeforeUpdate');
// return null;
/* componentDidUpdate의 sanpshot 인수(3번째)로 전달 된다. */
return {
message: '스냅샷 입니다.'
}
}
/* 5. 컴포넌트 업데이트 작업이 끝난 후 호출하는 메소드이다. */
componentDidUpdate(prevProps, prevState, snapshot) {
/*
리렌더링이 끝나고 화면이 update 된 후 실행된다.
업데이트가 끝난 직후이기 때문에 DOM관련 처리를 해도 되며,
컴포넌트가 이전에 가졌던 데이터를 prevProps, prevState로 접근할 수 있다.
또한, getSnapshotBeforeUpdate에서 반환하는 값을 세 번째 파라미터로 전달 받을 수도 있다.
*/
console.log('componentDidUpdate');
console.log(snapshot);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<TimePrinter/>);
</script>
컴포넌트가 DOM에서 null로 초기화될 때 호출되는 생명주기
<div id="root"></div>
<script type="text/babel">
const root = ReactDOM.createRoot(document.getElementById('root'));
class Greeting extends React.Component {
render() {
return <h1>Hello World</h1>;
}
/* 컴포넌트가 웹 브라우저 상에서 사라지기 직전에 호출하는 메소드이다. */
componentWillUnmount() {
/*componentDidMount 시점에 등록한 이벤트, 타이머, 직접 생성한 DOM이 있다면 여기서 다 null로 초기화 작업을 주로 하게 된다. */
console.log('componentWillUnmount');
}
}
root.render(<Greeting/>);
/* 5초 뒤 아무런 컴포넌트도 렌더링하지 않는 것으로 변경하여 언마운트 상황을 연출 */
window.setTimeout(() => {
root.render(null);
}, 5000);
</script>
static getDerivedStateFromError(error)
componentDidCatch(error, info)
문서화 주석
/**
@params - 함수의 매개변수 설명
@return - 반환 값 설명
*/
!!