Conditional Rendering


React에서는 필요한 기능을 캡슐화한 고유한 컴포넌트를 만들 수 있다. 그리고 애플리케이션의 상태에 따라 이들 중 
일부만 렌더링할 수 있다.


React에서의 조건부 렌더링은 자바스크립트에서 조건문이 하는 방식과 동일하게 작동한다. 자바스크립트의 if나 
조건 연산자같은 연사자들을 이용하여 현재 상태를 표시할 elements를 생성할 수도 있고, React가 거기에 맞춰
UI를 업데이트하도록 할 수도 있다.


아래의 두 component를 살펴보자

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}


사용자가 로그인 했는지의 여부에 따라 이들 중 하나의 component를 표시하는 Greeting이라는 component를
만들 수 있을 것이다.

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

CodePen에서 살펴보기.


이 예제는 isLoggedIn이라는 prop의 값에 따라 다른 greeting을 렌더링 한다.


Element Variables


elements를 저장하기 위해 변수들을 사용할 수 있다. 이렇게 할 경우 출력의 나머지 부분이 변경되지 않고 있는 상태에서
일부 component를 조건부로 렌더링 하는데 도움이 된다.


로그인 버튼과 로그아웃 버튼을 표시하는 새로운 두 개의 component를 살펴보자

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}


이 component들 아래에 LoginControl이라는 상태를 갖는 component를 만들 것이다.


이 component는 자신의 현재 상태에 따라 <LoginButton /> 또는 <LogoutButton />를 렌더링 할 것이다.
또한 이전 예제에서 보았던 <Greeting />도 렌더링 할 것이다.

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 = null;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

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

CodePen에서 살펴보기.


변수를 선언하고 if문을 사용하는 것은 component를 조건부로 렌더링하는 좋은 방법이며, 때때로 더 짧은
문법을 사용하는 것이 더 좋을 것이다. JSX에는 몇가지 inline 조건 처리 방법이 있는데 아래에서 설명할 것이다.


Inline If with Logical && Operator


중괄호({})를 사용함으로써 JSX의 어떤 표현식들을 내장시킬 수 있다. 여기에는 자바스크립트의 논리 연산자인
&&이 포함된다. 이 연산자를 사용하는 것이 조건부로 element를 포함시키는데 더 편한 경우도 있다.

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

CodePen에서 살펴보기.


이 예제는 자바스크립트 내에서 실행되기 때문에 true && expression은 항상 expression의 값을 갖게 되며
false && experssion은 항상 false의 값을 갖게 된다.


그러므로 만일 조건이 true라면 && 오른쪽의 element가 출력으로 표시된다. 반면에 조건이 false라면
React는 이 문장을 무시하고 건너 뛸 것이다.


Inline If-Else with Conditional Operator


elements를 inline으로 조건부 렌더링하는 방법에는 자바스크립트의 조건 연산자인 condition ? true : false 를 

사용하는 방법도 있다.


아래의 예제에서 작은 텍스트 블록을 렌더링 하기 위해 이 방법을 사용할 것이다.

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}
It can also be used for larger expressions although it is less obvious what's going on:

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}


자바스크립트에서와 마찬가지로 어떤 것이 더 가독성이 좋은가 하는 선택은 당신에게 달려있다. 또한 조건이 더 복잡해
진다는 것은 component를 분리하기에 좋은 시점이란 것을 명심하자.


Preventing Component from Rendering


아주 드문 경우로 어떤 component가 다른 component로부터 렌더링 되었음에도 불구하고 숨겨야 할 경우가 있다.
이런 경우 렌더링될 출력값 대신 null을 리턴하면 된다.


아래의 예제에서 <WarningBanner />는 warn이라는 prop의 값에 따라 렌더링이 결정된다. 만일 prop의 값이
false라면 component는 렌더링되지 않는다.

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(prevState => ({
      showWarning: !prevState.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')
);

CodePen에서 살펴보기.


component로부터 null을 리턴받게 되는 경우 render 메소드는 component의 lifecycle 메소드들을
작동시키지 않는다. 하지만 componentWillUpdate와 componentDidUpdate는 여전히 호출된다.







저작자 표시
신고

'Study > React' 카테고리의 다른 글

React 살펴보기 #8  (0) 2017.05.15
React 살펴보기 #7  (0) 2017.04.20
React 살펴보기 #6  (0) 2017.03.27
React 살펴보기 #5  (0) 2017.03.04
React 살펴보기 #4  (0) 2017.02.17
React 살펴보기 #3  (0) 2017.02.11


Handling Events


React elements에서의 이벤트 처리는 DOM elements에서의 이벤트 처리와 비슷하다.
다만 약간의 문법적인 차이가 있을 뿐이다.

  • React 이벤트는 소문자보다는 카멜 케이스를 이용하여 명명한다.
  • JSX를 이용하여 문자열보다는 함수형의 이벤트 핸들러를 전달한다.


HTML의 예는 다음과 같다.

<button onclick="activateLasers()">
  Activate Lasers
</button>


React는 이와 조금 다르다.

<button onClick={activateLasers}>
  Activate Lasers
</button>


또다른 차이점이라면 React에서는 기본적인 동작을 막기 위해 false를 리턴할 수 없다는 것이다.
대신에 명시적으로 preventDefault를 호출해야 한다. 예를들어 일반적인 HTML에서는 새로운 페이지가 열리는
기본적인 링크 동작을 막기 위해 아래와 같은 코드를 사용한다.

<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>


React에서는 위의 코드 대신에 다음과 같이 한다.

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}


여기서 e는 synthetic 이벤트이다. React에서는 W3C의 사양에 따라 이러한 synthetic 이벤트들을 정의한다.
따라서 개발자들은 브라우저간의 호환성을 걱정할 필요가 없다. 더 많은 것을 알고 싶다면 SyntheticEvent의 참조
가이드를 보라.


보통 React를 이용할 때는 DOM elements가 생성된 후 이벤트 리스너를 추가하기 위해 addEventListener
호출할 필요가 없다. 대신에 초기 렌더링 되는 시점에 리스너를 제공해주면 된다.
ES6 class를 이용하여 component를 정의한 경우 이벤트 핸들러는 클래스의 메서드로 만드는 것이 일반적이다.
예를들어 아래의 Toggle component는 사용자가 “ON”이나 “OFF”를 토글할 수 있는 버튼을 렌더링한다.

class Toggle extends React.Component {
 constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make this work in the callback
    this.handleClick = this.handleClick.bind(this);
 }

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

CodePen에서 확인하기


위의 코드에서 JSX callback 내에 있는 this의 의미에 대해 주의해야 한다. JavaScript에서는 클래스 메소드가
기본적으로 bound 되지 않는다. 만약 this.handleClick을 bind하는 것을 잊은 상태로 onClick에 전달했다면
함수가 실제로 호출되었을 때 this는 undefined가 된다.


이 것은 React만의 특별한 동작은 아니다. JavaScript에서 함수가 작동하는 방식의 일부이다. 일반적으로
onClick={this.handleClick}처럼 뒤에 ()가 없는 함수를 참조하는 경우 그 함수를 bind 하는 것이다.


만일 bind를 호출하는 것이 번거롭다면 이를 해소할 두가지 방법이 있다. 만일 실험적인 속성 초기화 문법을 사용
한다면 callback의 정확한 bind를 위해 속성 초기화를 이용할 수 있다.

class LoggingButton extends React.Component {
 // This syntax ensures this is bound within handleClick.
 // Warning: this is *experimental* syntax.
 handleClick = () => {
    console.log('this is:', this);
 }

 render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
 }
}


