react.org

React.JS

1장 Hello World

React는 프레임워크가 아닌 라이브러리 이다.
프레임워크:기능 구현에만 집중할 수 있도록, 기본적으로 필요한 기능을 갖추어 놓은 뼈대
라이브러리:반복적으로 사용되는 코드들을 모아 재사용할 수 있도록 Class나 Function으로 만들어진 것.

가장 기본적인 React 코드

ReactDOM.render(
  <h1>Hello, world!</h1>
  document.getElementById('root')
);

참고:개발자를 위한 웹 기술 : MDN

2장 JSX 소개

const element = <h1>Hello, world!</h1>;

문자열도, HTML도 아닌 JSX (JavaScript 확장 문법)
JSX: JavaScript의 모든 기능을 포함하고 있음. JSX는 React element를 생성.

[코드1]

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

ReactDOM.render(
  element,
  document.getElementById('root')
);


[코드2]

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

JSX의 중괄호안에는 유효한 모든 JavaScript 표현식을 포함 가능
JSX의 속성 정의:

""를 이용해 문자열 리터럴 정의 
const element = <div tabIndex="0"></div>;
{}를 사용 속성에 JavaScript 표현식 삽입
const element = <img src={user.avatarUrl}></img>;

JSX XSS 공격 방지: React DOM은 JSX에 삽입된 모든 값을 렌더정에 이스케이프한다. 애플리케이션에서 명시적으로 작성되지 않은 내용은 주입되지 않는다.
이런 특성으로 XSS(cross-site-scripting)공격이 방지 된다.

3장 엘리먼트 렌더링

Element는 React의 가장 작은 단위.
Element는 React의 element는 Browser DOM Element와 달리 일반 객체(plain Obejct)이며, 쉽게 생성 가능.
ReactDOM은 React Element와 일치하도록 DOM을 업데이트.
Element는 Component의 구성요소.

React로 구현된 애플리케이션은 일반적으로 하나의 루트 DOM 노드가 있습니다.
React를 기존 앱에 통합하려는 경우 원하는 만큼 많은 수의 독립된 루트 DOM 노드가 있을 수 있습니다.

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
__> root DOM 노드에 React Element를 렌더링
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);

React Element는 불변객체이므로 생성 이후에는 해당 element의 자식이나 속성을 변경할 수 없다.
위 코드는 지금 까지 배운 내용으로 1초마다 Render를 다시 호출하여 그리는 방법이다.

4장 Components and Props

Component를 통해 UI를 재사용 가능한 여러 조각으로 나눔.
Component는 JavaScript 함수와 유사함.
Props는 속성 데이터

  • 함수 컴포넌트
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
객체 props 속성을 받은 후, React Element를 반환.
JavaScript 함수이기 때문에 '함수 컴포넌트'라고 호칭함.
  • 클래스 컴포넌트
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

React 관점에서 두 가지 유형 Component는 동일

  • 컴포넌트 렌더링
const element = <div />;
div => DOM 태그

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

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);
Welcome => 
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을 효율적으로 업데이트합니다

주의: 컴포넌트의 이름은 항상 대문자로 시작합니다.
React는 소문자로 시작하는 컴포넌트를 DOM 태그로 처리합니다.

  • 컴포넌트 합성

    Componet는 자신의 출력에 다른 Component를 참조할 수 있습니다.
    즉, 모든 세부 단계에서 동일한 추상 컴포넌트를 사용할 수 있습니다.

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')
);
=> Welcome을 여러 번 렌더링하는 App Component
  • 컴포넌트 추출

    Componet는 자신의 출력에 다른 Component를 참조할 수 있습니다.
    즉, 모든 세부 단계에서 동일한 추상 컴포넌트를 사용할 수 있습니다.

1단계 추출전

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

2단계

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>
  );
}
function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

3단계

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>
  );
}
function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}
function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">
        {props.user.name}
      </div>
    </div>
  );
}

