리액트 스터디
성능 잡기
- 이름 없는 콜백함수 쓰지말고 함수나 오브젝트는 선언해서 쓰기.. style도 {color: red} 이런식으로 object 형식으로 넣는데.. 변수로 만들어서 넣는게 좋음! => 변수로 미리 만들어 놓는 것이 메모리에 미리 할당해 두는 것인데 이게 성능적으로 더 이득임.
- 애니메이션 막주지말고 되도록이면 transform ㄱㄱ
- 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 쓰는 이유
- props 쓰기 싫어서. props는 Component가 깊어질수록 여러번 전송해야 함. redux는 변수 저장해 놓고 모든 컴포넌트가 같은 state 공유 가능
- 데이터 관리가 용이. 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)
- jquery $.ajax()
- axios.get() => 이걸 리액트에서 잘 쓴다!!
- 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가 변경될 때만 재렌더링된다.