React State

2JE0·2022년 1월 2일
0

React

목록 보기
3/17
post-thumbnail

1. State


2. Event Handler

//ExpandItem.js
import "./ExpendItem.css";
import ExpendDate from "./ExpendDate";
import Card from "./Card";
function ExpendItem(props) {
  const clickHandler = () => {
    console.log("clicked!");
  };
  return (
    <Card className="expense-item">
      <ExpendDate date={props.date} />
      <div className="expense-item__description">
        <h2>{props.title}</h2>
        <div className="expense-item__price">${props.amount}</div>
        <button onClick={clickHandler}>click</button>
      </div>
    </Card>
  );
}

export default ExpendItem;

3. 컴포넌트의 기능 수행

처음에 살펴봤듯, React의 기본 동작은 자바스크립트코드로 수행된다. 따라서 App.js라는 파일이 수행되고나면 다시 렌더링 되지 않는다.
이 말을 왜 하느냐면 다음 eventHandler 버튼을 통해서 글자를 바꾸고 싶기 때문이다.

  • 다음코드는 Title이 바뀌길 바라면서 수정한 코드이다.
    • title을 변수로 만들었으며, 버튼이 클릭되면 title 변수의 문자열이 바뀌도록 설정 해놓았다.
  • 하지만 버튼을 클릭해도 내용은 바뀌지 않는다.
  • 함수가 잘 작동하지 않아서 일까? 아니다. console.log();를 통해서 출력을 해보면 title은 잘 바뀌고 있다.
  • 앞서 말했듯, react가 웹페이지를 한번 렌더링하고나서 title 변수가 바뀌고 화면에 출력되려면 다시 렌더링 되어야하는데, 그럴 필요성이 없으니 다시 그 함수를 수행하지 않고 따라서 렌더링이 되지 않는 것이다.
import "./ExpendItem.css";
import ExpendDate from "./ExpendDate";
import Card from "./Card";
function ExpendItem(props) {
  let title = props.title;
  const clickHandler = () => {
    title = "clicked!";
    console.log(title);
  };
  return (
    <Card className="expense-item">
      <ExpendDate date={props.date} />
      <div className="expense-item__description">
        <h2>{title}</h2>
        <div className="expense-item__price">${props.amount}</div>
        <button onClick={clickHandler}>Change Title</button>
      </div>
    </Card>
  );
}

export default ExpendItem;


4. useState 사용하기

3에서 살펴봤던 렌더링이 안되는 문제는 useState라는 React Hook을 통해서 해결 할 수 있다. useState안에 변수는 항상 업데이트가 되도록 관리된다.

  • import React, { useState } from "react"; 처음 useState를 import하는것으로부터 시작된다. 다음 예제를 살펴보자
function ExpendItem(props) {
  let arr = ["a", "b", "c", "d", "e", "f"];
  let [title, setTitle] = useState(arr[0]);

  const clickHandler = () => {
    let index = (arr.indexOf(title) + 1) % arr.length;

    setTitle(arr[index]);
    console.log(title);
  };

  return (
    <Card className="expense-item">
      <ExpendDate date={props.date} />
      <div className="expense-item__description">
        <h2>{title}</h2>
        <div className="expense-item__price">${props.amount}</div>
        <button onClick={clickHandler}>Change Title</button>
      </div>
    </Card>
  );
}

export default ExpendItem;
  • 이전에 있던 코드를 입맛대로 변형한 것이다.
    let [title, setTitle] = useState(arr[0]);
    • useState 안의 인자는 초기 값을 나타낸다.
    • useState는 지금 있는 값과 바뀔 값으로 바꾸는 함수를 반환한다.
    • click 했을 때 바뀌는 값을 click 했을 때 함수에서 호출해주면 click 할때마다 새롭게 렌더링 된다.
function ExpendItem(props) {
  let [title, setTitle] = useState(props.title);

  const clickHandler = () => {
    setTitle("Updated");
    console.log(title);
  };

  return (
    <Card className="expense-item">
      <ExpendDate date={props.date} />
      <div className="expense-item__description">
        <h2>{title}</h2>
        <div className="expense-item__price">${props.amount}</div>
        <button onClick={clickHandler}>Change Title</button>
      </div>
    </Card>
  );
}

5. User Input

