본문 바로가기

4. Node.js | React.js

4/22(토) IT K-DT(-) / 11.폼~13.합성vs상속

11. 폼

HTML 폼 엘리먼트는 폼 엘리먼트 자체가 내부 상태를 가지기 때문에, 

React의 다른 DOM 엘리먼트와 다르게 동작함.

예를 들어, 순수한 HTML에서 이 폼은 name을 입력받음.


<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

이 폼은 사용자가 폼을 제출하면 새로운 페이지로 이동하는 기본 HTML 폼 동작을 수행함.

React에서 동일한 동작을 원한다면 그대로 사용하면 됩니다.

대부분 JavaScript 함수로 폼의 제출을 처리하고 사용자가 폼에 입력한 데이터에 접근하도록 하는 것을 추천.

이를 위한 표준 방식은 “제어 컴포넌트(controlled components)“라고 불리는 기술을 이용하는 것.

11-1. 제어 컴포넌트 (Controlled Component)

HTML에서 <input>, <textarea>, <select>와 같은 폼 엘리먼트는 

일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트함.

React에서는 변경할 수 있는 state가 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트됨.

우리는 React state를 “신뢰 가능한 단일 출처(single source of truth)“로 하여 두 요소의 결합이 가능.

이후 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어함.

이렇게 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트(controlled component)“라고 함.

예를 들어, 이전 예시가 전송될 때 이름을 기록하길 원한다면 

폼을 제어 컴포넌트(controlled component)로 작성할 수 있음.


class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

value attribute는 폼 엘리먼트에 설정되므로 표시되는 값은 항상 this.state.value가 되고 React state는 신뢰 가능한 단일 출처 (single source of truth)가 됩니다. React state를 업데이트하기 위해 모든 키 입력에서 handleChange가 동작하기 때문에 사용자가 입력할 때 보여지는 값이 업데이트됨.

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

다른 UI 엘리먼트에 input의 값을 전달하거나 다른 이벤트 핸들러에서 값을 재설정할 수 있음.

11-2. textarea 태그

HTML에서 <textarea> 엘리먼트는 텍스트를 자식으로 정의함.


<textarea>
  Hello there, this is some text in a text area
</textarea>

React에서 <textarea>는 value attribute를 대신 사용.

<textarea>를 사용하는 폼은 한 줄 입력을 사용하는 폼과 비슷하게 작성할 수 있음.


class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Please write an essay about your favorite DOM element.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('An essay was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Essay:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

this.state.value를 생성자에서 초기화 → textarea는 일부 텍스트를 가진 상태로 시작됨.

11-3. select 태그

HTML에서 <select>는 드롭 다운 목록을 만듦.

예) 과일 드롭 다운 목록을 만드는 HTML의 예시

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

selected 옵션이 존재 → Coconut 옵션이 초기값이 됨.

React에서는 selected attribute를 사용하는 대신 최상단 select태그에 'value attribute'를 사용.

한 곳에서 업데이트만 하면 되기 때문에 제어 컴포넌트에서 사용하기가 더욱 편리함.


<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

전반적으로 <input type="text">, <textarea> 및 <select> 모두 유사하게 동작함.

모두 제어 컴포넌트를 구현하는데 value attribute를 허용함.

select 태그에 multiple 옵션을 허용할 시, value attribute에 배열을 전달할 수 있음.


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

11-4. file input 태그

HTML에서 <input type="file">은

파일을 저장소에서 서버로 업로드하거나, File API를 통해 JavaScript로 조작함.
<input type="file" /> 값이 읽기 전용이므로, React에서는 비제어 컴포넌트임.

11-5. 다중 입력 제어

여러 input 엘리먼트를 제어해야하는 경우

각 엘리먼트에 name attribute를 추가하고,

event.target.name 값을 통해 핸들러가 어떤 작업을 할 지 선택이 가능하도록 해줌.

예)


class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