Props는 읽기 전용 변경하지 말 것. 상태관리는 state 다음장에서 :)

5장 State and Lifecycle

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

=> UI 업데이트 하기위해 Redering을 계속 새로하는 코드

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}
function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}
setInterval(tick, 1000);

=> Clock을 캡슐화하였지만, 매초 UI 업데이트 하는 것이 Clock의 세부내용이 되어야함.

  • 함수에서 클래스로 변환하기
  1. React.Component를 확장하는 동일한 이름의 ES6 class를 생성합니다.
  2. render()라고 불리는 빈 메서드를 추가합니다.
  3. 함수의 내용을 render() 메서드 안으로 옮깁니다.
  4. render() 내용 안에 있는 props를 this.props로 변경합니다.
  5. 남아있는 빈 함수 선언을 삭제합니다.
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
 render 메서드는 업데이트가 발생할 때마다 호출되지만, 
 같은 DOM 노드로 <Clock />을 렌더링하는 경우 Clock 클래스의 단일 인스턴스만 사용됩니다. 
 이것은 로컬 state와 생명주기 메서드와 같은 부가적인 기능을 사용할 수 있게 해줍니다.
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}
setInterval(tick, 1000);

!!클래스 컴포넌트는 항상 props로 기본 constructor를 호출해야 합니다.!!

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')
);
  • 생명주기 LIFE CYCLE

마운팅 : 처음 렌더링 될 때마다 설정
언마운팅 : 생성되었던 DOM이 삭제될 때마다 설정

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 컴포넌트의 constructor를 호출합니다. 
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는 state가 변경된 것을 인지하고 화면에 
표시될 내용을 알아내기 위해 render() 메서드를 다시 호출합니다. 
이 때 render() 메서드 안의 this.state.date가 달라지고 렌더링 출력값은 
업데이트된 시각을 포함합니다. React는 이에 따라 DOM을 업데이트합니다.

5.Clock 컴포넌트가 DOM으로부터 한 번이라도 삭제된 적이 있다면 
React는 타이머를 멈추기 위해 componentWillUnmount() 생명주기 메서드를 호출합니다.

6장 이벤트 처리하기

React Element에서 event 처리하는 방식은 DOM Element에서 이벤트를 처리하는 방식과 매우 유사.
[차이점]

  • React 이벤트는 소문자 대신 CamelCase사용
[HTML]
<button onclick="activateLasers()">
  Activate Lasers
</button>
[REACT]
<button onClick={activateLasers}>
  Activate Lasers
</button>
  • JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달.
  • React에서는 return false;로 기본 동작을 방지할 수 없음. prventDefault를 명시적으로 호출해야함.
function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }
  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

binding

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
    // 콜백에서 `this`가 작동하려면 아래와 같이 바인딩 해주어야 합니다.
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}
ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

[bindin을 하지 않기위한 2가지 방법]

[[방법1]]
class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
  }
  //이 문법은 'this'가 handleClick 내에서 바인딩 되도록함.
  //주의 : 이 문법은 실험적인 방법임.
  handleClick = () => {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}
ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);
[[방법2]]
class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
  }
  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }
  render() {
    return (
      <button onClick={() => this.handleClick()}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}
ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

7장 조건부 렌더링

[컴포넌트 UserGreeting]
function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}
[컴포넌트 GuestGreeting]
function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}
ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={true} 
  //isLoggedIn의 값에 따라 다른 컴포넌트를 렌더링함.
  document.getElementById('root')
);
  • 엘리먼트 변수

    출력의 다른 부분은 변경하지 않은 채로 컴포넌트의 일부를 조건부로 렌더링 할 수 있다.

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;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }
    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}
function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}
function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}
function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}
ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);

8장 리스트와 key

function ListItem(props) {
  // value값만 지정.
  return <li>{props.value}</li>;
}
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // key는 이곳에서
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
map안에서 key를 설정하는 것이 좋음.
  • key로 지정만한 값이 없을 경우
[index사용 가능]
const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs
  <li key={index}>
    {todo.text}
  </li>
);

