Home 01-상태관리를-통한-동적인-페이지-구현
Post
Cancel

01-상태관리를-통한-동적인-페이지-구현

강의 링크

state를 활용한 동적 페이지

  • 상태를 선언한다.
  • 리액트의 이벤트와 바인드 한다.
  • 모델이 변경됨을 감지한 후, 변경된 부분만 DOM에 반영한다.

## 1. Event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 컴포넌트: 자바스크립트 함수일 뿐이다.

import "./ExpenseItem.css";
import ExpenseDate from "./ExpenseDate";
import Card from "./Card";

const ExpenseItem = ({ title, amount, date }) => {
  const clickHandler = () => {
    console.log("Clicked!");
  };
  return (
    // class는 자바스크립트 예약어이기 떄문에 불가능하다.
    <Card className="expense-item">
      <ExpenseDate date={date} />
      <div className="expense-item__description">
        <h2>{title}</h2>
        <div className="expense-item__price">${amount}</div>
      </div>

      {/* 이벤트 수신: 리액트는 모든 기본 이벤트를 on으로 시작하는 props로 수신할 수 있다.  */}
      <button onClick={clickHandler}>Change Title</button>
    </Card>
  );
};

export default ExpenseItem;

  • 이벤트를 수신함으로서, 유저의 행동에 따라 웹사이트가 변하는 동적인 웹사이트를 구현할 수 있다.
  • 이벤트에 따른 리랜더링(화면에서의 컴포넌트 변경)을 위해서는 state라는 개념이 필요하다.
    • 변화하는 데이터가 사용자 인터페이스에 반영하려면 state가 필요하다. 반응성 추가
    • 특정 컴포넌트가 재평가 되어야 한다고 리액트에게 알린다.
    • 일반적인 변수는 값이 변경되어도 재평가를 유발하지 않는다.


2. useState

컴포넌트가 다시 호출 되는 곳에서 변경된 값을 반영하기 위해 state로 값을 정의할 수 있게 하는 함수이다.

외부에서 데이터를 전달 받는 pops의 경우 read-only이다. 함수형 컴포넌트는 자체적으로 상태(변경 가능한 데이터)를 가지지 못하기 때문에 useState를 활용하여 state를 구현한다.

  • state는 컴포넌트가 소유한 로컬 데이터로 적용 범위는 현재 컴포넌트에 한정된다.


1) 기본 개념

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React, { useState } from "react";

import "./ExpenseItem.css";
import ExpenseDate from "./ExpenseDate";
import Card from "./Card";

const ExpenseItem = (props) => {
  // const [변수, 업데이트 함수] = useState(기본값)
  const [title, setTitle] = useState(props.title);

  // 클릭시, title의 값을 변경한다.
  const clickHandler = () => {
    setTitle("Updated!");
  };
  return (
    <Card className="expense-item">
      <ExpenseDate date={props.date} />
      <div className="expense-item__description">
        <h2>{title}</h2>
        <div className="expense-item__price">${props.amount}</div>
      </div>
      <button onClick={clickHandler}>Change Title</button>
    </Card>
  );
};

export default ExpenseItem;

  • 초기값은 초기 렌더링 시에만 사용된다. 이후 다시 랜더링 될 때는 이 값은 무시된다.
  • useState()함수는 두 가지를 반환한다.
    • state
    • state를 업데이트 하는 함수: 예측가능성
  • 특정 함수로만 state를 업데이트할 수 있는 이유는 변경을 예측(추적)할 수 있다. => 데이터 관리를 보다 쉽게 만든다.
  • 등호 연산자로 state를 변경하지 않기 때문에 const를 사용해도 괜찮다.


리랜더링 과정

리액트는 실제 DOM이 아닌, 가상 DOM을 통해 선언된 상태를 비교 분석하여 UI를 업데이트한다.

이전/ 현재 가상 DOM 트리를 비교하는 재조정 알고리즘을 통해 변경된 트리의 요소를 감지하여 해당 요소만 업데이트하여 DOM에 반영한다.

  • 위의 예시의 경우, 클릭 시 해당 state를 변경하는 함수를 실행하고 이는 JSX 코드를 다시 평가한다.
  • 지난번과 비교하여 감지된 변화를 화면에 랜더한다.


2) 데이터 관리

state는 데이터를 저장하기 위해서 사용될 수도 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React, { useState } from "react";
import "./ExpenseForm.css";

const ExpenseForm = () => {
  // 데이터를 저장하기 위해, useState를 사용할 수도 있다.
  // 컴포넌트 당 별도의 상태를 갖고 각각 업데이트하고 관리할 수 있다. 
  const [enteredTitle, setEnteredTitle] = useState("");
  const [enteredAmount, setEnteredAmount] = useState("");
  const [enteredDate, setEnteredDate] = useState("");

  const titleChangeHandler = (event) => {
    setEnteredTitle(event.target.value);
  };
  const amountChangeHandler = (event) => {
    setEnteredAmount(event.target.value);
  };
  const dateChangeHandler = (event) => {
    setEnteredDate(event.target.value);
  };

  return (
	// 생략
  );
};

export default ExpenseForm;
  • 하나의 컴포넌트가 여러 개의 state를 가질 수 있다.
  • 그러나 객체를 사용하여 하나의 state로 관리할 수도 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React, { useState } from "react";
import "./ExpenseForm.css";

