12 분 소요

학습 목표

  • 조건부 렌더링이 무엇인지 설명할 수 있다.
  • 리액트에서 사용하는 배열을 어떻게 다루는지 설명할 수 있다.
  • 폼태그를 리액트형식으로 사용할 수 있다.
  • State 끌어올리기에 대해 설명할수 있다.
  • React에서 사용하는 합성과 상속에 대해 구분과 사용법에 대해 설명 할 수 있다.

1. 조건부 렌더링

  • JavaScript에서의 조건 처리(if & 조건연산자)와 똑같이 동작
  • 조건을 현 상태를 나타내는 엘리먼트를 만드는데에 사용.

1.1 기본 예시(권한제어)

  • 컴포넌트 정의

      // 회원용
      function UserGreeting(props) {
          return <h1>Welcome back!</h1>;
      }
      // 게스트용
      function GuestGreeting(props) {
          return <h1>Please sign up.</h1>;
      }
      // 부모 컴포넌트
      function Greeting(props) {
      // 로그인 상태변수
      const isLoggedIn = props.isLoggedIn;
          // true: UserGreeting / false : GuestGreeting
          return (isLoggedIn) ? <UserGreeting /> : <GuestGreeting />;
      }
        
      ReactDOM.render(
          // Try changing to isLoggedIn={true}:
          <Greeting isLoggedIn={false} />,
          document.getElementById('root')
      );
    

1.2 엘리먼트 변수

  • element를 저장하기 위해 변수를 사용할 수 있음.
  • 출력의 다른 부분은 변하지 않은 채로 컴포넌트의 일부를 조건부 렌더링.

  • 예시(로그아웃 & 로그인)

      // 로그인 버튼
      function LoginButton(props){
          return(
              <button onClick={props.onClick}>
              Login
              </button>
          );
      }
    
      // 로그아웃 버튼
      function LogoutButton(props){
          return(
              <button onClick={props.onClick}>
               Logout
              </button>
          );
      }
    
      //로그인 제어
      class LoginControl extends React.Component{
          constructor(props){
              super(props);
              this.handleLoginClick = this.handleLoginClick.bind(this);
              this.handleLogoutClick = this.handleLogoutClick.bind(this);
              this.state = {isLoggedIn : false};
          }
            
          handleLoginClick(){
              this.setState({isLoggedIn : true})
          }
    
          handleLogoutClick(){
              this.setState({isLoggedIn : false});
          }
    
          render(){
              const isLoggedIn = this.state.isLoggedIn;
              let button = (isLoggedIn) ? <LogoutButton onClick={this.handleLogoutClick}/> : <LoginButton onClick={this.handleLoginClick}/>;
              return(
                  <div>
                  <Greeting isLoggedIn={isLoggedIn} />
                  {button}
                  </div>
              );
          }
      }
      ReactDOM.render(
          <LoginControl />,
          document.getElementById('root')
      );
    

1.3 ‘&&’ 연산자로 If문 인라인 표현

  • 중괄호를 이용해서 ‘&&’ 연산자를 사용할 표현식을 포함 할 수 있음.
  • {True && expression} => 항상 expression 의 상태
  • {False && expression} => 항상 false
function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);
  • falsy 표현식을 반환시 뒤에 선언한 표현식은 건너뛰지만, falsy 표현식이 반환.

      // <div>0</div> 이 반환됨.
      render() {
      const count = 0;    //  falsy
      return (
          <div>
          { count && <h1>Messages: {count}</h1>}
          </div>
      );
      }
    
  • falsy 표현식 : false 같은 것; 즉, 아래와 같이 조건부가 기본적으로 false의 조건을 갖는 것이다.

      // data에 해당 값들을 넣으면
      // if(data) => data = false 가 되는 것들
      console.log(!3);
      console.log(!'hello');
      console.log(!['array?']);
      console.log(![]);
      console.log(!{ value: 1 });
    