이러한 문법은 React app 생성 시 기본적으로 사용된다.
만일 속성 초기화 문법을 사용하지 않는다면 callback에 화살표 함수를 사용할 수 있다.

class LoggingButton extends React.Component {
   handleClick() {
     console.log('this is:', this);
   }

   render() {
      // This syntax ensures this is bound within handleClick
      return (
        <button onClick={(e) => this.handleClick(e)}>
          Click me
        </button>
      );
   }
}


이 문법에서의 문제점은 LoggingButton이 렌더링되는 시점에 서로 다른 각각의 callback이 생성된다는 것이다.
대부분의 경우에 이 것은 문제되지 않는다. 하지만 만일 callback이 prop으로서 하위 component로 전달된다면
이 컴포넌트들은 추가적으로 다시 렌더링 될 수 있다. 따라서 이런 종류의 성능 관련 문제를 피하기 위해서 일반적으로
생성자에서 binding 하거나 속성 초기화 문법을 사용할 것을 권장한다.






저작자 표시
신고

'Study > React' 카테고리의 다른 글

React 살펴보기 #8  (0) 2017.05.15
React 살펴보기 #7  (0) 2017.04.20
React 살펴보기 #6  (0) 2017.03.27
React 살펴보기 #5  (0) 2017.03.04
React 살펴보기 #4  (0) 2017.02.17
React 살펴보기 #3  (0) 2017.02.11


State and Lifecycle


이전 섹션 중 하나였던 시계 예제를 되돌아보자.
지금까지 우리는 UI를 업데이트 시키는 단지 한가지 방법만을 배웠다.
렌더링된 출력물을 바꾸기 위해 우리는 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);

CodePen에서 확인하기


이번 섹션에서는 Clock 컴포넌트를 진짜로 재활용 가능하고 캡슐화된 상태로 만드는 방법을 배울 것이다.
이 새로운 컴포넌트는 자체 타이머를 가질 것이고 매초마다 스스로 업데이트 할 것이다.


아래 코드를 캡슐화 시키는 것으로부터 시작하자.


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

CodePen에서 확인하기


하지만 위의 코드는 중요한 요건이 빠져있다. 타이머를 설정하고 매초마다 UI를 업데이트 하는 것은 Clock 내에
상세하게 구현되어야 한다는 것이다.


이상적인 형태로 우리는 시계 코드를 한번만 작성해 놓고 시계 스스로가 업데이트 할 수 있도록 할 것이다.


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


이렇게 구현하기 위해서는 Clock 컴포넌트 안에 “state”를 추가해야 한다.
State는 props와 유사하지만 private 속성을 가지고 있고 전적으로 컴포넌트에 의해 관리된다.
이전 내용에서 class로 선언된 컴포넌트는 추가적인 기능들이 있다고 했는데 Local state가 바로 그 것이고
이 Local state는 class들 안에서만 사용할 수 있다.


Converting a Function to a Class


다음의 5단계를 통해 functional로 만들어진 Clock 컴포넌트를 class로 만들 수 있다.


  1. functional 컴포넌트와 동일한 이름으로 React.Component를 상속받는 ES6 클래스를 생성한다.
  2. render()라는 비어있는 메소드 하나를 추가한다.
  3. functional 컴포넌트의 함수 본체를 render() 메소드 안으로 옮긴다.
  4. render() 메소드 내에 있는 props를 this.props로 수정한다.
  5. 비어있는 functional 컴포넌트를 삭제한다.

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

CodePen에서 확인하기


이제 Clock 는 funtional이 아닌 class로 선언되었다.
Class로 선언되었기 때문에 local state나 lifecycle hooks같은 기능들을 사용할 수 있다.


Adding Local State to a Class


3단계를 거쳐 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>
	);
 }
}


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


기본 생성자에게 props를 전달하는 방법에 주의하라.


constructor(props) {
	super(props);
	this.state = {date: new Date()};
 }


Class 컴포넌트에서는 기본 생성자를 호출할 때 반드시 props를 파라미터로 넘겨야 한다.


3) <Clock /> element에서 date prop을 삭제한다.


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


마침내 timer 코드를 시계 컴포넌트 안에 추가하였다.
결과 코드는 다음과 같다.


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

CodePen에서 확인하기


다음으로는 Clock 컴포넌트가 자신의 timer를 설정하고 매초마다 스스로 업데이트되도록 만들 것이다.


Adding Lifecycle Methods to a Class


많은 컴포넌트를 가진 애플리케이션에서 컴포넌트가 사용한 리소스를 컴포넌트가 사용 종료될 때 메모리에서
해제 시키는 것은 매우 중요하다.


우리는 Clock컴포넌트가 처음 DOM 내에 렌더링 되는 시점에만 timer를 설정하려고 한다. React에서는
이 과정을 “mounting”이라고 한다.


한편 DOM에서 생성된 컴포넌트가 제거될 때마다 timer를 지우려고 한다. React에서는 이 것을 “unmounting”
이라고 한다.


컴포넌트 내에는 컴포넌트가 mount 되거나 unmount 될 때마다 어떤 코드를 실행하는 특별한 메소드를 선언할
수 있다.


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

 componentDidMount() {

 }

 componentWillUnmount() {

 }

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


이런 메소드들을 “lifecycle hooks”라고 부른다.
componentDidMount() hook는 컴포넌트의 출력이 DOM에 렌더링 된 후 실행된다. 그래서 이 메소드는
timer를 설정하기에 좋은 위치이다.


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

this에 timer ID를 어떻게 저장하는지에 유의하라.


this.props는 React 자체에 의해 설정되고 this.state는 특별한 의미가 있지만 만일 시각적인 출력에 
사용되지 않는 어떤 정보를 저장할 필요가 있는 경우에는 수작업을 통해 자유롭게 필드를 추가할 수 있다.


render()안에서 사용되지 않는 것들은 state가 되어서는 안된다.


componentWillUnmount() lifecycle hook에서는 timer를 지울 것이다.


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


마침내 매초마다 실행되는 tick() 메소드를 구현할 차례이다.
컴포넌트의 local state를 주기적으로 업데이트하기 위해 this.setState()를 사용할 것이다.


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

CodePen에서 확인하기


이제 Clock은 매초 작동할 것이다.


무슨일이 일어나고 있는지, 그리고 메소드가 호출되는 순간 어떤 순서로 진행되는지 간단하게 살펴보자


  1. React.render()로 <Clock />이 전달되면 React는 Clock 컴포넌트의 생성자를 호출한다. Clock은 현재 시간을 표시해야 하기 때문에 현재 시간을 가진 객체로 this.state를 초기화 한다. 뒤에 이 state를 업데이트한다.
  2. 그리고 나서 React는 Clock 컴포넌트의 render()메소드를 호출한다. 이러한 방식으로 React는 화면에 무엇을 표시해야 하는지 알게 된다. 이후 React는 Clock의 렌더링 출력에 맞게 DOM을 업데이트한다.
  3. Clock의 출력이 DOM에 삽입되면, React는 componentDidMount() lifecycle hook를 호출한다. 그 안에서 Clock 컴포넌트는 브라우저에게 매초마다 tick()을 호출하는 timer를 설정하도록 요청한다.
  4. 매초마다 브라우저는 tick() 메소드를 호출한다. 그 안에서 Clock 컴포넌트는 현재 시간을 가지고 있는 객체를 가지고 setState() 메소드를 호출함으로써 UI 업데이트를 계획한다. setState() 호출 덕분에 React는 state가 변경된 것을 알 수 있게 되고 render() 메소드를 다시 호출하여 화면에 어떤 것이 표시되어야 하는지 알게 된다. 이 때는 render() 내에 있는 this.state.date가 달라지고 렌더링 출력에 업데이트된 시간이 포함된다. 이를 통해 React는 DOM을 업데이트 한다.
  5. Clock 컴포넌트가 DOM에서 제거되면 React는 componentWillUnmount() lifecycle hook를 호출하게 되고 timer가 중지된다.