9장 Form

  • 제어 컴포넌트
Form Element
<input>
<textarea>
<select>

폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로, state 속성에 유지되며, setState()에 의해 업데이트 된다.

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>
    );
  }
}
ReactDOM.render(
  <NameForm />,
  document.getElementById('root')
);
  • Select 태그
class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleChange(event) {
    this.setState({value: event.target.value});
  }
  handleSubmit(event) {
    alert('Your favorite flavor is: ' + this.state.value);
    event.preventDefault();
  }
  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>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
ReactDOM.render(
  <FlavorForm />,
  document.getElementById('root')
);

->selected 옵션에 의해 Coconut 옵션이 초기화 됨. select 태그에 value 어트리뷰트를 사용함.

전반적으로 
<input type="text">, <textarea><select> 모두 매우 비슷하게 동작합니다. 
모두 제어 컴포넌트를 구현하는데 value 어트리뷰트를 허용합니다.
<select multiple={true} value={['B', 'C']}>
-> value 어트리뷰트에 배열 전달 가능.

10장 state 끌어올리기

언제 필요한가?
동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 경우.
가장 가까운 공통 조상으로 state를 끌어 올린다.

[1단계]
BoilingVerdict라는 이름의 컴포넌트 생성.
섭씨 온도: celsius prop를 받아서 온도가 끓기에 충분한지 여부 출력.
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>
    );
  }
}
ReactDOM.render(
  <Calculator />,
  document.getElementById('root')
);
  • 두개의 input 입력필드

섭씨 입력필드, 화씨 입력필드 두개를 만들어 두 필드 간에 동기화 상태를 유지.

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>
    );
  }
}
ReactDOM.render(
  <Calculator />,
  document.getElementById('root')
);
--> 두 개의 입력 필드를 갖게 되었지만, 둘 중 하나의 온도를 입력하더라도 다른 하나는 갱신되지 않는 문제가 있는 상태. 현재 입력 온도 정보가 TemperatureInput에 숨겨져 있너서 Calculator가 정보를 알 수 없는 상태이다.
  • state 끌어올리기
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 {
  //TemperatureInput의 변경사항
  //this.state.temperature 대신 this.props.temperature을 읽어오도록 변경.
  //state를 변경하고 싶을 경우 this.setState() 대신 Calculator로 부터 건내받은 this.props.onTemperatureChange()를 호출하도록 수정
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(e) {
    // this.setState({temperature : e.target.value});
    // 이제 TemperatureInput에서 온도를 갱신하고 싶다면 onTemperatureChange 호출.
    // 해당 예제 처럼이 아니라 일관되게 value, onChange로 사용해도 된다.
    this.props.onTemperatureChange(e.target.value);
  }
  render() {
    // const temperature = this.state.temperature;
    // 기존 state에서 props로 로 대체
    // 이제 TemperatureInput는 값을 제어할 권한이 없다.
    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'};
  } 
  //TemperatureInput이 가지고 있던 state를 Calulator로 옮겨옴.
  //이를 통해 두 입력 필드가 서로 간에 일관된 값을 유지하도록 만들 수 있다.
  //두 TemperatureInput가 컴포넌트의 props가 같은 부모인 Calculator로부터 전달되기 때문에
  //두 입력 필드는 항상 동기화된 상태를 유지할 수 있다.
  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>
    );
  }
}
ReactDOM.render(
  <Calculator />,
  document.getElementById('root')
);
  • 입력값 변경시
    1.input 태그의 onChange에 지정된 함수가 호출됨. 위 예시의 경우에는 TemperatureInput의 handleChange 메서드
  1. TemperatureInput 컴포넌트의 handleChange 메서드는 새로운 입력 값과 함께 this.props.onTemperatureChange()를 호출한다.
  2. 이전 렌더링 단계에서 Calculator는 섭씨, 화씨 TemperatureInput별로 onChange 메서드를 지정해 놓았기때문에, 수정된 입력 필드에 따라 Calculator의 두 메서드중 하나가 호출 됨.
  3. 이들 메소드 내부에서 Calculator 컴포넌트가 현재 수정된 입력 필드 입력 단위와 함께, this.setState()를 호출하게 됨으로써 React에게 자신을 다시 렌더링하도록함.
  4. React는 UI가 어떻게 보여야 하는지 알아보기 위해 Calculator 컴포넌트의 render함수를 호출함.
  5. React가 Calculator가 전달한 새로운 props와 함께 각 TemperatureInput 컴포넌트의 render 메서드를 호출함.
  6. React가 BoilingVerdict 컴포넌트에게 섭씨온도를 props로 건네면서 그 컴포넌트 render함수 호출함.
  7. React DOM은 물의 끊는 여부와 올바른 입력값을 일치시키는 작업과 함께, DOM을 갱신함. 값을 변경한 입력 필드는 값을 그대로 받고, 다른 필드는 변환된 온도가 값으로 갱신된다.
    --> 입력 필드의 값 변경시마다, 동일한 절차를 거치고 두 입력 필드는 동기화된 상태로 유지된다.