input 태그 name과 일치하는 state의 업데이트 목적으로 computed property name 구문을 사용함.


this.setState({
  [name]: value
});

또한, setState()는 자동적으로 현재 state에 일부 state를 병합함 → 바뀐 부분에 대해서만 호출.

11-6. 제어되는 Input Null 값

제어 컴포넌트에 'value prop'을 지정하면 의도하지 않는 한 사용자가 변경할 수 없음.

value를 설정했는데 여전히 수정이 가능한 상황 → value를 undefined나 null로 설정했을 수 있음.

예)


ReactDOM.createRoot(mountNode).render(<input value="hi" />);

setTimeout(function() {
  ReactDOM.createRoot(mountNode).render(<input value={null} />);
}, 1000);

11-7. 제어 컴포넌트의 대안

데이터를 변경할 수 있는 모든 방법에 대해 이벤트 핸들러를 작성.

React 컴포넌트를 통해 모든 입력 상태를 연결해야 함.

특히 기존의 코드를 React로 변경할 경우나,

React가 아닌 라이브러리와 React 애플리케이션을 통합할 때 어려움을 겪을 수 있음.

이러한 경우에 입력 폼을 구현하기 위한 대체 기술인 '비제어 컴포넌트'의 활용이 가능.

11-8. 완전한 해결책

유효성 검사, 방문한 필드 추적 및 폼 제출 처리와 같은 해결이 필요한 경우

→ Formik을 사용 (제어 컴포넌트 및 state 관리에 기초)

12. State 끌어올리기

종종 동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 경우,

가장 가까운 공통 조상으로 state를 끌어올리는 것을 추천함.

예) BoilingVerdict(섭씨온도를 의미하는 celsius prop를 받아서 이 온도가 물이 끓기에 충분한지 여부를 출력)라는 이름의 컴포넌트를 사용하여 주어진 온도에서 물의 끓는 여부를 추정하는 온도 계산기를 만듦. 

 

1) BoilingVerdict 컴포넌트 생성


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

 

2) Calculator 컴포넌트 생성

→ 온도를 입력할 수 있는 <input>을 렌더링하고 그 값을 this.state.temperature에 저장함.
또한 현재 입력값에 대한 BoilingVerdict 컴포넌트를 렌더링.


class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    return (
      <fieldset>
        <legend>Enter temperature in Celsius:</legend>
        <input
          value={temperature}
          onChange={this.handleChange} />
        <BoilingVerdict
          celsius={parseFloat(temperature)} />
      </fieldset>
    );
  }
}


3) 두 번째 Input 추가
섭씨/화씨 입력 필드의 추가 및 두 필드 간 동기화 상태를 유지
→ Calculator에서 TemperatureInput 컴포넌트를 추출.

→ 또한 "c" 또는 "f"의 값을 가질 수 있는 scale prop를 추가.


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

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

위의 코드에 따라 Calculator가 분리된 두 개의 온도 입력 필드를 렌더링하도록 할 수 있음.


class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

위의 코드에 따라 두 개의 입력 필드를 가짐.

둘 중 한 곳에 온도를 입력하더라도 다른 하나는 갱신되지 않는 문제가 있음.

→ 두 입력 필드 간에 동기화 상태가 유지되지 않는 상황.

 

현재 입력된 온도 정보가 TemperatureInput 안에 숨겨져 있으므로 Calculator의 값을 알 수 없음.

→ Calculator에서 BoilingVerdict도 역시 보여줄 수 없는 상황.

4) 변환 함수 작성
섭씨를 화씨로, 또는 그 반대로 변환해주는 함수를 작성.


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

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

두 함수는 숫자를 변환함.

'temperature 문자열'과 '변환 함수'를 인수로 하여 문자열을 반환하는 또 다른 함수를 작성.

