티스토리 뷰

React

React Native UI 구성 및 문법 1

마시멜로co. 2021. 7. 14. 11:49

저번글에서는 개발환경을 구축하고, 리액트 네이티브 프로젝트를 생성하였습니다.

https://marshmello.tistory.com/69?category=1213283 

 

React Native 설치 및 프로젝트 생성하기

저번글에서는 리액트 네이티브가 무엇인지 작성하였습니다. https://marshmello.tistory.com/68?category=1213283 이번글에서는 리액트 네이티브로 프로젝트를 생성해 보도록 하겠습니다. 1. 자바 설치 및 환

marshmello.tistory.com

이번글에서는 리액트 네이티브 프로젝트를 생성한 후 코드를 작성해보겠습니다.

 

 

1. JSX란

JSX는 Javascript xml의 약자입니다. JSX는 페이스북이 만든 자바스크립트 확장한 문법인데, UI 코드를 태그의 형태로 코드 내에 포함시키게 해줍니다.

 

React Native는 리액트를 기반으로 ‘모바일 웹 앱’이나 ‘하이브리드 앱’이 아닌 ‘네이티브 앱’을 제작할 수 있는 오픈소스 프레임워크입니다.

 

React Native에서의 UI개발은 네이티브 개발이기 때문에 HTML 태그와 CSS를 사용할 수 없습니다. 그렇기 때문에 JSX를 이용하여 UI를 구성합니다.

더 자세히 JSX에 대해 알고 싶다면, react 공식 홈페이지에서 소개하는 JSX를 참조하시기 바랍니다.

https://ko.reactjs.org/docs/introducing-jsx.html

 

JSX 소개 – React

A JavaScript library for building user interfaces

ko.reactjs.org

* 위 react 공식 홈페이지 내용을 공부한 의식에 흐름따라 아래의 내용들은 진행할 것임을 사전에 공지합니다.

 

VSCode 프로그램을 열고, 터미널에 아래의 명령어를 입력하여, Expo를 이용한 리액트 네이티브 프로젝트를 생성합니다.

my-app는 프로젝트 명입니다. 알맞게 변경하여 사용하세요.

expo init my-app

black 선택 후 엔터를 누릅니다.

App.js 파일을 열어보면 다음과 같습니다.

import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

위에 희한한 태그 문법은 문자열도, HTML도 아닙니다. 이런 코드를 JSX라고 합니다.

리액트네이티브는 이 JSX 문법을 사용하여, 코드가 작성되므로 필수 문법입니다.

 

2. 변수선언

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

위 예시 처럼 name이라는 변수를 선언한 후 중괄호로 감싸 JSX 안에 사용하였습니다.

 

속성에 따옴표를 이용해 문자열 리터럴을 정의할 수 있습니다.

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

중괄호를 사용하여 어트리뷰트에 JavaScript 표현식을 삽입할 수도 있습니다

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

또 여러 자식요소를 사용할 경우 반드시 하나의 부모로 감싸야합니다.

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <StatusBar style="auto" />
    </View>
  );
}

만약 <View> 부모 요소가 없다면 컴파일시 에러가 발생합니다. 

 

 

3. 렌더링

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

이 안에 들어가는 모든 엘리먼트를 React DOM에서 관리하기 때문에 이것을 “루트(root)” DOM 노드라고 부릅니다.

 

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

 

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

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

위 코드를 실행하면 화면에 “Hello, world”가 보일 겁니다.

 

4. Components and Props

컴포넌트란? 재사용이 가능한 조립 블록으로 화면에 나타나는 UI요소입니다. 단순히 UI 역할만 하는것이 아니라 부모로부터 받은 속성Props이나 자신의 상태status에 따라 표현이 달라지고 다양한 기능을 수행합니다.

 

리엑트 네이티브는 데이터와 UI요소의 집합체라고 할 수 있는 컴포넌트를 이용하여 화면을 구성하게 됩니다. 

리액트 네이티브에서는 다양한 내장 컴포넌트들이 제공됩니다. https://reactnative.dev/docs/components-and-apis

 

Core Components and APIs · React Native

React Native provides a number of built-in Core Components ready for you to use in your app. You can find them all in the left sidebar (or menu above, if you are on a narrow screen). If you're not sure where to get started, take a look at the following cat

reactnative.dev

