제어 컴포넌트와 비제어 컴포넌트

이윤우·2024년 3월 21일
0

React

목록 보기
10/10

1. useState와 useRef

1) useState(initialState)

useState는 컴포넌트에 state 변수를 추가할 수 있게 해주는 React 훅입니다.

1. 매개 변수

  • initialState: 초기 state를 설정할 값

2. 반환값(두 개의 값을 가진 배열)

  • 첫 번째 원소: 현재 state. (첫 번째 렌더링 중에는 전달한 initialState와 일치)
  • 두 번쨰 원소: state를 다른 값으로 업데이트하고 리렌더링을 촉발할 수 있는 set(설정자)함수
import { useState } from "react";

export default function InputSample() {
  const [text, setText] = useState("");

  const onChange = (e) => {
    setText(e.target.value);
  };
  const onReset = (e) => {
    setText("");
  };

  return (
    <div>
      <input onChange={onChange} value={text} />
      <button onClick={onReset}>초기화</button>
      <div>
        <b>: {text}</b>
      </div>
    </div>
  );
}

3. 주의사항

  • useState는 훅이므로 컴포넌트의 최상위 레벨이나 커스텀훅에서만 호출할 수 있습니다. 반복문이나 조건문 안에서는 호출할 수 없습니다. 필요한 경우 새 컴포넌트를 추출하고 state를 그 안으로 옮기세요.

4. 사용법
1️⃣ 이전 state를 기반으로 state 업데이트 하기
age가 42라고 가정합니다. 이 핸들러는 setAge(age + 1)를 세 번 호출합니다.

function handleClick() {
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
}

그러나 클릭을 해보면 age는 45가 아니라 43이 됩니다! 이는 set함수를 호출해도 이미 실행 중인 코드에서 age state 변수가 업데이트되지 않기 때문입니다. 따라서 각 setAge(age + 1) 호출은 setAge(43)이 됩니다.
이 문제를 해결하려면 다음 state 대신 setAge업데이터 함수를 전달할 수 있습니다.

function handleClick() {
  setAge(a => a + 1); // setAge(42 => 43)
  setAge(a => a + 1); // setAge(43 => 44)
  setAge(a => a + 1); // setAge(44 => 45)
}

여기서 a => a + 1은 업데이터 함수입니다. 이 함수는 대기 중인 state를 가져와서 다음 state를 계산합니다.
React는 업데이터 함수를 큐에 넣습니다. 그러면 다음 렌더링 중에 동일한 순서로 호출합니다.
1. a => a + 1은 대기 중인 state로 42를 받고 다음 state로 43을 반환합니다.
2. a => a + 1은 대기 중인 state로 43를 받고 다음 state로 44을 반환합니다.
3. a => a + 1은 대기 중인 state로 44를 받고 다음 state로 45을 반환합니다.
대기 중인 다른 업데이트가 없으므로, React는 결국 45를 현재 state로 저장합니다.

항상 업데이터를 사용하는 것이 더 좋을까?

사용해서 나쁠 건 없지만 항상 그래야만 하는 것은 아닙니다.
대부분의 경우, 이 두 가지 접근 방식에는 차이가 없습니다. 다만 동일한 이벤트 내에서 여러 업데이트를 수행하는 경우에는 업데이터가 도움이 될 수 있습니다. state 변수 자체에 접근하는 것이 어려운 경우에도 유용합니다.

2️⃣ 초기 state 다시 생성하지 않기
React는 초기 state를 한 번 저장하고 다음 렌더링부터는 이를 무시합니다.

function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos());
}

createInitalTodos()의 결과는 초기 렌더링에만 사용되지만, 여전히 모든 렌더링에서 이 함수를 호출하게 됩니다. 이는 큰 배열을 생성하거나 값비싼 계산을 수행하는 경우 낭비가 될 수 있습니다.
아 문제를 해결하기 위해 useState초기화 함수를 전달합니다.
함수를 호출한 경과인 createInitialTodos()가 아니라 함수 자체인 createInitialTodos를 전달합니다.

3️⃣ key로 state 재설정하기
컴포넌트에 다른 key를 전달하여 컴포넌트의 state를 재설정할 수 있습니다.

import { useState } from 'react';

export default function App() {
  const [version, setVersion] = useState(0);

  function handleReset() {
    setVersion(version + 1);
  }

  return (
    <>
      <button onClick={handleReset}>Reset</button>
      <Form key={version} />
    </>
  );
}

function Form() {
  const [name, setName] = useState('Taylor');

  return (
    <>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
      />
      <p>Hello, {name}.</p>
    </>
  );
}

