73일차 - react tutorial 5탄

·2024년 3월 27일

react

  • 리액트는 batch update(일괄업데이트)를 한다

setState

  • state는 일반 변수와 다르게 값이 변하게 되면 렌더링이 일어난다. 즉, 값이 변하게 되면 연관있는 컴포넌트들이 다시 렌더링이되어 화면이 바뀌게 된다. state와 함께 사용되는 함수는 setState이다. setState는 state 값을 변경시켜주는 함수이다
  • 바뀐 상태값을 반영하는데 이 넘버는, 콘솔로그에 있는건 반영되지 않는다.
  • 상태가 업데이트 되가전의 값을 출력하고 ,화면에는 상태가업데이트 된 후의 값을 출력
  • 즉시 다시실행이 아니다. 얘를 호출해도 다시 그냥 반영되지 않는다.
  • state값을 변화시킬 함수
  • 연속 이벤트 처리

    setNumber를 호출하는 두 가지 방식에는 중요한 차이가 있습니다.

    1. setNumber(number + 1);
      이 방식에서는 현재 상태인 number를 직접 참조하여 새로운 상태 값을 계산합니다. 이 경우, setNumber가 호출될 때마다 number의 현재 값에 1을 더한 값을 새로운 상태 값으로 설정합니다. 그러나 이 방식의 문제점은 상태 업데이트가 비동기적으로 이루어진다는 것입니다. 만약 여러 상태 업데이트가 동시에 일어나면, 모든 업데이트가 같은 number 값에 기반하기 때문에 예상치 못한 결과를 초래할 수 있습니다. 예를 들어, 연속적인 클릭이 빠르게 발생하면, 각 업데이트가 같은 초기 number 값에서 시작할 수 있습니다.
    1. setNumber((_number) => _number + 1);
      이 방식에서는 업데이터 함수를 사용합니다. 여기서 _number는 현재 상태의 가장 최신 값을 나타냅니다. 이 방식을 사용하면, React는 상태 업데이트를 실행할 때 현재의 상태 값을 받아서 이를 기반으로 다음 상태 값을 계산합니다. 이는 여러 상태 업데이트가 연속으로 발생할 때 각각의 업데이트가 이전 업데이트가 완료된 후의 최신 상태를 기반으로 실행되도록 보장합니다. 따라서, 이 방식은 상태 업데이트가 비동기적으로 이루어지는 환경에서 더 안정적이며 예측 가능한 결과를 제공합니다.

    간단히 말해, 첫 번째 방식은 현재 상태를 직접 참조하여 새 상태를 설정하는 반면, 두 번째 방식은 상태 업데이트를 위한 함수를 사용하여 현재 상태의 가장 최신 값을 기반으로 새 상태를 계산합니다. 두 번째 방식은 상태 업데이트가 연속적으로 발생하는 경우에 더욱 안정적인 결과를 보장합니다.

setTodos

// v1
setTodos([...todos, newTitle.trim()]);
setTodos([...todos, newTitle.trim()]);

// v2
setTodos((_todos) => [...todos, newTitle.trim()]);
setTodos((_todos) => [...todos, newTitle.trim()]);

이 코드에서 발생하는 현상은 React의 상태 업데이트와 이벤트 처리 방식 때문입니다.

React에서 useState 훅을 사용하여 상태를 관리할 때, 상태 업데이트는 비동기적으로 이루어집니다. 즉, setNumber(number + 1)을 호출하면 React는 이 변경을 예약하고, 이벤트 처리 함수가 완료된 후에야 상태를 실제로 업데이트합니다.

문제의 핵심은 console.logsetNumber 호출 직후에 바로 실행되는데, 이때는 상태 업데이트가 아직 반영되지 않았기 때문입니다. 따라서, console.log는 업데이트 이전의 number 상태 값을 출력합니다.

하지만, 버튼을 클릭할 때마다 React는 상태를 업데이트하고 컴포넌트를 다시 렌더링합니다. 이 렌더링 과정에서는 새로운 상태 값이 반영되어 화면에 표시되므로, 버튼에 표시된 숫자는 최신 상태 값을 반영하여 1, 2, 3, 4 등으로 증가합니다.

