[TIL] 2022/03/14

yongkini ·2022년 3월 14일
0

Today I Learned

목록 보기
121/173

Today I Learned

React 개발시에 JSX 내부는 'lean'하게 개발하자.

예를 들어, onClick 이벤트 등을 jsx 내부에 쓸 때 복잡한 로직의 함수를 넣는 등의 행동을 '지양'하자. jsx 내부 로직은 항상 lean하게 유지한다는 생각을 갖자.

리액트는 최초 '한번만' render 한다.

?? 무슨말일까. SPA 를 지원하는 리액트는 User의 interaction에 따라 화면을 계속해서 업데이트한다. 그런데, 한번만 render한다?. 이말은 구체적으로 말해보면, 리액트가 최상위 컴포넌트인 'App'을 렌더링하고, 재귀적으로 그 하위 컴포넌트를 렌더링한 이후에(최초로) 리액트가 뭔가를 업데이트(혹은 리렌더링)했을 때 그것은 'state'에 의해서 이뤄지는 것이지 index.js 에 있는 React.render() 때문은 아니다. 그리고 리액트는 애초에 철학 자체가 이러한 state 개념을 도입해서 state 변화를 감지하여 rerender를 하도록 설계됐다.

State는 스냅샷처럼 이전값들을 저장한다.

state를 쓰다보면 분명히 함수 내에서 선언된

const [state, setState] = useState(null);

state가 setState의 사용 or the other reasons로 함수가 재실행됨에도 해당 state의 초기값인 null 이 state값이 아닌 setState으로 바꿔준 state 값이 state가 되는 것을 볼 수 있다. 이는 React hook이 제공하는 기능으로 state 값을 함수 내에 변수로 결속시켜놓는 것이 아니라 리액트 내부에서 스냅샷처럼 state 값을 따로 저장을 하기 때문에 함수가 재실행돼도 state값은 계속해서 최신값을 유지하는 것이다. 그래서 함수가 재실행(리렌더)되는 상황 속에서도 유지하고자 하는 값이 있다면(데이터가 있다면) 그것이 바로 state에 저장 혹은 관리해야하는 데이터인 것이다.

이전 state에 의존하는 상태 업데이트를 할 때

사실 이러한 경우는 굉장히 빈번하게 일어나기 때문에 이 부분에 대해서 신경써주는 것이 필요하다. 예를 들어, 이전 state 값을 활용해서 값을 업데이트 해줘야할 때에

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

const ExpenseForm = () => {
  const [expense, setExpense] = useState({ title: "", amount: "", date: "" });

  const changeExpenseHandler = (e) => {
    setExpense({ ...expense, [e.target.name]: e.target.value });
  };

  const submitExpenseHandler = () => {
    console.log(expense);
  };

  return (
    <div className="new-expense__controls">
      <div className="new-expense__control">
        <label>Title</label>
        <input type="text" name="title" onChange={changeExpenseHandler} />
      </div>
      <div className="new-expense__control">
        <label>Amount</label>
        <input
          type="number"
          min="0.01"
          step="0.01"
          name="amount"
          onChange={changeExpenseHandler}
        />
      </div>
      <div className="new-expense__control">
        <label>Date</label>
        <input
          type="date"
          min="2019-01-01"
          max="2022-12-31"
          name="date"
          onChange={changeExpenseHandler}
        />
      </div>
      <div className="new-expense__actions">
        <button type="submit" onClick={submitExpenseHandler}>
          Add Expense
        </button>
      </div>
    </div>
  );
};

export default ExpenseForm;

이런 코드를 가정해보자. 이 코드는 3개의 input을 받아서 input이 change(onChange를 이용)될 때마다 그 값을 갱신하여 하나의 객체에 그 값을 저정하는 식으로 state를 설계해뒀다. 이상황에서

  const changeExpenseHandler = (e) => {
    setExpense({ ...expense, [e.target.name]: e.target.value });
  };

이 부분이 정상적으로 작동하려면 '...expense' 이 부분이 과거가 아닌 최신 정보를 정확하게 받아와야 원하는 기능을 구현할 수 있다. 위에서 말했듯이 state는 데이터 스냅샷과 같은 것으로 만약에 과거 데이터가 ...expense에 의해 할당되면 원하는 기능 구현에 실패하게 된다(하지만 대부분 경우 위의 코드로도 잘 작동할 것이다). 따라서 좀더 확실하게 과거의 state를 가져다 쓰기 위해서는 이런식으로 코드를 작성해주자

  const changeExpenseHandler = (e) => {
    setExpense((prevState) => {
      return { ...prevState, [e.target.name]: e.target.value };
    });
  };

