JiSoo's Devlog

[Udemy React 완벽 가이드] Section 5 본문

Frontend/React

[Udemy React 완벽 가이드] Section 5

지숭숭숭 2024. 1. 4. 11:35

데이터의 렌더링 목록

  • App.js에서 정의된 expenses를 렌더링
  • Expenses 배열인 데이터 배열에 접근해서 모든 배열 요소에 대해 JSX 요소를 생성한다
  • map 메소드다른 배열을 기반으로 새로운 배열을 생성하는데 원본 배열에 있는 모든 요소들을 변환한다
  • 함수의 결괏값은 새로 생성될 배열에 추가될 요소이다
  • map은 매개변수로 함수를 취하고 그 함수는 배열에 있는 모든 요소를 실행해서 현재 매개변수로 실행되고 있는 요소를 얻는다
{props.items.map(expense => ( //함수의 오른쪽 부분에는 이 expense를 매핑할 JSX요소를 반환해야 한다
  <ExpenseItem
    title={expense.title}  //expense는 title를 추출하는데 사용된다
    amount={expense.amount}
    date={expense.date} 
  />
))}
 
  • expense 객체를 특별한 객체, JSX 요소로 변환
  • expense는 자동적으로 함수를 매개변수로 전달하는 게 그게 바로 map이 작동하는 방식이기 때문이고 expense는 title를 추출하는 데 사용된다
  • 배열을 JSX 아이템들로 가득 찬 배열로 변환해 주는 map 표현식만 남긴다

 

Expenses.js

  return (
    <div>
      <Card className="expenses">
        <ExpensesFilter
          selected={filteredYear}
          onChangeFilter={filterChangeHandler}
        />
        {props.items.map((expense) => (
          <ExpenseItem
            title={expense.title}
            amount={expense.amount}
            date={expense.date}
          />
        ))}
      </Card>
    </div>
  );
};
 

 

 

데이터 목록 렌더링 하기

  • 더미 할 일 항목의 목록을 동적으로 출력

 

App.js

import React from 'react';

import Todo from './Todo';
import './styles.css';

const DUMMY_TODOS = [
    'Learn React',
    'Practice React',
    'Profit!'
];

// don't change the Component name "App"
export default function App() {
    return (
        <ul>
          {DUMMY_TODOS.map(todo=> <Todo text={todo}/>)}
        </ul>
    );
}
 

Todo.js

import React from 'react';

export default function Todo(props) {
    return <li>{props.text}</li>;
}
 

 

 

State 저장 목록 사용

  • 스프레드 연산자는 객체뿐만 아니라 배열에도 사용 가능

 

App.js

import React, { useState } from "react";

import NewExpense from "./components/NewExpense/NewExpense";
import Expenses from "./components/Expenses/Expenses";

const DUMMY_EXPENSES = [
  {
    id: "e1",
    title: "Toilet Paper",
    amount: 94.12,
    date: new Date(2020, 7, 14),
  },
  { id: "e2", title: "New TV", amount: 799.49, date: new Date(2021, 2, 12) },
  {
    id: "e3",
    title: "Car Insurance",
    amount: 294.67,
    date: new Date(2021, 2, 28),
  },
  {
    id: "e4",
    title: "New Desk (Wooden)",
    amount: 450,
    date: new Date(2021, 5, 12),
  },
];

const App = () => {
  const [expenses, setExpenses] = useState(DUMMY_EXPENSES);

  const addExpenseHandler = (expense) => {  //이 함수는 새로운 expense가 추가될 때마다 작동
    setExpenses((prevExpenses) => {
      return [expense, ...prevExpenses];  
    //리액트에 의해 자동으로 이전 expenses를 얻게 되고 여기서 매개변수로 얻는 이 expense를 추가하는 새 배열을
    반환하고 스프레드 연산자와 함께 이전 expenses를 추가한다
    });
  };

  // return React.createElement(
  //   'div',
  //   {},
  //   React.createElement('h2', {}, "Let's get started!"),
  //   React.createElement(Expenses, { items: expenses })
  // );

  return (
    <div>
      <NewExpense onAddExpense={addExpenseHandler} />
      <Expenses items={expenses} />
    </div>
  );
};

export default App;
 
  • 배열의 상수 expenses를 가지고 컴포넌트 함수 밖에서 추출하고 App 컴포넌트 함수에서 useState를 호출해 DUMMY EXPENSES를 전달할 수 있다
  • 이 배열은 연습용 데이터로 가득 찬 초기 상태 값을 가지면서 화면에 표시할 수 있는 초기 expense를 갖는다
  • 동일한 상태의 이전 스냅샷을 기반으로 하는 경우에 우리의 상태를 업데이트할 수 있는 깔끔한 방법

 

 

