ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React
    Note 2020. 11. 19. 14:23

    React(리액트)

    사용자 인터페이스를 만들기 위한 JavaScript 라이브러리

    ko.reactjs.org

     

    React – 사용자 인터페이스를 만들기 위한 JavaScript 라이브러리

    A JavaScript library for building user interfaces

    ko.reactjs.org

     

    간단한 컴포넌트

    리액트 컴포넌트는 render()라는 메서드를 구현하는데, 이것은 데이터를 입력받아 화면에 표시할 내용을 반환하는 역할을 한다. 컴포넌트로 전달된 데이터는 render() 안에서 this.props를 통해 접근할 수 있다. 

    JSX

    자바스크립트의 확장 문법

    JSX는 리액트 "엘리먼트(element)"를 생성한다. React를 사용하기 위해서 JSX가 꼭 필요한 것은 아니지만 대부분의 사람들은 자바스크립트 코드 안에서 UI 관련 작업을 할 때 시각적으로 도움을 받는다고 한다.

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

    JSX 문법

    1. 반드시 하나의 엘리먼트로 감싸야한다.

    2. 자바스크립트 코드를 적용할 땐 {} 안에 작성한다.

    3. JSX 내부에선 if문을 사용할 수 없다(IIFE or 삼항연산자 사용).

    <div>
    	{
        	(1 + 1 === 2)? (<h1> 정답 </h1>) : (<h2> 오답 </h2>)
    	}
    </div>

    4. 엘리먼트의 클래스 이름을 적용할 때, className을 사용한다.

     

    상태를 가지는 컴포넌트

    컴포넌트는 this.props를 이용해 입력 데이터를 다루는 것 외에도 내부적인 상태 데이터를 가질 수 있다. 이는 this.state로 접근할 수 있다. 컴포넌트의 상태 데이터가 바뀌면 render()가 다시 호출되어 마크업이 갱신된다.

     

    외부 플러그인을 사용하는 컴포넌트

    React는 유연하며 다른 라이브러리나 프레임워크를 함께 활용할 수 있다.

     

     

    엘리먼트 렌더링

    엘리먼트는 리액트 앱의 가장 작은 단위, 엘리먼트는 화면에 표시할 내용을 기술한다.

    브라우저 DOM 엘리먼트와 달리 리액트 엘리먼트는 일반 객체(plain object)이고 쉽게 생성할 수 있다. 리액트 DOM은 리액트 엘리먼트와 일치하도록 DOM을 업데이트한다.

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

     

    DOM에 엘리먼트 렌더링하기

    HTML 파일 어딘가에 <div>가 있다고 가정해보자.

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

    이 안에 들어가는 모든 엘리먼트를 리액트 DOM에서 관리하기 때문에 이것을 "루트(root)" DOM 노드라고 부른다. 리액트로 구현된 애플리케이션은 일반적으로 하나의 루트 DOM 노드가 있다. 리액트를 기존 앱에 통합하려는 경우에는 더 많은 독립된 루트 DOM 노드가 있을 수 있다.

     

    리액트 엘리먼트를 루트 DOM 노드에 렌더링 하려면 ReactDOM.render()로 전달하면 된다.

     

    DOM에 엘리먼트 렌더링하기

    리액트 엘리먼트는 불변객체이다. 엘리먼트를 생성한 이후에는 해당 엘리먼트의 자식이나 속성을 변경할 수 없다. 지금까지 공부한 내용으로는 UI를 업데이트하는 유일한 방법은 새로운 엘리먼트를 생성하고 이걸 다시 ReactDOM.render()로 전달하는 것이다.

     

    setInterVal() 콜백을 이용해 초마다 ReactDOM.render()를 호출하는 예시

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

     

    변경된 부분만 업데이트하기

    리액트 DOM은 해당 엘리먼트와 그 자식 엘리먼트를 이전의 엘리먼트와 비교하고 DOM을 원하는 상태로 만드는데 필요한 경우에만 DOM을 업데이트한다.

     

     

    Component, Props

    컴포넌트를 통해 UI를 재사용 가능한 개별적인 여러 조각으로 나누고, 각 조각을 개별적으로 살펴볼 수 있다. 개념적으로 컴포넌트는 자바스크립트 함수와 유사하다. "Props"라고 하는 임의의 입력을 받은 후, 화면에 어떻게 표시하는지를 기술하는 리액트 엘리먼트를 반환한다.

     

    컴포넌트 정의하는 가장 간단한 방법은 자바스크립트 함수로 작성하는 것이다.

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

    또한 ES6 class를 사용하여 컴포넌트를 정의할 수 있다.

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

     

    페이지에 "Hello, world"를 렌더링 하는 예시

    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    const element = <Welcome name="world" />;
    ReactDOM.render(
      element,
      document.getElementById('root')
    );

    위 예시에서 다음과 같은 일이 일어난다.

    1. <Welcome name="world" /> 엘리먼트로 ReactDOM.render()를 호출합니다.
    2. React는 {name: 'world'}를 props로 하여 Welcome 컴포넌트를 호출합니다.
    3. Welcome 컴포넌트는 결과적으로 <h1>Hello, world</h1> 엘리먼트를 반환합니다.
    4. React DOM은 <h1>Hello, world</h1> 엘리먼트와 일치하도록 DOM을 효율적으로 업데이트합니다.

    주의: 컴포넌트의 이름은 항상 대문자로 시작한다.

    리액트는 소문자로 시작하는 컴포넌트를 DOM 태그로 처리한다.

     

     

    조건부 렌더링

    리액트에서는 원하는 동작을 캡슐화하는 컴포넌트를 만들 수 있다. 이렇게 하면 애플리케이션의 상태에 따라서 컴포넌트 중 몇 개만을 렌더링 할 수 있다. 조건부 렌더링은 자바스크립트에서의 조건 처리와 같이 동작한다. if나 조건부 연산자와 같은 자바스크립트 연산자를 현재 상태를 나타내는 엘리먼트를 만드는 데 사용하면 된다. 그러면 리액트는 현재 상태에 맞게 UI를 업데이트할 것이다.

     

    아래 두 컴포넌트가 있다고 해보자.

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

     

    이제 사용자의 로그인 상태에 맞게 위 컴포넌트 중 하나를 보여주는 Greeting 컴포넌트를 만든다.

    function Greeting(props) {
      const isLoggedIn = props.isLoggedIn;
      if (isLoggedIn) {
        return <UserGreeting />;
      }
      return <GuestGreeting />;
    }
    
    ReactDOM.render(
      <Greeting isLoggedIn={false} />,
      document.getElementById('root')
    );

    위 예시는 isLoggedIn prop에 따라서 다른 인사말을 렌더링 한다.

     

    리스트와 Key

    먼저 자바스크립트에서 리스트를 어떻게 변환하는지 보자. 

    아래는 map()을 이용하여 numbers 배열의 값을 두배로 만드는 코드이다.

    const numbers = [11, 22, 33, 99];
    const doubled = numbers.map((number) => number * 2);
    console.log(doubled);

    이 코드는 콘솔에 [22, 44, 66, 198]을 출력한다. 

    리액트에서 배열을 엘리먼트 리스트로 만드는 방식은 이와 거의 동일하다.

     

    여러 개의 컴포넌트 렌더링 하기

    엘리먼트 모음을 만들고 중괄호 {}를 이용하여 JSX에 포함시킬 수 있다. 아래의 자바스크립트 map()을 이용하여 numbers 배열을 반복 실행한다. 각 행목에 대해 <li> 엘리먼트를 반환하고 엘리먼트 배열의 결과를 listItems에 저장한다.

    const numbers = [11, 22, 33, 99];
    const listItems = numbers.map((number) => 
      <li>{number}</li>
    );

    listItems 배열을 <ul> 엘리먼트 안에 포함하고 DOM에 렌더링 한다.

    ReactDOM.render(
      <ul>{listItems}</ul>,
      document.getElementById('root')
    );

    위 코드는 아래와 같이 보여준다.

    • 11
    • 22
    • 33
    • 99

     

    기본 리스트 컴포넌트

    일반적으로 컴포넌트 안에서 리스트를 렌더링 한다. 이전 예제를 numbers 배열을 받아서 순서 없는 엘리먼트 리스트를 출력하는 컴포넌트로 리팩토링할 수 있다.

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <li>{number}</li>
      );
      return (
        <ul>{listItems}</ul>
      );
    }
    
    const numbers = [11, 22, 33, 99];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );

     

    이 코드를 실행하면 리스트의 각 항목에 key를 넣어야 한다는 경고가 표시된다. "key"는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 속성이다. 이제 numbers.map() 안에서 리스트의 각 항목에 key를 할당하여 키 누락 문제를 해결해보자.

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <li key={number.toString()}>
          {number}
        </li>
      );
      return (
        <ul>{listItems}</ul>
      );
    }
    
    const numbers = [11, 22, 33, 99];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );

     

    Key

    키는 리액트가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다. 키는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 한다. 

     

    키를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다. 대부분의 경우 데이터의 ID를 key로 사용한다.

    const todoItems = todos.map((todo) =>
      <li key={todo.id}>
        {todo.text}
      </li>
    );

     

    렌더링 한 항목에 대한 안정적인 ID가 없으면 최후의 수단으로 항목의 인덱스를 키로 사용할 수 있다. 그러나 항목의 순서가 바뀔 수가 있고 이로 인해 성능이 저하되거나 컴포넌트의 상태(state)와 관련된 문제가 발생할 수 있기에 추천하지 않는다.

     

     

    올바른 key 사용법

    function ListItem(props) {
      // 여기에는 key를 지정할 필요가 없다.
      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 = [11, 22, 33, 99];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );

    map() 함수 내부에 있는 엘리먼트에 key를 넣어 주는 게 좋다고 한다.

     

    Props, State

    리액트 컴포넌트에서 다루는 데이터는 props와 state로 나뉜다. props는 부모 컴포넌트가 자식 컴포넌트에서 주는 값이다. 자식 컴포넌트에서는 props를 받아오기만 하고, 받아온 props를 직접 수정할 수는 없다. state는 컴포넌트 내부에서 선언하며 내부에서 값을 변경할 수 있다. 

     

     

     

    State, Lifecycle

    리액트 컴포넌트 안의 state(상태)와 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);

     

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

     

    함수에서 클래스로 변환하기

    다섯 단계로 Clock과 같은 함수 컴포넌트를 클래스로 변환할 수 있다.

     1. React.Component를 확장하는 동일한 이름의 ES6 class를 생성한다.

     2. render()라고 불리는 빈 메서드를 추가한다.

     3. 함수의 내용을 render() 메서드 안으로 옮긴다.

     4. render() 내용 안에 있는 propsthis.props로 변경한다

     5. 남아있는 빈 함수 선언을 삭제한다.

     

    Clock은 이제 함수가 아닌 클래스로 정의된다.

    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와 생명주기 메서드와 같은 부가적인 기능을 사용할 수 있게 해 준다.

     

    클래스에 로컬 State 추가하기

     

    세 단계에 걸쳐서 date를 props에서 state로 이동해보자.

    1. render() 메서드 안에 있는 this.props.datethis.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 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>
        );
      }
    }

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

     

    3. <Clock /> 요소에서 date prop을 삭제한다.

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

     

    타이머 코드는 나중에 컴포넌트로 추가하기로 하고 결과는 다음과 같다.

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

     

    State를 올바르게 사용하기

    setState()에 대해서 알아야 할 세 가지가 있다.

     

    직접 State를 수정하지 마라

    이 코드는 컴포넌트를 다시 렌더링 하지 않는다.

    this.state를 지정할 수 있는 유일한 공간은 바로 constructor이다.

     

    State 업데이트는 비동기적일 수도 있다

    리액트는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리할 수 있다.

    this.propsthis.state가 비동기적으로 업데이트될 수 있기 때문에 다음 state를 계산할 때 해당 값에 의존해서는 안된다.

     

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

     

    이를 수정하기 위해 객체보다는 함수를 인자로 사용하는 다른 형태의 setState()를 사용한다. 그 함수는 이전 state를 첫 번째 인자로 받아들일 것이고 업데이트가 적용된 시점의 props를 두 번째 인자로 받아들일 것이다.

     

    State 업데이트는 병합된다

    setState()를 호출할 때 리액트는 제공한 객체를 현재 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는 완전히 대체된다.

    'Note' 카테고리의 다른 글

    CSS basic box model  (0) 2020.11.21
    JSX 조건부 렌더링  (0) 2020.11.20
    BMP, JPG, PNG  (0) 2020.11.17
    HTTP 응답 코드  (0) 2020.11.16
    node.js, module  (0) 2020.11.16

    댓글