728x90
리액트 공식문서 학습기록
https://react.dev/learn
You Might Not Need an Effect
불필요한 Effect를 제거하는 방법
- 렌더링을 위해 데이터를 변환하는데는 Effect가 필요하지 않음. ex) 데이터 필터링 -> 컴포넌트 최상위에서 변환하셈
- 사용자 이벤트를 처리하기 위해 Effect가 필요하지 않음. ex) /api/buy 요청을 보냈을때 -> 구매버튼 클릭 이벤트 핸들러에서 무슨일이 일어났는지 정확히 알 수 있음.
props나 상태 기반으로 상태 업데이트
상태가 변경되면 어차피 리렌더링이 일어나니 불필요한 작업을 줄이자가 핵심인듯
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// fullName은 어차피 리렌더링 후에 노출되므로 Effect를 쓰지 않아도 됨
const fullName = firstName + ' ' + lastName;
}
비용이 많이 드는 계산 캐싱
useMemo를 사용하여 반복되는 계산을 캐싱해줌.
import { useMemo, useState } from 'react';
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
const visibleTodos = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);
}
props 변경 시 모든 상태 재설정
key를 활용하자
export default function ProfilePage({ userId }) {
return <Profile userId={userId} key={userId} />;
}
function Profile({ userId }) {
// 컴포넌트가 키로 설정되어 있어 키 변동 시 자동으로 초기화됨
const [comment, setComment] = useState('');
}
props 변경 시 일부 상태 조정
렌더링 시 계산해라
function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selectedId, setSelectedId] = useState(null);
const selection = items.find((item) => item.id === selectedId) ?? null;
}
이벤트 핸들러 간 논리 공유
코드가 Effect 또는 이벤트 핸들러에 있어야하는지 명확하지 않은 경우. -> 컴포넌트가 사용자에게 표시되었기 때문에 실행되어야 하는 코드에만 Effect를 쓸것.
이 예제는 페이지가 표시되었기때문이 아니고 사용자가 버튼을 누른거에 맞춰 표시되어야함.
function ProductPage({ product, addToCart }) {
function buyProduct() {
addToCart(product);
showNotification(`Added ${product.name} to the shopping cart!`);
}
function handleBuyClick() {
buyProduct();
}
function handleCheckoutClick() {
buyProduct();
navigateTo('/checkout');
}
// ...
}
POST 요청 보내기
function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
// 화면에 표시되고 실행
useEffect(() => {
post('/analytics/event', { eventName: 'visit_form' });
}, []);
function handleSubmit(e) {
e.preventDefault();
// 이벤트 핸들러로 실행
post('/api/register', { firstName, lastName });
}
// ...
}
계산 체인
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
// 렌더링시 계산됨
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
if (isGameOver) {
throw Error('Game already ended.');
}
// 이벤트 핸들러에서 다음 상태때 계산됨
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount <= 3) {
setGoldCardCount(goldCardCount + 1);
} else {
setGoldCardCount(0);
setRound(round + 1);
if (round === 5) {
alert('Good game!');
}
}
}
}
}
어플리케이션 초기화
컴포넌트 바깥에서 실행 혹은 바깥에서 변수 받아오기
let didInit = false;
function App() {
useEffect(() => {
if (!didInit) {
didInit = true;
loadDataFromLocalStorage();
checkAuthToken();
}
}, []);
// ...
}
상태 변경에 대한 부모 컴포넌트 알림
function Toggle({ onChange }) {
const [isOn, setIsOn] = useState(false);
function updateToggle(nextIsOn) {
// 여러개의 이벤트의 상태를 한번에 처리
setIsOn(nextIsOn);
onChange(nextIsOn);
}
function handleClick() {
updateToggle(!isOn);
}
function handleDragEnd(e) {
if (isCloserToRightEdge(e)) {
updateToggle(true);
} else {
updateToggle(false);
}
}
// ...
}
부모에게 데이터 전달
props를 써라. 그래야 추적이 간단하다.
function Parent() {
const data = useSomeAPI();
return <Child data={data} />;
}
function Child({ data }) {
// ...
}
외부 스토어 구독
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
function useOnlineStatus() {
// useSyncExternalStore 를 사용하여 외부 스토어를 구독한다.
return useSyncExternalStore(
subscribe, // 같은 함수에 대해 재구독하지 않는다.
() => navigator.onLine, // How to get the value on the client
() => true // How to get the value on the server
);
}
function ChatIndicator() {
const isOnline = useOnlineStatus();
// ...
}
데이터 가져오기
아래 예제처럼 정리하여 마지막 요청을 제외한 응답을 무시하게 만들기
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [page, setPage] = useState(1);
useEffect(() => {
let ignore = false;
fetchResults(query, page).then((json) => {
if (!ignore) {
setResults(json);
}
});
return () => {
ignore = true;
};
}, [query, page]);
function handleNextPageClick() {
setPage(page + 1);
}
// ...
}
과제 1
단순화하기
👉 todos와 showActive만 상태로 관리하면 됨. 불필요한 상태를 제거하자!
import { useState } from 'react';
import { initialTodos, createTodo } from './todos.js';
const TodoList = () => {
const [todos, setTodos] = useState(initialTodos);
const [showActive, setShowActive] = useState(false);
const activeTodos = todos.filter((todo) => !todo.completed);
const visibleTodos = showActive ? activeTodos : todos;
return (
<>
<label>
<input
type="checkbox"
checked={showActive}
onChange={(e) => setShowActive(e.target.checked)}
/>
Show only active todos
</label>
<NewTodo onAdd={(newTodo) => setTodos([...todos, newTodo])} />
<ul>
{visibleTodos.map((todo) => (
<li key={todo.id}>
{todo.completed ? <s>{todo.text}</s> : todo.text}
</li>
))}
</ul>
<footer>{activeTodos.length} todos left</footer>
</>
);
};
export default TodoList;
const NewTodo = ({ onAdd }) => {
const [text, setText] = useState('');
const handleAddClick = () => {
setText('');
onAdd(createTodo(text));
};
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={handleAddClick}>Add</button>
</>
);
};
과제 2
컴포넌트에서 목록을 다시 계산하는 useEffect를 제거하기
👉 useMemo를 사용하자!
import { useState, useMemo } from 'react';
import { initialTodos, createTodo, getVisibleTodos } from './todos.js';
const TodoList = () => {
const [todos, setTodos] = useState(initialTodos);
const [showActive, setShowActive] = useState(false);
const [text, setText] = useState('');
const visibleTodos = useMemo(
() => getVisibleTodos(todos, showActive),
[todos, showActive]
);
function handleAddClick() {
setText('');
setTodos([...todos, createTodo(text)]);
}
return (
<>
<label>
<input
type="checkbox"
checked={showActive}
onChange={(e) => setShowActive(e.target.checked)}
/>
Show only active todos
</label>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={handleAddClick}>Add</button>
<ul>
{visibleTodos.map((todo) => (
<li key={todo.id}>
{todo.completed ? <s>{todo.text}</s> : todo.text}
</li>
))}
</ul>
</>
);
};
export default TodoList;
과제 3
Effect 제거하고 다른 방법 찾아보기
👉 입력 폼을 외부 컴포넌트로 내보내고 props로 전달하도록 변경
import { useState } from 'react';
const EditContact = (props) => {
return <EditForm {...props} key={props.savedContact.id} />;
};
export default EditContact;
const EditForm = ({ savedContact, onSave }) => {
const [name, setName] = useState(savedContact.name);
const [email, setEmail] = useState(savedContact.email);
return (
<section>
<label>
Name:{' '}
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
<label>
Email:{' '}
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<button
onClick={() => {
const updatedData = {
id: savedContact.id,
name: name,
email: email,
};
onSave(updatedData);
}}
>
Save
</button>
<button
onClick={() => {
setName(savedContact.name);
setEmail(savedContact.email);
}}
>
Reset
</button>
</section>
);
};
과제 4
thank 메세지와 폼 전송은 별개임
👉 불필요한 이펙트 제거
import { useState, useEffect } from 'react';
const Form = () => {
const [showForm, setShowForm] = useState(true);
const [message, setMessage] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
setShowForm(false);
sendMessage(message);
}
if (!showForm) {
return (
<>
<h1>Thanks for using our services!</h1>
<button onClick={() => {
setMessage('');
setShowForm(true);
}}>
Open chat
</button>
</>
);
}
return (
<form onSubmit={handleSubmit}>
<textarea
placeholder="Message"
value={message}
onChange={e => setMessage(e.target.value)}
/>
<button type="submit" disabled={message === ''}>
Send
</button>
</form>
);
}
const sendMessage = (message) => {
console.log('Sending message: ' + message);
}
'공부공부 > React' 카테고리의 다른 글
[react 공식문서] 28 Separating Events from Effects (0) | 2024.02.14 |
---|---|
[react 공식문서] 27 Lifecycle of Reactive Effects (0) | 2024.02.14 |
[react 공식문서] 25 Synchronizing with Effects (0) | 2024.02.14 |
[react 공식문서] 24 Manipulating the DOM with Refs (0) | 2024.02.14 |
[react 공식문서] 23 Referencing Values with Refs (0) | 2024.02.14 |