또한 하나의 입력값에 기반해 나머지 입력값을 계산하는 용도로 사용할 예정.
→ 잘못된 temperature 값에 대해 빈 문자열을 반환하고 값을 소수점 세 번째 자리로 반올림하여 출력.


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();
}

예를 들어,

tryConvert('abc', toCelsius)는 빈 문자열을 반환하고, tryConvert('10.22', toFahrenheit)는 '50.396'을 반환함.


5) State 끌어올리기
현재는 두 TemperatureInput 컴포넌트가 각각의 입력값을 각자의 state에 독립적으로 저장하고 있음.


class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    // ...

그러나 두 입력값이 서로의 것과 동기화된 상태여야 함.

섭씨온도 입력값을 변경할 경우 화씨온도 입력값 역시 변환된 온도를 반영할 수 있어야 하며,

그 반대의 경우에도 마찬가지여야 함.

React에서 state의 공유 → 컴포넌트 간 가장 가까운 공통 조상으로 state를 끌어올려 가능(state 끌어올리기). TemperatureInput이 개별적으로 가지고 있던 지역 state를 지우는 대신 Calculator로 그 값을 이동.

Calculator가 공유될 state를 소유하고 있으면 

이 컴포넌트는 두 입력 필드의 현재 온도에 대한 “진리의 원천(source of truth)“이 됨. 

→ 두 입력 필드 서로 간에 일관된 값의 유지 가능.

두 TemperatureInput 컴포넌트의 props가 같은 부모인 Calculator로부터 전달 → 두 입력 필드는 항상 동기화

TemperatureInput 컴포넌트에서 this.state.temperature를 this.props.temperature로 대체할 예정.

지금은 this.props.temperature가 이미 존재한다고 가정하며, 추후 이 값을 Calculator로부터 받음.


  render() {
    // Before: const temperature = this.state.temperature;
    const temperature = this.props.temperature;
    // ...

props는 읽기 전용이므로,

temperature가 지역 state였을 때는 값의 변경 시 TemperatureInput의 this.setState()만을 호출함.

→ 이제 temperature가 부모로부터 prop로 전달되므로 TemperatureInput은 값을 제어할 능력이 없음.

React에서는 보통 이 문제를 컴포넌트를 “제어” 가능하게 만드는 방식으로 해결함.

DOM <input>이 value와 onChange prop를 건네받는 것과 비슷한 방식으로,

사용자 정의된 TemperatureInput 역시 temperature와 onTemperatureChange props를

자신의 부모인 Calculator로부터 받을 수 있음.

TemperatureInput에서 온도를 갱신해야 할 경우, this.props.onTemperatureChange를 호출.


  handleChange(e) {
    // Before: this.setState({temperature: e.target.value});
    this.props.onTemperatureChange(e.target.value);
    // ...

사용자 정의 컴포넌트에서 temperature와 onTemperatureChange prop의 이름이 의미를 갖진 않음.

→어떤 이름이건 사용할 수 있음.

onTemperatureChange prop는 부모 컴포넌트인 Calculator로부터 temperature prop와 함께 제공됨.

이를 이용해 자신의 지역 state를 수정해서 변경사항을 처리 → 변경된 값을 받은 두 필드는 모두 리렌더링됨. 


TemperatureInput 컴포넌트에 대해 지역 state를 제거하고, this.props.temperature로 읽어오도록 변경.

state를 변경할 경우,this.setState() 대신 this.props.onTemperatureChange()를 호출.


class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.onTemperatureChange(e.target.value);
  }

  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

Calculator 컴포넌트에서, temperature와 scale의 현재 입력값을 이 컴포넌트의 지역 state에 저장함.

이것은 입력 필드들로부터 “끌어올린” state이며 그들에 대한 “진리의 원천(source of truth)“으로 작용.

또한 두 입력 필드를 렌더링하기 위해서 알아야 하는 모든 데이터를 최소한으로 표현한 것.

섭씨 입력 필드에 37을 입력할 때 Calculator 컴포넌트의 state 상황.


{
  temperature: '37',
  scale: 'c'
}

이후 화씨 입력 필드의 값을 212로 수정한 후 Calculator 컴포넌트의 state 상황.


{
  temperature: '212',
  scale: 'f'
}

가장 최근에 변경된 입력값과 그 값이 나타내는 단위를 저장하는 것만으로도 충분함.

이후 현재의 temperature와 scale에 기반해 다른 입력 필드의 값을 추론할 수 있음.

두 입력 필드의 값이 동일한 state로부터 계산되기 때문에 이 둘은 항상 동기화된 상태를 유지함.


class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};
  }

  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }

  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }

  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

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