View컴포넌트와 Text 컴포넌트가 대표적인 내장 컴포넌트입니다. 위사이트에서 각 내장컴포넌트별 사용예제와 설명을 참고하면 많은 실 사용시 많은 도움이 됩니다.

 

<커스텀 컴포넌트 제작>

개념적으로 컴포넌트는 JavaScript 함수와 유사합니다. “props”라고 하는 임의의 입력을 받은 후, 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환합니다.

 

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

이 함수는 데이터를 가진 하나의 *props 객체 인자(여기서는 props)를 받은 후 React 엘리먼트를 반환하므로 유효한 React 컴포넌트입니다. 이러한 컴포넌트는 JavaScript 함수이기 때문에 말 그대로 “함수 컴포넌트”라고 호칭합니다.

 

* props란 properties를 줄인 표현으로 부모 컴포넌트로부터 전달된 속성값 혹은 상속받은 속성값을 말합니다. 

부모컴포넌트가 자식 컴포넌트의 props를 설정하면 자식 컴포넌트에서는 해당 props를 사용할 수 있지만 변경하는 것은 불가능합니다. props의 변경이 필요한 경우 props를 설정 및 전달한 부모 컴포넌트에서 변경해야합니다.

 

<propTypes>잘못된 props가 전달되었다는 경고메시지를 통해 알리는 방법으로 PropTypes를 사용하는 방법이 있습니다.

npm install prop-types

PropTypes를 이용하면 컴포넌트에서 전달받아야 하는 Props의 타입과 필수 여부를 지정할 수 있습니다.

https://ko.reactjs.org/docs/typechecking-with-proptypes.html

 

PropTypes와 함께 하는 타입 확인 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

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

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

 

React의 관점에서 볼 때 위 두 가지 유형의 컴포넌트는 동일합니다.

 

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을 효율적으로 업데이트합니다.

컴포넌트의 이름은 항상 대문자로 시작합니다. 

 

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 formatDate(date) {
  return date.toLocaleDateString();
}

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

const comment = {
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'https://placekitten.com/g/64/64',
  },
};
ReactDOM.render(
  <Comment
    date={comment.date}
    text={comment.text}
    author={comment.author}
  />,
  document.getElementById('root')
);

이 컴포넌트는 구성요소들이 모두 중첩 구조로 이루어져 있어서 변경하기 어려울 수 있으며, 각 구성요소를 개별적으로 재사용하기도 힘듭니다. 이 컴포넌트에서 몇 가지 컴포넌트를 추출하겠습니다.

 

Avatar 추출

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

추출 후 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 컴포넌트를 추출하겠습니다.

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

 

최종 코드

function formatDate(date) {
  return date.toLocaleDateString();
}

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

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

const comment = {
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'https://placekitten.com/g/64/64',
  },
};
ReactDOM.render(
  <Comment
    date={comment.date}
    text={comment.text}
    author={comment.author}
  />,
  document.getElementById('root')
);

 

함수 컴포넌트나 클래스 컴포넌트 모두 컴포넌트의 자체 props를 수정해서는 안 됩니다. 다음 sum 함수를 살펴봅시다.

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

이런 함수들은 순수 함수라고 호칭합니다. 입력값을 바꾸려 하지 않고 항상 동일한 입력값에 대해 동일한 결과를 반환하기 때문입니다.

반면에 다음 함수는 자신의 입력값을 변경하기 때문에 순수 함수가 아닙니다.

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

모든 React 컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 동작해야 합니다.

 

5. state와 생명주기

props는 부모 컴포넌트에서 받은 값을 변경할 수 없는 반면, state는 컴포넌트 내부에서 생성되고 값을 변경할 수 있으며 이를 이용해 컴포넌트 상태를 관리합니다. status란 컴포넌트에서 변화 할 수 있는 값을 나타내며, 상태가 변하면 컴포넌트는 리렌더링됩니다. 

 

과거 리액트 네이티브의 경우 함수형 컴포넌트에서 상태를 관리 할 수 없었기때문에 상태를 관리해야 하는 컴포넌트는 반드시 클래스형 컴포넌트를 사용해야했습니다. 하지만 리액트 16.8버전 이후 버전을 사용하는 리액트 네이티브 0.59버전부터는 Hooks라는 것을 사용해 함수형 컴포넌트에서도 상태를 관리 할 수 있게 되었습니다.

 