그러면 100% 확실하게 이전 state값을 가져다 쓸 수 있다. 리액트가 이것이 가장 최신 state인 것을 확실하게 보장하며 activate시키기 때문이다(setState 내의 함수를).

react에서 map 메서드를 통해 데이터를 렌더링할 때 key를 쓰는 이유

   여태까지 당연히 key props를 해줘야한다고 단순히 외우고 사용하기만 했었는데, key props를 왜 써주는지에 대해서 제대로 알아보자. 결론적으로 key props를 써주는 이유는 key props를 써주지 않으면 예를 들어, 배열에 요소가 추가 됐을 때 리액트는 맨뒤에 있던 요소와 똑같은 요소를 맨뒤에 추가하고, 나머지를 새로운 데이터로 덮어 씌운다. 다시 말해보면, 예를 들어, 0, 1, 2, 3, 4의 인덱스를 가진 자료가 있었다고 했을 때 5의 인덱스를 가진 데이터를 맨 끝에 추가한다고 생각해보자. 이 때 key props가 없다면 리액트는 먼저 맨 끝에 4를 추가한다. 그 다음에 0, 1, 2, 3, 4, 4 이 상태의 데이터를 앞에서부터 데이터를 순서대로 덮어씌운다. 그래서 결국 데이터는 0, 1, 2, 3, 4, 5로 된다. 즉, 데이터 하나가 추가되면 하나를 추가하고, 데이터 전부를 덮어씌우는 식으로 업데이트하게 되는 것이다. 이는 상당히 비효율적이다. 왜냐하면, 하나의 데이터를 추가했다면, 말그대로 새로운 데이터 하나만 업데이트 하면 되는데, 나머지도 같이 리렌더링하는 것이기 때문이다. 따라서 key props를 써줘야 리액트가 데이터의 고유성을 파악하여 데이터를 전부다 리렌더링하지 않고, 추가하고자 하는 데이터만 추가하여 리렌더링할 수 있도록 해준다.
  한번더 정리해보면, 결국 key props를 쓰면 배열의 각 데이터에 고유성을 부여하여 리액트가 데이터 하나하나에 대해서 구분할 수 있도록해서 데이터를 추가하거나, 변화를 줄 때 변화되는 부분만 처리할 수 있게되고, 그렇지 않으면 고유성에 대한 구분을 못하게 되기 때문에 모두 같은 데이터로 취급하여 데이터가 변화하면 그 공간에 대해 변화를 주고, 다시 다 리렌더링하게 되는 결과가 나타난다.

** 참고 : map 메서드를 쓸 때 key 값은 문자열도되고 숫자도 된다(모든 원시 자료형 사용 가능 => 고유하기만 하면 된다! 이 때, 데이터 순서가 바뀌거나 배열로 컨트롤 하는 컴포넌트 내에 state가 있으면 map에 index는 쓰지 않는 것을 권장함 => 오류를 야기할 수 있음)

simple한 JSX snippet을 유지하는 개발 방식도 좋다

보통 react를 개발하다보면 && 연산자나 삼항 연산자를 써서 조건부 렌더링을 쓰는 경우가 많고, 이것이 잘못된 것도 아니고 누군가에겐 이것이 convention일 정도로 정석 개발 방식이다. 하지만, jsx를 최대한 simple하게 유지하는 방법론을 택하는 경우 컴포넌트(함수) 내에서 변수를 선언해서 리렌더링 되는 경우에 if 문으로 분기처리를 해서 jsx 내에서 조건부 렌더링을 구현하는 것이 아닌 컴포넌트(함수) 내부 로직에서 구현하는 방법도 쓸 수 있다는 것을 기억하자.

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

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

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

export default ExpensesList;

조건부 렌더링을 위의 방법으로도 쓸 수 있다는 것 기억하자. 포인트는 리렌더링이 되면 함수를 재실행한다는 점과 함수가 재실행되면서 return을 하면 차이를 비교해서 새로운 부분만 업데이트 한다는 것이다.

profile
완벽함 보다는 최선의 결과를 위해 끊임없이 노력하는 개발자

0개의 댓글