이후 어떤 입력 필드를 수정하던 Calculator의 this.state.temperature와 this.state.scale이 갱신됨.

입력 필드 중 하나는 있는 그대로의 값을 받으므로 사용자가 입력한 값이 보존되고,

다른 입력 필드의 값은 항상 다른 하나에 기반해 재계산됨.

입력값을 변경할 때,
React는 DOM <input>의 onChange에 지정된 함수를 호출함.

위 예시의 경우 TemperatureInput의 handleChange 메서드에 해당함.
TemperatureInput 컴포넌트의 handleChange 메서드는 새로 입력된 값과 함께 this.props.onTemperatureChange()를 호출.

onTemperatureChange를 포함한 이 컴포넌트의 props는 부모 컴포넌트인 Calculator로부터 제공받음.

 

이전 렌더링 단계에서, 

Calculator는 섭씨 TemperatureInput의 onTemperatureChange → Calculator의 handleCelsiusChange 메서드화씨 TemperatureInput의 onTemperatureChange → Calculator의 handleFahrenheitChange 메서드로 지정.

 

따라서 우리가 둘 중에 어떤 입력 필드를 수정하느냐에 따라서 Calculator의 두 메서드 중 하나가 호출됨.
이들 메서드는 내부적으로 Calculator 컴포넌트가 새 입력값, 그리고 현재 수정한 입력 필드의 입력 단위와 

함께 this.setState()를 호출 → React에게 자신을 다시 렌더링하도록 요청.
React는 UI가 어떻게 보여야 하는지 알아내기 위해 Calculator 컴포넌트의 render 메서드를 호출함. 

두 입력 필드의 값은 현재 온도와 활성화된 단위를 기반으로 재계산됨. 

→ 온도의 변환이 이 단계에서 수행됨.
React는 Calculator가 전달한 새 props와 함께 각 TemperatureInput 컴포넌트의 render 메서드를 호출함.

또한, UI가 어떻게 보여야 할지를 파악함.
React는 BoilingVerdict 컴포넌트에게 섭씨온도를 props로 건네면서 그 컴포넌트의 render 메서드를 호출.
React DOM은 물의 끓는 여부와 올바른 입력값을 일치시키는 작업과 함께 DOM을 갱신함.

값을 변경한 입력 필드는 현재 입력값을 그대로 받고, 다른 입력 필드는 변환된 온도 값으로 갱신됨.
입력 필드의 값을 변경할 때마다 동일한 절차를 거치고 두 입력 필드는 동기화된 상태로 유지됨.

변경이 일어나는 데이터에 대해 “진리의 원천(source of truth)“을 하나만 두어야 함.

보통의 경우, state는 렌더링에 그 값을 필요로 하는 컴포넌트에 먼저 추가됨.

이후 다른 컴포넌트도 역시 그 값이 필요하게 되면 그 값을 그들의 가장 가까운 공통 조상으로 끌어올림.

다른 컴포넌트 간에 존재하는 state를 동기화시키는 것 보다, 하향식 데이터 흐름의 이용을 추천.

state를 끌어올리는 작업은 양방향 바인딩 접근 방식보다 더 많은 “보일러 플레이트” 코드를 유발하나,

