*props.childern
props.childern 사용시 사용자 지정 컴포넌트 내 <div></div>안에 있는 컨텐츠들.
props.childern children은 예약어
import './Card.css';
function Card(props){
return (
<div className="card">{props.children}</div>
)
}
export default Card;
import './ExpenseItem.css';
import Card from './Card';
import ExpenseDate from './ExpenseDate';
function ExpenseItem(props) {
return (
<Card className='expense-item'>
<ExpenseDate date={props.date}></ExpenseDate>
<div className='expense-item__description'>
<h2>{props.title}</h2>
<h2 className='expense-item__price'>${props.amount}</h2>
</div>
</Card>
);
}
export default ExpenseItem;
공통된 코드를 따로 js와 css 파일로 나누어 두면, 추후에 다른 컴포넌트와의 합성에 유리하다.
따로 빼낸 Card.js와 Card.css는 해당 css가 적용되는 또 다른 컴포넌트에서도 활용가능하다.
+ etc,
리액트는 선언형(Declarative) JavaScript 코드로 작성한다 (=강의에서 JSX코드라고 일컫음)
여기서 JSX란, '리액트 프로젝트에서만 활성화되는 특수한 비표준 구문'을 일컫는다. ex) return <div></div>
선언형이란, 원하는 결과(대상 UI 등)를 정의하고 라이브러리(React)가 단계를 구성하는 것을 일컫는다.
컴포넌트란 일반적으로 해당 컴포넌트가 사용될 때 화면에 표시되는 HTML(JSX)코드를 반환하는 JS함수를 말한다.
React를 사용해서 DOM노드에 마운트되는 하나의 루트 컴포넌트를 가진 컴포넌트 트리를 구축합니다.
하나의 루트 노드를 가지는 컴포넌트 트리 하나를 구축한다.
* State
리액트는 처음 렌더링되었을 때 모든 과정을 실행하고 끝낸다. (반복하지 않음)
그렇다면 렌더링 이후 값이나 컴포넌트를 변경하고 싶을때는 state 사용한다.
import React, { useState } from 'react';
useState는 리액트 라이브러리에서 제공하는 함수로, 컴포넌트 함수가 다시 호출되는 곳에서 변경된 값을 반영하기 위해 state로 값을 정의할 수 있게 해주는 함수
state를 사용하면 리액트가 값을 바꾸고 업데이트하게 할 수 있다.
import React, { useState } from 'react';
import './ExpenseItem.css';
import Card from '../UI/Card';
import ExpenseDate from './ExpenseDate';
const ExpenseItem = (props) => {
const [title, setTitle] = useState(props.title);
//[]사용하면 동시에 두 개 이상의 요소에 값을 할당해줄 수 있다.
//title = props.title(초기값)
//setTitle = 나중에 새로운 title을 할당하기위해 호출되는 함수(값을 업데이트하는 함수)
const clickHandler = () => {
setTitle('Updated!'); //업데이트되는 state는 바로 업데이트되는 것이 아니라 업데이트를 예약해놓은 것.
console.log(title); //그래서 콘솔에서 title을 찍었을 때 변경된 제목이 아니라 이전의 제목이 출력된다.
};
//setTitle함수를 호출하는 것은 어떤 변수에 새로운 값을 할당하는 것이 아니라
//const [title, setTitle] = useState(props.title); 구절이 메모리 어딘가에서 리액트로 관리된다.
//그리고 useState('Update'); 업데이트되는 함수를 호출할 때 이 변수는 새로운 값만 받는 것이 아니다.
//state가 변할 때, 이 컴포넌트의 함수를 다시 호출하고 싶으면 state를 업데이트하는 함수를 호출하면된다.
//근데 왜 상수 const를 사용했을까?
return (
<Card className='expense-item'>
<ExpenseDate date={props.date}></ExpenseDate>
<div className='expense-item__description'>
<h2>{title}</h2>
<h2 className='expense-item__price'>${props.amount}</h2>
</div>
<button onClick={clickHandler}>Change Title</button>
</Card>
// 버튼에 직접 이벤트를 주려면 on~을 사용해야한다.
// 버튼에 직접 함수를 적으려면 {}안에 화살표 함수 작성하면 실행 가능 !
// ex) <button onClick={()=>{console.log("Clicked!")}}>Change Title</button>
// 하지만 그때그때 함수 생성해서 실행하는 것은 비효율적임.
);
}
export default ExpenseItem;
state는 컴포넌트 인스턴스별로 나누어져 있고, event발생시에만 상태를 업데이트 할 수 있는 것이 아니라 setTimeout()등을 이용해 일정시간 이후 상태를 업데이트할수도 있고, 다양항 상황에서 업데이트할 수 있음
const [userTitle, setUserTitle] = useState('');
useState를 사용하면 현재상태, 변화된 상태를 저장할 수 있다.
이 상태를 각각 따로 저장하기 위해서는 상수 const []배열을 이용해서 각각의 값을 변수에 저장해줄 수 있다.
다만, 여러 State의 변경을 해야하는 상황이 생길 수 있는데,
import React, { useState } from 'react';
const ExpenseForm = () => {
const [enterTitle, setEnterTitle] = useState('');
const [enterAmount, setEnterAmout] = useState('');
const [enterDate, setEnterDate] = useState('');
const titleChangeHandler = (event) => {
setEnterdTitle(event.target.value);
}
const amountChangeHandler = (event) => {
setEnterdAmount(event.target.value);
}
const dateChangeHandler = (event) => {
setEnterdDate(event.target.value);
}
return(
<form>
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input type="text" onChange={titleChangeHandler} />
</div>
<div className="new-expense__control">
<label>Amount</label>
<input
type="number"
min="0.01"
step="0.01"
onChange={amountChangeHandler}
/>
</div>
<div className="new-expense__control">
<label>Date</label>
<input
type="date"
min="2019-01-01"
max="2023-12-31"
onChange={dateChangeHandler}
/>
</div>
</div>
<div className="new-expense__actions">
<button type="submit">Add Expense</button>
</div>
</form>
)
}
export defalut ExpenseForm;
변경해야하는 값들에 각각 useState()를 적용하는 방법이 있고(다수의 상태 접근 방법),
하나의 State에서 여러 값을 관리하는 방법이 있다.(하나의 State에서 객체로 접근해 관리하는 방법)
import React, { useState } from 'react';
const ExpenseForm = () => {
const [userInput, setUserInput] = useState({
enterTitle: '',
enterAmount: '',
enterDate:'',
});
const titleChangeHandler = (event) => {
setEnterdTitle({
...userInput,
enterTitle: event.target.value,
});
}
const amountChangeHandler = (event) => {
setEnterdAmount({
...userInput,
enterAmount: event.target.value,
});
}
const dateChangeHandler = (event) => {
setEnterdDate({
...userInput,
enterDate: event.target.value,
});
}
useState내에서 객체를 이용해서 여러 상태를 한번에 관리해줄 수 있다.
다만 이런 경우 상태 변경 시 유의해야하는데, Title만 변경해줄 경우 나머지 Amount와 Date의 상태가 사라져버리기 때문이다.
(하나의 key와 value만을 가져와서 사용한다면 나머지 다른 상태가 사라져버림)
userInput객체를 복사해준 뒤, 상태를 바꾸려는 Title만 오버라이딩 해주면 다른 변수들의 상태를 유지한 채 Title만 변경할 수 있다.
다만 위와같은 방법은 이전의 State에 의존하고 있는 방법이다.
이전의 상태에 의존하고 있을 경우 익명함수를 사용해서 State를 관리해주는 것이 더 좋다. ✔️
const amountChangeHandler = (event) => {
// setEnteredAmount(event.target.value);
setUserInput({
...userInput,
enteredAmount: event.target.value,
})
};
그렇다면, 이제 입력을 통해 변경된 데이터를 저장한 form 을 전송해보자.
button의 타입이 submit이기 때문에 버튼 클릭시 이벤트를 부여하지 않아도 괜찮다(onClick)
대신 최상단 <form>에 onSubmit을 부여하여 제출이 되었을 때 이벤트를 부여한다.
return (
<form onSubmit={submitHandler}>
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input type="text"
value={enteredTitle}
onChange={titleChangeHandler} />
</div>
<div className="new-expense__control">
<label>Amount</label>
<input
type="number"
min="0.01"
step="0.01"
value={enteredAmount}
onChange={amountChangeHandler}
/>
</div>
<div className="new-expense__control">
<label>Date</label>
<input
type="date"
min="2019-01-01"
max="2023-12-31"
value={enteredDate}
onChange={dateChangeHandler}
/>
</div>
</div>
<div className="new-expense__actions">
<button type="submit">Add Expense</button>
</div>
</form>
);
};
이 submitHandler함수에서는 submit이 작동됐을 시 동작하는 이벤트를 포함하고 있다.
form을 통해 전송해야하는 데이터가 한개가 아닌 다수이기 때문에 모든 데이터를 변수에 묶어주어야 한다.
const submitHandler = (event) => {
const ExpenseData = {
title: enteredTitle,
amount: enteredAmount,
date: new Date(enteredDate)
//날짜객체로 변환한 enteredDate를 전송한다.
};
console.log(ExpenseData);
};
submit이 진행될 경우 데이터가 서버에 전송되고, 페이지 리로드가 진행된다.
우리는 페이지 리로드를 막은 채로 값이 전송되기를 원한다.
//preventDefault사용하면 기본적으로 보내지는 자바스크립트를 방지할 수 있음
//페이지 리로드 방지할 수 있음
event.preventDefault();
preventDefalut()를 사용해서 페이지 리로드를 방지한다.
setEnteredTitle('');
setEnteredAmount('');
setEnteredDate('');
submit이후 input에 데이터가 남아있지 않기를 원하기 때문에 빈칸으로 바꾸어준다.
const submitHandler = (event) => {
//preventDefault사용하면 기본적으로 보내지는 자바스크립트를 방지할 수 있음
//페이지 리로드 방지할 수 있음
event.preventDefault();
const ExpenseData = {
title: enteredTitle,
amount: enteredAmount,
date: new Date(enteredDate)
//날짜객체로 변환한 enteredDate를 전송한다.
};
console.log(ExpenseData);
//submit이후에 빈칸으로 만들어주기 위함
setEnteredTitle('');
setEnteredAmount('');
setEnteredDate('');
};
* 상향식 컴포넌트 통신
부모 >> 자식에게 데이터를 넘기는 방법은 props를 사용하면 가능하지만,
자식 >> 부모에게 데이터를 넘기는 방법은 ?
함수를 통해서 전달한다.
사용자 지정 컴포넌트 ExpenseForm에 on속성(함수)을 추가하고(이름은 마음대로) 그 on속성에 함수를 추가해준다.(함수에함수)
const NewExpense = () => {
return(
<div className="new-expense">
<ExpenseForm onSaveExpenseData={saveExpenseDataHandler}>
</div>
)
}
그 후 saveExpenseDataHandler함수에서 파라미터로 가져온 데이터를 넣어준다.
const saveExpenseDataHandler = (enteredExpenseData) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString()
}
}
파라미터명은 사용자가 임의로 지정해주고, 이 파라미터는 자식컴포넌트에서 전달한 데이터를 뜻한다.
const expenseData = {
title: enteredTitle,
amount: enteredAmount,
date: new Date(enteredDate)
//날짜객체로 변환한 enteredDate를 전송한다.
};
//자식 컴포넌트에서 onSaveExpenseData함수에 expenseData를 인자로 전달하면,
//부모 컴포넌트에서 정의된 동일한 함수 onSaveExpenseData에서 파라미터로 데이터를 받아올 수 있다.
props.onSaveExpenseData(expenseData);
상향식 컴포넌트 통신을 위한 단계,
1. 부모컴포넌트에서 사용자지정컴포넌트에 on함수를지정해준다. (이거는 관례일뿐 꼭 on을 붙이지 않아도 된다 ex. onSaveExpenseData)
2. 지정해준 on함수가 가리키는 포인터 함수를 설정해준다. (ex.saveExpenseDataHandler) = 인자를 가리키는 포인터 함수
3. 부모컴포넌트 내에 해당 포인터 함수를 선언해주고, 파라미터로 자식컴포넌트의 데이터를 받아온다.(파라미터의 이름은 사용자가 임의로 지정해주면 된다)
4. 보내고 싶은 데이터 객체를 생성해준다.(받아온 파라미터 객체 복사 후, 추가해주고싶은 데이터의 값 추가해주기)
5. 자식컴포넌트 내 함수에 props를 받아와 props.on함수(부모로 넘길 데이터) 를 지정해준다.
*형제컴포넌트간 데이터와 상태의 전송
같은 부모 컴포넌트를 두고 있는 형제 컴포넌트간에는 직접적으로 데이터를 전송하고 받아올 수 있는 수단이 없음.
이런 경우, 가장 가까이에 있는 부모 컴포넌트를 이용해서 데이터를 주고받는 방법을 사용해야한다.
[React]함수형 컴포넌트에 props 전달
이번 포스팅에서는 React에서 함수형 컴포넌트에 props를 전달하는 방법과 props의 개념에 대해 소개합니다. 목차 props 개념 부모 컴포넌트가 자식 컴포넌트에 props 전달 자식 컴포넌트가 부모 컴포
developer-talk.tistory.com
이러한 과정을 상태 끌어올리기(Lifting State Up) 라고 표현한다.
상태 끌어올리기 라는 것은 상위 컴포넌트의 상태를 변경 하는 함수 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다. 는 의미이다.
State 끌어올리기 – React
A JavaScript library for building user interfaces
ko.reactjs.org
*Stateless 컴포넌트 vs Stateful 컴포넌트
Stateless 컴포넌트 = presentational = dumb 컴포넌트 : 어떤 State도 갖지 않고, 단지 데이터를 출력하기 위해 존재
Stateful 컴포넌트 = smart : State가 존재하는 컴포넌트
대부분의 컴포넌트들은 Stateless 컴포넌트
+etc)
useState는 정확히 두 요소로 이루어진 배열을 반환한다.
두번째 요소는 언제나 상태에 새 값을 설정하기 위해 호출하는 함수로, 해당 함수를 호출하면 React가 컴포넌트를 재평가하도록 트리거한다.
이전 상태에 의존하는 상태를 업데이트하려면, 상태업데이트 함수의 "함수양식"을 대신 사용해야한다. ✔️
'FE·Client > React' 카테고리의 다른 글
[React] Portals, Ref, uncontrolled component (0) | 2023.01.11 |
---|---|
[React] 리액트 컴포넌트 스타일링, styled-components, 미디어쿼리, CSS 모듈 (0) | 2023.01.09 |
[React] 동적 배열 할당, 조건부 렌더링 (1) | 2023.01.06 |
[React] React 초기 설정, JSX, toISOString, props, toLocalString() (1) | 2022.12.27 |
[React] React npm start시 오류 해결(Node.js 버전 이슈, react-script 버전 이슈) (0) | 2022.12.14 |