1.4 컴포넌트가 렌더링하는 것을 막기.

  • 다른 컴포넌트에 의해 렌더링될 때, 컴포넌트 자체를 숨기고자 랜더링 결과대신 null을 반환.
  • 예시

      function WarningBanner(props){
          if(!props.warn){
              // 숨김
              return null;
          }
          return (
              // 표현
                  <div className="warning">
                      Warning!
                  </div>
          );
      }
    
      class Page extends React.Component{
          constructor(props){
              super(props);
              this.state = {showWarning : true};
              this.handleToggleClick = this.handleToggleClick.bind(this);
          }
    
          handleToggleClick(){
              this.setState(state=> ({
                  showWarning: !state.showWarning
              }));
          }
    
          render(){
              return(
                  <div>
                      <WarningBanner warn={this.state.showWarning} />
                      <button onClick={this.handleToggleClick}>
                      {this.state.showWarning ? 'Hide' : 'Show'}
                      </button>
                  </div>
    
              );
          }
      }
    
      ReactDOM.render(
          <Page />,
          document.getElementById('root')
      );
    

2. 리스트와 Key

2.1 JavaScript

  • map()을 이용한 각 원소 값을 2배로 만들기.

      const numbers = [1, 2, 3, 4, 5];
      const doubled = numbers.map((number) => number * 2);
      console.log(doubled);
    

2.2 React

  • React에서 배열을 엘리면트 리스트로 만드는 방식은 JavaScript와 거의 동일.

  • 여러개의 컴포넌트 렌더링

      const numbers = [1,2,3,4,5];
      const listItems = numbers.map((numbers)=>
          <li>{numbers}</li>
      );
    
      ReactDOM.render(
          <ol>{listItems}</ol>,
          document.getElementById('root')
      );
    
      //출력
      1.1
      2.2
      3.3
      4.4
      5.5
    

2.3 Key

  • 2.2의 소스코드대로 출력하면 key가 필요하다는 경고 메시지 발생.
  • React가 어떤 항목을 업데이트 하는데 Key를 통해 식별.
  • key를 배열 내부의 엘리먼트에 지정.
  • 숫자배열

      const numbers = [1,2,3,4,5];
      const listItems = numbers.map((number)=>
          <li key={number.toString()}>
              {number}
          </li>
      );
    
  • 데이터의 key 적용(String)

      const todoItems = todos.map((todo)=>{
          <li key={todo.id}>
              {todo.text}
          </li>
      });
    
  • 배열의 INDEX(권장 하지 않음)

      const todoItems = todos.map((todo, index)=>
          <li key={index}>
              {todo.text}
          </li>
      )
    
  • 잘못된 예시 - Key는 주변 배열의 context에서만 의미가 있음.

      function ListItem(props) {
      const value = props.value;
      return (
          // 틀렸습니다! 여기에는 key를 지정할 필요가 없습니다!
          <li key={value.toString()}>
          {value}
          </li>
      );
      // collect case
      // return <li>{props.value}</li>;
      }
    
      function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
          // 틀렸습니다! 여기에 key를 지정해야 합니다.
          // collect case
          // <ListItem key={number.toString()} value={number} />
          <ListItem value={number} />
    
      );
      return (
          <ul>
          {listItems}
          </ul>
      );
      }
    
      const numbers = [1, 2, 3, 4, 5];
      ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
      );
    
  • Key는 배열 안에서 형제 사이에서만 고유한 값이어야 함.

      // 동일한 key(post.id) 사용가능
      const sidebar = (
          <ul>
          {props.posts.map((post) =>
              <li key={post.id}>
              {post.title}
              </li>
          )}
          </ul>
      );
    
      const content = props.posts.map((post) =>
          <div key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.content}</p>
          </div>
      );
    
      const posts = [
      {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
      {id: 2, title: 'Installation', content: 'You can install React from npm.'}
      ];
    
  • key는 힌트를 제공하지만, 컴포넌트로 전달하지 않음.

      // 컴포넌트에서 key와 동일한 값이 필요하면 다른 이름의 prop으로 명시적으로 전달함.
      // Post 컴포넌트는 props.id는 읽을수 있지만, props.key는 읽을 수 없음.
      const content = posts.map((post)=>
          <Post
              key={post.id}
              id={post.id}
              title={post.title}
          />
      );
    
  • JSX에 map() 포함시키기
      function NumberList(props){
    
          const numbers = props.numbers;
          const listItems = numbers.map((number)=>
              <ListItem key={numbers.toString()}
                  value={number} />
          );
    
          return (
              <ul>
                  {listItems}
                  /* 인라인 처리
                      <ListItem key={number.toString()}
                          value={number}
                      />
                  */
                  // 인라인 방식을 남용하기 보단, 컴포넌트로 추출하는 것을 권장.
              </ul>
          );
      }
    

3. 폼(form) 엘리먼트

  • HTML 에서의 form 은 자체가 내부 상태를 가지므로, React의 다른 DOM 엘리먼트와 다르게 동작함.
  • 앞서 나올 <input>, <textarea>, <selelct> 태그들이 모두 비슷하게 동작하고, 구현하는데 value 속성을 허용.
  • 순수 HTML

      <form>
          <label>
          Name:
              // name을 입력받음
              <input type="text" name="name"/>
          </label>
          <input type="submit" value="Submit"/>
      </form>
    
  • React에서 동일하게 사용 가능

3.1 제어 컴포넌트(Controlled Component)

  • 대부분 JavaScript 함수로 폼의 submit을 처리하고 사용자가 폼에 입력한 데이터에 접근하도록 하는 것이 편리해서 이를 위한 표준 방식의 기술을 이용.
  • HTML에서 <input> , <textarea>, <select> 와 같은 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트 함.
  • React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며, setState()에 의해 업데이트 됨.
  • React state를 신뢰 가능한 단일 출처(Single sourece of truth)로 만들어서 두 개의 요소를 결합할 수 있음.
  • 폼을 렌더링 하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어.
  • 이러한 방식으로 React에 의해 값이 제어되는 input form 엘리먼트를 제어 컴포넌트라고 칭함.
  • 예시

      // value 속성은 폼 엘리먼트에 설정되므로 표시되는 값은 항상 this.state.value가 되고 React state는 신뢰가능한 단일출저가 된다.
      // React state를 업데이트하기 위해, 모든 키 입력에서 handleChange가 동작하기 때문에 사용자가 입력할 때 보여지는 값이 업데이트 된다.
      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){
              // 동작마다 React state 업데이트
              this.setState({
                  value: event.target.value
              })
          }
          handleSubmit(event){
              // submit event
              alert("He's name is " + 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>
              );
          }
      }
    
      ReactDOM.render(<NameForm/>, document.getElementById('root'));
    