간단히 말해, console.log는 상태가 업데이트 되기 전의 값을 출력하고, 화면에는 상태가 업데이트 된 후의 값을 출력하기 때문에 이러한 차이가 발생합니다.

todoListItem에 수정 기능 추가

내생각의 흐름

  • 내 기억에 필요했던건 삼항 연산자를 통해서 참이면 수정할 수 있는 input창을 보여주고 거짓이면 수정버튼만을 보여줬던 것 같다!
  • 그렇기에 필요한건, 참거짓을 가려줄 editModeStatus, 수정한 내용을 담을 inputTodoValue를 추가적으로 변수 선언해주고 수정버튼만을 보여줄 readView와 수정폼을 보여줄 editView를 만들어줬다.
const TodoListItem = ({ todo, todos, index, setTodos }) => {
	console.log(`index : ${index}`);

	const [editModeStatus, setEditModeStatus] = useState(false);
	const [inputTodoValue, setInputTodoValue] = useState("");

	const modifyTodo = () => {
		if (todo == inputTodoValue) return;

		if (inputTodoValue == "") {
			setInputTodoValue(todo);
		}

		setTodos(
			todos.map((_todo, _index) => (_index == index ? inputTodoValue : _todo))
		);
		setEditModeStatus(false);
	};

	const removeTodo = () => {
		const newTodos = todos.filter((_, _index) => index != _index);
		setTodos(newTodos);
	};

	const editView = (
		<>
			<div className="flex gap-x-3">
				<input
					className="input input-bordered"
					type="text"
					placeholder={todo}
					value={inputTodoValue}
					onChange={(e) => setInputTodoValue(e.target.value)}
				/>
				<button onClick={modifyTodo}>수정 완료</button>
				&nbsp;
				<button onClick={() => setEditModeStatus(false)}>수정 취소</button>
			</div>
		</>
	);

	const readView = (
		<>
			<button
				className="btn btn-outline btn-accent"
				onClick={() => setEditModeStatus(true)}
			>
				수정
			</button>
		</>
	);

	return (
		<>
			<li className="flex items-center gap-x-3">
				<span>
					{index + 1}번 째 할일 : {todo}
				</span>
				<button className="btn btn-outline btn-secondary" onClick={removeTodo}>
					삭제
				</button>
				{editModeStatus ? editView : readView}
			</li>
		</>
	);
};

react 차이점

const Num1Button = ({ num, setNum }) => {
	const onClick = () => {
		setNum(num + 1);
		setNum(num + 1);
	};

	return <button onClick={onClick}>증가</button>;
};

const Num2Button = ({ setNum }) => {
	const onClick = () => {
		setNum((num) => num + 1);
		setNum((num) => num + 1);
	};

	return <button onClick={onClick}>증가</button>;
};

const App = () => {
	const [num1, setNum1] = useState(0);
	const [num2, setNum2] = useState(0);

	return (
		<>
			num1 : {num1} <Num1Button num={num1} setNum={setNum1} />
			<hr />
			num2 : {num2} <Num2Button setNum={setNum2} />
		</>
	);
};

todoList 구현 부분연습해보기!

재시작, 새 할일 입력, 리스팅, 2차원 데이터

const NewTodoForm = ({todos,addTodo:_addTodo}) => {
	const [newTodoTitle, setNewTodoTitle] = useState("");
	
	const addTodo = () => {
		if(newTodoTitle.trim().length == 0) return;
		
		const id = (todos.length >= 0 ? todos.length + 1: todos.length);
		const title = newTodoTitle;
		const newTodo = {
			id,
			title
		};
		_addTodo(newTodo);
		setNewTodoTitle('');
		
	}
	
	return (
		<>
			<div className="flex items-center gap-x-3">
				<input
					className="input input-bordered"
					type="text"
					placeholder="새 할일 입력해"
					value={newTodoTitle}
					onChange={(e) => setNewTodoTitle(e.target.value)}
				/>
				<button className="btn btn-primary" onClick={addTodo}>할 일 추가</button>
			</div>
		</>
	);
};