리액트 개발팀은 클래스형 컴포넌트를 삭제할 계획이 없다고 하지만, 장기적으로 함수형 컴포넌트를 이용해서 Hooks를 사용하는 것이 주된 개발 방법으로 활용될 것이라고 했기 때문에 여기서는 함수형 컴포넌트를 이용하여 상태를 관리하는 방법에 대해 알아보겠습니다. 

 

리액트 Hooks 중 useState는 함수형 컴포넌트에서 상태를 관리할 수 있도록 해줍니다.

https://reactnative.dev/docs/intro-react#state

 

React Fundamentals · React Native

To understand React Native fully, you need a solid foundation in React. This short introduction to React can help you get started or get refreshed.

reactnative.dev

const [state, setState] = useState(initialState);

useStatus는 상태를 관리하는 변수와 그 변수를 변경할 수 있는 setter 함수를 배열로 반환합니다. 상태 변수는 직접 변경하는 것이 아니라 useStatus 함수에서 반환한 setter 함수를 이용합니다.

 

useStatus 함수를 호출할 때 파라미터에 생성되는 상태의 초깃값을 전달 할 수 있고, 초깃값을 전달하지 않으면 undefined로 설정되어 에러가 발생할 수 있으므로 항상 초깃값을 설정하는 것이 좋습니다. 

import React, { useState } from "react";
import { StyleSheet, TouchableOpacity, Text, View,Button } from 'react-native';
const Counter = () => {
    const [count, setCount] = useState(0);
    return (
        <View style={styles.container}>
            <Text style={styles.text} >Count: {count}</Text>
            <Button title="+1" onPress={() => setCount(count + 1)} />        
            <Button title="-1" onPress={() => setCount(count - 1)} /> 
        </View>
    );
};


const styles = StyleSheet.create({
    container: {
        alignItems: "center",
    },
    text: {
        margin: 10,
        fontSize: 30
    },

});
export default Counter;

useStatus 함수를 이용하여 숫자의 상태를 관리할 count 변수와 상태를 변경할 수 있는 setCount setter 함수를 만들고 , count의 초깃값을 0으로 설정하였습니다. 그리고 버튼 컴포넌트를 이용하여 현재 count 값에서 1씩 증가하는 +1버튼과 1씩 감소하는 -1버튼을 만들고 Text 컴포넌트를 이용해서 현재 coun값을 볼 수 있도록 작성했습니다.

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

위 소스는 단순히 렌더링 된 출력값을 변경하기 위해 ReactDOM.render()를 호출합니다. 이 소스를  Clock 컴포넌트를 생성하여, 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 컴포넌트는 스스로 타이머를 설정할 것이고 매초 스스로 업데이트할 것입니다.

 

그러나 Clock이 타이머를 설정하고 매초 UI를 업데이트하는 것이 Clock의 구현 세부사항이 되어야 합니다.

 

이것을 구현하기 위해서 Clock 컴포넌트에 “state”를 추가해야 합니다.

State는 props와 유사하지만, 비공개이며 컴포넌트에 의해 완전히 제어됩니다

 

Clock함수에서 클래스로 아래와 같이 작성합니다.

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

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

1) React.Component를 확장하는 동일한 이름의 ES6 class를 생성합니다.
2) render()라고 불리는 빈 메서드를 추가합니다.
3) 함수의 내용을 render() 메서드 안으로 옮깁니다.
4) render() 내용 안에 있는 props를 this.props로 변경합니다.
5) 남아있는 빈 함수 선언을 삭제합니다.

 

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

render 메서드는 업데이트가 발생할 때마다 호출되지만, 같은 DOM 노드로 <Clock />을 렌더링하는 경우 Clock 클래스의 단일 인스턴스만 사용됩니다. 이것은 로컬 state와 생명주기 메서드와 같은 부가적인 기능을 사용할 수 있게 해줍니다.

 

클래스에 로컬 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 constructor를 추가합니다. 클래스 컴포넌트는 항상 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>
    );
  }
}

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

 

많은 컴포넌트가 있는 애플리케이션에서 컴포넌트가 삭제될 때 해당 컴포넌트가 사용 중이던 리소스를 확보하는 것이 중요합니다.

Clock이 처음 DOM에 렌더링 될 때마다 타이머를 설정하려고 합니다. 이것은 React에서 “마운팅”이라고 합니다.

또한 Clock에 의해 생성된 DOM이 삭제될 때마다 타이머를 해제하려고 합니다.