const ExpenseForm = () => {
  const [userInput, setUserInput] = useState({
    enteredTitle: "",
    enteredAmount: "",
    enteredDate: "",
  });

 // 이전 state를 사용한다면, 아래와 같은 방식으로 사용해야 한다. 
  const titleChangeHandler = (event) => {
    setUserInput((prevState) => {
      return {
        ...prevState,
        enteredTitle: event.target.value,
      };
    });
  };    
  
 // 스프레드 연산자와 오버라이드를 통해 useInput의 특정 키만 업데이트
 // 그러나 이전 state가 최신 업데이트가 되지 않아 오류가 발생할 수도 있다. 
 // 위의 방식이 더 좋다. 
  const amountChangeHandler = (event) => {
    setUserInput({
      ...userInput,
      enteredAmount: event.target.value,
    });
  };
  const dateChangeHandler = (event) => {
    setUserInput({
      ...userInput,
      enteredDate: event.target.value,
    });
  };

  return (
	// 생략
  );
};

export default ExpenseForm;
  • state 업데이트가 이전 state에 의존하고 있다면, 업데이트가 오랫동안 되지 않은 state를 사용하지 않도록 해야 한다.


3) 양방향 바인딩

state를 사용하여 양방향 바인딩을 구현할 수 있다.

변경되는 입력 값만 수신하는 것이 아니라 입력에 새로운 값을 다시 전달할 수도 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import React, { useState } from "react";
import "./ExpenseForm.css";

const ExpenseForm = () => {
  const [enteredTitle, setEnteredTitle] = useState("");
  const [enteredAmount, setEnteredAmount] = useState("");
  const [enteredDate, setEnteredDate] = useState("");

  const titleChangeHandler = (event) => {
    setEnteredTitle(event.target.value);
  };
  const amountChangeHandler = (event) => {
    setEnteredAmount(event.target.value);
  };
  const dateChangeHandler = (event) => {
    setEnteredDate(event.target.value);
  };

  const submitHandler = (event) => {
    event.preventDefault();
    const expenseData = {
      title: enteredTitle,
      amount: enteredAmount,
      date: new Date(enteredDate),
    };
    console.log(expenseData);

    // 리셋하기
    setEnteredTitle("");
    setEnteredAmount("");
    setEnteredDate("");
  };

  return (
    <form className="new-expense__controls" onSubmit={submitHandler}>
      <div className="new-expense__control">
        <label>Title</label>
        <input
          type="text"
          // 양방향 바인딩
          value={enteredTitle}
          onChange={titleChangeHandler}
        />
      </div>
      <div className="new-expense__control">
        <label>Amount</label>
        <input
          type="number"
          min="0.01"
          step="0.01"
          // 양방향 바인딩
          value={enteredAmount}
          onChange={amountChangeHandler}
        />
      </div>
      <div className="new-expense__control">
        <label>Date</label>
        <input
          type="date"
          min="2019-01-01"
          max="2022-12-31"
          // 양방향 바인딩
          value={enteredDate}
          onChange={dateChangeHandler}
        />
      </div>
      <div className="new-expense__actions">
        <button type="submit">Add Expense</button>
      </div>
    </form>
  );
};

export default ExpenseForm;

  • input의 value 속성을 state와 연결하여 양방향으로 바인딩할 수 있다.


3. Emit (데이터-끌어올리기)

props와의 반대 개념으로 자식이 부모에게 데이터를 전달한다.


1) 부모 컴포넌트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React from "react";
import "./NewExpense.css";

import ExpenseForm from "./ExpenseForm";

const NewExpense = (props) => {
  const saveExpenseDataHandler = (enteredExpenseData) => {
    const expenseData = {
      ...enteredExpenseData,
      id: Math.random().toString(),
    };
      
    // App.vue에게 emit
    props.onAddExpense(expenseData);
  };

  return (
    <div className="new-expense">
       {/* onSaveExpenseData라는 이벤트를 통해 자식에게 데이터를 받는다.  */}
      <ExpenseForm onSaveExpenseData={saveExpenseDataHandler} />
    </div>
  );
};

export default NewExpense;

  • onSaveExpenseData함수의 매개변수를 통해 데이터를 받아서 저장한다.
  • App 컴포넌트(최상위 컴포넌트)로 데이터를 끌어올린다.
    • 형제 컴포넌트들도 데이터를 사용하기 위해서는 결국 App.vue까지 데이터를 끌어올려야 한다.
    • App컴포넌트에 데이터가 있으면, props를 통해 다른 컴포넌트들에게 데이터를 전달하기 용이하다.


2) 자식 컴포넌트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, { useState } from "react";
import "./ExpenseForm.css";

const ExpenseForm = (props) => {
  // 생략
  const submitHandler = (event) => {
    event.preventDefault();
    const expenseData = {
      title: enteredTitle,
      amount: enteredAmount,
      date: new Date(enteredDate),
    };

    // 데이터를 부모에게 emit
    props.onSaveExpenseData(expenseData);

    setEnteredTitle("");
    setEnteredAmount("");
    setEnteredDate("");
  };

  return (
    <form className="new-expense__controls" onSubmit={submitHandler}>
      {/* 생략 */}
    </form>
  );
};

export default ExpenseForm;

  • props를 사용해서 부모 컴포넌트로부터 함수를 받고 자식 컴포넌트에서 그 함수를 통해 데이터를 보낸다.


This post is licensed under CC BY 4.0 by the author.