Using State Correctly


setState()에 대해 반드시 알아야 할 사항이 3가지 있다.


Do Not Modify State Directly


아래 예제는 컴포넌트를 다시 렌더링하지 않는다.


// Wrong
this.state.comment = 'Hello';


대신에 setState()를 사용해야 한다.


// Correct
this.setState({comment: 'Hello'});


this.state를 이용하여 할당할 수 있는 유일한 위치는 생성자이다.


State Updates May Be Asynchronous


React는 성능 향상을 위해 다수의 setState() 호출을 하나의 업데이트로 일괄 처리한다.
this.props와 this.state는 비동기방식으로 처리되기 때문에 이후에 올 state에 대한 계산 결과 값을 
신뢰해서는 안된다.


예를들어 다음 코드는 카운터 업데이트에 실패할 것이다.


// Wrong
this.setState({
 counter: this.state.counter + this.props.increment,
});


이 코드를 수정하기 위해서는 파라미터로 객체가 아닌 함수를 받는 두 번째 형식의 setState()를 사용해야 한다.
파라미터로 전달되는 함수는 첫 번째 인자로 이전 state를, 두 번째 인자로는 업데이트된 시점의 props를 받는다.


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


위 코드에서는 화살표 함수를 사용했지만 일반 형식의 함수를 사용해도 된다.


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


State Updates are Merged


setState()를 호출할 때 React는 현재 state에 제공한 객체들을 병합한다.
아래 예제를 보면 state에는 몇개의 독립된 변수들이 있다.


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


그리고 서로 다른 setState()를 호출함으로써 각각의 변수를 독립적으로 업데이트 할 수 있다.


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

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


병합은 얕게 이루어지기 때문에 this.setState({comments})는 this.state.posts를 온전하게 두지만 
this.state.comments는 완전히 대체한다.


The Data Flows Down


부모 컴포넌트나 자식 컴포넌트 모두 어떤 컴포넌트가 state를 가지고 있는 컴포넌트인지 state가 없는 컴포넌트인지
알지 못한다. 또한 함수로 정의된 컴포넌트인지 클래스로 만들어진 컴포넌트인지도 알 수 없다.


이러한 이유로 state는 종종 local 또는 encapsulated라고 불리운다. state를 소유하고 설정하는 컴포넌트 외에는
state에 접근할 수 없다.


어떤 경우에는 자식 컴포넌트에 props를 통해 state를 전달하기도 한다.


<h2>It is {this.state.date.toLocaleTimeString()}.</h2>


이러한 방식은 사용자 정의 컴포넌트에서도 작동한다.


<FormattedDate date={this.state.date} />


FormattedDate 컴포넌트는 date를 props로 전달받게 되는데, 이것이 Clock의 state로부터 온 것인지,
Clock의 props로부터 온 것인지, 아니면 직접 손으로 타이핑을 해서 전달 받은 것인지 알지 못한다. 


function FormattedDate(props) {
 return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

CodePen에서 확인하기


이 것을 일반적으로 “top-down” 또는 “unidirectional” data flow라고 부른다. 어떤 state든 반드시 특정 
컴포넌트에 소유되어있어야 하고 그 state로부터 파생된 어떤 data 또는 UI라도 트리 구조의 아래쪽으로만 영향을
미칠 수 있다.


컴포넌트의 트리 구조를 props의 폭포로 생각한다면 각 컴포넌트의 state는 임의의 지점에서 합류하는 또다른
수원으로 생각할 수 있으며 이 또한 아래로 흐르게 되는 것이다.


모든 컴포넌트가 실제로 독립되어있다는 것을 보여주기 위해 3개의 <Clock>을 가진 App을 생성하였다.


function App() {
 return (
	<div>
	  <Clock />
	  <Clock />
	  <Clock />
	</div>
 );
}

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

CodePen에서 확인하기


각각의 Clock은 자신이 가진 timer만을 설정하고 있으며 각각의 timer는 독립적으로 업데이트된다.


React App들에서는 stateful인지 stateless인지 하는 것은 시간이 지남에 따라 변경될 수 있는 세부 구현 사항으로
여겨진다. stateless 컴포넌트를 stateful 컴포넌트 내에서 사용할 수도 있고 그 반대도 가능하다.






저작자 표시
신고

'Study > React' 카테고리의 다른 글

React 살펴보기 #8  (0) 2017.05.15
React 살펴보기 #7  (0) 2017.04.20
React 살펴보기 #6  (0) 2017.03.27
React 살펴보기 #5  (0) 2017.03.04
React 살펴보기 #4  (0) 2017.02.17
React 살펴보기 #3  (0) 2017.02.11

Components and Props

component는 UI를 독립적이고 재사용 가능한 부분으로 나누는 개별적인 조각으로 생각할 수 있다.
개념상으로는 component는 JavaScript의 함수와 같다. component는 (“props”라고 불리는)임의의 입력을 
받아서 화면에 그려지는 방식을 기술하는 React elements를 리턴해준다.


Functional and Class Components

가장 간단하게 component를 만드는 방법은 JavaScript 함수를 만드는 것이다.

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

이 함수는 하나의 “props”라는 데이터를 포함한 객체를 인자로 받아서 React elements를 리턴해주므로 유효한
React component라고 할 수 있다. 이러한 component는 말 그대로 JavaScript의 함수(function)와 같은

형태이기에 “functional”이라고 부른다.


물론 ES6 class를 이용해서도 component를 만들 수 있다.

class Welcome extends React.Component {
 render() {
	return <h1>Hello, {this.props.name}</h1>;
 }
}

React의 관점에서 봤을 때 위의 두 component는 동일하다.


Class들은 우리가 다음 장에서 논의할 추가적인 특징들을 가지고 있다. 그 때까지는 간단하게 functional 
components를 사용할 것이다.


Rendering a Component 

지금까지는 분명 DOM tag를 표현하는React elements들 만을 보아왔다.

const element = <div />;

그러나 elements는 사용자 정의 components를 표현할 수도 있다.

const element = <Welcome name="Sara" />;

React가 elements를 통해 사용자 정의 component가 표현되는 과정을 감지할 때 React는 하나의 객체형태로
JSX 속성을 component에 전달하게 되는데 우리는 이 객체를 “props”라고 부른다.


예를 들어 아래 코드는 화면에 “Hello, Sara”를 표시하게 된다.

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

const element = <Welcome name="Sara" />;
ReactDOM.render(
 element,
 document.getElementById('root')
);

CodePen에서 확인해보자

이 코드에서 무슨 일이 벌어지는 지 되짚어보자.

  1. 우리는 <Welcome name="Sara" />라는 element를 인자로 하여 ReactDOM.render()를 호출한다.
  2. React는 {name: 'Sara'}를 props로 하여 Welcome component를 호출한다.
  3. Welcome component는 그 결과 값으로 <h1>Hello, Sara</h1>라는 element를 리턴한다.
  4. React DOM은 <h1>Hello, Sara</h1>와 일치하도록 효율적으로 DOM을 업데이트 한다.

경고 :
component이름은 항상 대문자로 시작해야 한다.
예를들어 <div />는 DOM tag를 표시하며 <Welcome />는 component를 표시한다 그리고 Welcome
같은 scope 내에 있어야만 한다.

Composing Components

component는 그 자신의 출력에서 다른 component를 참조할 수 있다. 이를 통해서 모든 세부적인 수준에서라도 
공통적으로 이용 가능한 component의 추상화를 사용할 수 있다. React에서는 버튼, form, 다이얼로그, 화면 등이
일반적으로 component로 표현된다.


예를 들면 Welcome을 여러번 렌더링할 수 있는 App이라는 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')
);

