import React, { useRef } from 'react'; // useRef 임포트 필수!
const UseRef_example = () => {
// useRef(0) => counterRef 변수에 초기값 0인 ref 객체 생성
const counterRef = useRef(0);
// counterUp 함수 => 버튼 클릭 시 실행됨
const counterUp = () => {
// counterRef.current => counterRef 객체의 현재 값을 추적
// current는 ref 객체의 속성으로, 값에 접근하거나 변경할 때 사용함
counterRef.current += 1; // counterRef의 값을 1 증가시킴
console.log('Counter:', counterRef.current); // 변경된 값 콘솔창 출력
};
return (
<div>
<p>ref 값 : {counterRef.current}</p>
<button onClick={counterUp}>counterUp 버튼</button>
</div>
);
};
export default UseRef_example;

counterUp 버튼을 눌러 보자!

counterUp 버튼을 누르면 콘솔창에는 1씩 증가하는 것을 볼 수 있다.
하지만 화면에 보이는 ref 값은 바뀌지 않고 여전히 0인 것을 확인할 수 있다.
① 먼저 할 일을 입력하고 + 버튼을 누르면, todos 배열에 새로운 할 일을 추가하는 onInsert 함수를 만들어주자!
이때, useState 말고 useRef를 사용할 거임!
왜❓🤔
👉 드래그로 확인
지금 todos 배열을 보면 각 할 일마다 1, 2, 3이라는 고유 id 값이 있는데 이 id 값은 화면에 렌더링될 필요가 없기 때문!
useRef로 nextId라는 참조 객체 생성하고 id 3번 이후에 추가되어야 하니까 id 초기값은 4로 설정해준다.
② + 버튼을 누르면 실행 될 onInsert 함수를 만들 차례!
이때 useCallback으로 감싸줄 거임!
왜❓🤔
👉 코드 하단에 retrun ( )을 보면 < TodoInsert onInsert= {onInsert} /> 요렇게 props로 전달되는데 이때, 불필요함 함수 재생성을 방지해 불필요한 렌더링을 막기 위해 useCallback으로 감싸주는 것!
import React, { useState, useRef, useCallback } from 'react';
import TodoTemplate from '../components/TodoTemplate';
import TodoInsert from '../components/TodoInsert';
import TodoList from '../components/TodoList';
const UseRef_todoApp_App = () => {
/* todos 배열 안에 들어 있는 객체
=> 각 항목의 고유 id, 내용, 완료 여부를 알려 주는 값이 있음.
*/
const [todos, setTodos] = useState([
{
id: 1,
text: '리액트 기초 알아보기',
checked: true,
},
{
id: 2,
text: '컴포넌트 스타일링해 보기',
checked: true,
},
{
id: 3,
text: '일정 관리 앱 만들어 보기',
checked: false,
},
]);
// nextId라는 useRef 객체를 생성
// 초기값은 4
const nextId = useRef(4);
const onInsert = useCallback(
// text 값을 받아 todo 객체를 생성해 todos 배열에 추가
(text) => {
const todo = {
id: nextId.current, // nextId.current 값을 사용해 고유 ID 생성
text, // 입력받은 text 값
checked: false, // 기본 checked 값은 false
};
// 새로운 배열을 setTodos 함수에 전달해 todos 상태를 업데이트
// concat => 원본 배열을 변경하지 않고, 새로운 배열을 반환
setTodos(todos.concat(todo)); // 새로운 todo 배열을 todos 배열에 추가
nextId.current += 1; // useRef로 생성된 객체 nextId의 current 속성 값에 1씩 더함
},
[todos], // 의존성 배열 => todos 배열이 변경될 때만 onInsert 함수가 새로 생성됨
);
return (
<TodoTemplate>
{/* <TodoInsert /> */}
<TodoInsert onInsert={onInsert} />
{/* <TodoList /> */}
{/* todos 배열을 TodoList로 전달 */}
<TodoList todos={todos} />
</TodoTemplate>
);
};
export default UseRef_todoApp_App;
③ Todo 앱의 입력 폼에 작성하는 컴포넌트도 작성해보자!
사용자가 입력한 할 일을 받아서 부모 컴포넌트로 전달하는 역할을 한다.
이 컴포넌트는 useState 훅을 사용해 입력값을 관리하고,
마찬가지로 useCallback을 사용해 함수들이 불필요하게 재생성되지 않도록 최적화시켰다.
import React, { useState, useCallback } from 'react';
import { MdAdd } from 'react-icons/md'; // 아이콘 사용
import '../TodoInsert.scss';
const UseRef_todoApp_TodoInsert = ({ onInsert }) => {
// value => 현재 상태 값 (사용자가 입력한 값).
// setValue => value를 업데이트하는 함수.
const [value, setValue] = useState(''); // 초기값은 빈문열
// onChange 함수 => input 필드에서 값이 변경될 때마다 호출됨
const onChange = useCallback((e) => {
// e.target.value => input에 입력된 값을 전달 받고
// 그 값을 value 상태로 업데이트함.
setValue(e.target.value);
}, []);
// onSubmit 함수 => 폼을 제출했을 때 호출됨
const onSubmit = useCallback(
(e) => {
// 자식 컴포넌트인 UseRef_todoApp_TodoInsert에서 이 onInsert(value)를 호출하면,
// 입력된 텍스트가 부모 컴포넌트로 전달됨.
onInsert(value);
setValue(''); // 새 할일 제출 후, value 값 초기화
// submit 이벤트는 브라우저에서 새로고침을 발생시킴.
// 이를 방지하기 위해 preventDefault 함수를 호출함.
e.preventDefault();
},
[onInsert, value],
);
return (
<form className="TodoInsert" onSubmit={onSubmit}>
{/* placeholder => 입력 필드(input, textarea 등)의 힌트 텍스트 */}
<input
placeholder="할 일을 입력하세요"
value={value}
onChange={onChange}
/>
<button type="submit">
<MdAdd />
</button>
</form>
);
};
export default UseRef_todoApp_TodoInsert;

새로운 할 일을 입력하고 + 제출 버튼을 누르기 전을 보면
기존 todos 3개가 나와 있음.
새로운 할 일을 추가하고, + 제출 버튼까지 눌러보자!


확인하면 + 제출 버튼을 누름과 동시에 새 할일 제출 후, value 값 초기화되어 placeholder="할 일을 입력하세요"가 나와있고 onInsert가 호출되면서 TodoListItem이 새롭게 추가된 것을 볼 수 있다.