[TIL] React

Ha Young Do·2021년 5월 30일
0

React


사용자와의 interaction이 많은 앱은 DOM의 여러 가지 상태를 좀 더 효율적으로 관리하고 기능 개발에만 집중할 수 있도록 React와 같은 프론트엔드 라이브러리 혹은 프레임워크를 사용한다.

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

JSX

JSX란 React의 component를 화면에 보여주기 위해 사용하는 JavaScript 확장 문법이다. 형태상 HTML과 JavaScript 문법과 흡사한 면이 많다. JSX 표현은 Babel과 같은 컴파일러 소프트웨어를 이용해 JavaScript로 해석된다.

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
); // JSX를 이용해 작성된 React element

ReactDOM.render(element, document.getElementById('root')); // React element를 DOM에 render

위의 코드는 아래의 코드와 같다.

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

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

두 번째 코드 예시와 같이 React.createElements 등과 같은 method를 사용하여 JSX 없이 React를 사용할 수도 있으나, 복잡도가 높고 가독성이 낮아 선호하지 않는다.

Using JavaScript with JSX

  1. component 안에서 return 되는 JSX문은 하나의 element로 감싸야 한다.
  2. JavaScript 표현식을 적용할 땐 중괄호 안에 작성한다.
const name = 'Ha Young';
const hello = (
  <h1 className="greeting">
    Hello, {name}! // DOM에 "Hello, Ha Young"으로 render
  </h1>
)
  1. 조건부 rendering이 필요할 때에는 boolean && expression 혹은 삼항 연산자를 사용한다.
  • boolean && expression: boolean값으로 해석되는 표현식과 조건부 rendering 할 표현식을 && 연산자로 묶으면 boolean값이 true일 때 rendering 되고, false일 때 rendering 되지 않는다.
function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      } // unreadMessages가 0개일 경우 h2 태그는 render 되지 않는다
    </div>
  );
}
  • 삼항 연산자: 어떤 값의 truthy/falsy 여부에 따라 render할 내용이 달라지는 경우 사용한다. (JSX 내부에 if문을 사용할 수 없다)
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />
      }
    </div>
  );
}
  1. 클래스 이름 적용시 class가 아닌 className을 사용한다.
  2. JavaScript 표현식처럼 if문, for문 내부에 쓸 수 있다. (반대로 JSX 내부에 if문을 쓰는 것을 불가능)
  • for문을 이용해 일련의 같은 element들을 render할 수 있다.
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

목록을 render할 때에는 목록의 항목마다 고유한 key를 주는 것이 권장된다.

Component

React에서 component란 하나의 의미를 가진 독립적인 단위 모듈을 말한다. 내가 직접 customize할 수 있는 고유 HTML tag라고 표현할 수도 있다. 개발하는 앱에 따라 필요한 기능을 만들어 구성할 수 있기에 재사용성이 높고 직관적이다.

Props

properties의 줄임인 props는 하나의 component안에서 접근 가능한 attribute들을 말한다.

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

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

위의 코드에서 element 변수는 "Sara" 라는 name props를 가진 Welcome component의 한 instance이다. 이 경우 화면에는 props.name값이 "Sara"로 대체된 "Hello, Sara"라는 h1 element가 render된다.

  • 중요: props는 read-only로, 외부에서 받은 그대로 사용하되 component 내부에서 직접 수정 혹은 재할당하려 해서는 안 된다.

State & Lifecycle

State란, component의 lifecycle 안에서 변화할 수 있는 속성을 말한다. state를 사용하는 component로 만드려면 ES6 문법의 class의 형태와 흡사한 class component로 만들어 주어야 한다. 이후 constructor method 내부에서 this.state로 state를 정의해 줄 수 있다. Constructor 외부에서 state를 수정할 경우 직접 재할당하거나 push 등의 method를 사용하여 수정해 주지 않고 아래와 같이 this.setState를 사용한다.

