본문 바로가기

React/공식문서(구버전)

[리액트 공식문서] 주요 개념 9 ~ 12

9. 폼

- React application에서 사용자가 폼을 제출할 때, JavaScript 함수로 1) 폼의 제출을 처리하고 2) 사용자가 폼에 입력한 데이터에 접근하도록 하는 것이 편리하다. 이를 "제어 컴포넌트(controlled components)"라고 불리는 기술을 이용하는 것이다.

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

- HTML에서 <input>, <textarea>, <select> 와 같은 폼 태그들은 사용자의 입력을 기반으로 자신의 데이터를 관리하고 업데이트 한다.(value attribute를 통해 자체적인 Data를 갖고 그 데이터는 DOM에 저장된다)
- React에서는 변경할 수 있는 데이터가 일반적으로 컴포넌트의 state 속성에 유지되며 setState( )에 의해 업데이트 된다. (=> input 태그에 입력된 데이터가 컴포넌트 state로 관리된다는 말이다)

- React state를 "신뢰 가능한 단일 출처(single source of truth)"로 만들고 폼을 렌더링 하는 React 컴포넌트는 폼에 발생하는 사용자의 입력값을 제어한다. 이러한 방식으로 React state에 의해 값이 제어되는 입력 폼 엘리먼트를 "제어 컴포넌트(controlled component)"라고 한다.

 

function NameForm() {
  const [value, setValue] = useState("");
  
  function handleChange(e) {
    setValue(e.target.value);
  }
  
  function handleSubmit(e) {
    alert("A name was submitted: " + {value});
    e.preventDefault();
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <lable>
        Name:
        <input type="text" value={value} onChange={handleChange} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}


- 제어 컴포넌트로 사용하면, input의 값은 항상 React state에 의해 결정된다.

2) textarea 태그

- HTML에서 <textarea> 엘리먼트는 텍세트를 자식으로 정의한다.
- React에서 <textarea>는 value 어트리뷰트를 대신 사용한다.

 

function EssayForm() {
  const [value, setValue] = uesState("Please write an essay about your favorite DOM element")

  function handleChange(event) {
    setValue(event.target.value);
  }
  
  function handleSubmit(event) {
    alert("An essay was submitted: " + {value});
    event.preventDefault();
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <label>
        Essay:
        <textarea value={value}
                  onChange={handleChange}
        />
      </label>
      <input type="submit" value="Submit" />
    </form>
  )
}


3) select 태그

- React에서 selected 어트리뷰트를 사용하는 대신 최상단 select 태그에 value 어트리뷰트를 사용한다.

 

function FlavorForm() {
  const [valeu, setValue] = useState();
  
  function handleChange(event) {
    setValue(event.target.value);
  }
  
  function handleSubmit(event) {
    alert("Your favorite flavor is: " + {value});
    event.preventDefault();
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <label>
        Pick your favorite flavor:
      </label>
      <select value={value} onChange={handleChange}>
        <option value="grapefruit">Grapefruit</option>
        <option value="lime">Lime</option>
        <option value="coconut">coconut</option>
        <option value="mango">Mango</option>
      </select>
      <input type="submit" value="Submit" />
    </form>
  )
}


- 전반적으로 <input type="text">, <textarea>, <select> 모두 제어 컴포넌트를 구현하는데 value 어트리뷰트를 허용합니다.
⚠️ select 태그에 multiple 옵션을 허용한다면 value 어트리뷰트에 배열을 전달할 수 있다.

 

<select multiple={true} value={['B', 'C']}>


4) file input 태그

 

<input type="file" />


- 값이 읽기 전용이기 때문에 React에서는 비제어 컴포넌트이다.

5) 다중 입력 제어하기

- 여러 input 엘리먼트를 제어해야할 때, 각 엘리먼트에 name 어트리뷰트를 추가하고 event.target.name 값을 통해 핸들러가 어떤 작업을 할 지 선택할 수 있게 해준다.

 

function Reservation() {
  const [inputs, setInputs] = useState({ isGoing: true, numberOfGuests: 2 });
  
  function handleInputChange(event) {
    const target = event.target;
    const name = target.name;
    const value = target.type === "checkbox" ? target.checked : target.value;
    
    setInputs({ ...inputs, [name]: value });
  }
  
  return (
    <form>
      <label>
        Is going:
        <input 
          name="isGoing"
          type="checkbox"
          checked={inputs.isGoing}
          onChange={handleInputChange}
        />
      </label>
      
      <br />
      
      <label>
        Number of guests:
        <input 
          name="numberOfGuests"
          type="number"
          value={inputs.numberOfGuests}
          onChange={handleInputChange}
        />
      </label>
    </form>
  )
}

 