버그를 찾고 격리하기 더 쉽게 만든다는 장점이 있음.

어떤 state든 간에 특정 컴포넌트 안에서 존재하기 마련이고,

그 컴포넌트가 자신의 state를 스스로 변경할 수 있으므로 버그가 존재할 수 있는 범위가 크게 감소함.

또한 사용자의 입력을 거부하거나 변형하는 자체 로직을 구현할 수도 있음.

어떤 값이 props 또는 state로부터 계산될 수 있다면, 해당 값을 state에 두면 안됨.

예를 들어 celsiusValue와 fahrenheitValue를 둘 다 저장하는 대신,

최근에 변경된 temperature와 scale만 저장하면 됨.

다른 입력 필드의 값은 항상 그 값들에 기반해서 render() 메서드 안에서 계산될 수 있음.

이를 통해 사용자 입력값의 정밀도를 유지한 채 다른 필드의 입력값에 반올림을 지우거나 적용할 수 있음.

UI에서 무언가 잘못된 부분이 있을 경우, 

React Developer Tools를 이용하여 props를 검사하고 state를 갱신할 책임이 있는 컴포넌트를 찾을 때까지 

트리를 따라 탐색하는 것을 추천함→ 소스 코드에서 버그를 추적할 수 있게해줌.

13. 합성 (Composition) vs 상속 (Inheritance)

React는 강력한 합성 모델을 가지고 있음.

따라서, 컴포넌트 간 코드를 재사용 할 일이 있는 경우, 상속 대신 합성을 사용하는 것을 추천함.
(일반적으로 개발 실무에서 상속을 통해 발생하는 문제는 '합성'을 통해 해결하는 방법을 사용.)

13-1. 컴포넌트에서 다른 컴포넌트를 담기


어떤 컴포넌트들은 어떤 자식 엘리먼트가 들어올 지 미리 예상하기 어려움.

이런 상황은 범용적인 ‘박스’ 역할을 하는 Sidebar 혹은 Dialog와 같은 컴포넌트에서 특히 자주 볼 수 있음.
→ 특수한 children prop을 사용하여 자식 엘리먼트를 출력에 그대로 전달하는 것을 추천함.

예) 다른 컴포넌트에서 JSX를 중첩하여 임의의 자식을 전달하는 예시


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

 


function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

<FancyBorder> JSX 태그 안에 있는 것들이 FancyBorder 컴포넌트의 children prop으로 전달됨

→ FancyBorder는 {props.children}을 <div> 안에 렌더링하기 때문에 전달된 엘리먼트들이 최종 출력됨.

종종 컴포넌트에 여러 개의 “구멍”이 필요한 경우가 있는데, children 대신 자신만의 방식을 적용할 수 있음.


function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}


<Contacts />와 <Chat />같은 React 엘리먼트는 객체이므로 제한없이 prop으로 전달할 수 있음.

13-1. 특수화

“특수한 경우”인 컴포넌트를 고려해야 하는 경우가 있음.

(WelcomeDialog는 Dialog의 특수한 경우라고 할 수 있음.)


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!" />
  );
}

클래스로 정의된 컴포넌트에서도 동일하게 적용됨.


function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </FancyBorder>
  );
}

class SignUpDialog extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSignUp = this.handleSignUp.bind(this);
    this.state = {login: ''};
  }

  render() {
    return (
      <Dialog title="Mars Exploration Program"
              message="How should we refer to you?">
        <input value={this.state.login}
               onChange={this.handleChange} />
        <button onClick={this.handleSignUp}>
          Sign Me Up!
        </button>
      </Dialog>
    );
  }

  handleChange(e) {
    this.setState({login: e.target.value});
  }

  handleSignUp() {
    alert(`Welcome aboard, ${this.state.login}!`);
  }
}


props와 합성은 안전한 방법으로 컴포넌트의 모양과 동작을 커스터마이징하는데 필요한 유연성을 제공함.