CodePen에서 확인해보자


일반적으로 새로운 React app을 만들 때에는 제일 먼저 App이라는 하나의 component를 만들어 시작한다. 
하지만 만일 기존에 존재하는 app에 React를 통합하는 경우라면 버튼 같은 작은 component로부터 시작하여
점차로 view 계층의 상위 요소로 구성해 나갈 수 있다.


경고 :
Components는 반드시 하나의 root element만을 리턴해야 한다. 이 때문에 모든 <Welcome />element를 포함한 <div />를 추가한 것이다.

Extracting Components

components를 더 작은 components로 나누는 것을 두려워 하지 말라.


예를 들어 아래의 Comment component를 살펴보자

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

CodePen에서 확인해보자


이 component는 props의 author (객체 타입), text (문자열 타입), 그리고 date (날짜 타입)에 접근 할 수
있다. 그리고 소셜 미디어 웹사이트의 comment를 표시한다.


이 component는 모두 중첩된 구조로 되어있어 수정하기가 어렵고 각각을 개별적인 부분으로 재사용하기도 
어렵다. 이 component로부터 몇개의 작은 component를 추출해보자.


우선 Avatar를 추출해보자

function Avatar(props) {
 return (
	<img className="Avatar"
	  src={props.user.avatarUrl}
	  alt={props.user.name}
	/>
 );
}

Avatar는 Commnet안에서 렌더링 될 것을 알 필요가 없다. 즉, 다른 어떤 component에서 렌더링이 될 수도
있는 것이다. 때문에 prop을 author보다 일반적인 user로 이름 붙였다.


props의 이름은 component가 사용되는 맥락보다는 component 자체의 관점에서 지어줄 것을 권장한다.


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

다음으로는 Avatar를 렌더링하고 다음에 사용자의 이름을 렌더링하는 UserInfo component를 추출해보자

function UserInfo(props) {
 return (
	<div className="UserInfo">
	  <Avatar user={props.user} />
	  <div className="UserInfo-name">
		{props.user.name}
	  </div>
	</div>
 );
}

이렇게 하면 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>
 );
}

CodePen에서 확인해보자


component를 추출하는 작업은 처음에는 귀찮은 작업으로 보일 수도 있다. 그러나 재사용 가능한 components의
모음을 가지고 있으면 더 큰 app에서 발생하는 비용을 줄여줄 것이다. 경험에 비추어보았을 때 UI의 일부가 여러번
반복해서 사용되거나(ButtonPanelAvatar), 그 자체로 충분히 복잡하다면(AppFeedStory
Comment) 이러한 것들은 재사용 가능한 component를 만들기 위한 좋은 후보자들이다.


Props are Read-Only

component가 function으로 선언되었건 Class로 선언되었건 props를 수정할 수는 없다. 아래의 sum 
function을 살펴보자

function sum(a, b) {
 return a + b;
}

이런 함수는 전달받은 입력값을 변경하지도 않고 같은 값이 입력된다면 항상 리턴 값도 동일하기 때문에 “pure”라고 
부른다.


반면에 전달 받는 입력 값을 변경하는 아래 함수는 impure다.

function withdraw(account, amount) {
 account.total -= amount;
}

React는 꽤 유연하지만 하나의 엄격한 규칙이 있다.


모든 React components는 props에 대해서는 반드시 pure로 작동해야 한다.


물론 애플리케이션의 UI들은 유동적이며 시간이 지남에 따라 변한다. 다음 장에서는 “state”라는 새로운 개념에 대해
소개할 것이다. State는 이러한 규칙을 위반하지 않으면서도 사용자의 동작, 네트워크 응답 및 기타 다른 요소들에

대한 응답으로 시간 경과에 따라 출력을 변경할 수 있도록 해준다.

저작자 표시
신고

'Study > React' 카테고리의 다른 글

React 살펴보기 #7  (0) 2017.04.20
React 살펴보기 #6  (0) 2017.03.27
React 살펴보기 #5  (0) 2017.03.04
React 살펴보기 #4  (0) 2017.02.17
React 살펴보기 #3  (0) 2017.02.11
React 살펴보기 #2  (0) 2017.02.06


Redering Elements

Elements는 React 애플리케이션을 만드는 가장 작은 구성요소이다.
elements는 화면에 보여주고자 하는 것에 대한 표현이다.

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

브라우저의 DOM elements와 달리 React elements는 일반적인 객체이며 생성하는데 비용이 적게 든다.
React DOM은 React elements와 일치하도록 DOM을 업데이트한다.

주의 :
elements는 더 널리 알려진 개념인 “components”와 혼동할 수 있다. components에 대해서는 다음 
장에서 설명할 것이다. Elements는 component가 어떻게 구성되는지를 알려주는 요소이며 따라서 다음 
장으로 넘거가기 전에 이 장을 반드시 읽을 것을 권한다.



Rendering an Element into the DOM

HTML 문서 안 어딘가에 <div> 태그가 있는 상황을 가정해보자

<div id="root"></div>

이 <div> 태그 안에 있는 모든 것들은 React DOM에 의해 관리될 것이기 때문에 “root” DOM node라 부를 
것이다. React만으로 만들어지는 애플리케이션은 보통 한 개의 root DOM node를 갖게 된다. 만일 기존의 
애플리케이션에 React를 통합하는 상황이라면 원하는 만큼의 각각 독립된 root DOM node를 만들 수도 있다.
root DOM node 안에 React elements를 렌더링하기 위해서는 React.render() 함수를 통해 (함수의 
파라미터로) React elements와 root DOM node를 모두 전달해준다.

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

CodePen에서 실행해보자
위의 코드는 화면에 “Hello, world”를 표시할 것이다.


Updating the Rendered Element

React elements는 불변성을 가지고 있다. 어떤 element를 생성했다면 그 children이나 속성들을 수정할 수
없다. 하나의 element는 영화 속의 한 프레임과 같다. 그것은 특정 시점에 UI를 표현해 주는 것이다.


우리가 아는 한 UI를 업데이트하는 유일한 방법은 새로운 element를 만들어 React.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);

CodePen에서 실행해보자

위의 코드는 매초마다 setInterval의 callback 함수에서 React.render()를 호출한다.

주의 :
일반적으로 모든 React 애플리케이션은 React.render()를 한 번만 호출한다. 다음 장에서 이러한 
코드가 어떻게 상태(state)를 포함하는 components로 캡슐화 되는지에 대해 배우게 될 것이다. 각각이 
연관되는 내용이므로 주제를 건너 뛰지 않을 것을 권한다.



React Only Updates What's Necessary

React DOM은 element와 그 children을 이전 요소와 비교하여 DOM을 원하는 상태로 만드는 데 필요한 
DOM 업데이트 만 적용한다.


CodePen 이용하여 마지막 예제를 확인할 수 있다.


비록 우리가 tick을 호출할때마다 전체 UI 구조를 표현할 element를 생성했다고 할지라도 실제로는 그 내용이
변경되는 text 노드만이 React DOM에 의해서 업데이트 된다.