"key" 이해하기

  • key props는 ExpenseItem 안에서 사용하는 것이 아니지만 props 대신 어떤 컴포넌트에도 추가할 수 있다
  • 고유한 id가 있다면 그걸로 설정해 주면 되겠지만 없다면 두 번째 인자 설정해 주면 되는데 이는 map에 전달하는 함수에서 자동으로 얻어지는 것으로 그 함수는 자동으로 인덱스를 관리해 준다
  • 하지만 특정한 아이템에 대한 인덱스가 항상 똑같고 아이템 콘텐츠에 직접적으로 첨부된 것이 아니기 때문에 좋은 방법은 아니다
  • 특정 컨텐츠를 갖는 모든 아이템들은 분명하게 고유한 id를 갖고 있어야 한
  • 어떤 원시 값도 고유 식별자로 사용할 수 있고 어떤 숫자나 문자열도 고유 식별자가 될 수 있다
{props.items.map((expense) => (
          <ExpenseItem
            key={expense.id}
            title={expense.title}
            amount={expense.amount}
            date={expense.date}
          />
        ))}
 
  • 목록의 아이템을 매핑할 때는 항상 key를 추가해야 한다

 

 

목록 작업하기

Expenses.js

import React, { useState } from "react";

import ExpenseItem from "./ExpenseItem";
import Card from "../UI/Card";
import ExpensesFilter from "./ExpensesFilter";
import "./Expenses.css";

const Expenses = (props) => {
  const [filteredYear, setFilteredYear] = useState("2020");

  const filterChangeHandler = (selectedYear) => {
    setFilteredYear(selectedYear);
  };

  const filteredExpenses = props.items.filter((expense) => {
    return expense.date.getFullYear().toString() === filteredYear;
  });

  return (
    <div>
      <Card className="expenses">
        <ExpensesFilter
          selected={filteredYear}
          onChangeFilter={filterChangeHandler}
        />
        {filteredExpenses.map((expense) => (
          <ExpenseItem
            key={expense.id}
            title={expense.title}
            amount={expense.amount}
            date={expense.date}
          />
        ))}
      </Card>
    </div>
  );
};

export default Expenses;
 
 

 

 

조건부 내용 출력하기

  • 조건부 콘텐츠는 각각 다른 상황에서 다양한 출력값을 렌더링 하는 것
{filteredExpenses.length === 0 ? (
          <p>No expenses found.</p>
        ) : (
          filteredExpenses.map((expense) => (
            <ExpenseItem
              key={expense.id}
              title={expense.title}
              amount={expense.amount}
              date={expense.date}
            />
          ))
        )}
 
 
{filteredExpenses.length === 0 && <p>No expenses found.</p>}  
        {filteredExpenses.length > 0 &&  //자바스크립트는 && 연산자를 사용하도록 동작
          filteredExpenses.map((expense) => (
            <ExpenseItem
              key={expense.id}
              title={expense.title}
              amount={expense.amount}
              date={expense.date}
            />
          ))}
 
let expenseContent = <p>No expenses found.</p>  //변수에 기본값 할당

  if(filteredExpenses.length >0){
    expenseContent = filteredExpenses.map((expense) => (
      <ExpenseItem
        key={expense.id}
        title={expense.title}
        amount={expense.amount}
        date={expense.date}
      />
    ))
  }
 

 

 

 

조건에 따라 콘텐츠 출력하기

  • 버튼 클릭하면 경고 상자 표시
import React from 'react';

export default function App() {
    const [btnClick, setBtnClick] = React.useState(false);
    
    let btn;
    
    if(btnClick){
        btn = (<div id="alert">
          <h2>Are you sure?</h2>
          <p>These changes can't be reverted!</p>
          <button onClick={()=>setBtnClick(false)}>Proceed</button>
        </div>)
    }
    
    return (
      <div>
        {btn}
        <button onClick={()=>{
                setBtnClick(!btnClick);
        }} >Delete</button>
      </div>    
    );
}
 

 

 

조건 명령문 반환 추가하기

  • 또 다른 컴포넌트의 도움으로 렌더링을 위한 별개의 로직을 사용하고 다른 JSX 코드를 반환할 수 있다

 

ExpensesList.js

import React from "react";

import ExpenseItem from "./ExpenseItem";
import "./ExpensesList.css";

const ExpensesList = (props) => {
  if (props.items.length === 0) {
    return <h2 className="expenses-list__fallback">Found no expenses.</h2>;
  }

  return (
    <ul className="expenses-list">
      {props.items.map((expense) => (
        <ExpenseItem
          key={expense.id}
          title={expense.title}
          amount={expense.amount}
          date={expense.date}
        />
      ))}
    </ul>
  );
};

export default ExpensesList;
 
 

 

 

조건 명령문 반환 추가하기

NewExpense.js

import React, { useState } from "react";
import "./NewExpense.css";
import ExpenseForm from "./ExpenseForm";

const NewExpense = (props) => {
  const [isEditing, setIsEditing] = useState(false);

  const saveExpenseDataHandler = (enteredExpenseData) => {
    const expenseData = {
      ...enteredExpenseData,
      id: Math.random().toString(),
    };
    props.onAddExpense(expenseData);
    setIsEditing(false);
  };

  const startEditingHandler = () => {
    setIsEditing(true);
  };

  const stopEditingHandler = () => {
    setIsEditing(false);
  };
  return (
    <div className="new-expense">
      {!isEditing && (
        <button onClick={startEditingHandler}>And New Expense</button>
      )}
      {isEditing && (
        <ExpenseForm
          onSaveExpenseData={saveExpenseDataHandler}
          onCancel={stopEditingHandler}
        />
      )}
    </div>
  );
};