const TodoList = (todos) => {
	return <>
		{JSON.stringify(todos)}
		</>
}

const App = () => {
	const [todos, setTodos] = useState([]);

	const addTodo = (newTodo) => {
		setTodos([...todos, newTodo]);
	};

	return (
		<>
			<NewTodoForm addTodo={addTodo} todos={todos}/>
			<hr/>
			<TodoList todos={todos} />
		</>
	);
};

할 일 목록 UI 수정 및 아이템 삭제 기능

  • 리액트(React), addTodo 매개변수 변경

const NewTodoForm = ({ addTodo: _addTodo }) => {
	const [newTodoTitle, setNewTodoTitle] = useState("");

	const addTodo = () => {
		if (newTodoTitle.trim().length == 0) return;
		const title = newTodoTitle.trim();
		_addTodo(title);
		setNewTodoTitle("");
	};

	return (
		<>
			<div className="flex items-center gap-x-3">
				<input
					className="input input-bordered"
					type="text"
					placeholder="새 할일 입력해"
					value={newTodoTitle}
					onChange={(e) => setNewTodoTitle(e.target.value)}
				/>
				<button className="btn btn-primary" onClick={addTodo}>
					할 일 추가
				</button>
			</div>
		</>
	);
};

const TodoList = (todos) => {
	return <>{JSON.stringify(todos)}</>;
};

const App = () => {
	const [todos, setTodos] = useState([]);
	const [lastTodoId, setLastTodoId] = useState(0);

	const addTodo = (title) => {
		const id = lastTodoId + 1;

		const newTodo = {
			id,
			title
		};

		setTodos([...todos, newTodo]);

		setLastTodoId(id);
	};

	return (
		<>
			<NewTodoForm addTodo={addTodo} />
			<hr />
			<TodoList todos={todos} />
		</>
	);
};

주석달아서 설명해보기!

console.clear();

import React, { useState } from "https://cdn.skypack.dev/react@18";
import ReactDOM from "https://cdn.skypack.dev/react-dom@18";

// 할일을 입력하는 폼 컴포넌트
const TodoWriteForm = ({ addTodo: _addTodo }) => {
	const [newTodoTitle, setNewTodoTitle] = useState(""); // 새 할일의 제목을 저장하는 state

	// 새로운 할일을 추가하는 함수
	const addTodo = () => {
		if (newTodoTitle.trim().length == 0) return; // 입력된 값이 없으면 함수 종료
		_addTodo(newTodoTitle); // 부모 컴포넌트로 새로운 할일을 전달
		setNewTodoTitle(""); // 입력 필드 초기화
	};

	return (
		<>
			<div className="flex gap-x-3">
				<input
					className="input input-bordered"
					type="text"
					placeholder="할 일 적어"
					value={newTodoTitle}
					onChange={(e) => setNewTodoTitle(e.target.value)} // 입력 값 변경 시 state 업데이트
				/>
				<button className="btn btn-primary" onClick={() => addTodo()}>
					할 일 추가
				</button>
			</div>
		</>
	);
};