우리의 경험에 의하면 “전체 시간 안에서 UI가 어떻게 변하는지”에 대해 생각하는 것보다 “특정한 시점에 UI가 
어떻게 보여져야 하는지”에 대해 생각하는 것이 버그를 줄일 수 있는 방법이다.

저작자 표시
신고

'Study > React' 카테고리의 다른 글

React 살펴보기 #6  (0) 2017.03.27
React 살펴보기 #5  (0) 2017.03.04
React 살펴보기 #4  (0) 2017.02.17
React 살펴보기 #3  (0) 2017.02.11
React 살펴보기 #2  (0) 2017.02.06
React 살펴보기 #1  (0) 2017.02.06


앞서의 두 포스팅은 Quick Start에 있는 내용이었고 이번 번역할 JSX In Depth는 Advanced Guides에
있는 내용이입니다. 하지만 JSX를 한 번에 살펴보는 것이 좋을 것 같아 바로 이 JSX In Depth를 번역합니다.


JSX 심화

기본적으로 JSX는 단지 React.createElement(component, props, ...children) 함수에 대한
문법적인 편의성만을 제공한다. 아래 JSX 코드를 보자

<MyButton color="blue" shadowSize={2}>
 Click Me
</MyButton>

이 코드는 컴파일 후 다음과 같이 바뀐다.

React.createElement(
 MyButton,
 {color: 'blue', shadowSize: 2},
 'Click Me'
)

만일 태그에 자식이 없다면 아래 코드와 같이 self-closing 태그(/>)를 사용할 수 있다.

<div className="sidebar" />

이 코드는 아래와 같이 컴파일된다.

React.createElement(
 'div',
 {className: 'sidebar'},
 null
)

특정 JSX가 컴파일 후 어떻게 javascript 코드로 바뀌는지를 테스트해보고 싶다면 the online Babel compiler
를 사용하면 된다.


Specifying The React Element Type

JSX 태그의 처음 부분은 React element의 type을 결정한다.
대문자로 시작된 JSX 태그는 React Component를 가리킨다. 이러한 태그들은 해당 이름으로 된 변수들의
직접적인 참조로 컴파일되기 때문에 만일 <Foo />라는 JSX 표현식을 사용하게 되면 Foo라는 객체는
같은 scope 내에 있어야 한다. 


React Must Be in Scope

JSX가 React.createElement 함수를 호출하도록 컴파일되기 때문에 React 라이브러리는 JSX 코드의
scope 내에 있어야 한다.


예를 들면 아래 코드는 비록 React와 CustomButton이 javascript로부터 직접 참조되지 않더라도
2개의 import문이 모두 필요하다.

mport React from 'react';
import CustomButton from './CustomButton';

function WarningButton() {
 // return React.createElement(CustomButton, {color: 'red'}, null);
 return <CustomButton color="red" />;
}

자바 스크립트 번들을 사용하지 않고 React를 스크립트 태그로 추가했다면 이미 React 글로벌의 scopre 내에 
있다고 할 수 있을 것이다.


Using Dot Notation for JSX Type

JSX 내에서 React 컴포넌트는 점(.) 표시로 참조될 수 있다. 이 것은 많은 React 컴포넌트를 추출할 수 있는
단일 모듈을 사용할 때 편리하다. 예를들면 만일 MyComponents.DatePicker가 컴포넌트라면 아래 예제와
같이 JSX에서 직접 사용 가능하다

import React from 'react';

const MyComponents = {
 DatePicker: function DatePicker(props) {
	return <div>Imagine a {props.color} datepicker here.</div>;
 }
}

function BlueDatePicker() {
 return <MyComponents.DatePicker color="blue" />;
}


User-Defined Components Must Be Capitalized

element가 소문자로 시작한다면 이것은 <div>나 <span>같은 기존 html 태그로 참조하게 될 것이고 그 결과
React.createElement에 ‘div’나 ‘span’같은 문자열로 전달될 것이다.


하지만 <Foo />와 같이 대문자로 시작을 한다면 React.createElement(Foo) 형태로 컴파일 될 것이고
작성된 javascript 파일에서 선언되거나 import된 component와 상응하게 될 것이다.


따라서 component 이름은 대문자로 시작하는 것을 권장한다. 만일 작성된 코드에 소문자로 시작되는 
component가 존재한다면 그것을 JSX에 사용하기 전에 대문자로 시작되는 변수에 먼저 할당을 해야 한다.



예를들면 아래 코드는 정상적으로 작동하지 않을 것이다.

import React from 'react';

// Wrong! This is a component and should have been capitalized:
function hello(props) {
 // Correct! This use of <div> is legitimate because div is a valid HTML tag:
 return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
 // Wrong! React thinks <hello /> is an HTML tag because it's not capitalized:
 return <hello toWhat="World" />;
}

위의 코드를 수정하기 위해서는 hello라는 이름을 Hello로 바꾸고 <Hello />로 그 것을 참조해야 한다.

import React from 'react';

// Correct! This is a component and should be capitalized:
function Hello(props) {
 // Correct! This use of <div> is legitimate because div is a valid HTML tag:
 return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
 // Correct! React knows <Hello /> is a component because it's capitalized.
 return <Hello toWhat="World" />;
}


Choosing the Type at Runtime 

일반적인 표현식을 React element type으로 사용할 수 없다. 만일 일반적인 표현식을 element type으로
사용하고 싶다면 먼저 그 표현식을 대문자로 시작하는 변수에 할당하여야 한다. 이러한 상황은 종종 prop을
기반으로 하여 서로 다른 컴포넌트를 렌더링하고자 할 때 발생한다.

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
 photo: PhotoStory,
 video: VideoStory
};

function Story(props) {
 // Wrong! JSX type can't be an expression.
 return <components[props.storyType] story={props.story} />;
}

이 문제를 바로잡기 위해서는 먼저 대문자로 시작하는 변수에 할당해야 한다.

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
 photo: PhotoStory,
 video: VideoStory
};

function Story(props) {
 // Correct! JSX type can be a capitalized variable.
 const SpecificStory = components[props.storyType];
 return <SpecificStory story={props.story} />;
}


Props in JSX

JSX에서 prop을 지정하는 방법에는 몇가지가 있다.


JavaScript Expressions

javascript의 표현식을 {}로 둘러 싸서 prop으로 전달할 수 있다. 아래 예에서 보자.

<MyComponent foo={1 + 2 + 3 + 4} />

MyComponent에서 props.foo는 10이 된다. 1+2+3+4가 {}둘러싸여 javascript 연산으로 평가되었기
때문이다.


if문이나 for 루프는 javascript의 표현식이 아니기 때문에 JSX 내에서 직접 사용할 수는 없다. 대신에 다음의
예제처럼 코드를 이용하여 처리할 수 있다.

function NumberDescriber(props) {
 let description;
 if (props.number % 2 == 0) {
	description = <strong>even</strong>;
 } else {
	description = <i>odd</i>;
 }
 return <div>{props.number} is an {description} number</div>;
}


String Literals

문자열도 prop으로 전달할 수 있다. 아래의 두 JSX표현은 동일한 것이다.

<MyComponent message="hello world" />

<MyComponent message={'hello world'} />

문자열로 전달할 때 그 문자열은 HTML escaping 처리되지 않는다. 따라서 아래 두 코드도 동일하다.

<MyComponent message="&lt;3" />

<MyComponent message={'<3'} />

이런 동작들 사이에 서로 관련성은 없으며 다만 완벽한 설명을 위해 언급된 것 뿐이다.


Props Default to "True"