2) useRef()

useRef는 렌더링에 필요하지 않은 값을 참조할 수 있는 React훅입니다.

1. 매개변수

  • initialValue: ref객체의 current 프로퍼티 초기 설정값입니다. 여기에는 어떤 유형의 값이든 지정할 수 있습니다. 이 인자는 초기 렌더링 이후부터는 무시됩니다.

2. 반환값(단일 프로퍼티를 가진 객체)

  • current: 처음에는 전달한 initialValue로 설정됩니다. 나중에는 다른 값으로 바꿀 수 있습니다. ref 객체를 JSX 노드의 ref 속성으로 React에 전달하면 React는 current 프로퍼티를 설정합니다.

3. 주의사항

  • ref.current 프로퍼티는 state와 달리 변이할 수 있습니다. 그러나 렌더링에 사용되는 객체를 포함하는 경우 해당 객체를 변이해서는 안 됩니다.
  • ref.current 프로퍼티를 변경해도 React는 컴포넌트를 다시 렌더링하지 않습니다. ref는 일반 JavaScript 객체이기 때문에 React는 사용자가 언제 변경했는지 알지 못합니다.

4. 사용법
1️⃣ ref로 값 참조하기
ref를 변경해도 리렌더링을 촉발하지 않습니다. 즉, ref는 컴포넌트의 시각적 출력에 영향을 미치지 않는 정보를 저장하는 데 적합합니다.

예를 들어, Interval ID를 저장했다가 나중에 불러와야 하는 경우 ref에 넣을 수 있습니다.

function handleStartClick() {
  const intervalId = setInterval(() => {
    // ...
  }, 1000);
  intervalRef.current = intervalId;
}

나중에 ref에서 해당 interval ID를 읽어 해당 interval을 취소할 수 있습니다.

function handleStopClick() {
  const intervalId = intervalRef.current;
  clearInterval(intervalId);
}

2️⃣ ref로 DOM 조작하기

먼저 초기값이 null인 ref객체를 선언합니다.

import { useRef } from 'react';

function MyComponent() {
  const inputRef = useRef(null);
  // ...
}

그런 다음 ref 객체를 ref 속성으로 조작하려는 DOM 노드의 JSX에 전달합니다.

// ...
return <input ref={inputRef} />;

React가 DOM 노드를 생성하고 화면에 그린 후, React는 ref 객체의 current 프로퍼티를 DOM 노드로 설정합니다. 이제 DOM 노드 <input>에 접근해 focus()와 같은 메서드를 호출할 수 있습니다.

function handleClick() {
  inputRef.current.focus();
}

노드가 화면에서 제거되면 React는 current 프로퍼티를 다시 null로 설정합니다.

2. 제어 컴포넌트와 비제어 컴포넌트

1) 제어 컴포넌트(Controlled Component)

Push & Single Source of Truth

  • React에 값이 완전히 제어되는 Input Element
  • State를 값으로 넘기고 그 State를 다룰 수 있는 핸들러를 콜백으로 넘긴다.
const ControlledInput = () => {
  const [value, setValue] = useState('');
  const handleChange = ({target: { value }}) => {
    setValue(value);
  };
  
  return <input value={value} onChange={handleChange} />;
}

2) 비제어 컴포넌트(Uncontrolled Component)

Pull

  • 전통적인 HTML처럼 DOM에 제어되는 input Element
  • 오직 사용자만 값과 상호작용
const UnControlledInput = () => {
  const inputRef = useRef(null);
  const handleSubmit = (event) => {
    event.preventDefault();
    // inputRef.current.value
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} />
      <button type="submit">Submit</button>
    </form>
  );
};

3) 비교

제목1ControlledUncontrolled
지향점PushPull
성능잦은 리렌더링구현하는 방법에 따라 다르지만 성능에 이점이 있을 수 있음
동적 핸들링상태를 중심으로 개발하기 때문에 상태 변경에 따른 제어가 용이함.
이벤트 핸들러 관리 비용 지불
DOM을 직접 조작하기 때문에 제어가 어려움
관찰&유효성 검사상태 변경 => UI 자동으로 업데이트. 유저와 많은 인터렉션하는데 용이함DOM을 직접 조작하기 때문에 핸들링이 어렵고 값 비싼 비용 지불
1회성 값 검색
(ex. onSubmit)
제출 이벤트 발생시 호출
실시간 필드 유효성 검사 지원
조건별 제출 버튼 비활성화 제어 기능
특정 입력 형식(input type) 강제
하나의 데이터를 여러 방식의 이벤트로 입력 가능
동적 입력 가능

0개의 댓글