6) 제어되는 Input Null 값

- 제어 컴포넌트에 value prop을 지정하면 의도하지 않는 한 사용자가 변경할 수 없다. value를 설정했는데 여전히 수정할 수 있다면 실수로 value를 undefined나 null로 설정했을 수 있다.

10. State 끌어올리기

- 종종 동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 필요가 있다. 이럴 때는 가장 가까운 공통 조상으로 state를 끌어올리는 것이 좋다.
- 이번 섹션에서 주어진 온도에서 물의 끓는 여부를 추정하는 온도 계산기를 만들어보자

1) BoinlingVerdict 컴포넌트 만들기

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil!</p>
  }
  return <p>The water would not boil!</p>
}


2) Calculator 컴포넌트 만들기

function Calculator() {
  const [temperature, setTemperature] = useState("");
  
  function handleChange(e) {
    setTemperature(e.target.value);
  }
  
  return (
    <fieldset>
      <legend>Enter temperature in Celsius:</legend>
      <input 
        value={temperature}
        onChange={handleChange} />
      <BoilingVerdict
        celsius={parseFloat(temperature)} />
    </fieldset>
  )
}


3) 두 번째 Input 추가하기

- Calculator 컴포넌트에서 TemperatureInput 컴포넌트를 추출해보자.

 

const scaleNames = {
  c: "Celsius",
  f: "Fahrenheit"
}

function TemperatureInput() {
  const [temperature, setTemperature] = useState("");
  
  function handleChange(e) {
    setTemperature(e.target.value);
  }
  
  return (
    <fieldset>
      <input
        value={temperature}
        onChange={handleChange} />
    </fieldset>
  )
}

function Calculator() {
  return (
    <TemperatureInput scale="c" />
    <TemperatureInput scale="f" />
  )
}

 

4) 변환 함수 작성하기

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

funtion tryCovert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return "";
  }
  
  const output = convert(input);
  const rounded = Math.round(output * 100) / 1000;
  return rounded.toString();
}

 

5) State 끌어올리기

- 현재는 두 TemperatureInput 컴포넌트가 각각의 입력값을 각자의 state에 독립적으로 저장하고 있다.

- 우리는 두 입력값이 서로의 것고 동기화된 상태로 있길 원한다.

- React에서 state를 공유하는 일은 그 값을 필요로 하는 컴포넌트 간의 가장 가까운 공통 조상으로 state를 끌어올림으로써 이뤄낼 수 있다. 이렇게 하는 방법을 "state 끌어올리기" 라고 부른다. 

- Temperature 컴포넌트가 개별적으로 가지고 있던 지역 state를 지우는 대신 부모 컴포넌트인 Calculator로 그 값을 옮겨놓아보자.

- Calculator가 공유될 state를 소유하고 있으면 이 컴포넌트는 두 입력 필드 Temperature 컴포넌트의 현재 온도에 대한 "진리의 원천"이 된다. 2개의 TemperatureInput 컴포넌트가 부모로부터 부모의 state가 props로 전달되기 때문에, 두 입력 필드는 항상 동기화된 상태를 유지할 수 있게 된다.

 

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return '';
  }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}

function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

function TemperatureInput({ temperature, scale, onTemperatueChange }) {
  function handleChange(e) {
    onTemperatueChange(e.target.value);
  }
  
  return (
    <fieldset>
      <legend>Enter temperature in {scaleNames[scale]}:</legend>
      <input value={temperature}
             onChange={handleChange} />
    </fieldset>
  )
}

function Calculator() {
  const [temperature, setTemperature] = React.useState("");
  const [scale, setScale] = React.useState("c");
  
  function handleCelsiusChange(temperature) {
    setTemperature(temperature);
    setScale("c");
  }

  function handleFahrenheitChange(temperature) {
    setTemperature(temperature);
    setScale("f");
  }

  const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
  const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

  return (
    <div>
      <TemperatureInput
        scale="c"
        temperature={celsius}
        onTemperatureChange={handleCelsiusChange} />
      <TemperatureInput
        scale="f"
        temperature={fahrenheit}
        onTemperatureChange={handleFahrenheitChange} />
      <BoilingVerdict
        celsius={parseFloat(celsius)} />
    </div>
  );
}

6) 교훈

- React 애플리케이션 안에서 변경이 일어나는 데이터에 대해서는 "진리의 원천(source of truth)"을 하나만 두어야 한다.

