본문 바로가기

React

[React] 리액트 기본 개념

리액트 스터디

성능 잡기

  1. 이름 없는 콜백함수 쓰지말고 함수나 오브젝트는 선언해서 쓰기.. style도 {color: red} 이런식으로 object 형식으로 넣는데.. 변수로 만들어서 넣는게 좋음! => 변수로 미리 만들어 놓는 것이 메모리에 미리 할당해 두는 것인데 이게 성능적으로 더 이득임.
  2. 애니메이션 막주지말고 되도록이면 transform ㄱㄱ
  3. lazy, Suspense 써서 컴포넌트를 사용할 때만 import 해올 수 있음. 리액트는 import한거를 처음에 다 가져와서 속도가 느려질 수 있는데, 이를 해결할 수 있는 방법임.

컴포넌트를 lazy하게 import하는 방법

// import Detail from "./components/Detail.js"; 기존 방법
let Detail = lazy(() => import("./components/Detail.js"));

위에 처럼 lazy로 컴포넌트를 가져온 후

<Suspense fallback={<div>로딩중이에요</div>}>
  <Detail shoes={shoes} stock={stock} setStock={setStock} />
</Suspense>

Suspense로 컴포넌트를 감싼다. fallback에는 컴포넌트가 로드될 때까지 보여줄 UI를 넣는다.

리액트스럽게 개발하자

  • 중요한 데이터는 App.js에 저장하는 것이 국룰. 상위 -> 하위 형태로 만다는 것이 좋음. 혹은 redux 파일에 보관

props 지옥에서 벗어나는 법

useContext 사용하기

먼저 범위를 생성해준다. 범위란 같은 변수값을 공유해주는 범위다. 생성한 범위로 값을 공유하고 싶은 컴포넌트를 감싸주면 됩니다.

export let 재고context = React.createContext();

위에 처럼 범위를 생성하고 export하면 다른 컴포넌트에서도 이 범위를 쓸 수 있다.

<재고context.Provider value={stock}>
  <Suspense fallback={<div>로딩중이에요</div>}>
    <Detail shoes={shoes} stock={stock} setStock={setStock} />
  </Suspense>
</재고context.Provider>

위에 처럼 감싼 부분에서 value를 공유할 수 있다.

Redux 사용하기

Redux 쓰는 이유

  1. props 쓰기 싫어서. props는 Component가 깊어질수록 여러번 전송해야 함. redux는 변수 저장해 놓고 모든 컴포넌트가 같은 state 공유 가능
  2. 데이터 관리가 용이. store에 수정하는 방법을 정의해 놓을 수 있어서
import { createStore } from "redux";

let store = createStore(cartReducer);

먼저, store를 만들고 시작한다. redux는 초기 데이터와 그 데이터를 수정할 수 있는 방법을 정의해 놓는 reducer를 생성함으로써 동작한다. 따라서 store를 만들 때는 reducer를 파라미터로 줘서 만들어준다.

export const cartReducer = (state = [], action) => {
  if (action.type === "항목추가") {
    let copy = [...state];
    let found = copy.find((i) => i.name === action.payload.name);

    if (found) {
      copy.map((i) => {
        if (i.name === found.name) {
          i.quantity += parseInt(action.payload.quantity);
        }
      });
      return copy;
    }

    copy.push(action.payload);
    return copy;
  } else if (action.type === "수량증가") {
    let copy = [...state];
    copy[action.payload].quantity++;
    return copy;
  } else if (action.type === "수량감소") {
    let copy = [...state];
    copy[action.payload].quantity--;
    return copy;
  } else {
    // reducer는 항상 state를 퉤 뱉어야 한다.
    return state;
  }
};

위에는 장바구니 추가와 수량 수정하는 코드이다. reducer는 항상 state를 return해야한다. if문 혹은 switch문으로 state를 상황에 맞게 조작하고 수정된 데이터를 퉤 뱉어야한다.

// state 꺼내쓰는 더 쉬운 방법
let state = useSelector((state) => state);
let dispatch = useDispatch();

state는 모든 컴포넌트에서 꺼내쓸 수 있다. useSelector로 현재 저장된 데이터를 가져올 수 있고, dispatch로 데이터를 실어보내서 state를 수정할 수도 있다.

async 문제

리액트에서 useState의 변경 함수 set어쩌구는 async하게 동작한다. 내가 순차적으로 코드를 짰다고 해도 async하게 동작하기 때문에 의도와 다르게 동작할 수 있다.

이 문제는 useEffect로 해결할 수 있다.

useEffect(() => {
  // 컴포넌트 등장 & 업데이트시 실행
  axios.get();

  return () => {
    // 컴포넌트 없어질때 실행
    second;
  };
}, []);