11장 합성(Composition) vs 상속(Inheritance)

React는 강력한 합성 모델을 가지고 있다. 상속 대신 합성을 사용하여 컴포넌트 간에 코드를 재사용하는 것이 좋다.

  • 컴포넌트 안에 다른 컴포넌트 담기
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>
  );
}
ReactDOM.render(
  <WelcomeDialog />,
  document.getElementById('root')
);

범용적인 박스 역할을 하는 sidebar혹은 Dialog와 같은 컴포넌트는 chidren prop를 사용하여 자식 엘리먼트를 출력에 그대로 전달하는 것이 좋다.

  • 특수화

더 "구체적인" 컴포넌트가, "일반적인" 컴포넌트를 렌더링하고 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!" />
  );
}

React에서 상속 계층 구조로 작성하는 것은 적합하지 않음.
props와 합성은 명시적이고 안전한 방법으로 컴포넌트의의 모양과 동작을 커스터마이징하는데 필요한 유연성을 제공함.

12장 React로 사고하기

  • 1단계 : UI를 컴포넌트 계층 구조로 나누기
    컴포넌트는 새로운 함수나 객체를 만들 때처럼 만든다.
    하나의 컴포넌트는 한 가지일을 하는 것이 이상적이다(단일 책임 원칙)즉, 하나의 컴포넌트가 커져서 여러개의 역할을 하게 된다면 해당 컴포넌트는 보다 작은 하위 컴포넌트로 나뉘어져야 한다.
  • 2단계 : React로 정적인 버전 만들기
    데이터 모델을 가지고 UI 렌더링은 되지만 아무 동작도 없는 버전을 만들어 보자.(state를 사용하지 않고 props만을 이용해서 만들 것)
  • 3단계 : UI state에 대한 최소한의 표현 찾아내기
    애플리케이션을 올바르게 만들기 위해서는 최소의 state 집합을 고려해보아야 한다.(핵심: 중복배제원칙). 가장 최소한의 state를 구성하고 이를 통해 나머지 모든 것들이 필요에 따라 계산되도록 만들 것.
  • 4단계 : state의 적절한 위치 찾기.
  1. state를 기반으로 렌더링하는 모든 컴포넌트를 찾으세요.
  2. 공통 소유 컴포넌트 (common owner component)를 찾으세요. (계층 구조 내에서 특정 state가 있어야 하는 모든 컴포넌트들의 상위에 있는 하나의 컴포넌트).
  3. 공통 혹은 더 상위에 있는 컴포넌트가 state를 가져야 합니다.
  4. state를 소유할 적절한 컴포넌트를 찾지 못하였다면, state를 소유하는 컴포넌트를 하나 만들어서 공통 오너 컴포넌트의 상위 계층에 추가하세요.

좋은 웹페이지 즐겨찾기