만일 prop에 아무런 값을 넘기지 않는다면 prop은 기본적으로 true라는 값을 갖게 된다. 아래의 두
JSX 표현식은 동일하다.

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

일반적으로 이러한 표현은 ES6의 축약 표현에서 {foo}가 {foo: foo}를 의미하기 때문에 권장하지 않는다.
대신에 {foo: true}와 같이 명시적으로 사용해야 한다. 이러한 동작은 단지 HTML의 동작과 일치하도록 하기
위한 것일 뿐이다.


Spread Attributes

이미 object 형태의 props이 존재하고 이것을 JSX로 전달하려고 한다면 전체 props object를 모두
전달하기 위한 전개(spread) 연산자로 를 사용할 수 있다. 아래의 두 컴포넌트는 동일한 동작을 한다.

function App1() {
 return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
 const props = {firstName: 'Ben', lastName: 'Hector'};
 return <Greeting {...props} />;
}

전개(Spread) 연산자는 generic containers를 만들때 유용하다. 그러나 이러한 사용이 코드를 더 복잡하게
만들 수도 있다. 다수의 관련 없는 props를 컴포넌트에 넘김으로써 관리하기가 힘들어질 수 있기 때문이다.
따라서 이러한 문법은 반드시 필요한 부분에만 적절하게 사용하여야 한다.


Children in JSX

여는 태그와 닫는 태그가 있는 JSX 표현에서 이 두 태그 사이에 있는 내용은 props.children라는 특별한
prop으로 전달된다. children을 전달하는데는 몇가지 다른 방법이 있다.


String Literals

우선 여는 태그와 닫는 태그 사이에 문자열을 넣을 수 있으며, 이 때의 props.children는 단지 문자열로만
취급된다. 이러한 방식은 기존 HTML 요소를 사용하는 경우 유용하다. 아래의 예를 보자

<MyComponent>Hello world!</MyComponent>

이 코드는 유효한 JSX 표현식이며 MyComponent의 props.children의 값은 단순히 “Hello world!”라는
문자열이다. HTML은 escaping되지 않으므로 일반적으로 아래 코드와 같이 HTML을 작성하듯이 JSX를 작성할
수 있다.

<div>This is valid HTML &amp; JSX at the same time.</div>

JSX는 각 줄의 첫 번째와 마지막에 있는 공백은 모두 제거한다. 또한 비어있는 줄도 제거한다. 태그에 인접한
줄바꿈도 제거된다. 문자열 중간에 있는 줄바꿈은 하나의 공백으로 처리된다. 이런 이유로 아래의 모든 코드는
동일하다.

<div>Hello World</div>

<div>
 Hello World
</div>

<div>
 Hello
 World
</div>

<div>

 Hello World
</div>


JSX Children

JSX elements도 children으로 사용을 할 수 있으며 이러한 표현은 중첩된 컴포넌트를 표현하는데 유용하다.

<MyContainer>
 <MyFirstComponent />
 <MySecondComponent />
</MyContainer>

서로 다른 유형의 children을 함께 사용할 수 있기 때문에 문자열 children을 JSX children과 함께 사용할 수
있다. 이러한 방식은 JSX를 HTML과 유사하게 사용할 수 있는 또다른 방법이며 따라서 아래의 코드는 JSX로도
또 HTML로도 유효한 표현식이다.

<div>
 Here is a list:
 <ul>
	<li>Item 1</li>
	<li>Item 2</li>
 </ul>
</div>

React 컴포넌트는 다수의 React elements를 리턴할 수 없다. 하지만 하나의 JSX 표현식에는 다수의
children이 있을 수 있기 때문에 다수의 요소로 컴포넌트를 렌더링 하고 싶다면 위의 코드와 같이 div
씌운 중첩된 구조를 만들면 된다.


JavaScript Expressions

{}로 감싼다면 어떠한 javascript 표현식이라도 children으로 전달할 수 있다. 아래 예제들은 동일한 코드이다.

<MyComponent>foo</MyComponent>

<MyComponent>{'foo'}</MyComponent>

이러한 방식은 임의의 길이를 갖는 JSX 표현식의 목록을 렌더링 하는데 유용하다. 아래 예제는 HTML 목록을
렌더링하는 코드이다.

function Item(props) {
 return <li>{props.message}</li>;
}

function TodoList() {
 const todos = ['finish doc', 'submit pr', 'nag dan to review'];
 return (
	<ul>
	  {todos.map((message) => <Item key={message} message={message} />)}
	</ul>
 );
}

javascript 표현식은 다른 유형의 children과 함께 섞어서 사용할 수 있다. 이 것은 문자열 template 대신에
사용하면 유용하다.

function Hello(props) {
 return <div>Hello {props.addressee}!</div>;
}


Functions as Children

보통 JSX에 삽입되는 javascript 표현식은 문자열이나 React element나 이들의 목록으로 평가된다. 그러나
props.children은 렌더링 방법에 대해 알 필요가 없다는 점 뿐만 아니라 어떤 종류의 데이터도 전달할 수
있다는 점에서 다른 종류의 prop과 동일하게 작동한다. 예를들어 컴포넌트에서 callback을 props.children
으로 사용할 수도 있는 것이다.

// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
 let items = [];
 for (let i = 0; i < props.numTimes; i++) {
	items.push(props.children(i));
 }
 return <div>{items}</div>;
}

function ListOfTenThings() {
 return (
	<Repeat numTimes={10}>
	  {(index) => <div key={index}>This is item {index} in the list</div>}
	</Repeat>
 );
}

커스텀 컴포넌트에 전달된 children은 렌더링이 진행되기 전에 React가 해석할 수 있는 것이라면 어떤 형태로도
바뀔 수 있다. 이러한 방식은 일반적인 사용법은 아니지만 JSX가 수행할 수 있는 작업을 확장하고자 할 때 유용하게
사용될 수 있다.


Booleans, Null, and Undefined Are Ignored

falsenullundefined 그리고 true 등도 모두 유효한 children들이다. 이 것들은 별도로 렌더링 하지
않는다. 아래 JSX 표현식들은 모두 동일하게 렌더링된다. 

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

이러한 표현들은 React elements를 조건에 따라 렌더링할 때 유용하다. 아래 예제는 만일 showHeader가
true이면 <Header />만 렌더링한다.

<div>
 {showHeader && <Header />}
 <Content />
</div>

한 가지 주의할 점은 숫자 0과 같이 false를 의미하는 듯한 값은 React에서 렌더링 된다는 점이다. 예를들면 
아래의 코드에서 만일 props.messages가 비어있는 배열이라면 props.messages.length는 0
되어 false로서 기능하는 것이 아니라 0이 출력되게 된다.

<div>
 {props.messages.length &&
	<MessageList messages={props.messages} />
 }
</div>

반대로 falsetrue null 그리고 undefined 과 같은 것들을 화면상에 출력하기 위해서는 먼저
이것들을 문자열로 변환해야 한다.

<div>
 My JavaScript variable is {String(myVariable)}.
</div>


저작자 표시
신고

'Study > React' 카테고리의 다른 글

React 살펴보기 #6  (0) 2017.03.27
React 살펴보기 #5  (0) 2017.03.04
React 살펴보기 #4  (0) 2017.02.17
React 살펴보기 #3  (0) 2017.02.11
React 살펴보기 #2  (0) 2017.02.06
React 살펴보기 #1  (0) 2017.02.06


JSX 소개


아래 변수 선언을 살펴보자


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