// 할일 목록의 각 항목을 표시하는 컴포넌트
const TodoListItem = ({
	todo,
	index,
	removeTodo: _removeTodo,
	modifyTodo: _modifyTodo
}) => {
	const [editMode, setEditMode] = useState(false); // 수정 모드 상태를 저장하는 state
	const [newTodoTitle, setNewTodoTitle] = useState(todo); // 수정된 할일 제목을 저장하는 state

	// 할일을 삭제하는 함수
	const removeTodo = () => {
		_removeTodo(index); // 부모 컴포넌트로 삭제할 할일의 인덱스 전달
	};

	// 할일을 수정하는 함수
	const modifyTodo = () => {
		if (newTodoTitle.trim().length == 0) return; // 수정된 값이 없으면 함수 종료
		_modifyTodo(index, newTodoTitle.trim()); // 부모 컴포넌트로 수정된 할일의 인덱스와 내용 전달
		cancleToReadMode(); // 수정 모드를 취소하고 읽기 모드로 변경
	};

	// 수정 모드를 취소하고 읽기 모드로 변경하는 함수
	const cancleToReadMode = () => {
		setNewTodoTitle(todo); // 수정된 내용 초기화
		setEditMode(false); // 수정 모드 비활성화
	};

	// 수정 모드로 변경하는 함수
	const changeEditMode = () => {
		setEditMode(true); // 수정 모드 활성화
	};

	return (
		<>
			<li className="flex items-center gap-x-3">
				<span>{index + 1}번 째 할일 :</span>
				{editMode ? ( // 수정 모드인지 확인하여 렌더링
					<>
						<input
							type="text"
							className="input input-bordered"
							placeholder="수정 사항 적어"
							value={newTodoTitle}
							onChange={(e) => setNewTodoTitle(e.target.value)} // 수정된 값 업데이트
						/>
						<button className="btn btn-secondary" onClick={modifyTodo}>
							수정
						</button>
						<button className="btn btn-secondary" onClick={cancleToReadMode}>
							수정 취소
						</button>
					</>
				) : (
					<>
						{todo}
						<button className="btn btn-secondary" onClick={changeEditMode}>
							수정
						</button>
					</>
				)}

				<button className="btn btn-secondary" onClick={removeTodo}>
					삭제
				</button>
			</li>
		</>
	);
};

// 할일 목록을 표시하는 컴포넌트
const TodoList = ({ todos, removeTodo, modifyTodo }) => {
	return (
		<div>
			{todos.length == 0 ? (
				<h5>님 할일 없음????</h5>
			) : (
				<>
					<h5>새 할일</h5>
					<nav>
						<ul>
							{todos.map((todo, index) => (
								<TodoListItem
									key={index}
									todo={todo}
									index={index}
									removeTodo={removeTodo}
									modifyTodo={modifyTodo}
								/>
							))}
						</ul>
					</nav>
				</>
			)}
		</div>
	);
};

// 전체 애플리케이션 컴포넌트
const App = () => {
	const [todos, setTodos] = useState([]); // 할일 목록을 저장하는 state

	// 새로운 할일을 추가하는 함수
	const addTodo = (newTitle) => {
		if (newTitle.trim().length == 0) return; // 입력된 값이 없으면 함수 종료
		setTodos([...todos, newTitle.trim()]); // 새로운 할일을 추가하여 state 업데이트
	};

	// 할일을 삭제하는 함수
	const removeTodo = (index) => {
		const newTodos = todos.filter((_, _index) => _index != index); // 해당 인덱스의 할일을 제외한 새로운 배열 생성
		setTodos(newTodos); // 새로운 할일 목록으로 state 업데이트
	};

	// 할일을 수정하는 함수
	const modifyTodo = (index, todo) => {
		const newTodos = todos.map((_todo, _index) => (_index != index ? _todo : todo)); // 해당 인덱스의 할일 수정
		setTodos(newTodos); // 새로운 할일 목록으로 state 업데이트
	};

	return (
		<>
			<TodoWriteForm addTodo={addTodo} /> {/* 할일을 추가하는 폼 */}
			<hr />
			<TodoList todos={todos} removeTodo={removeTodo} modifyTodo={modifyTodo} /> {/* 할일 목록 */}
		</>
	);
};

ReactDOM.render(<App />, document.getElementById("root")); // 애플리케이션 렌더링

참고자료

동기적, 비동기적

  • 동기적이란?
    어떤 작업을 요청했을 때 그 작업이 종료될때 까지 기다린 후 다음 작업을 수행하는 방식
  • 비동기적이란?
    어떤 작업을 요청했을 때 그 작업이 종료될때 까지 기다리지 않고 다른 작업을 하고 있다가, 요청했던 작업이 종료되면 그에 대한 추가 작업을 수행하는 방식

TODO

  • 리액트 복습(혼자 TodoList 처음부터 만들어보기)
  • 재료공구 게시글 리스트 피그마 퍼블리싱

느낀점

  • 리액트와 확실히 친해진 느낌이 든다! 예전보다 막 그렇게 낯설지가 않아!
profile
우당탕탕 연이의 개발일기

0개의 댓글