두번째 파라미터는 값을 받으며, 그 값이 변경될 때마다 axios.get()줄에 있는 코드가 실행된다. []와 같이 빈 배열로 설정하면 변경될 일이 없기 때문에 처음 한번만 실행된다.

Ajax란?

서버에 새로고침없이 요청할 수 있게 도와줌.

  • GET 요청: 주소창에 URL 때려박기 (특정페이지/자료읽기 등)
  • POST 요청: 로그인할 때 아이디, 비밀번호 같이 정보를 숨겨서 전달하려 할 때 사용 (읽기보단 서버로 정보 전달용)

Ajax 쓰는 방법 (feat. axios)

  1. jquery $.ajax()
  2. axios.get() => 이걸 리액트에서 잘 쓴다!!
  3. fetch() 쌩자바스크립트
import axios from "axios";

// GET 요청하기
axios
  .get("https://codingapple1.github.io/shop/data2.json")
  .then((result) => {
    setShoes([...shoes, ...result.data]);
  }) // ajax 성공시
  .catch(() => {
    console.log("실패했어요");
  }); // ajax 실패시

// POST 요청하기
axios.post("서버URL", { id: codingapple, pw: 1234 }).then();

리액트에서 라우팅까지?

리액트에서는 서버가 없어도 /list와 같이 라우팅을 할 수 있다.

import { BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

라우팅 하고자 하는걸 BrowserRouter로 감싸야 한다. BrowserRouter말고도 HashRouter라고 있는데 HashRouter는 /뒤에 #이 오며 #뒤에는 절대 서버에 요청을 하지 않는다. (라우팅을 안전하게 할 수 있게 도와줌)

<Switch>
  <Route exact path="/">
    <div>메인 페이지입니다.</div>
  </Route>

  <Route path="/detail/:id">
    <Detail shoes={shoes} />
  </Route>

  <Route path="/cart">
    <Cart />
  </Route>
</Switch>

근데 리액트는 Switch없이 쓰면 매칭되는 걸 다 보여줌. 내가 지정한 path에 그것만 보여주고 싶으면 Switch로 꼭 감싸야함.

:/id는 파라미터로 주소창에 입력한걸 받겠다는 뜻임.

state 데이터를 기억하게 하려면? 딱히 리액트에만 국한된 내용은 아님 (feat. 로컬스토리지, 세션스토리지)

두 가지 방법이 있다.

  • 서버로 보내서 DB에 저장 하던가
  • 아니면 브라우저 저장공간에 저장

브라우저 저장공간 이용하기

둘 다 5MB 정도 텍스트만 저장가능

  • 로컬스토리지: 브라우저 청소를 하지 않는 이상 안지워짐
  • 세션스토리지: 브라우저 끄면 없어짐(휘발성)

로컬스토리지에 object 자료 저장하려면 깨짐 배열 저장해도 문자만 저장됨, 배열의 형태x ==> 오직 문자만 저장할 수 있다.

JSON.stringify() 해서 json형태로 바꿔줘야함, 다 따옴표가 쳐져있는 형태 JSON.parse() 따옴표 제거하는 역할

클릭 시 다른 path로 이동하는 법

useHistory 사용하기

import { useHistory } from "react-router-dom";
let history = useHistory();
<div
  onClick={() => {
    history.push(`/detail/${props.shoes.id}`); // 페이지 이동을 강제로 시켜줌, redux reset없이 하기 위해
  }}
></div>

자식 컴포넌트가 재렌더링 되지 않도록하는 memo

리액트에서는 부모 컴포넌트가 변경되면 그의 자식 컴포넌트 모두가 재렌더링된다. 자식 컴포넌트가 많으면 쓸데없는 자원 소비가 커지기 때문에 그 자식 컴포넌트에 변화가 있을 때만 변경해주도록 하는 memo를 사용하기도 한다. 그러나 기존 props와 하나하나 비교해서 변경이 되었는지 확인하기 때문에 props가 굉장히 많으면 오히려 연산 시간이 오래걸릴 수 있다.

function Parent(props) {
  return (
    <div>
      <Child1 이름={props.이름} />
      <Child2 나이={props.나이} />
    </div>
  );
}

function Child1() {
  useEffect(() => {
    console.log("렌더링됨1");
  });
  return <div>1111</div>;
}

let Child2 = memo(function () {
  useEffect(() => {
    console.log("렌더링됨2");
  });
  return <div>2222</div>;
});

위와 같은 예제에서 memo를 붙여준 Child2는 부모 Parent가 변경되어도 재렌더링되지 않으며, Child2가 변경될 때만 재렌더링된다.