여태까지 만들었던 페이지는 매우 정적이므로, 새로운 데이터를 입력받아 목록에 추가하려고 한다. 그러기 위해서 form을 만든다.

//ExpenseForm.js
import react from "react";
import "./ExpenseForm.css";
const ExpenseForm = () => {
  return (
    <form>
      <div className="new-expense__controls">
        <div className="new-expense__control">
          <label>Title</label>
          <input type="text" />
        </div>
        <div className="new-expense__control">
          <label>Amount</label>
          <input type="number" min="0.01" step="0.01" />
        </div>
        <div className="new-expense__control">
          <label>Date</label>
          <input type="date" min="2019-01-01" max="2022-12-31" />
        </div>
      </div>
      <div className="new-expense__actions">
        <button type="submit">Add Expense</button>
      </div>
    </form>
  );
};

export default ExpenseForm;
//NewExpense.js
import react from "react";
import "./NewExpense.css";
import ExpenseForm from "./ExpenseForm";
const NewExpense = () => {
  return (
    <div className="new-expense">
      <ExpenseForm />
    </div>
  );
};

export default NewExpense;

6. Event Listening

이벤트리스너의 기능을 테스트 하기 위해서 입력 창에 바뀌는 정보는 콘솔에 출력해보려고 한다.
먼저 바뀌는 값을 콘솔에 출력하는 함수를 만든다.

const titleChangeHandler = (event) => {
    console.log(event.target.value);
  };

그리고 폼의 인풋박스에다가 추가해준다.

import react from "react";
import "./ExpenseForm.css";
const ExpenseForm = () => {
  const titleChangeHandler = (event) => {
    console.log(event.target.value);
  };
  return (
    <form>
      <div className="new-expense__controls">
        <div className="new-expense__control">
          <label>Title</label>
          <input type="text" onChange={titleChangeHandler} />
        </div>
        <div className="new-expense__control">
          <label>Amount</label>
          <input type="number" min="0.01" step="0.01" />
        </div>
        <div className="new-expense__control">
          <label>Date</label>
          <input type="date" min="2019-01-01" max="2022-12-31" />
        </div>
      </div>
      <div className="new-expense__actions">
        <button type="submit">Add Expense</button>
      </div>
    </form>
  );
};

export default ExpenseForm;

7. 여러 State 다루기

한 컴포넌트 안에 여러개의 State를 다룰 수 있다.

  let [enteredTitle, setEnteredTitle] = useState("");
  let [enteredAmount, setEnteredAmount] = useState("");
  let [enteredDate, setEnteredDate] = useState("");
  const titleChangeHandler = (event) => {
    setEnteredTitle(event.target.value);
  };
  const amountChangeHandler = (event) => {
    setEnteredAmount(event.target.value);
  };
  const dateChangeHandler = (event) => {
    setEnteredDate(event.target.value);
  };

8. 여러 State 다루기(더 나은 방법)

세가지 State를 객체로서 한번에 다루는 방법도 있다.

import react, { useState } from "react";
import "./ExpenseForm.css";
const ExpenseForm = () => {
  const [userState, setUserState] = useState({
    enteredTitle: "",
    enteredAmount: "",
    enteredDate: "",
  });
  const titleChangeHandler = (event) => {
    setUserState({
      ...userState,
      enteredTitle: event.target.value,
    });
  };
  const amountChangeHandler = (event) => {
    setUserState({
      ...userState,
      enteredTitle: event.target.value,
    });
  };
  const dateChangeHandler = (event) => {
    setUserState({
      ...userState,
      enteredTitle: event.target.value,
    });
  };
  return (
    <form>
      <div className="new-expense__controls">
        <div className="new-expense__control">
          <label>Title</label>
          <input type="text" onChange={titleChangeHandler} />
        </div>
        <div className="new-expense__control">
          <label>Amount</label>
          <input
            type="number"
            min="0.01"
            step="0.01"
            onChange={amountChangeHandler}
          />
        </div>
        <div className="new-expense__control">
          <label>Date</label>
          <input
            type="date"
            min="2019-01-01"
            max="2022-12-31"
            onChange={dateChangeHandler}
          />
        </div>
      </div>
      <div className="new-expense__actions">
        <button type="submit">Add Expense</button>
      </div>
    </form>
  );
};

export default ExpenseForm;

9. 이전 state에 의존하는 State update

setUserState({
      ...userState,
      enteredTitle: event.target.value,
    });
