[React] Component, Props, State

MinJi·2024년 8월 13일

FrontEnd

목록 보기
4/13

React

  • 사용자 인터페이스(UI)를 렌더링하기 위한 JavaScript 라이브러리이다.
  • React를 통해 작은 요소들을 재사용 가능하고 중첩할 수 있는 컴포넌트로 조합할 수 있다.

요소 렌더링

요소

  • React 앱의 가장 작은 구성 블록
  • 한번 요소를 만들었다면 그 자식이나 속성을 변경할 수 없다.

DOM에서 요소 렌더링

  • React로 구축한 애플리케이션은 보통 단일 루트 DOM 노드를 가진다.
  • React를 기존 앱에 통합하는 경우, 분리된 루트 DOM 노드가 있을 수도 있따.
  • React 요소를 루트 DOM 노드에 렌더링하고 싶다면, ReactDOM.render()에 둘 다 전달하면 된다.
      const element = <h1>Hello, world</h1>;
      ReactDOM.render(
        element,
        document.getElementById('root')
      );

렌더링된 요소 업데이트

  • 새로운 요소를 만들어서 ReactDOM.render()로 전달한다.
  • React DOM은 요소와 그 자식을 이전 요소와 비교하고, DOM을 원하는 상태로 만드는 데 필요한 DOM 업데이트만 적용한다.

하단의 예제는 setInterval() 콜백을 이용해 매 초마다 ReactDOM.render()를 호출한다.

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

조건부 렌더링

조건부로 JSX 반환하기

if (isPacked) {
  return <li className="item">{name}</li>;
}
return <li className="item">{name}</li>;

조건부로 아무것도 반환하지 않기

  • null 반환
if (isPacked) {
  return null;
}
return <li className="item">{name}</li>;

삼항 조건 연산자(? :)

return (
  <li className="item">
    {isPacked ? name + ' ✔' : name}
  </li>
);

논리 AND 연산자(&&)

  • React 컴포넌트에서 조건이 참일 때 일부 JSX를 렌더링하고 그렇지 않으면 아무것도 렌더링하지 않는다.
return (
  <li className="item">
    {name} {isPacked && '✔'}
  </li>
);
  • && 왼쪽에 숫자를 두면 안된다.
    - JavaScript는 자동으로 왼쪽을 Bool로 변환하는데 왼쪽이 0이라면 리액트는 0을 렌더링 한다.
    messageCount && <p>New messages</p> -> X
    messageCount > 0 && <p>New messages</p> ->

리스트 렌더링

  • 서로 다른 데이터를 동일한 컴포넌트의 여러 인스턴스로 표시해야하는 경우, 해당 데이터를 JavaScript 객체와 배열에 저장하고 map()과 filter() 같은 메서드를 사용하여 해당 객체에서 컴포넌트 리스트를 렌더링 할 수 있다.

배열을 데이터로 렌더링하기

예시

<ul>
  <li>Creola Katherine Johnson: mathematician</li>
  <li>Mario José Molina-Pasquel Henríquez: chemist</li>
  <li>Mohammad Abdus Salam: physicist</li>
  <li>Percy Lavon Julian: chemist</li>
  <li>Subrahmanyan Chandrasekhar: astrophysicist</li>
</ul>
  1. 데이터를 배열로 이동한다.
const people = [
  'Creola Katherine Johnson: mathematician',
  'Mario José Molina-Pasquel Henríquez: chemist',
  'Mohammad Abdus Salam: physicist',
  'Percy Lavon Julian: chemist',
  'Subrahmanyan Chandrasekhar: astrophysicist'
];
  1. people의 요소를 새로운 JSX 노드 배열인 listItems에 매핑한다.
const listItems = people.map(person => <li>{person}</li>);
  1. <ul>로 래핑된 컴포넌트의 listItems를 반환한다.
return <ul>{listItems}</ul>;

배열의 항목들을 필터링하기

예시

const people = [{
  id: 0,
  name: 'Creola Katherine Johnson',
  profession: 'mathematician',
}, {
  id: 1,
  name: 'Mario José Molina-Pasquel Henríquez',
  profession: 'chemist',
}, {
  id: 2,
  name: 'Mohammad Abdus Salam',
  profession: 'physicist',
}, {
  id: 3,
  name: 'Percy Lavon Julian',
  profession: 'chemist',
}, {
  id: 4,
  name: 'Subrahmanyan Chandrasekhar',
  profession: 'astrophysicist',
}];
  1. 직업이 chemist인 사람들만 필터링해서 새로운 배열 chemists를 생성한다.