this.setState(prevState => ({
  ...prevState,
  thingToAdd
});

OR

this.setState({newState});

Props vs. State

  • 부모로부터 props를 통해 전달된다 -> props
  • 시간이 지나도 변하지 않는다 -> props
  • 컴포넌트 안의 다른 state나 props를 가지고 계산 가능하다 -> props

Function Component

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

인자로 props를 받고, JSX를 return한다. 위에서 상속받지 않는 props는 instance 생성 시 넘겨 준다.

Class Component

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
    this.tick = this.tick.bind(this);
  }
  
  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')
);
  • constructor method 내부에 props와 state를 정의한다. (위에서 전달받는 props의 경우 constructor의 인자로 받아 super(props)로 전달해 준다.)
  • 바로 JSX를 return 하는 것이 아니라 render()라는 method의 return값으로 준다.
  • component 내부에서 쓰일 method들을 따로 정의해 줄 수 있다. 이 경우 constructor 안에서 this.handleClick = this.handleClick.bind(this); 등과 같이 this binding을 해 주어야 한다.

Lifecycle Methods


Class component의 경우 component lifecycle 내에서 특정 순간에 쓰이는 내장 lifecycle method가 있다. Lifecycle event가 발생할 때마다 해당 method가 불리고, React DOM이 다시 render 된다.

  • 생성: componentDidMount()
  • 업데이트: componentDidUpdate()
  • 제거: componentWillUnmount()

React Data Flow

React에서는 웹 어플리케이션을 만들 때 가장 하위의 component부터 설계해 나가는 bottom-up 방식으로 접근한다. 보통 다음과 같은 절차로 설계한다.

  1. component tree를 그린다.
  2. 각각의 component에서 필요한 데이터가 무엇인지 정의한다.
  3. 필요한 데이터 중 state와 props를 구분한다.
  4. 데이터를 어느 component에 둘지 결정한다.
  • data flows down: 항상 부모 컴포넌트가 자식 컴포넌트에게 props의 형태로 데이터를 전달하기 때문에, 하나의 상태를 기반으로 두 component가 영향을 받는다면 두 component를 모두 소유하는 component를 찾아 그 곳에 상태를 위치해야 한다.

Lifting State Up

React에서는 항상 데이터가 아래로 흐른다면, 하위 component에서 상위 component에 위치한 state에 영향을 주어야 할 때 문제가 생긴다. 이를 해결하기 위한 개념이 lifting state up으로, 콜백의 형태로 하위 component에 상태를 변경시키는 함수를 전달하는 방식으로 접근한다.

class App extends React.Component {
  constructor(props) {
  	super(props);
    this.state = {
      tasks: ["do laundry", "go grocery shopping", "study react"]
    };
    this.setTodos = this.setTodos.bind(this);
  }
  
  setTodos(newTask) {
  	this.setState(prevState => {
      ...prevState,
      newTask
    });
  }

  render() {
  	const todos = this.state.tasks
    return (
      <>
        <TodoCount todos={todos} />
        <TodoList todos={todos} />
        <AddTodo setTodos={setTodos} />
      </>
    );
  }
}

function AddTodo({ setTodos }) {
  function handleSubmit(event) {
    event.preventDefault();
    const todo = event.target.elements.todo.value;
    setTodos(todo);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" id="todo" />
      <button type="submit">Add Todo</button>
    </form>
  );
}

위의 코드에서 AddTodo라는 component는 App의 하위 component로, 이 곳에서 submit event가 발생하면 App의 this.state.tasks 목록에 새로운 할 일을 추가해야 한다. App의 setTodos() method를 AddTodo에 전달해, AddTodo의 handleSubmit() event handler에서 setTodos를 호출하는 형식으로 해결한다.

Create React App

npx create-react-app my-app
cd my-app
npm start

Create React App 패키지를 통해 간단하게 React를 기반으로 한 프로젝트를 jump-start할 수 있다. 위의 코드를 실행하면 my-app이라는 디렉토리에 boilerplate code로 이루어진 새로운 프로젝트가 생성된다.

https://reactjs.org/docs/
https://www.freecodecamp.org/news/what-is-lifting-state-up-in-react/
profile
Codestates Software Engineering Full IM 28th

0개의 댓글