렌더링 리스트 & 조건부 Contents

2JE0·2022년 1월 6일
0

React

목록 보기
4/17
post-thumbnail

1. 리스트 렌더링

Expense.js 파일을 보면 아직 까지는 ExpenseItem 컴포넌트가 하드코딩 되어 있다.


이렇게 코드를 짜면 Add Expense기능 뿐 아니라 Filter의 기능까지 수행하지 못하는 정적인 코드가 되어버린다.
따라서 expenses 배열에 있는 데이터들을 동적으로 렌더링 해야한다.


⨀ javascript function Array.map()

이 자바스크립트 함수를 이용해서 동적으로 렌더링한다.

      {props.data.map((expenses) => (
        <ExpendItem
          title={expenses.title}
          amount={expenses.amount}
          date={expenses.date}
        />
      ))}

props.data에서 받아온 배열을 이용해 각각의 요소를 expenses로 받는다면 ExpendItem 컴포넌트 안에 집어 넣어서 반환하는 것이다.
이렇게하면 하드코딩 된 ExpendItem 컴포넌트를 제거해도 같은 화면이 보인다.


2. State 저장목록 사용

여태까지는 Add Expense 버튼을 눌러도 리스트가 추가되지는 않았다. 이제부터 추가하는 기능을 만들어 보려고 한다.

  • 먼저 expenses 데이터 배열을 전역변수인 DUMMY_EXPENSES로 바꿔준다.
  • 그리고 useState 를 사용하여 추가되는 요소를 expenses에 넣어준다.
  const [expenses, setExpenses] = useState(DUMMY_EXPENSES);

  const DataFromNewExpenseToApp = (expense) => {
    setExpenses((prevExpense) => {
      return [expense, ...prevExpense];
    });
  };

State 글에서 이전 state에 의존하는 state update 글에도 있지만 위의 코드를 다음과 같이 바꿨을 때는 문제가 생길 수도 있다.
setExpenses([expense, ...expenses]);
왜냐하면 여러 state를 업데이트 할 때에는 겹치는게 있을 수도 있는데 함수를 이용하면 그 업데이트 되는 정보들을 순차적으로 처리할 수 있기 때문이다.


3. Key

여태까지 만든 앱을 살펴보면 Add Expense 버튼을 누를때마다 렌더링을 다시하기 때문에
div1
div2
가 있는데 div3을 추가할 상황이 된다면
1 -> 3으로 바꾸고
2- > 1으로 바꾸고
마지막 요소를 추가해 2를 넣을 것이다.

이런 상황을 성능적으로 옳지 못하다. key를 이용하면 react는 개별의 컴포넌트를 이해하고 다시 렌더링 되어 업데이트 되는 일이 없이 추가되는 요소에 대해서만 추가할 것이다.

동적으로 렌더링 하는 부분을 다음과 같이 바꿔준다.

      {props.data.map((expenses) => (
        <ExpendItem
          key={expenses.id}
          title={expenses.title}
          amount={expenses.amount}
          date={expenses.date}
        />
      ))}

4. Year Filter

연도를 선택하면 그 연도에 맞는 데이터만 렌더링 하려고 한다.

처음 짠 코드

{props.data.map((ele) => {
        let year = ele.date.getFullYear();
        if (year == newDate) {
          return (
            <ExpendItem
              key={ele.id}
              title={ele.title}
              amount={ele.amount}
              date={ele.date}
            />
          );
        }
      })}

모범 답안

  const selectedYearFilter = props.data.filter(
    (ele) => ele.date.getFullYear().toString() === newDate
  );
  
  
      {selectedYearFilter.map((expenses) => (
        <ExpendItem
          key={expenses.id}
          title={expenses.title}
          amount={expenses.amount}
          date={expenses.date}
        />
      ))}

5. 조건부 렌더링 3가지 방법

연도 필터를 이용했을때 컨텐츠가 없다면 없다는 문구를 출력하고 싶을때 쓰는 방법이다.

첫번째 방법 : 삼항 연산자

 {selectedYearFilter.length === 0 ? (
        <p style={{ color: "white" }}>No Contents Found</p>
      ) : (
        selectedYearFilter.map((expenses) => (
          <ExpendItem
            key={expenses.id}
            title={expenses.title}
            amount={expenses.amount}
            date={expenses.date}
          />
        ))
      )}

두번째 방법 : && 연산자

      {selectedYearFilter.length === 0 && (
        <p style={{ color: "white" }}>No Contents Found</p>
      )}
      {selectedYearFilter.map((expenses) => (
        <ExpendItem
          key={expenses.id}
          title={expenses.title}
          amount={expenses.amount}
          date={expenses.date}
        />
      ))}

세번째 방법 : 변수 선언

  let expenseContents = <p style={{ color: "white" }}>No Contents Found</p>;
  if (selectedYearFilter.length > 0) {
    expenseContents = selectedYearFilter.map((expenses) => (
      <ExpendItem
        key={expenses.id}
        title={expenses.title}
        amount={expenses.amount}
        date={expenses.date}
      />
    ));
  }