컴포넌트 클래스에서 특별한 메서드를 선언하여 컴포넌트가 마운트되거나 언마운트 될 때 일부 코드를 작동할 수 있습니다.

 

생명주기 메서드를 클래스에 추가하기

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

위에 사용된 componentDidMount() ,componentWillUnmount()과 같은  메서드들은 “생명주기 메서드”라고 불립니다.

componentDidMount() 메서드는 컴포넌트 출력물이 DOM에 렌더링 된 후에 실행됩니다.

 

componentDidMount() 메서드가 타이머를 설정하기에 적합합니다.

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

this (this.timerID)에서 어떻게 타이머 ID를 제대로 저장하는지 주의해주세요.

this.props가 React에 의해 스스로 설정되고 this.state가 특수한 의미가 있지만, 타이머 ID와 같이 데이터 흐름 안에 포함되지 않는 어떤 항목을 보관할 필요가 있다면 자유롭게 클래스에 수동으로 부가적인 필드를 추가해도 됩니다.

componentWillUnmount() 생명주기 메서드 안에 있는 타이머를 종료/분해해 보겠습니다.

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

마지막으로 Clock 컴포넌트가 매초 작동하도록 하는 tick()이라는 메서드를 구현해 보겠습니다.

이것은 컴포넌트 로컬 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')

 메서드가 어떻게 호출되는지 순서는 다음과 같습니다.

  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() 생명주기 메서드를 호출합니다.

 

 

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

 

1) 직접 State를 수정하지 마세요. 예를 들어, 아래 코드는 컴포넌트를 다시 렌더링하지 않습니다.

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

대신에 setState()를 사용합니다. 아래의 this.state를 지정할 수 있는 유일한 공간은 바로 constructor입니다.

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


2) State 업데이트는 비동기적일 수도 있습니다.

React는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리할 수 있습니다.
this.props와 this.state가 비동기적으로 업데이트될 수 있기 때문에 다음 state를 계산할 때 해당 값에 의존해서는 안 됩니다.

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

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

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

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

위에서는 화살표 함수를 사용했지만, 일반적인 함수에서도 정상적으로 작동합니다.

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

별도의 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는 완전히 대체됩니다.

 

3) 데이터는 아래로 흐릅니다.

 

부모 컴포넌트나 자식 컴포넌트 모두 특정 컴포넌트가 유상태인지 또는 무상태인지 알 수 없고, 그들이 함수나 클래스로 정의되었는지에 대해서 관심을 가질 필요가 없습니다.

이 때문에 state는 종종 로컬 또는 캡슐화라고 불립니다. state가 소유하고 설정한 컴포넌트 이외에는 어떠한 컴포넌트에도 접근할 수 없습니다.

 

컴포넌트는 자신의 state를 자식 컴포넌트에 props로 전달할 수 있습니다.

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

FormattedDate 컴포넌트는 date를 자신의 props로 받을 것이고 이것이 Clock의 state로부터 왔는지, Clock의 props에서 왔는지, 수동으로 입력한 것인지 알지 못합니다.

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

일반적으로 이를 “하향식(top-down)” 또는 “단방향식” 데이터 흐름이라고 합니다.

 

모든 state는 항상 특정한 컴포넌트가 소유하고 있으며 그 state로부터 파생된 UI 또는 데이터는 오직 트리구조에서 자신의 “아래”에 있는 컴포넌트에만 영향을 미칩니다.

 

트리구조가 props들의 폭포라고 상상하면 각 컴포넌트의 state는 임의의 점에서 만나지만 동시에 아래로 흐르는 부가적인 수원(water source)이라고 할 수 있습니다.

 

모든 컴포넌트가 완전히 독립적이라는 것을 보여주기 위해 App 렌더링하는 세 개의 <Clock>을 만들었습니다.

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

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

 Clock은 자신만의 타이머를 설정하고 독립적으로 업데이트를 합니다.

 

React 앱에서 컴포넌트가 유상태 또는 무상태에 대한 것은 시간이 지남에 따라 변경될 수 있는 구현 세부 사항으로 간주합니다. 유상태 컴포넌트 안에서 무상태 컴포넌트를 사용할 수 있으며, 그 반대 경우도 마찬가지로 사용할 수 있습니다.

 

 

오늘 글에서는 리액트 네이티브 문법에 대한 가장 핵심내용을 작성해보았습니다.

 

다음글에서는 이벤트와 스타일링에 대한 글을 쓰겠습니다. 

 

감사합니다.

 

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크