React

베니·2022년 5월 12일
0

REACT

목록 보기
1/2

컴포넌트 합성

const FancyBorder = (props) => {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

const WelcomeDialog = () => {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

react의 컴포넌트에는 상속기능이 없다. 그래서 컴포넌트를 재사용하려면 컴포넌트 합성을 통해 할 수 있다.

useState

const App = () => {
  const [title, setTitle] = useState(0);

  return (
    <div>
      <h2>{title}</h2>
    </div>
  );
}

useState의 초깃값은 해당 컴포넌트가 처음 그려질때만 적용되며 그 후부터는 최신의 state 값을 추적해 적용한다.

const App = () => {
  const [value, setValue] =  useState(0);

  const onClick = () => {
    setValue(value+1);
    setValue(value+1);
    setValue(value+1);
  }
  
  return (
    <div className="App">
      <button onClick={onClick}>+</button>
      <h1>{value}</h1>
    </div>
  );
}

위에 예제는 value는 1로 렌더가 된다. 그 이유는 useState는 비동기로 작동하기 때문이다. 리액트가 효율적으로 렌더링하기 위해 여러 개의 상태 값 변경 요청을 일괄 처리(batching) 처리하기 때문이다.

const singleObj = Object.assign(
  {},
  objectFromSetState1,
  objectFromSetState2,
  objectFromSetState3,

검색해본 결과 일괄처리를 코드로 풀면 위에 예제가 된다고 한다. 이를 Object Composition이라고 한다.

이런 비동기적인 방법을 해결하기 위해서는 setState에 값을 그대로 전달하는 것이 아니라 함수를 전달하면된다. 이를 함수형 업데이트라고 한다.

const App = () => {
  const [value, setValue] =  useState(0);

  const onClick = () => {
    setValue((prev)=>prev+1);
    setValue((prev)=>prev+1);
    setValue((prev)=>prev+1);
  }
  
  return (
    <div className="App">
      <button onClick={onClick}>+</button>
      <h1>{value}</h1>
    </div>
  );
}

위에 예제는 value는 3으로 렌더가 되다. 그 이유는 객체와 같은 형태가 아니라 함수이기 때문에 따로 Object Composition을 하지 않고 호출된 순서대로 큐에 넣고 큐에 넣어진대로 함수를 실행한다.

funciton addNumber(prev) {
  return prev+1;
}
const App = () => {
  const [value, setValue] =  useState(0);

  const onClick = () => {
    setValue(addNumber);
    setValue(addNumber);
    setValue(addNumber);
  }
  
  return (
    <div className="App">
      <button onClick={onClick}>+</button>
      <h1>{value}</h1>
    </div>
  );
}

또, 함수형 업데이트를 진행할때 인자로 넘겨주는 함수는 컴포넌트 외부에 분리하는 것이 추후 테스트에 좋다고 한다.

조건부 내용 출력

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;
  });

  let expensesContent = <p>No expenses found.</p>;

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

  return (
    <div>
      <Card className='expenses'>
        <ExpensesFilter
          selected={filteredYear}
          onChangeFilter={filterChangeHandler}
        />
        {expensesContent}
      </Card>
    </div>
  );
};

평소에 나는 return 부분에 JSX 코드에서 filter, map의 메소드와 삼항연산자, 조건연산자를 사용하는 코드를 작성해왔었다. 하지만 위에 예제처럼 한다면 JSX부분에 코드를 깔끔하게 유지할 수 있고 JSX를 렌더하는 로직을 더 가독성있게 코드를 짤 수 있다.

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>
  );
};

위에 예제는 따로 컴포넌트를 하나 더 만들어 배열에 길이가 0일 경우 JSX를 리턴하는 형식의 코드이다.

Portal

Protal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법이다.

<body>
  <div id="root"></div>  
  <div id="modal"></div>
</body>

포탈을 사용하기 위해서는 react dom 트리 최상단 root 형제요소로 포탈을 사용할 곳을 지정한다.

import ReactDom from 'react-dom';

const ModalPortal = ({ children }) => {
  const el = document.getElementById('modal');
  return ReactDom.createPortal(children, el);
};

export default ModalPortal;

ReactDom에 createPortal 메서드를 이용해서 포탈을 만들 수 있다.

import ModalPortal from "../Components/Modal/Portal";
import Modal from "./Modal/Modal";

const App = () => {
  return (
    <ModalPortal>
      <Modal />
    </ModalPortal>
  );
};

export default App;