6. 조건문 반환 컴포넌트

ExpenseList.js 를 생성하여 컴포넌트를 분리하려고 한다.
컴포넌트를 분리했을 때 장점은 return을 통해서 조건문 반환을 할 수 있다는 점이다. (컴포넌트를 분리하기 전에는 불가능했던 것)

그리고 ExpenseList 요소를 <ul>로 감싸고 ExpendItem 컴포넌트들을 <li>로 감싼다.

const ExpenseList = (props) => {
  let expenseContents = (
    <h2 className="expenses-list__fallback">No Contents Found</h2>
  );
  if (props.item.length === 0) {
    return expenseContents;
  }

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

7. 조건부 렌더링 : state(bool)

Add Expense 창을 열었다 닫았다 하는 기능을 추가해보려고 한다.
처음 구현을 했을 때에는 addExpenseTab이라는 state 변수를 만들어서 변수로서 컨트롤 하려고 했는데, 더 좋은 방법이 있는 것 같다.
true 일때와 false 일때로 나눠서 구현하는 것이다.


import React from "react";
import "./NewExpense.css";
import ExpenseForm from "./ExpenseForm";
import { useState } from "react/cjs/react.development";
const NewExpense = (props) => {
  //

  const dataFromFormToNewExpense = (prevData) => {
    const newExpenseData = {
      ...prevData,
      id: Math.random().toString(),
    };
    props.onClickAddExpense(newExpenseData);
    setAddExpenseTab(<button onClick={openAddExpense}>open</button>);
  };

  const afterClickCancel = () => {
    setAddExpenseTab(<button onClick={openAddExpense}>open</button>);
  };

  const openAddExpense = () => {
    setAddExpenseTab(
      <ExpenseForm
        onClickAddExpense={dataFromFormToNewExpense}
        onClickCancel={afterClickCancel}
      />
    );
    console.log("update");
  };

  let [addExpenseTab, setAddExpenseTab] = useState(
    <button onClick={openAddExpense}>open</button>
  );

  return <div className="new-expense">{addExpenseTab}</div>;
};

export default NewExpense;

다른방법

  1. const [isEditing, setIsEditing] = useState(false); 을 통해서 bool state를 만듭니다.
  2. && 을 사용하여 조건부 렌더링 한다.
{!isEditing && <button onClick={openAddExpense}>open</button>}
      {isEditing && (
        <ExpenseForm
          onClickAddExpense={dataFromFormToNewExpense}
          onClickCancel={afterClickCancel}
        />
      )}
  1. 클릭하는 함수들에서 적절하게 setIsEditing을 바꿔준다.

8. Chart 만들기

다음의 차트를 만드는 방법은 꽤나 복잡했어서 강의내용을 전부 이해한 후, 재구성해서 만들었다.

  • 먼저 Chart.jsChartBar.js 파일을 만든다.
  • Expenses.js 파일에서 <Chart /> 컴포넌트를 렌더링 할 예정이다. props로는 filteredYear을 넣어준다. 그렇게 되면 해당 연도의 데이터가 전부 넘어가게 된다.
  • Chart에서는 12달의 데이터를 초기화 해준다.
    그리고 해당 월의 지출값을 더해주고 최대값을 구해준다.(막대의 최대를 정해주기 위함)
    • 최대값 구해주는 로직 : 배열을 만들고 map으로 배열의 value만 뽑아낸 후 spread operator를 이용하여 max()안에 넣어준다.
  • 해당 데이터들을 <ChartBar /> 컴포넌트의 props로 넘겨준다.
    -ChartBar.js에서 동적 스타일링을 해준다 (Bar 높이)
//Chart.js
import React from "react";
import "./Chart.css";
import ChartBar from "./ChartBar";
const Chart = (props) => {
  let yearlyData = [
    { label: "Jan", value: 0 },
    { label: "Feb", value: 0 },
    { label: "Mar", value: 0 },
    { label: "Apr", value: 0 },
    { label: "May", value: 0 },
    { label: "Jun", value: 0 },
    { label: "Jul", value: 0 },
    { label: "Aug", value: 0 },
    { label: "Sep", value: 0 },
    { label: "Oct", value: 0 },
    { label: "Nov", value: 0 },
    { label: "Dec", value: 0 },
  ];

  for (let expense of props.data) {
    let monthIdx = expense.date.getMonth();
    yearlyData[monthIdx].value += expense.amount;
  }
  let valueArr = yearlyData.map((ele) => ele.value);
  let maxValue = Math.max(...valueArr);
  return (
    <div className="chart">
      {yearlyData.map((ele) => (
        <ChartBar
          key={ele.label}
          value={ele.value}
          maxValue={maxValue}
          label={ele.label}
        />
      ))}
    </div>
  );
};

export default Chart;
//ChartBar.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 }}
        ></div>
      </div>
      <div className="chart_bar__label">{props.label}</div>
    </div>
  );
};

export default ChartBar;

0개의 댓글

관련 채용 정보