조금은 우스꽝스럽게도 보이는 이 문법은 문자열도 아니고 그렇다고 HTML도 아니다.
이것은 JSX라고 불리는 javascript의 확장 문법이다. React를 이용하여 UI를 표현할 때 JSX를 사용하는 것을
권장한다. JSX는 템플릿 언어를 연상시키기도 하지만 javascript의 모든 기능을 사용할 수 있다.


JSX는 React의 “elements”를 생성한다. “elements”를 DOM 구조에 렌더링하는 과정은 다음 장에서 살펴볼
것이고 여기에서는 React를 시작하는데 필요한 JSX의 기본적인 내용을 살펴볼 것이다.


Embedding Expressions in JSX


중괄호({})를 이용하면 어떠한 javascript 표현이라도 JSX에 포함시킬 수 있다. 예를 들어 ‘2+2’라는 수식이나
user.name같은 객체 표현식 또는 formatName(user)같은 함수 호출도 가능하다.


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 is an Expression Too


컴파일 후에 JSX 표현식은 일반적인 javascript의 객체들로 바뀐다. 이 말은 if문이나 for loop 내에서
JSX표현식을 변수에 할당 하거나 함수의 인자로 사용하거나 함수의 리턴 값으로 사용할 수 있다는
것이다.


function getGreeting(user) {
  if (user) {
	return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

Specifying Attributes with JSX


특정 문자열을 속성 값으로 사용하기 위해 따옴표(“”)를 이용할 수 있다.


const element = <div tabIndex="0"></div>;


또한 중괄호({})를 이용하여 속성 값으로 javascript 표현식을 사용할 수도 있다.


const element = <img src={user.avatarUrl}></img>; 


속성 값으로 사용하기 위해 중괄호 내에서 따옴표를 사용하면 안된다. 이렇게 사용할 경우 JSX는 이 속성
값을 표현식이 아닌 문자열로 인식하게 된다(즉, { 2+2 }는 4로 인식하지만 {“2+2”}는 2+2라는 
문자열로 인식한다는 뜻이다). 중괄호는 표현식에 따옴표는 문자열에 각가 사용하되 두 기호를 동시에
사용하면 안된다.


Specifying Children with JSX


태그의 내용이 비어있을 경우에는 XML의 형식과 같이 />로 닫아주어야 한다. 


const element = <img src={user.avatarUrl} />;


JSX 태그는 자식을 포함할 수 있다.


const element = (
 <div>
	<h1>Hello!</h1>
	<h2>Good to see you here.</h2>
 </div>
);

주의 : 
JSX는 HTML 보다는 javascript에 가깝기 때문에 React DOM은 HTML 속성 스타일의 표기법이 아닌
camelCase 형식으로 속성 이름을 표기하도록 규정하고 있다.
예를 들면 class는 className으로, tabindex는 tabIndex로 표현하는 식이다.

JSX Prevents Injection Attacks


JSX에서의 사용자 입력은 안전하다.


const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;


기본적으로 React DOM에서는 렌더링하기 전에 JSX에 포함되는 사용자 입력 값들을 escaping 처리한다.
이런 방식은 애플리케이션에서 명시적으로 구현된 코드가 아닌 다른 코드의 주입을 결코 허용하지 않기 때문에 
안전을 보장할 수 있다. 모든 외부 코드들은 렌더링 전에 문자열로 처리된다. 이것은 XSS(cross-site-scripting)
공격을 막아준다.


JSX Represents Objects


Babel은 JSX 코드를 React.createElements()호출로 컴파일한다. 따라서 아래 두 블럭의 코드는 동일하다.


const element = (
 <h1 className="greeting">
	Hello, world!
 </h1>
);

-

const element = React.createElement(
 'h1',
 {className: 'greeting'},
 'Hello, world!'
);


React.createElements()는 버그 방지를 위한 몇가지 체크를 수행하지만 본질적으로 아래 코드와 같이
객체를 생성한다.


// Note: this structure is simplified
const element = {
 type: 'h1',
 props: {
	className: 'greeting',
	children: 'Hello, world'
 }
};


이런 객체들을 “React elements”라고 부른다. 이러한 객체들은 화면에서 보고자 하는 UI에 대한 기술로
생각해도 좋다. React는 이러한 객체를 읽고 이를 사용하여 DOM을 구성하고 최신 상태로 유지한다.


다음 장에서 DOM 구조에 React elements를 렌더링하는 방법을 살펴볼 것이다.
(제 블로그에서는 JSX에 대한 연속성을 위해 다음 포스팅에서는 JSX In Death를 번역할 것입니다.)


팁 :
“Babel” 문법 구조를 찾아내기 쉽도록 ES6와 JSX에 대해 highlight 기능이 있는 편집기를 사용하는 것을
권장한다.


저작자 표시
신고

'Study > React' 카테고리의 다른 글

React 살펴보기 #6  (0) 2017.03.27
React 살펴보기 #5  (0) 2017.03.04
React 살펴보기 #4  (0) 2017.02.17
React 살펴보기 #3  (0) 2017.02.11
React 살펴보기 #2  (0) 2017.02.06
React 살펴보기 #1  (0) 2017.02.06


React 개요


(이 글은 Till60 카테고리에 포함되었던 내용인데 글의 성격상 React 번역 부분만 따로 옮겨온
것입니다.)


아래 내용은 React 공식 홈페이지의 메인 페이지 내용을 의역한 것이다.


  1. React는 UI의 상호작용을 보다 쉽게 처리할 수 있는데, 변경된 데이터와 관련된 내용만을 업데이트
    하고 렌더링 하는 방식으로 이를 효율적으로 수행한다. 이러한 방식은 결국 예측 가능한 코드를 만들 
    수 있게 하고 따라서 디버깅이 쉬워진다.
  2. 자신의 상태를 관리할 수 있는 캡슐화된 컴포넌트를 만듦으로써 복잡한 UI를 쉽게 구성할 수 있으며

    이러한 로직을 자바스크립트로 작성하기 때문에 다양한 데이터 형식을 쉽게 전달할 수 있고 또 DOM

    에 의존하지 않고 상태를 유지할 수 있다.

  3. React는 특정한 기술 스택에 의존하지 않고 React 내에서 새로운 기능들을 개발 할 수 있도록 구현

    되었다. 또한 React Native를 이용하면 Node를 이용하여 서버측에 렌더링 하거나 강력한 모바일

    앱에 렌더링 하는 것도 가능하다.


◆ 간단한 컴포넌트


React의 기본은 입력 데이터를 전달받아 화면에 표시해주는 render()함수를 구현하는 것이다.
아래 코드는 XML 구문과 유사한 JSX를 이용하여 구현된 코드이다. 하지만 JSX 사용이 필수적인
것은 아니다.


class HelloMessage extends React.Component {
	render() {
			return <div>Hello {this.props.name}</div>;
	}
}
ReactDOM.render(<HelloMessage name="Jane" />, mountNode);


위의 코드에서 눈여겨 봐야 할 것은 HelloMessage라는 클래스 이름과 ReactDOM.render의 
호출이다.ReactDOM.render의 호출을 통해 비로소 화면에 데이터가 출력되는데 이 함수 안에서
HelloMessage 클래스가 어떻게 사용되고 있는지 또 그 안에 name이라는 property가 어떻게 
HelloMessage 클래스로 전달되는지(ReactDOM.render 함수의 파라미터에 있는 name은 
this.props.name이라는 형태로 HelloMessage 클래스 안으로 전달된다)를 주의해서 보자. 
이 코드의 결과는 브라우저 화면에 “Hello Jane” 이라는 문자열을 출력하게 된다.


◆ 상태를 갖는 컴포넌트


클래스 내에서 this.props를 이용하여 접근 가능한 입력 데이터 외에도 this.state로 접근 가능한 
내부 상태 데이터도 관리할 수 있다. 컴포넌트의 상태가 변경되면 render() 함수를 다시 호출하여 
렌더링 된 마크업 문서를 업데이트하게 된다.


class Timer extends React.Component {
 		constructor(props) {
				super(props);
				this.state = {secondsElapsed: 0};
		 }

 		tick() {
				this.setState((prevState) => ({
	  				secondsElapsed: prevState.secondsElapsed + 1
				}));
		  }

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

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

 		render() {
				return (
	  				<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
				);
		 }
}

ReactDOM.render(<Timer />, mountNode);


Component의 lifecycle에 대해서는 추후 자세히 알아보기로 하고 여기서는 간단하게 흐름만 알아보도록 하자

1) 우선 constructor에서 이 컴포넌트의 상태를 나타낼 this.state에 0값으로 설정된 secondsElapsed라는 
요소를 할당하고 있다.


2) render() 함수가 호출된다.