const chemists = people.filter(person =>
  person.profession === 'chemist'
);
  1. chemists를 매핑한다.
const listItems = chemists.map(person =>
  <li>
     <img
       src={getImageUrl(person)}
       alt={person.name}
     />
     <p>
       <b>{person.name}:</b>
       {' ' + person.profession + ' '}
       known for {person.accomplishment}
     </p>
  </li>
);
  1. 컴포넌트에서 listItems를 반환한다.
return <ul>{listItems}</ul>;

key

  • 각 배열 항목에 다른 항목 중에서 고유하게 식별할 수 있는 문자열 또는 숫자를 key로 지정한다.
  • 각 컴포넌트가 어떤 배열 항목에 해당하는지 React에 알려주어 나중에 일치시킬 수 있도록 한다.
  • 형제 간에 고유해야 한다.
  • 변경되어서는 안된다.
  • 재정렬로 인해 위치가 변경되더라도 React가 생명주기 내내 해당 항목을 식별할 수 있게 해준다.

컴포넌트와 props

컴포넌트

함수형 및 클래스 컴포넌트

  1. 자바스크립트 함수로 작성하여 컴포넌트를 정의한다.
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
  • 단일 props 객체 인수를 받고 react 요소를 반환한다.
  • 함수형 컴포넌트
  1. ES6 Class를 사용해 컴포넌트를 정의한다.
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

컴포넌트 렌더링

요소에 유저가 정의한 컴포넌트를 나타낼 수 있다.

const element = <Welcome name="Sara" />;
  • 유저가 정의한 컴포넌트를 나타내는 요소를 볼 때 JSX 속성을 이 컴포넌트에 단일 객체(=props)로 전달한다.

하단의 예시는 "Hello, Sara"를 페이지에 렌더링한다.

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);
  1. <Welcome name="Sara" /> 요소로 ReactDOM.render() 를 호출한다.
  2. React가 {name: 'Sara'} 를 props로 하여 Welcome 컴포넌트를 호출한다.
  3. Welcome 컴포넌트가 그 결과로 <h1>Hello, Sara</h1> 요소를 반환한다.
  4. React DOM이 <h1>Hello, Sara</h1> 과 일치하도록 DOM을 효율적으로 업데이트한다.

컴포넌트 결합

  • 컴포넌트는 출력될 때 다른 컴포넌트를 참조할 수 있다.
  • 컴포넌트는 다른 컴포넌트를 렌더링할 수 있지만, 그 정의를 중첩해서는 안된다.

예시) Welcome을 여러번 렌더링하는 App 컴포넌트를 만들 수 있다.

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

컴포넌트 추출

  • 컴포넌트를 더 작은 컴포넌트로 쪼갠다.

기존코드

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}
  • author, text, date를 props로 받는다.
  • 댓글을 의미한다.
  1. Avatar 추출
function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />

  );
}
  • 컴포넌트 자체 관점에서 props 이름을 짓는 것을 권장한다.
  • 따라서, author 대신 user라는 이름을 사용한다.
  1. Comment 단순화
function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <Avatar user={props.author} />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}
  1. UserInfo 추출
function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">
        {props.user.name}
      </div>
    </div>
  );
}
  1. Comment 단순화
function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

컴포넌트 정의하기

export default function Profile() {
  return (
    <img
      src="https://i.imgur.com/MK3eW3Am.jpg"
      alt="Katherine Johnson"
    />
  )
}
  1. 컴포넌트 내보내기
  • export default 접두사는 표준 JavaScript 구문이다.
  • 다른 파일에서 가져올 수 있도록 파일에 주요 기능을 표시할 수 있다.
  1. 함수 정의하기
  • function Profile() { }을 사용하면 Profile이라는 이름의 JavaScript 함수를 정의할 수 있다.
  • 이름은 대문자로 시작해야 한다.
  1. 마크업 추가하기
  • <img/> 는 HTML처럼 작성되었지만 실제로는 JavaScript이다. = JSX 구문
  • 한 줄에 모두 작성할 수 있지만, 마크업이 모두 return 키워드와 같은 라인에 있지 않은 경우에는 괄호로 묶어야 한다.

Props

  • 부모 컴포넌트는 props를 줌으로써 몇몇의 정보를 자식 컴포넌트에게 전달한다.
  • props는 수정할 수 없다.
  • props를 사용하면 부모 컴포넌트와 자식 컴포넌트를 독립적으로 생각할 수 있다.

컴포넌트에 props 전달하기

  1. 자식 컴포넌트에 props 전달하기
  • Avatar에 props를 전달한다.(person, size)