setUserState((prevState) => {
      return { ...prevState, enteredTitle: event.target.value }
    });

의문점 : 저렇게 해야하는 이유, anonymous function 이해x


10. Submit Handler

다시 useState는 예전방식으로 돌아와서 form이 submit 되었을 때 이벤트를 다루는 방법에 대해서 알아보자

  • <form onSubmit={submitHandler}> form이 submit 처리 되었을 때 수행할 함수를 선언한다.

  • submit 테스트 하기

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

    console.log(expenseData);
  };

11. 양방향 바인딩

form이 submit되는 것을 event.preventDefault();로 막았다. 그렇다면 Add Expense버튼을 눌렀을 때 input box가 초기화 되기 위해선 어떻게 해야할까?
submitHandler 함수에서 setEntered***('')를 이용해야 할 것이다. 그런데 또 알아야 할것이 setEntered***('')함수는 entered***변수의 state를 관리하는것인데 input box에는 변수가 없다.
따라서 inputbox의 value를 변수로 지정해줘야한다.

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

    console.log(expenseData);
    setEnteredTitle("");
    setEnteredAmount("");
    setEnteredDate("");
  };
<input
	type="text"
	value={enteredTitle}
	onChange={titleChangeHandler}
/>

12. 👍자식-부모간 컴포넌트 통신(상향식)👍

부모 컴포넌트에서 자식 컴포넌트로 정보를 전달할 때에는 props를 이용하면 됐다. 그런데 이 프로젝트에서 볼수 있듯이 ExpenseForm.js에서 받은 정보는 App.js가 알 수 있어야 expenses배열에 저장할 수 있을 것이다.
자식 컴포넌트가 가지고 있는 정보는 어떻게 부모 컴포넌트로 옮길까?

먼저 ExpenseForm에서 NewExpense로 옮겨야 한다. 과정은 다음과 같다.

  • NewExpense.js에서 자식 컴포넌트를 찾는다.
    <ExpenseForm onClickAddExpense={dataFromFormToNewExpense} /> 다음과 같이 props를 추가한다. 추가한 props는 AddExpense버튼이 눌렸을때 실행되는것은 dataFromFormToNewExpense함수 라는 뜻이다.
  • dataFromFormToNewExpense 함수는 다음과 같다.
const dataFromFormToNewExpense = (prevData) => {
    const newExpenseData = {
      ...prevData,
      id: Math.random().toString(),
    };
    console.log(newExpenseData);
  };
  • newExpenseData 라는 객체를 생성해주고 id를 추가해준다. 그리고 props에서 받아온 onClickAddExpense함수에 만든 객체를 인자로 넣어주고 실행시킨다.

13. Expenses Filter(연습)

Expenses Filter을 만든 후에 데이터를 보내는 연습을 해보자
ExpenseFilter.js를 만든다.

import React from "react";

import "./ExpenseFilter.css";

const ExpensesFilter = (props) => {
  const pickDate = (event) => {
    props.onClickDate(event.target.value);
  };
  return (
    <div className="expenses-filter">
      <div className="expenses-filter__control">
        <label>Filter by year</label>
        <select onChange={pickDate} value={props.selectedDate}>
          <option value="2022">2022</option>
          <option value="2021">2021</option>
          <option value="2020">2020</option>
          <option value="2019">2019</option>
        </select>
      </div>
    </div>
  );
};

export default ExpensesFilter;
  • 이전에 배웠던대로 onChangepickDate를 이용해서 데이터를 상향식으로 보내주는건 익숙해졌다.
  • 그런데 하나 더 해줘야 할게 있다.
 const [newDate, setNewDate] = useState("2020");
  const DateFromFilterToExpenses = (prevDate) => {
    setNewDate(prevDate);
  };
 <ExpensesFilter
      selectedDate={newDate}
      onClickDate={DateFromFilterToExpenses}
    />
  • 바로 selectedDateprops로 다시 내려보내주는 일인데, 이것이 필요한 이유는 다음과 같다.
  • 드롭다운 메뉴의 초기값을 Expenses.js에서 알 수는 없다. 따라서 임의의 값을 useState로 초기화 해 준후, 그 값을 ExpenseFilter로 보내서 드롭다운 메뉴의 시작을 초기화한 값으로 해주는 것이다.

0개의 댓글

관련 채용 정보