export default NewExpense;
 

ExpenseForm.js

import React, { useState } from "react";

import "./ExpenseForm.css";

const ExpenseForm = (props) => {
  const [enteredTitle, setEnteredTitle] = useState("");
  const [enteredAmount, setEnteredAmount] = useState("");
  const [enteredDate, setEnteredDate] = useState("");
  // const [userInput, setUserInput] = useState({
  //   enteredTitle: '',
  //   enteredAmount: '',
  //   enteredDate: '',
  // });

  const titleChangeHandler = (event) => {
    setEnteredTitle(event.target.value);
    // setUserInput({
    //   ...userInput,
    //   enteredTitle: event.target.value,
    // });
    // setUserInput((prevState) => {
    //   return { ...prevState, enteredTitle: event.target.value };
    // });
  };

  const amountChangeHandler = (event) => {
    setEnteredAmount(event.target.value);
    // setUserInput({
    //   ...userInput,
    //   enteredAmount: event.target.value,
    // });
  };

  const dateChangeHandler = (event) => {
    setEnteredDate(event.target.value);
    // setUserInput({
    //   ...userInput,
    //   enteredDate: event.target.value,
    // });
  };

  const submitHandler = (event) => {
    event.preventDefault();

    const expenseData = {
      title: enteredTitle,
      amount: enteredAmount,
      date: new Date(enteredDate),
    };

    props.onSaveExpenseData(expenseData);
    setEnteredTitle("");
    setEnteredAmount("");
    setEnteredDate("");
  };

  return (
    <form onSubmit={submitHandler}>
      <div className="new-expense__controls">
        <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>
      <div className="new-expense__actions">
        <button type="button" onClick={props.onCancel}>
          Cancel
        </button>
        <button type="submit">Add Expense</button>
      </div>
    </form>
  );
};

export default ExpenseForm;
 
 
 
 

 

 

차트 추가하기

  • 데이터 포인터를 통해 차트 바가 동적으로 출력되도록 하고 모든 데이터 포인트를 차트 바에 매핑할 것이다
  • 모든 차트의 바는 전체 차트의 최댓값을 기준으로 값을 표시한다

 

Chart.js

import React from "react";
import ChartBar from "./ChartBar";
import "./Chart.css";
const Chart = (props) => {
  return (
    <div className="chart">
      {props.dataPoints.map((dataPoint) => (
        <ChartBar
          key={dataPoint.label}
          value={dataPoint.value}
          maxValue={null}
          label={dataPoint.label}
        />
      ))}
    </div>
  );
};

export default Chart;
 

 

 

동적 스타일 추가하기

  • style 속성을 리액트로 응용프로그램을 구축할 때 약간 다르게 동작한다
  • 최댓값을 기준으로 값을 넣어 차트의 바를 채우고 싶다

 

CharBar.js

import React from "react";

import "./ChartBar.css";

const ChartBar = (props) => {
  let barFillHeight = "0%";  //초깃값

  if (props.maxValue > 0) {
    barFillHeight = Math.round((props.value / props.maxValue) * 100) + "%";
  }

  return (
    <div className="chart-bar">
      <div className="chart-bar__inner">
        <div
          className="chart-bar__fill"
          style={{ height: barFillHeight }}  //style의 값으로 자바스크립트를 사용해야 한다
        ></div>
      </div>
      <div className="chart-bar__label">{props.label}</div>
    </div>
  );
};

export default ChartBar;
 

Char.js

import React from "react";
import ChartBar from "./ChartBar";
import "./Chart.css";
const Chart = (props) => {
  const dataPointValues = props.dataPoints.map((dataPoint) => dataPoint.value);
  const totalMaximum = Math.max(...dataPointValues);

  return (
    <div className="chart">
      {props.dataPoints.map((dataPoint) => (
        <ChartBar
          key={dataPoint.label}
          value={dataPoint.value}
          maxValue={totalMaximum}
          label={dataPoint.label}
        />
      ))}
    </div>
  );
};

export default Chart;
 
 

 

 

퀴즈

someArray.map((element) => <p>{element}</p>)
 

이 코드 예시는 어떤 작업을 수행하나요?

  • 배열을 React에서 출력 가능한 JSX 요소로 채워진 새 배열로 변환한다(someArray)

 

JSX 요소를 나열할 때 왜 특수한 "키" 프로퍼티를 추가해야 하나요?

  • React가 목록 요소를 올바르게 식별 및 업데이트(필요시) 하는 데 필요하기 때문이다

 

React 컴포넌트에서 조건부 콘텐츠를 출력하는 데 관한 설명으로 옳은 것은 무엇인가요?

  • 컴포넌트에서 다른 결과를 출력 또는 반환하기 위해 if 검사의 정규 삼항 표현식으로 작업할 수 있다

 

728x90