3.2 textarea 태그

  • HTML 에서 textarea 엘리먼트는 텍스트를 자식으로 정의함.
  • React에서는 value 속성을 대신 사용.
  • 예시

      ~~~js
      // 이전 내용과 같음
      render() {
          return (
              <form onSubmit={this.handleSubmit}>
                  <label>
                  Essay:
                  <textarea value={this.state.value} onChange={this.handleChange} />
                  </label>
                  <input type="submit" value="Submit" />
              </form>
          );
      }
      ~~~
    

3.3 select 태그

  • HTML 에서는 select는 드롭다운 목록
  • React에서는 selected 속성을 사용하는 대신, 최상단 select태그에 value 속성을 사용.
  • 한 곳에서 업데이트만 하면 되기 때문에, 제어 컴포넌트에서 사용하기 편리함.
  • 예시

      class FlavorForm extends React.Component{
          constructor(props){
              // selected 정의
              this.state = {value="coconut"}
              // 생략
          }
          // 이벤트정의 생략
          render(){
              return(
                  <form onSubmit={this.handleSubmit}>
                      <label>
                      Pick your favorite flavor:
                          <select value={this.state.value} onChange={this.handleChange}>
                              <option value="grapefruit">Grapefruit</option>
                              <option value="lime">Lime</option>
                              // 기존 HTML과 다르게 selected 속성을 여기에 정의하지 않음.
                              <option value="coconut">Coconut</option>
                              <option value="mango">Mango</option>
                          </select>
                      </label>
                      <input type="submit" value="Submit" />
              </form>
              );
          }
      }
    
  • selelct 태그에 multiple 옵션을 허용한다면, value 속성에 배열을 전달할 수 있음.

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

3.4 file input 태그

  • HTML에서 <input type=”file”> 는 사용자가 하나이상의 파일을 자신의 디바이스 저장소에서 서버로 업로드하거나 File API를 통해 JavaScript로 조작할 수 있음.
  • 값이 Read-Only 이여서, React에서는 비제어 컴포넌트임.