3) Component의 lifecycle에 의해 componentDidMount가 호출되며 이 안에서 자바스크립트의 
setInterval 함수를 통해 1초에 한 번씩 Timer 클래스에 구현된 tick()함수를 호출한다.


4) tick()함수에서는 이전 상태 값을 파라미터로 전달받아 여기에 1을 더한 후 this.state에 할당된 seconds-
Elapsed에 값을 새로 설정한다.


이러한 과정을 거쳐서 변경된 this.state만을 새롭게 렌더링하여 화면에 보여주게 된다.


◆ Application


props와 state를 이용하여 만든 ToDo Application이다. 이 예제에서는 state를 이용하여 사용자가 
입력한 텍스트 뿐만 아니라 항목들의 목록을 추적하여 관리한다. 이러한 과정에서 event handler는 
마치 inline으로 렌더링 되는 것 처럼 보이지만 실제로는 event delegation을 이용하여 수집되고 
구현된다.


class TodoApp extends React.Component {
	constructor(props) {
			super(props);
			this.handleChange = this.handleChange.bind(this);
			this.handleSubmit = this.handleSubmit.bind(this);
			this.state = {items: [], text: ''};
	}

 		render() {
				return (
	  			<div>
					<h3>TODO</h3>
					<TodoList items={this.state.items} />
					<form onSubmit={this.handleSubmit}>
		  			<input onChange={this.handleChange} value={this.state.text} />
		  			<button>{'Add #' + (this.state.items.length + 1)}</button>
					</form>
	  			</div>
  			 );
 		}

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

 		handleSubmit(e) {
				e.preventDefault();
				var newItem = {
	  				text: this.state.text,
	 				 id: Date.now()
				};
				this.setState((prevState) => ({
	  				items: prevState.items.concat(newItem),
	  				text: ''
				}));
		 }
	}

	class TodoList extends React.Component {
 		render() {
  			return (
	  			<ul>
	   			{this.props.items.map(item => (
		  				<li key={item.id}>{item.text}</li>
					))}
	  			</ul>
				);
 		}
	}

	ReactDOM.render(<TodoApp />, mountNode);


앞서 상태를 갖는 컴포넌트에서와 같이 흐름을 살펴보도록 하자.

1) TodoApp 클래스에서는 생성자에서 이벤트를 처리할 두 함수인 handleChange와 handleSubmit을
this. handleChange와 this. handleSubmit에 할당한다. this.state에는 items라는 배열과 text라는
문자열로 구성된 object를 할당한다.


2) handleChange 함수는 event 객체를 인자로 전달받아 this.state 중 text에 event target에 입력된
값을 할당한다.


3) handleSubmit에서는 handleChange에서 this.state에 할당한 text 값과 날짜를 기반으로 한 id
값을 이용하여 새로운 item을 생성하고 이 item을 this.state의 items 배열에 추가(concat)한 후
setState 호출로 인해 새로 렌더링이 실행된다.


4) TodoList 클래스는 render 함수만 정의되어있으며 파라미터로 전달받은 item 객체를 이용하여 
li 리스트를 만든다.


ReactDOM.render 함수가 호출되면 1)에서 초기화가 진행되고 TodoApp 클래스의 render() 함수가
호출되는데 render 함수 내에서는 먼저 this.state 중 items 배열을 파라미터로 하여 TodoList 클래스를
렌더링한다. 즉, 4)의 과정이 실행된다. 이어서 input 폼에는 onChange 이벤트 발생 시 handleChange
함수를 호출한다. 2)의 과정이 실행되는 것이다. input의 value는 최초 상태 또는 item이 등록된 후에는
This.state.text 값이 ‘’로 설정되므로 비어있게 될 것이다. 버튼에는 this.state의 items 배열을 참조하여
현재까지 등록된 item + 1의 수가 표시된다. 마지막으로 Add 버튼을 클릭하면 handleSubmit 함수가
호출되면서 3)의 과정이 진행된다.


◆ 컴포넌트에서 외부 플러그인 사용


React는 컴포넌트에서 외부 라이브러리나 프레임워크와 인터페이스 할 때 유연할 뿐만 아니라 hooks를
제공하고 있다. 아래 예제는 Markdown 라이브러리인 remarkable을 이용하여 textarea의 text를
실시간으로 변환하는 예제이다.


class MarkdownEditor extends React.Component {
	constructor(props) {
			super(props);
			this.handleChange = this.handleChange.bind(this);
			this.state = {value: 'Type some *markdown* here!'};
	}

 		handleChange() {
				this.setState({value: this.refs.textarea.value});
 		}

 		getRawMarkup() {
				var md = new Remarkable();
				return { __html: md.render(this.state.value) };
 		}

 		render() {
				return (
	  				<div className="MarkdownEditor">
					<h3>Input</h3>
					<textarea
		  				onChange={this.handleChange}
		  				ref="textarea"
		  				defaultValue={this.state.value} />
					<h3>Output</h3>
					<div
		  				className="content"
		  				dangerouslySetInnerHTML={this.getRawMarkup()}
					/>
	  				</div>
				);
 		}
	}

ReactDOM.render(<MarkdownEditor />, mountNode);


마찬가지로 흐름을 살펴보면

1) MarkdownEditor의 생성자에서 handleChange를 bind하고 this.state에는 value라는 키의 문자열
값을 할당한다.


2) handleChange 함수에서는 textarea의 값을 받아 setState를 통해 상태를 변화시키고 다시 렌더링한다.


3) getRawMarkup 함수에서는 remarkable 라이브러리 객체를 생성하고 this.state.value에 저장된
문자열을 remarkable 객체를 이용하여 렌더링한 후 리턴한다.


ReactDOM.render가 호출되면 1)의 초기화가 진행되고 render 함수가 호출되면 textarea의 onChange 
이벤트 처리를 통해 내용이 변경될 때마다 handleChange 함수를 호출하고 this.state를 변경한다. 
content라는 클래스명을 가진 div에서는 dangerouslySetInnerHTML을 통해 this.state에 할당된
값을 화면에 출력하는데 이 때 getRawMarkup() 함수를 호출한다. 즉, 3)의 과정을 거친 내용이 화면에
출력된다.

저작자 표시
신고

'Study > React' 카테고리의 다른 글

React 살펴보기 #6  (0) 2017.03.27
React 살펴보기 #5  (0) 2017.03.04
React 살펴보기 #4  (0) 2017.02.17
React 살펴보기 #3  (0) 2017.02.11
React 살펴보기 #2  (0) 2017.02.06
React 살펴보기 #1  (0) 2017.02.06

+ Recent posts

티스토리 툴바