- 보통 state는 렌더링에 그 값을 필요로 하는 컴포넌트에 먼저 추가된다. 그러고 나서 다른 컴포넌드도 역시 그 값이 필요하게 되면 그 값을 그들의 가장 가까운 공통조상으로 끌어올리면 된다. 

11. 합성 vs 상속

- 종종 동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 필요가 있다. 이럴 때는 가장 가까운 공통 조상으로 state를 끌어올리는 것이 좋다.
- 이번 섹션에서 주어진 온도에서 물의 끓는 여부를 추정하는 온도 계산기를 만들어보자

1) 컴포넌트에서 다른 컴포넌트를 담기

- 어떤 컴포넌트들은 어떤 자식 엘리먼트가 들어올 지 미리 예상할 수 없는 경우가 있습니다. 이러한 컴포넌트에서는 특수한 children prop을 사용하여 자식 엘리먼트 출력에 그대로 전달하는 것이 좋다.

 

function FancyBorder(props) {
  return (
    <div className={"FancyBorder FancyBorder-" + props.color}>
      {props.children}
    </div>
  )
}

2) 특수화

- 때로는 어떤 컴포넌트의 "특수한 경우"인 컴포넌트를 고려해야 하는 경우가 있다. React에서는 이 역시 합성을 통해 해결할 수 있다. 더 "구체적인" 컴포넌트가 일반적인 컴포넌트를 렌더링하고 props를 통해 내용을 구성한다.

 

function Dialog(props) { // 일반적인 컴포넌트
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() { // 구체적인 컴포넌트
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />
  );
}

12.  React로 생각하기

1) 1단계: UI를 컴포넌트 계층 구조로 나누기

- 모든 컴포넌트(와 하위 컴포넌트)의 주변에 박스를 그리고 그 각각에 이름을 붙인다.

- 하나의 컴포넌트는 한 가지 일을 하도록 나눈다. 

- 데이터 모델이 적절하게 만들어졌다면, UI(컴포넌트 구조)가 잘 연결된다. 각 컴포넌트가 데이터 모델의 한 조각을 나타내도록 분리한다.

2) 2단계: React로 정적인 버전 만들기

- 데이터 모델을 가지고 UI를 렌더링은 되지만 아무 동작도 없는 버전을 만든다.

- 데이터 모델을 렌더링하는 앱의 정적 버전을 만들기 위해 다른 컴포넌트를 재사용하는 컴포넌트를 만들고 props 를 이용해 데이터를 전달해준다. (정적 버전을 만들기 위해 state를 사용하지 말자!)

3) 3단계: UI State에 대한 최소한의 (하지만 완전한) 표현 찾아내기

- UI를 상호작용하게 만들려면 기반 데이터 모델을 변경할 수 있는 방법이 있어야 한다. 이를 React는 state를 통해 변경한다.

- 애플리케이션을 올바르게 만들기 위해서는 애플리케이션에서 필요로 하는 변경 가능한 state의 최소 집합을 생각해야 한다.

- 어떤 state가 되어야 할까? 1) 부모로부터 props를 통해 전달되면 X 2) 시간이 지나도 변하지 않는다면 X 3) 컴포넌트 안의 다른 state나 props를 가지고 계산가능하다면 X

4) 4단계: State가 어디에 있어야 할 지 찾기

- state를 기반으로 렌더링하는 모든 컴포넌트를 찾는다.

- 공통 소유 컴포넌트 (common owner component)를 찾는다. (계층 구조 내에서 특정 state가 있어야 하는 모든 컴포넌트들의 상위에 있는 하나의 컴포넌트).

- 공통 혹은 더 상위에 있는 컴포넌트가 state를 가져야 한다.

- state를 소유할 적절한 컴포넌트를 찾지 못했다면, state를 소유하는 컴포넌트를 하나 만들어서 공통 소유 컴포넌트의 상위 계층에 추가한다.

5) 5단계: 역방향 데이터 흐름 추가하기



참고 자료
https://react.vlpt.us/basic/09-multiple-inputs.html

 

9. 여러개의 input 상태 관리하기 · GitBook

9. 여러개의 input 상태 관리하기 지난 튜토리얼에서 우리는 input 상태를 관리하는 방법에 대하여 알아보았는데요, 이번에는 input 이 여러개일때는 어떻게 관리해야 하는지 알아보겠습니다. 우선

react.vlpt.us

https://www.youtube.com/watch?v=LD1LyvCCbCg