3.5 다중 입력 제어

  • 다중 input 엘리먼트를 제어해야 할때, 각 엘리먼트에 name 속성을 추가하고 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;
    
              // input 태그의 name에 일치하는 state를 업데이트 하기위해서, ES6 computed propety name 구문 사용
              // [태그이름 변수] : 해당 값
              this.setState({
              [name]: value
              });
          }
    
          render() {
              return (
              <form>
                  <label>
                  Is going:
                  /*
                      속성 정의 형식
                      name=변수 이름
                      type=event type
                      value(checked)={변수 이름}
                      onChange={this.이벤트 함수명}
                  */
                  <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>
              );
          }
      }
    

3.6 제어되는 Input Null 값

  • 제어 컴포넌트에 value prop을 지정하면, 의도 하지 않는 한 사용자가 변경할 수 없다. -> 수정 가능하면 실수로 value가 falsy(null, undefined) 하다는 것
  • 나쁜 예

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

3.7 제어 컴포넌트의 대안

  • 데이터를 변경할 수 있는 모든 방법에 대해 이벤트 핸들러를 작성한다는 것은 매우 지루하고 힘든 작업임.
  • 기존의 코드베이스를 React로 변경하고자 할 떄나 다른 것과 통합할 때 입력 폼을 구현하기 위한 대체기술인 비제어 컴포넌트가 있음.
  • 유효성 검사, 방문한 필드 추적 및 폼 제출 처리와 같은 완벽한 해결을 원한다면, Formik이 가장 대중적임.
  • Formik은 제어 컴포넌트 및 state 관리에 기초하기 때문에, 기본기를 탄탄히 해야 함.

4. State 끌어올리기(Lifting State Up)

4.1 기본 개념

  • 동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 필요가 있음.
  • 가장 가까운 공통 부모로 state를 끌어올리는 방법이 있음.
  • 기본 예시

      function BoilingVerdict(props){
          if(props.celsius >= 100) return <p>물이 끓습니다. </p>;
          return <p>물이 끓지 않았습니다.</p>
      }
    
      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>
              );
          }
      }
    
  • 두 번째 Input에 추가하기

      //Calulator에서 TemperatureInput 컴포넌트를 빼냄.
      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>
          );
      }
      }
    
      // 분리된 두 개의 온도 입력 필드를 렌더링 해줌
      class Calculator extends React.Component {
      render() {
          return (
          <div>
              <TemperatureInput scale="c" />
              <TemperatureInput scale="f" />
          </div>
          );
      }
      }
    

4.2 변환 함수 작성

  • 섭씨-화씨 변환함수
      function toCelsius(fahrenheit) {
      return (fahrenheit - 32) * 5 / 9;
      }
    
      function toFahrenheit(celsius) {
      return (celsius * 9 / 5) + 32;
      }
    
  • 올바르지 않은 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'
    

