아래 이미지를 보면 동일한 틀 즉, 컴포넌트가 반복되는 걸 확인할 수 있습니다.
map를 사용하면 list를 하나씩 만들 필요없이 갯수만큼 previeCard가 반복되어 렌더링을 하며 코드가 간편해지는 걸 확인 할 수 있습니다.
export default function PrevieCard() {
const cardInfos = [
{
imgSrc:"",
title:"",
subTitle:"",
suthor:"",
},
{
imgSrc:"",
title:"",
subTitle:"",
suthor:"",
},
{
imgSrc:"",
title:"",
subTitle:"",
suthor:"",
},
{
imgSrc:"",
title:"",
subTitle:"",
suthor:"",
}
]
}
return (
<>
{cardInfos.map((info)=> (
<PrevieCard
imgsrx={info.imgSrc}
title={info.title}
subTitle={info.subTitle}
author={info.suthor}
/>
))}
</>
)
변수에 담아서 사용할 수도 있습니다.
const result = cardInfos.map((info)=> (<PrevieCard imgsrx={info.imgSrc} title={info.title} subTitle={info.subTitle} author={info.suthor}/>))
return (
<>
{result}
</>
)
이떄 key 값이 필요한데 Key 필요한 이유는 무엇일까요?
리액트가 setState를 할때 비동기로 동작하고 연속적으로 setState를 호출했을 때 배치처리를 합니다. 그렇게 동작하는 이유는 리액트가 돔을 효율적으로 호출하기 위해서입니다.
배치 처리란
리액트에서 성능 최적화를 위해 여러 상태 업데이트를 하나의 렌더링으로 묶어 처리하는 메커니즘입니다. 이를 통해 불필요한 리렌더링을 방지합니다.
만약에 100개의 컴포넌트를 렌더링한다고 생각을 해보겠습니다. 컴포넌트가 리랜더링될 때마다 다시 맵을 돌게 되고 이전에 랜더링된 요소와 비교하여 어떤 요소가 변경되었는지 파악하고 바뀐부분만 돔에 업데이트를 시킵니다. 이 과정을 key는 어떤 요소가 변경 되었는지 파악을 하게 됩니다.
key
React가 어떤 항목을 변경, 추가 또는 삭제할 지 식별하는 것을 돕습니다.
사용하는 방법은 앨리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야합니다.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
key를 선택하기 좋은 방법은 리스트이 다른 항목들 사이에서 해당 항목을 고유하게 식별하는 문자열울 사용합니다. 이때 ID를 key로 사용합니다.
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
렌더링 한 랑목에 대한 안정적인 ID 없다면 최후의 수단으로 항목의 인덱스를 key로 사용할 수 있습니다. 항목의 순서가 바뀔수 있는 경우 key에 인덱스를 사용하는 것은 권장하지 않습니다.
const todoItems = todos.map((todo, index) =>
<li key={index}>
{todo.text}
</li>
);
자식에 대한 재귀적 처리
DOM노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성합니다.
자식 끝에 엘리먼트를 추가하면 두 트리 사이의 변경은 잘 작동합니다.
<ul>
<li>1</li>
<li>2</li>
</ul>
<ul>
<li>1</li>
<li>2</li>
<li>0</li>
</ul>
만약에 리스트의 맨 앞에 엘리먼트를 추가하는 경우에는 두 트리 변환은 형편없이 작동합니다. 왜냐하면 재랜더링 되었을 때 li의 1, 2가 같은 요소인 지 인지를 못합니다.
<ul>
<li>1</li>
<li>2</li>
</ul>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
이 문제를 해결하기 위해 key 속성을 지원합니다. 자식들이 key를 가지고 있다면, React는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인합니다.
아래 코드를 보면 2014 key를 가진 엘리먼트가 새로 추가되었고 2015와 2016 key를 가진 엘리먼트는 그저 이동만 한 것을 확인할 수 있습니다.
<ul>
<li key="2015">1</li>
<li key="2016">2</li>
</ul>
<ul>
<li key="2014">0</li>
<li key="2015">1</li>
<li key="2016">2</li>
</ul>
key로 컴포넌트 추출하겠습니다.
1. map를 돌리는 안에서 key를 지정해야합니다.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
2. 형제 사이에서만 고유한 값이어야 합니다.
배열 안에서 형제 사이에서 고유해야하고 전체 범위에서 고유할 필요는 없습니다.두 개의 다른 배열을 만들 시에만 동일한 key를 사용할 수 있습니다.
li태그와 h3, p태그는 형제는 아닙니다. 형제는 반복되는 li끼리 그리고 div끼리 형제라고 할 수 있습니다.
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}
const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Blog posts={posts} />);
이벤트 처리하기
1. 소문자 대신 캐멀 케이스를 사용합니다.
2. jsx를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달합니다.
<button onClick={activeLeasers}>
click me
</button>
3. false를 반환해도 기본동작을 방지 할 수 없습니다. 방지하고 싶다면 preventDefault를 명시적으로 호출해야합니다.
DOM 엘리먼트가 생성된 후 리스너를 추가하기 위해 addEventListener를 호출할 필요가 없습니다. 대신 엘리먼트가 처음 렌더링될 때 리스너를 제공하면 됩니다.
function Form() {
function handleSubmit(e) {
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
위에 나오는 코드에서 e는 합성 이벤트입니다.
이벤트 핸들러는 모든 브라우저에서 이벤트를 동일하게 처리하기 위한 이벤트 래퍼 SyntheticEvent 객체를 전달받습니다.
함수 컴포너틑에서 이벤트 처리하기
import React, { useState } from "react";
const Card = () => {
const [visible, setVisible] = useState(true)
const getCoupon = () =>{
alert("10할인 쿠폰");
}
const closeBanner = (e) =>{
e.stopPropagation(); // 기본동작 방지
setVisible(false)
}
return (
visible && (
<div
onClick={getCoupon} //캐멀 케이스 , jsx를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달합니다.
style={{
backgroundColor: "orange",
fontWeight: "bold",
height: "50px",
display: "flex",
justifyContent: "space-around",
alignItems: "center",
}}
>
이 곳을 클릭해서 쿠폰을 받아가세요.
<button
onClick={closeBanner}> 닫기</button>
</div>
)
);
}
export default Card;
ES6 클래스 사용하여 컴포넌트를 정의할 때, 일반적인 패턴은 이벤트 핸들러를 클래스의 메서드로 만드는 것입니다.
import React from "react";
class Toggle extends React.Component{
constructor(props){
super(props);
this.state = { isToggleOn:true}
this.handleClick = this.handleClick.bind(this) // 바인딩을 해주는 코드는 필수
}
handleClick(){
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn,
}))
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "ON" : "OFF"}
</button>
)}
}
export default Toggle;
만약에 클래스 필드 문법을 사용하고 있지 않다면, 콜백에 화살표 함수를 사용해도 됩니다.
import React from "react";
class Toggle extends React.Component{
handleClick(){
console.log('this',this);
}
render() {
return (
<button onClick={() => this.handleClick()}>
click me
</button>
)}
}
export default Toggle;
폼
사용자의 입력을 기반으로 데이터를 가지고있는 <input> <textarea> <select>을 폼이라고 합니다.
폼은 사용자가 폼을 제출하면 새로운 페이지로 이동하는 기본 HTML 폼동작을 수행하는데 동일한 동작을 원한다면 그대로 사용해도 됩니다.하지만, 대부분은 자바스크립트 함수로 폼의 제출 처리하고 사용자가 폼에 입력한 데이터에 접근하도록 하는 것이 편리하기 때문에 이 방식을 제어 컴포넌트하고 부릅니다.
제어 컴포넌트
<input> <textarea> <select> 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트합니다. 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트 됩니다.
import React, { useState } from "react";
const SimpleForm = () => {
const [name, setName] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
console.log("이름:", name);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
이름:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
</div>
<button type="submit">제출</button>
</form>
);
};
export default SimpleForm;
selected를 사용하지않고 this.state.value를 사용합니다.
<select value={this.state.value} onChange = {this.hamdleChange}?
다중 입력 제어하기
userInputs라는 객체를 사용하여 name과 emali 입력값을 관리하고 핸들러는 event.target.value를 값으로 하여 상태를 업데이트 해줍니다.
import React, { useState } from "react";
const SimpleForm = () => {
const [userInputs, setUserInputs] = useState({
name: "",
email: "",
});
const handleChange = (event) => {
const { name, value } = event.target;
setUserInputs({
...userInputs,
[name]: value,
});
};
const handleSubmit = (event) => {
event.preventDefault();
console.log("이름:", userInputs.name);
console.log("이메일:", userInputs.email);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
이름:
<input
type="text"
name="name"
value={userInputs.name}
onChange={handleChange}
/>
</label>
</div>
<div>
<label>
이메일:
<input
type="email"
name="email"
value={userInputs.email}
onChange={handleChange}
/>
</label>
</div>
<button type="submit">제출</button>
</form>
);
};
export default SimpleForm;
비제어 컴포넌트
모든 state 업데이트에 대한 이벤트 핸들러를 작성하는 대신 비제어 컴포넌트를 만들려면 ref를 사용하여 DOM에서 폼 값을 가져올 수 있습니다.
클래스 컴포넌트일 때는 ref를 사용 시에는 import {createRef} from "react"이고
함수 컴포넌트일 때는 ref를 사용 시에는 import {useRef} from "react'입니다.
아래 코드를 보면 알다시피 handleChange를 사용하지 않아도 상태 관리 없이 DOM에서 직접 값을 참조하기 때문에 코드가 매우 간결합니다.
import React, { useRef } from "react";
const SimpleForm = () => {
const inputRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
alert(`입력값: ${inputRef.current.value}`);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={inputRef} />
<button type="submit">제출</button>
</form>
);
};
export default SimpleForm;
Ref
render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공합니다.
1) 포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 떄
const handleSubmit = (event) => {
event.preventDefault();
inputRef.current.focus(); // 입력 후에도 포커스를 줍니다.
};
2) 애니메이션을 직접적으로 실행시킬 때
3) 서드 파티 DOM 라이브러리를 React와 같이 사용할 때
Ref 생성하기
React.createRef()를 통해 생성되고 ref 어트리뷰트를 통해 React 엘리먼트에 부착됩니다. 보통 컴포넌트의 인스턴스가 생성될 때 Ref를 프로퍼티로서 추가하고, 그럼으로서 컴포넌트의 인스턴스의 어느 곳에서도 Ref에 접근할 수 있게 합니다.
클래스
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
함수
import React, { useRef } from "react";
const MyComponent = () => {
const myRef = useRef(null);
return <div ref={myRef} />;
};
export default MyComponent;
Ref 접근하기
render 메서드 안에서 ref 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current어트리뷰트에 담기게 됩니다.
const node = this.myRef.current;
ref의 값은 노드의 유형에 따라 다릅니다
1) ref 어트리뷰트가 HTML 엘리먼트에 쓰였다면, 생성자에서 React.createRef()로 생성된 ref는 자신을 전달받은 DOM 엘리먼트를 current프로퍼티의 값으로서 받습니다.
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// textInput DOM 엘리먼트를 저장하기 위한 ref를 생성합니다.
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// DOM API를 사용하여 명시적으로 text 타입의 input 엘리먼트를 포커스합니다.
// 주의: 우리는 지금 DOM 노드를 얻기 위해 "current" 프로퍼티에 접근하고 있습니다.
this.textInput.current.focus();
}
render() {
// React에게 우리가 text 타입의 input 엘리먼트를
// 우리가 생성자에서 생성한 `textInput` ref와 연결하고 싶다고 이야기합니다.
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
2) ref 어트리뷰트가 커스텀 클래스 컴포넌트에 쓰였다면, ref객체는 마운트된 컴포넌트의 인스턴스를 current 프로퍼티의 값으로서 받습니다.
AutoFocusTextInput이 마운트되면 생명주기메서드인 componentDidMount가 실행되면서 CustomTextInput의 자식 컴포넌트가 가지고 있는 함수인 this.textInput.current.focusTextInput();를 부모에서 실행시킵니다.
즉, 부모(AutoFocusTextInput)에서 자식(CustomTextInput)이 가지고 있는 메서드(this.textInput.current.focusTextInput();)를 실행시킵니다.
CustomTextInput가 클래스 컴포넌트일 때에만 작동합니다.
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focusTextInput();
}
render() {
return (
<CustomTextInput ref={this.textInput} />
);
}
}
*함수 컴포넌트는 인스턴스가 없기 때문에 함수컴포넌트에 ref 어트리뷰트 사용할 수 없습니다.
Ref와 함수 컴포넌트
함수 컴포넌트는 인스턴스가 없기 때문에 함수 컨포넌트에 ref 어트리뷰트를 사용할 수 없습니다.
function MyFunctionComponent() {
return <input />;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
// 이 코드는 동작하지 않습니다.
return (
<MyFunctionComponent ref={this.textInput} />
);
}
}
그래서 함수컴포넌트에 ref를 사용할 수 있도록 forwardRef를 사용해야합니다. 또한, ref 어트리뷰트를 함수 컴포넌트에서 사용하는 것은 됩니다.
function CustomTextInput(props) {
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
ref 심화 - forwardRef
부모 컴포넌트에게 DOM ref를 공개하기
보기 드문 경우지만, 부모 컴포넌트에서 자식 컴포넌트의 DOM 노드에 접근하려 하는 경우도 있습니다. 자식 컴포넌트의 DOM 노드에 접근하는 것은 컴포넌트의 캡슐화를 파괴하기 때문에 권장되지 않습니다. 그렇지만 가끔가다 자식 컴포넌트의 DOM 노드를 포커스하는 일이나, 크기 또는 위치를 계산하는 일 등을 할 때에는 효과적인 방법이 될 수 있습니다.
자식 컴포넌트에 ref를 사용할 수 있지만, 이 방법은 자식 컴포넌트의 인스턴스의 DOM 노드가 아닌 자식 컴포넌트의 인스턴스를 가져온다는 점에서, 자식 컴포넌트가 함수 컴포넌트인 경우에는 동작하지 않는다는 점에서, 좋은 방법이 아닙니다.
함수컴포넌트에서는 ref 전달하기(ref forwarding)사용하면 됩니다. Ref 전달하기는 컴포넌트가 자식 컴포넌트의 ref를 자신의 ref로서 외부에 노출시키게 합니다.
Cat안에 있는 imgae사이즈을 forwardRef를 사용하가져오겠습니다.
부모 컴포넌트
import React from "react";
import Cat from "./Cat";
export default function CatParent() {
console.log("부모 컴포넌트 CatParent");
return (
<div>
<h4> 고양이가 세상을 구한다 ️</h4>
<div>
<Cat />
</div>
</div>
);
}
자식 컴포넌트
import React from "react";
const Cat = () => {
console.log("자식 컴포넌트 Cat");
return (
<div>
<img
src="https://static01.nyt.com/images/2016/03/30/universal/ko/well_cat-korean/well_cat-superJumbo-v2.jpg?quality=90&auto=webp"
alt="cat"
style={{ width: "150px" }}
/>
</div>
);
};
export default Cat;
부모 컴포넌트
useRef를 사용하여 자식의 DOM 접근 시도하겠습니다. 부모 컴포넌트에서 useRef를 생성하고 이를 자식 컴포넌트에 전달합니다. 하지만 이 상태에서는 자식 컴포넌트 내부의 DOM 요소를 직접 참조할 수 없습니다.
import React, { useRef } from "react";
import Cat from "./Cat";
export default function CatParent() {
console.log("부모 컴포넌트 CatParent");
const catRef = useRef();
return (
<div>
<h4> 고양이가 세상을 구한다 ️</h4>
<div>
<Cat ref={catRef} />
</div>
</div>
);
}
자식 컴포넌트
이 시점에서는 ref를 전달받아도 아무런 처리가 되지 않습니다. 이를 해결하기 위해 forwardRef를 사용해야 합니다. 자식 컴포넌트에서 forwardRef를 사용하여 부모로부터 전달받은 ref를 이미지 태그에 연결합니다.
import React, { forwardRef } from "react";
const Cat = forwardRef((props, ref) => {
console.log("자식 컴포넌트 Cat");
return (
<div>
<img
ref={ref}
src="https://static01.nyt.com/images/2016/03/30/universal/ko/well_cat-korean/well_cat-superJumbo-v2.jpg?quality=90&auto=webp"
alt="cat"
style={{ width: "150px" }}
/>
</div>
);
});
export default Cat;
버튼 클릭 이벤트로 이미지 크기 확인
부모 컴포넌트에서 버튼을 추가하고 클릭 이벤트를 통해 이미지 크기를 확인합니다. catRef.current.offsetWidth와 catRef.current.offsetHeight를 사용하여 이미지의 너비와 높이를 가져옵니다.
import React, { useRef } from "react";
import Cat from "./Cat";
export default function CatParent() {
console.log("부모 컴포넌트 CatParent");
const catRef = useRef();
const handleCheckSize = () => {
if (catRef.current) {
alert(`고양이 이미지 크기: ${catRef.current.offsetWidth}x${catRef.current.offsetHeight}`);
}
};
return (
<div>
<h4> 고양이가 세상을 구한다 ️</h4>
<div>
<Cat ref={catRef} />
<button onClick={handleCheckSize}>고양이 크기 알아보자</button>
</div>
</div>
);
}
ref 심화 - mutable
useRef
currnet 프로퍼티로 전달된 인자로 초기화된 변경 가능한 ref 객체를 반환합니다. 반환된 객체는 컨포넌트의 전 생애주기를 통해 유지 될 것입니다.
const refContainer = useRef(initialValue);
.current 프로퍼티에 변경 가능한 값을 담고 있는 상자입니다. DOM 접근 시에 refs를 사용했지만, React로 ref 샛체를 전달한다면 모드가 변경될때마다 변경된 DOM 노드에 그것의 .current 프로퍼티를 설정합니다.
그래서 ref 속성보다는 useRef()가 더 유용합니다. 그 이유는 클래스에서 인스턴스 필드를 사용하는 방법과 유사한 가변값을 유지하는데에 편리합니다.
useState에서 만든 state는 값을 변경하면 컴포넌트에 리랜더링이 일어나는데 useRef로 만든 가변할 수 있는 값은 리랜더링이 일어나지 않습니다. 아래 코드를 보며 알아보겠습니다.
처음에는 1년 후 고이 나이를 클릭해도 상태값이 변하지 않고 테스트를 클릭해야지 컴포넌트가 랜더링되면서 그제서야 값이 변합니다.
import React, { useRef, useState } from "react";
import Cat from "./Cat";
export default function CatParent() {
console.log("부모 컴포넌트 CatParent");
const catRef = useRef();
const ageRef = useRef(1);
const [state, setState] = useState(1);
return (
<div>
<h4> 고양이가 세상을 구한다 ️</h4>
<h3>나이 : {ageRef.current}살</h3>
<h4>상태값: {state}</h4>
<div>
<Cat ref={catRef} />
<button onClick={() => ageRef.currwnt = ageRef.current + 1}>1년 후 고양이 나이</button>
<button onClick={() => setState(state + 1)}}>테스트</button>
</div>
</div>
);
}
ref 심화 - callback ref
React에서 useRef를 사용하면 특정 DOM 노드나 컴포넌트 인스턴스에 접근할 수 있습니다. 하지만 useRef는 단순히 값을 저장하는 역할만 하며, ref가 변경되더라도 리렌더링을 트리거하지 않습니다.
만약 DOM 노드가 attach(연결)되거나 detach(해제)될 때 특정 코드를 실행해야 하는 경우, 콜백 Ref(Callback Ref)를 사용하는 것이 적합합니다. 콜백 Ref는 함수 형태로 작성되며, DOM 노드가 mount 또는 unmount될 때 호출됩니다.
부모컴포넌트
CatParent 컴포넌트는 catCallbackRef를 자식 컴포넌트인 <Cat />에 전달합니다, 그 후 고양이 이미지가 로드되면, 해당 DOM 노드가 catCallbackRef로 전달되어 높이를 계산하고 계산된 높이는 상태로 관리되어 화면에 표시하게 됩니다.
import React, { useRef, useState, useCallback } from "react";
import Cat from "./Cat";
export default function CatParent() {
const [age, setAge] = useState(1);
const [height, setHeight] = useState(0);
const catCallbackRef = useCallback((node) => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<div>
<h4>고양이가 세상을 구한다 🐾</h4>
<h3>나이: {age}살</h3>
<h4>키: {height}px</h4>
<div>
<Cat ref={catCallbackRef} />
<button onClick={() => setAge(prev => prev + 1)}>
1년 후 고양이 나이
</button>
<button onClick={() => setHeight(prev => prev + 1)}>
테스트
</button>
</div>
</div>
);
}
자식컴포넌트
부모로부터 전달받은 ref를 이미지 엘리먼트에 연결하고 이미지 로딩 상태를 관리합니다.
import React, { forwardRef } from "react";
const Cat = forwardRef((props, ref) => {
const [loaded, setLoaded] = useState(false)
return (
<div>
<img
ref={ref}
src="https://static01.nyt.com/images/2016/03/30/universal/ko/well_cat-korean/well_cat-superJumbo-v2.jpg?quality=90&auto=webp"
alt="cat"
style={{ width: "150px" }}
onLoad={() => {
setLoaded(true);
}
/>
</div>
);
});
export default Cat;
특징 | forward | useRef | callbackRef |
정의 | 부모 컴포넌트에서 자식 컴포넌트로 ref를 전달하기 위해 사용하는 React API. | useRef를 사용하여 .current 속성을 통해 DOM 요소나 값을 참조. | DOM 노드가 attach되거나 detach될 때 특정 작업을 수행하는 함수 기반 ref. |
사용 목적 | 자식 컴포넌트 내부의 DOM 요소나 인스턴스에 접근 가능하게 하기 위함. | DOM 요소 참조 또는 렌더링 간 값을 유지하기 위한 mutable 객체 생성. | DOM 노드의 생성/제거 시 작업 수행 및 동적 ref 관리. |
리렌더링 영향 | 리렌더링 시 ref는 그대로 유지되며 영향을 받지 않음. | .current 값 변경이 리렌더링을 트리거하지 않음. | 리렌더링 시 새로운 콜백 함수가 생성될 수 있음(useCallback으로 방지 가능). |
유연성 | 제한적: 주로 자식 컴포넌트와 DOM 요소 간 ref 전달에 사용. | 간단하고 직관적이며 대부분의 ref 작업에 적합. | 매우 유연하며 동적 노드 관리 및 상태와 결합 가능. |
성능 | 높은 성능: 단순히 ref를 전달하는 역할만 수행. | 가벼움: 렌더링 간 값 유지에 적합하며 성능 부담이 적음. | 약간의 오버헤드: 함수 실행으로 인해 성능 부담이 있을 수 있음. |
Key로 리스트 추가하기
import React, { useState } from "react";
//ToDo 컴포넌트는 단일 To-Do 항목을 렌더링합니다.
const ToDo = (props) => (
<tr>
<td>
<label>{props.id}</label>
</td>
<td>
<input />
</td>
<td>
<label>{props.createdAt.toTimeString()}</label>
</td>
</tr>
);
//ToDoList는 전체 To-Do 리스트를 관리하며, 사용자 인터페이스를 제공합니다.
export default function ToDoList() {
const date = new Date();
const todoCounter = 1;
const [state, setState] = useState({
todoCounter: todoCounter,
list: [
{
id: todoCounter,
createdAt: date,
},
],
});
// 생성 시간이 가장 빠른 순서대로 리스트를 정렬합니다.
function sortByEarliest() {
const sortedList = state.list.sort((a, b) => {
return a.createdAt - b.createdAt;
});
setState({
list: [...sortedList],
});
}
//생성 시간이 가장 늦은 순서대로 리스트를 정렬합니다.
function sortByLatest() {
const sortedList = state.list.sort((a, b) => {
return b.createdAt - a.createdAt;
});
setState({
list: [...sortedList],
});
}
//새로운 To-Do 항목을 리스트의 끝에 추가합니다.
function addToEnd() {
const date = new Date()
const nextId = state.todoCounter + 1;
const newList = [...state.list, { id: nextId, createdAt: date }];
setState({
list: newList,
todoCounter: nextId,
});
}
//새로운 To-Do 항목을 리스트의 시작에 추가합니다.
function addToStart() {
const date = new Date();
const nextId = state.todoCounter + 1;
const newList = [{ id: nextId, createdAt: date }, ...state.list];
setState({
list: newList,
todoCounter: nextId,
});
}
return (
<div>
<code>key=index</code>
<br />
<button onClick={addToStart}>Add New to Start</button>
<button onClick={addToEnd}>Add New to End</button>
<button onClick={sortByEarliest}>Sort by Earliest</button>
<button onClick={sortByLatest}>Sort by Latest</button>
<table>
<tr>
<th>ID</th>
<th />
<th>created at</th>
</tr>
{state.list.map((todo, id) => {
<ToDo key={id} {...todo} />
))}
</table>
</div>
);
}
'리액트' 카테고리의 다른 글
[React] swr과 React-Query (0) | 2025.04.11 |
---|---|
[React] SPA와 react-router-dom (0) | 2025.04.10 |
[React] useEffect 정복하기 (0) | 2025.04.07 |
[React] Lifecycle 자세히 알아보기 (0) | 2025.04.04 |
[React] State 자세히 알아보기 (0) | 2025.04.03 |