export default function Profile() {
  return (
    <Avatar
      person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
      size={100}
    />
  );
}
  1. 자식 컴포넌트 내부에서 props 읽기
  • Avatar 코드 내에서 변수를 사용하는 것처럼 사용할 수 있다.
function Avatar({ person, size }) {
  // person과 size는 이곳에서 사용가능합니다.
}

prop의 기본값 지정하기

function Avatar({ person, size = 100 }) {
  // ...
}
  • 기본값은 size prop이 없거나 size={undefined}일 때 사용된다.
  • size={null}이거나 size={0}일 경우, 사용되지 않는다.

JSX spread 문법으로 props 전달하기

  • 모든 props를 자식 컴포넌트에 전달하는 경우, 모든 props의 각각의 이름을 나열하지 않고 전달한다.
function Profile(props) {
  return (
    <div className="card">
      <Avatar {...props} />
    </div>
  );
}

State와 라이프사이클

  • 로컬 state는 클래스에서만 사용 가능하다.

함수 -> 클래스

  1. ES6 class를 같은 이름으로 만들고, React.Component 를 확장한다.
  2. 비어있는 render() 메서드를 하나 추가한다.
  3. 함수의 바디를 render() 메서드 안으로 옮긴다.
  4. render() 바디 내에서 props 를 this.props 로 바꾼다.
  5. 남아있는 빈 함수 선언문을 제거한다.

함수 코드

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

클래스로 변환

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

클래스에 로컬 state 추가

date를 props에서 state로 옮기기
1. render() 메서드 내의 this.props.date를 this.state.date로 바꾼다.

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
  1. this.state를 초기화하는 클래스 생성자를 추가한다.
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
  1. <Clock /> 요소에서 date prop을 삭제한다.

결과

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

클래스에 라이프사이클 메서드 추가

mounting

컴포넌트가 DOM에 최초로 렌더링 될 때 타이머 설정

unmounting

DOM에서 컴포넌트를 삭제했을 때 타이머 해제

라이프사이클 훅

  • 컴포넌트가 마운트되고 언마운트 될 때 특정 코드를 실행하기 위해 컴포넌트 클래스에 특별한 메서드를 선언할 수 있다.
  • 시각적 출력에 사용되지 않는 것을 저장해야하는 경우 클래스에 수동으로 필드를 추가할 수 있다.
componentDidMount() {
 this.timerID = setInterval(
      () => this.tick(),
      1000
 );
}
componentWillUnmount() {
  clearInterval(this.timerID);
}

전체 코드

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
  1. <Clock />ReactDOM.render() 에 전달될 때, React는 Clock 컴포넌트의 생성자 함수를 호출한다. Clock이 현 시간 화면에 보여질 때, 현 시간을 포함하는 this.state 객체를 초기화하며, 이 state는 추후 업데이트한다.
  2. React가 Clock 컴포넌트의 render() 메서드를 호출한다. React가 화면에 보여줄 부분을 나타낸다. 그다음 React는 Clock 의 렌더링 출력과 일치하도록 DOM을 업데이트합니다.
  3. Clock 출력이 DOM에 주입되었을 때, React는 componentDidMount() 라이프 훅을 호출한다. 내부에서, Clock 컴포넌트는 브라우저에게 컴포넌트의 tick() 메서드를 초당 한번 호출하는 타이머를 설정하라고 요구한다.
  4. 브라우저에서 매 초마다 tick() 메서드를 호출한다. 내부에서, Clock 컴포넌트는 현재 시간을 포함하는 객체로 setState() 를 호출하여 UI 업데이트를 예약한다. setState() 호출 덕분에, React는 상태가 변경된 걸 알고 있고, render() 메서드를 다시 한번 호출해 스크린에 무엇이 있어야 하는 지 알 수 있다. 이번에는, render() 메서드 내의 this.state.date 가 달라지므로 렌더 출력에 업데이트된 시간이 포함된다. React는 그에 따라 DOM을 업데이트한다.
  5. 만약 Clock 컴포넌트가 DOM에서 삭제되었다면, React는 componentWillUnmount() 라이프사이클 훅을 호출하고 타이머를 멈춘다.

setState()

  1. State 직접 수정할 수 없다.
  • setState()를 사용해 수정한다.
  1. State 업데이트는 비동기일 수 있다.
  • this.propsthis.state가 비동기로 업데이트될 수 있으므로, 다음 state를 계산할 때 해당 값을 신뢰해서는 안된다.
    예)
// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
})

// Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});
  1. State 업데이트는 병합된다.
  • setState() 를 호출할 때, React는 현재 state와 제공한 객체를 병합한다.

    1) state는 여러 독립적인 변수를 가질 수 있다.

    constructor(props) {
      super(props);
      this.state = {
        posts: [],
        comments: []
      };
    }

    2) 개별 setState()를 호출하여 아이템을 각각 업데이트할 수 있다.

    componentDidMount() {
      fetchPosts().then(response => {
        this.setState({
          posts: response.posts
        });
      });
    
      fetchComments().then(response => {
        this.setState({
          comments: response.comments
        });
      });
    }

state 흐름

  • 하향식(top-down), 단방향(unidirectional) 데이터 흐름
  • 모든 state는 항상 특정 컴포넌트가 가지며, 해당 state에서 파생된 모든 데이터 또는 UI는 트리의 컴포넌트 아래에만 영향을 미친다.
  • 부모 컴포넌트나 자식 컴포넌트는 특정 컴포넌트의 state 유무를 알 수 없으며 해당 컴포넌트가 함수나 클래스로 선언되었는 지 알 수 없다.
  • 컴포넌트는 자신의 state를 자식 컴포넌트에 props로 내려줄 수 있다.(유저가 정의한 컴포넌트에서도 동작)

state 끌어올리기

  • 일부 컴포넌트가 동일한 변경 데이터를 보여줘야 할 때 공통 조상에 state를 끌어올리는 것을 권장한다.

예) 온도 계산기
화씨, 도씨 입력하는 두 input 값이 동기화 되기를 원한다.

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</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 추가
1. 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>
    );
  }
}
  1. 변환 함수 작성(섭씨 <-> 화씨)
function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}
  1. 제어되는 컴포넌트 생성
  • 만약 Calculator 가 공유되는 state를 가지면, 이는 두 개의 input에서 사용할 수 있는 현재 온도에 대한 “신뢰 가능한 소스”가 되기 때문에 서로에게 일관된 값을 가질 수 있도록 지시할 수 있다. 양쪽 TemperatureInput 컴포넌트의 props가 같은 부모 Calculator 컴포넌트에서 오므로, 두 input은 항상 동기화 상태이다.
    render() {
        // Before: const temperature = this.state.temperature;
        const temperature = this.props.temperature;
        // ...
  • 커스텀 TemperatureInput 도 부모 Calculator 컴포넌트로부터 temperature 와 onTemperatureChange prop을 받게 만들 수 있습니다.
    handleChange(e) {
        // Before: this.setState({temperature: e.target.value});
        this.props.onTemperatureChange(e.target.value);
        // ...
  1. Calculator 수정
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>
    );
  }
}

[ 전체 코드 ]

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(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

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

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

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Calculator />);

input 수정 시

  1. React는 DOM <input> 이 바뀔 때마다 onChange 로 정의된 함수를 호출한다. 이 케이스에서는 TemperatureInput 컴포넌트의 handleChange 메서드가 이 역할을 수행한다.
  2. TemperatureInput 컴포넌트 안의 handleChange 메서드는 새 이상적인 값으로 this.props.onTemperatureChange() 를 호출한다. onTemperatureChange 를 포함한 props는 부모 컴포넌트인 Calculator 에서 제공한다.
  3. 렌더링되었을 때, 입력에 따라 Calculator는 다른 메소드를 호출한다.
    • 섭씨 TemperatureInput 의 onTemperatureChange -> handleCelsiusChange
    • 화씨 TemperatureInput 의 onTemperatureChange -> handleFahrenheitChange
  4. 이 메서드들에서 Calculator 컴포넌트는 React에게 새로운 입력 값과 막 수정된 input의 현재 scale로 this.setState() 를 호출해 스스로를 다시 렌더링하도록 요청한다.
  5. React는 Calculator 컴포넌트의 render 메서드를 호출하여 UI가 어떻게 보여야하는 지 알아내고 두 입력 값은 현재 온도와 활성 scale에 따라 다시 계산된다.(온도 변환 수행)
  6. React는 Calculator 에서 계산한 새 props로 개별 TemperatureInput 의 render 메서드를 호출함으로써 UI가 어떻게 보여야하는 지 알아낸다.
  7. React DOM은 이상적인 입력 값과 매치하는 DOM을 업데이트한다. 방금 수정한 input은 현재 값을 받고, 다른 input은 변환 후 온도를 업데이트합니다.

모든 업데이트는 같은 단계를 통하기 때문에 input은 동기화 상태를 유지한다.

0개의 댓글