4.3 State 끌어올리기

  • 두 가지 이상의 입력값이 서로의 것과 동기화된 상태를 유지해야함.
  • 상위 컴포넌트가 공유될 state를 소유하고 있으면, 각 input 필드의 값에 대한 “진리의 원천(source of truth)”로 인한 동기화.
  • 하위 컴포넌트에서 해당 변수{this.state.변수}를 {this.props.변수}로 대체

      render() {
      // Before: const temperature = this.state.temperature;
      // props은 Read-Only
      const temperature = this.props.temperature;
      // ...
    
  • 해당 변수가 부모 props로 전달되므로, 하위 컴포넌트는 제어할 능력이 없음.
  • React에서는 보통 이문제를 컴포넌트를 “제어” 가능하게 만드는 방식으로 해결.
  • DOM <input>이 value와 onChange prop를 건네받는 것과 비슷한 방식으로, 사용자 정의된 하위 컴포넌트 역시 해당 변수(temperature)와 이벤트 함수 props를 자신의 부모 컴포넌트에 건네 받을수 있음.
      handleChange(e) {
          // Before: this.setState({temperature: e.target.value});
          this.props.onTemperatureChange(e.target.value);
          // ...
    
  • 지역 state를 제거하고, 공유한 결과
      class TemperatureInput extends React.Component {
          // 지역 state 제거
          constructor(props) {
              super(props);
              this.handleChange = this.handleChange.bind(this);
          }
          handleChange(e) {
              // setState 대신 사용
              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>
              );
      }    
    
  • 최종 소스코드

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

4.2 과정

  1. React는 DOM input 태그의 onChange에 지정된 함수를 호출.
  2. 하위컴포넌트(TemperatureInput)의 값 변경 메소드(handleChange)는 새로 입력된 값과 함께 공유한 이벤트(this.props.onTemperatureChange())를 호출.
  3. 이전 렌더링 단계에서, 각각 정의한 상위 컴포넌트에 지정할 메소드에 해당 하위 컴포넌트의 메소드를 지정함.
  4. 이 메소드들은 내부적으로 상위 컴포넌트가 수정한 입력 필드에 대한 것이 this.setState()를 호출하게 함으로써 React에게 자신을 다시 렌더링하도록 요청.
  5. React UI가 어떻게 보여야 하는지 알기 위해서, 상위 컴포넌트의 render()를 호출하고 단위 같은 기능(온도의 변환)에 대해 재계산.
  6. React는 상위 컴포넌트가 전달한 새 props와 함께 각 하위컴포넌트의 render()를 호출해서 UI가 어떻게 보여야 할지 파악.
  7. React는 상태를 표시하는 컴포넌트(BoilingVerdict)에게 데이터를 props를 건네면서 그 컴포넌트의 render()를 호출.
  8. React DOM은 물의 끓는 여부와 올바른 입력값을 일치시키는 작업과 함께 DOM을 갱신.
  9. 입력 필드의 값을 변경할 때 마다 동일한 절차를 거치고 동기화 된 상태로 유지됨.

4.3 결론

  • React App 안에서 변경이 일어나는 데이터에 대해 “진리의 원천”을 하나만 두어야 함.
  • 하향식 데이터흐름을 권장
  • state를 끌어올리는 작업은 양방향 바인딩보단 더 많은 “보일러 플레이트”코드를 유발하지만, 디버깅이 쉽다는 장점이 있다.
  • 어떤 값이 props 또는 state에 의해 계산될 수 있다면, 아마도 그 값을 state에 두어서는 안 됨.

5. 합성 vs 상속

5.1 컴포넌트에 다른 컴포넌트를 담기

  • 어떤 컴포넌트는 어떤 자식 엘리먼트가 들어올지 미리 예상할수 없는 경우가 있다. -> 범용적인 ‘Box’역할을 하는 Sidebar혹은 Dialog와 같은 경우
  • children prop을 사용하여 자식 엘리먼트를 출력에 그대로 전달할 수 있다.

      function FancyBorder(props) {
      return (
          <div className={'FancyBorder FancyBorder-' + props.color}>
          {props.children}
          </div>
      );
      }
    
      // JSX를 중첩하여 임의의 자식을 전달
      function WelcomeDialog() {
      return (
          // FancyBorder 컴포넌트의 자식 prop으로 전달
          // FancyBorder는 {props.children}을 <div>안에 렌더링 하므로 각 엘리먼트들이 최종 출력 됨.
          <FancyBorder color="blue">
          <h1 className="Dialog-title">
              Welcome
          </h1>
          <p className="Dialog-message">
              Thank you for visiting our spacecraft!
          </p>
          </FancyBorder>
      );
      }
    
  • 여러개의 “구멍”이 필요할 수도있는데, 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 />
          } />
      );
      }
    

5.2 특수화

  • React에서 합성을 통해서 “특수한 경우”인 컴포넌트를 해결 할 수있음.
  • 클래스를 적용할 때도 같음.
  • Dialog의 특수한 경우

      // 특수한 경우
      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!" />
      );
      }
    
  • 클래스로 적용

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

5.3 상속에 대해서

  • Facebook에서는 컴포넌트를 상속 계층 구조로 작성을 권장할만한 사례를 아직 찾지 못함.
  • props와 합성은 명시적이고, 안전한 방법으로 컴포넌트의 모양과 동작을 사용자화하는데 필요한 모든 유연성을 제공.
  • 컴포넌트가 원시 타입의 값, React 엘리먼트 혹은 함수등 어떤 props도 받을수 있음.
  • UI가 아닌 기능을 여러 컴포넌트에서 재사용 하기 원한다면, 별도의 JavaScript 모듈로 분리하는 것을 권장(상속대신에 컴포넌트에서 해당 모듈들을 import해서 사용한다.).

출처

  • https://ko.reactjs.org/docs/