useEffect

  • deps에 빈 배열
    • 처음 컴포넌트 마운트 됐을 때 useEffect내 함수 호출
    • 컴포넌트 언마운트 될 때, 다시 그려지기 전에 cleanup 함수 호출(처음 컴포넌트가 그려지기 전에는 cleanup 함수 호출하지 않음)
  • deps에 의존 값 존재
    • 처음 컴포넌트 마운트 됐을 때 useEffect내 함수 호출
    • 의존 값이 업데이트 됐을 때
    • 컴포넌트 언마운트 될 때, 다시 그려지기 전에 cleanup 함수 호출(처음 컴포넌트가 그려지기 전에는 cleanup 함수 호출하지 않음)
  • 아예 파라미터를 안 넣었을 경우
    • 그냥 리렌더링 될 때마다 함수 호출

주의해야할게 있는데 useEffect 훅은 컴포넌트가 다 렌더가 되고난후 실행된다.
컴포넌트는 리렌더링될때마다 언마운트가 되기때문에 계속해서 리렌더링한다면 useEffect->cleanup->useEffect가 반복된다. 하지만 해당 컴포넌트를 그리지 않을 경우(모달을 끌때)는 다시 그리지 않으므로 useEffect->clanup에서 끝난다.

useReducer

useContext

const AuthContext = React.createContext({
  isLoggedIn: false,
  onLogout: () => {},
  onLogin: (email, password) => {}
});

export const AuthContextProvider = (props) => {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  useEffect(() => {
    const storedUserLoggedInInformation = localStorage.getItem('isLoggedIn');

    if (storedUserLoggedInInformation === '1') {
      setIsLoggedIn(true);
    }
  }, []);

  const logoutHandler = () => {
    localStorage.removeItem('isLoggedIn');
    setIsLoggedIn(false);
  };

  const loginHandler = () => {
    localStorage.setItem('isLoggedIn', '1');
    setIsLoggedIn(true);
  };

  return (
    <AuthContext.Provider
      value={{
        isLoggedIn: isLoggedIn,
        onLogout: logoutHandler,
        onLogin: loginHandler,
      }}
    >
      {props.children}
    </AuthContext.Provider>
  );
};

export default AuthContext;

react hooks의 규칙

  • hook은 오직 리액트 함수 내에서만 사용되어야 한다. (일반적인 js함수에서 호출하면 안된다.)
  • 리액트 함수 최상위에서 호출해야 한다.(반복문, 조건문, 중첩된 함수 내에서 hook을 호출하면 안된다.)

React.Memo

react의 컴포넌트는 사실 하위 컴포넌트으로 전해주는 props의 변경이 없어도 부모 컴포넌트가 리렌더되기만 해도 하위 컴포넌트를 리렌더한다. 그렇기 때문에 React.memo를 통해서 props의 변경이 없는 경우는 리렌더를 안해주는 것이 성능에 좋다.

state 및 컴포넌트 심화

useState를 사용하여 상태에 초깃값을 주는 코드는 처음 렌더링될때만 실행된다. 재평가 과정에서 useState의 코드를 만나면 새로운 상태는 생성되지 않고 해당 코드를 무시한다. 정리하자면 상태는 최초의 초기화 이후에는 갱신만 된다.

state 스케줄링 및 일괄 처리

상태를 변경할때 상태의 변경은 바로 일어나지 않고 상태 업데이트 예약을 걸어둔다.

useMemo, useCallback, React.memo

나는 useMemo, useCallback, React.memo로 메모리를 아끼려고 사용하는걸로 생각했었다. 하지만 useMemo, useCallback 등을 사용한다해도 똑같은 메모리는 계속 사용될 것이다. 최적화란 말이 어떤 최적화인지 곰곰히 생각해보니 렌더링에 관한 최적화였다. 처음부터 메모리와는 별개의 문제였다.. 이것을 이제 이해하다니.. 후... 정리하자면

useMemo는 연산이 오래걸리는 값을 메모리제이션해서 다음 리렌더링될때는 해당 연산을 하지 않고 메모리제이션 해놓은 값을 불러와서 리렌더링을 연산 시간을 줄여서 최적화 하는 것이다.

React.memo를 사용해 props, 상태가 변화가 없는 컴포넌트는 렌더링을 하지 않게 할 수 있지만 props로 함수를 넘겨주면 함수는 객체이기 때문에 렌더링할때마다 메모리의 주소 값이 바뀌기 때문에 리액트는 props가 바뀌었다고 생각한다. 그래서 useCallback을 사용해 props로 넘겨주는 함수를 메모리제이션해서 리렌더링을 발생하지 않을 수 있게할 수 있다.

커스텀 hook

profile
안녕하세요~

0개의 댓글