styled-component 이용해보기
yarn add styled-component 명령창에 입력
vscode-styled-components 플러그인 다운로드
(손에 매니큐어 칠하는 이미지)
import styled from "styled-components"; // 스타일 컴포넌츠를 임포트한다
import "./App.css";
const StBox = styled.div` //백틱 안에 넣어준다. div 말고 다른 태그들도 된다.
width: 100px;
height: 100px;
border: 1px solid red;
margin: 20px;
`;
function App() {
return <StBox>박스</StBox>;
}
export default App;
색 정보를 props로 넘겨줄 수 있다. 부모가 자식 컴포넌트에 보낼 때 처럼
템플릿 리터럴이라서 백틱을 쓰고 달러표시가 있다.
화살표에 빨간색이 생기는데 그래도 작동은 한다.
const StBox = styled.div`
width: 100px;
height: 100px;
border: 1px solid ${(props) => props.borderColor};
margin: 20px;
`;
function App() {
return (
<div>
<StBox borderColor="red">빨간 박스</StBox>;
<StBox borderColor="green">초록 박스</StBox>;
<StBox borderColor="blue"> 파랑 박스</StBox>;
</div>
);
}
getBoxName이라는 함수를 만들고
map으로 돌려준다.
const boxList = ["red", "green", "blue"];
// 색을 넣으면, 이름을 반환해주는 함수를 만듭니다.
const getBoxName = (color) => {
switch (color) {
case "red":
return "빨간 박스";
case "green":
return "초록 박스";
case "blue":
return "파란 박스";
default:
return "검정 박스";
}
};
function App() {
return (
<div>
{boxList.map((box) => (
<StBox borderColor={box}>{getBoxName({ box })}</StBox>
))}
</div>
);
}
React Hook - useState
기본 형식
const [state, setState] = useState(initialState);
원래는 useState라는 함수가 배열을 반환하고 이것을 구조 분해 문법으로 꺼내놓은 것이다. (이해 안됌)
State가 원시데이터 타임이 아닌 객체 데이터 타입인 경우에 불변성을 유지해줘야한다.
함수형 업데이트
//기존에 우리가 쓰던 방식
setState(number + 1)
//함수형 업데이트
setState(() => { })
- 기존 setNumber 쓴 경우
const App = () => {
const [number, setNumber] = useState(0);
return (
<div>
{/* 버튼을 누르면 1씩 플러스된다. */}
<div>{number}</div>
<button
onClick={() => {
setNumber(number + 1); // 첫번째 줄
setNumber(number + 1); // 두번쨰 줄
setNumber(number + 1); // 세번째 줄
//batch된다. React에서 명령을 하나로 모아서 한번만 명령을 내린다.
}}
>
버튼
</button>
</div>
);
}
- 함수형 setNumber 쓴 경우
const App = () => {
const [number, setNumber] = useState(0);
return (
<div>
{/* 버튼을 누르면 3씩 플러스 된다. */}
<div>{number}</div>
<button
onClick={() => {
setNumber((previousState) => previousState + 1);
setNumber((previousState) => previousState + 1);
setNumber((previousState) => previousState + 1);
//명령을 모아서 순차적으로 각각 한 번씩 실행시킨다.
}}
>
버튼
</button>
</div>
);
};
React Hook - useEffect
- useEffect가 속한 컴포넌트가 화면에 렌더링 되거나 사라졌을 때 useEffect가 실행된다.
- (컴포넌트가 mount 되거나 unmount 되었을 때 실행)
- console에 두번씩 찍히는 이유는 strictmode 가 적용되어 있기 때문이다
- 무한 실행되는 useEffect
- 예시 코드스니펫을 복붙하면 input에 한글자 넣을 때 마다. useEffect가 무한 실행되는 것을 볼 수있다.
- State가 변경되어서 리렌더링이 일어났고 리렌더링이 일어나서 useEffect가 실행된 것이다.
- 해결 방법은 의존성 배열
- useEffect를 제어할 수 있는 방법에는 의존성 배열이 있다.
- 배열 안의 값이 바뀔 때에만 useEffect를 실행해라는 설정
- 빈 배열을 넣은 경우
- 배열 안에 바뀔 값 자체가 없다 = 어떤 값이 변하더라도 useEffect는 실행이 안된다.
- 이걸 넣으니까 처음 렌더링 할 때만 useEffect가 실행되고 그 이후 state가 변경되더라도 useEffect 실행되지 않는다
useEffect(() => {
console.log("hello useEffect");
}, []); //마지막에 들어간 대괄호가 의존성 배열이다.
- 배열에 값을 넣어준 경우
useEffect(() => {
console.log("hello useEffect");
}, [value]);// 의존성 배열 안의 밸류 값이 변할 때마다 useEffect 실행
cleanUp을 이용해 useEffect를 활용하는 방법
-아직 안 배워서 간단하게만 설명하고 지나감
- 사용방법
useEffect(() => {
//화면에 컴포넌트가 나타났을(mount) 때 실행하고자 하는 함수를 넣어주세요
return () => {
//화면에서 컴포넌트가 사라졌을 (unmount) 때 실행하고자 하는 함수를 넣어주세요.
}
}, []);
예를 들어 버튼을 클릭해서 다른 링크로 넘어가서 현재 화면 컴포넌트가 사라졌을 때
return 이후의 내용이 실행된다.
strict mode와 life cycle에 대해서 알아보자
strict mode는 모던 자바스크립트 튜토리얼 페이지에서 읽었고
lift cycle은 입문 주차에 검색해봤는데 둘 다 기억이 가물가물
엄격모드에 대하여
https://ko.javascript.info/strict-mode
lifecycle에 대한 정리글
리액트 라이프사이클의 이해
시작하기 전에 리액트 라이프 사이클을 원래 알고는 있었지만 정확하게 한번도 정리해본 적이 없는 것 같아서 글을 쓰게 되었다. 더불어 리액트 라이프 사이클과 최근 사용되는 Hooks와도 비교해
kyun2da.dev
이 글을 봤을 때 mount와 unmount는 라이프 사이클의 마지막 단계이고, Dom을 이용하여 보여주고 없애는 것 같다.
Redux 기본 설정
redux 패키지랑, 리액트랑 리덕스를 연결해주는 패키지를 함께 다운 받았다
yarn add redux react-redux
- src 폴더 안에 redux 폴더 생성
- redux 폴더 안에 config, modules 폴더 생성
- config폴더 안에 configStore.js(파일 생성
- config 폴더 : redux의 설정과 관련된 파일 있는 곳
- configStore.js : 중앙 statestore를 만들어주는 코드가 있는 곳
- module 폴더 : 기능마다 필요한 State들을 그룹 모아 놓는 곳
- ex) todolist를 만드는 state 파일 todo.js 가 module 폴더 안에 있을 수 있다.
redux의 구성
redux는 reducer를 포함한 store다
1. action을 일으킨다(dispatch)
2. reducer가 자동 실행된다
3. reducer가 action에 맞게 store 의 데이터를 수정해준다.
1. configStore.js의 중앙 statestore를 만들어주는 코드 넣는다.
2. index.js에 redux 관련한 import 해준다
3. 원하는 module counter.js 생성한다.
4. module을 configstore.js에 import하고 reducer에 counter 넣어주어 store과 연결한다.
5. App.js에서 연결 잘 되었는지 useSelector로 store 조회해 state 값 가져와 본다.
- configStore.js의 중앙 statestore를 만들어주는 코드
import { createStore } from "redux";
import { combineReducers } from "redux";
/*
1. createStore()
리덕스의 가장 핵심이 되는 스토어를 만드는 메소드(함수) 입니다.
리덕스는 단일 스토어로 모든 상태 트리를 관리한다고 설명해 드렸죠?
리덕스를 사용할 시 creatorStore를 호출할 일은 한 번밖에 없을 거예요.
*/
/*
2. combineReducers()
리덕스는 action —> dispatch —> reducer 순으로 동작한다고 말씀드렸죠?
이때 애플리케이션이 복잡해지게 되면 reducer 부분을 여러 개로 나눠야 하는 경우가 발생합니다.
combineReducers은 여러 개의 독립적인 reducer의 반환 값을 하나의 상태 객체로 만들어줍니다.
*/
const rootReducer = combineReducers({});
const store = createStore(rootReducer);
export default store;
강사님은 폴더 파일 아이콘이 기능을 설명해주게 되어 있어서 검색해서 찾아봤다.
- index.js 기본 코드
// 원래부터 있던 코드
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
// 우리가 추가할 코드
import store from "./redux/config/configStore";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
//App을 Provider로 감싸주고, configStore에서 export default 한 store를 넣어줍니다.
<Provider store={store}>
<App />
</Provider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
- counter.js 코드
counter를 만들어야하는데 counter.js를 modules 폴더에 넣었다.
useState랑 달리 초기값을 객체 형태로 넣어놓았는데,
객체, 배열, 원시 데이터 다 가능하다.
객체에 여러 변수를 넣을 수 있다.
const initialState = {
number: 0,
};
// 리듀서 //변화를 일으키는 함수 //setState와 같은 역할한다
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
- 이제 store 랑 연결하자
- configStore.js
import { createStore } from "redux";
import { combineReducers } from "redux";
import counter from "../modules/couter"; //counter import 했다.
const rootReducer = combineReducers({
counter:counter, //counter 리듀서를 넣어주었다.
});
const store = createStore(rootReducer);
export default store;
막간 화살표 함수 복습
아래 두 코드는 같은 것
const counterStore = useSelector((state) => state); //화살표 함수
const counterStore = useSelector(function (state) {
return state
});
- 연결이 잘 되었는지 확인하자
- App.js
- component에서 store를 조회할 때는 useSelector를 사용해야한다. (react-redux에서 import 해온다)
import React from "react";
import { useSelector } from "react-redux"; // import 해주세요.
const App = () => {
const counterStore = useSelector((state) => state);
//현재 프로젝트에 존재하는 모든 redux module의 state를 가져와라
const number = useSelector((state) => state.counter.number);
//counter라는 module의 number만 가져와라
console.log(counterStore);
return <div></div>;
};
export default App;
값을 변경하는 법 (counter.js 모듈에 있는 state 값 변경)
1. 컴포넌트에서 명령을 생성한다 ( App.js)
- 명령을 action 이라고 한다.
- action은 객체다.
- 반드시 type을 키 값으로 가져야한다
- value는 대문자로 작성한다 (상수라서)
- ex) {type: "PLUS_ONE"}
2. 명령(액션 객체) 보내려면 useDispatch라는 전달자 함수 Hook을 이용해야한다.
(react-redux에서 import 해온다) ( App.js)
3. 리듀서에서 액션객체 받는다 (configStore.js)
4. 변화를 만들어내는 함수인 리듀서에서 액션 객체의 조건이 일치하면 state 값 변경하는 코드 구현한다 (configStore.js)
6. useSelector로 변경된 State 값 확인한다. ( App.js)
- App.js
import React from "react";
import { useSelector, useDispatch } from "react-redux"; // 사용할 Hook들 import 해주세요.
const App = () => {
const number = useSelector((state) => state.counter.number);
//useSelector로 state 값 가져오기
const dispatch = useDispatch();
// useDispatch를 상수에 지정한다
// 이거 없애고 바로 함수 이용하니까 오류가 난다.
return (
<div>
{number}
<button
onClick={() => {
dispatch({ type: "PlUS_ONE" }); //형식에 맞는 액션 객체를 보내준다
}}
>
+1
</button>
<button
onClick={() => {
dispatch({ type: "MINUS_ONE" });
}}
>
-1
</button>
</div>
);
};
- configStore.js
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서 //변화를 일으키는 함수 //setState와 같은 역할한다
const counter = (state = initialState, action) => {
switch (action.type) {
case "PlUS_ONE":
return {
number: state.number + 1,
};
case "MINUS_ONE":
return {
number: state.number - 1,
};
default:
return state;
}
};
// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
action creator
사용방법
함수와 action value 상수를 이용하여
- counter.js
- 상수와 action creator를 module에서 만든다
- 리듀서에서의 이름도 문자열이 아닌 상수로 바꾼다
//액션 value를 상수들로 만든다
const PlUS_ONE = "PlUS_ONE";
const MINUS_ONE = "MINUS_ONE";
//Action Creator 만들어 준다. 함수다
export const plusOne = () => {
return {
type: PlUS_ONE,
};
};
export const minusOne = () => {
return {
type: MINUS_ONE,
};
};
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서 //변화를 일으키는 함수 //setState와 같은 역할한다
const counter = (state = initialState, action) => {
switch (action.type) {
case PlUS_ONE: // case에서도 문자열이 아닌 위에서 선언한 함수를 넣어준다.
return {
number: state.number + 1,
};
case MINUS_ONE:// case에서도 문자열이 아닌 위에서 선언한 함수를 넣어준다.
return {
number: state.number - 1,
};
default:
return state;
}
};
// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
- App.js
- action creator import 한다
- dispatch() 안에 있던 액션 객체 지우고 action creator 넣는다.
// src/App.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
//사용할 action creator를 import 한다.
import { plusOne, minusOne } from "./redux/modules/counter";
const App = () => {
const number = useSelector((state) => state.counter.number);
const dispatch = useDispatch();
return (
<div>
{number}
<button
onClick={() => {
dispatch(plusOne()); //액션객체를 Action Creator로 변경
}}
>
+1
</button>
<button
onClick={() => {
dispatch(minusOne()); //액션객체를 Action Creator로 변경
}}
>
-1
</button>
</div>
);
};
export default App;
왜 쓰는지? (그냥 기본 방식보다 손이 더 많이 가는 것 같은데 길게 보면 더 좋다)
1. 휴먼에러 방지하려고
액션 객체 이름을 상수로 지정해두면 자동 완성 보조기능을 지원받을 수가 있어 오타를 줄일 수 있다.
2. 유지보수 효율성이 증가한다
아주 많은 곳에 쓰인 action creator 를 수정하려면 action creator 안의 상수의 값을 한 번만 수정하면 된다.
3. 코드 가독성이 증가한다
module 안에 action creator가 정리되어 있기 때문에 module이 어떤 action을 가지는지 쉽게 확인할 수 있다.
리덕스 공식문서에 있는 action creator에 대한 글을 읽어보라고 해서 읽었는데 뭐라는지 모르겠다.
payload
액션 객체에 더 많은 키와 값을 넣어서 보내는 것을 payload라고 한다.
type 속성명을 고정이지만 나머지 속성명은 우리가 원하는대로 정할 수 있다. 하지만 커뮤에서 payload로 담아주는게 잘 되더라 해서 우리도 payload라고 속성명을 사용할거다.
N이 들어가는 것 같이 복잡한 액션 명령을 만들어서 reducer에 보내줄 때 N도 같이 담아 보내주는 것을 payload라고 한다.
payload를 이용한 코드
counter.js
const PLUS_PL = "PLUS_PL";
const MINUS_PL = "MINUS_PL";
export const plusPL = (payload) => {
//매개변수로 payload를 넣는다
return {
type: PLUS_PL,
payload, //키와 밸류가 같으면 하나만 써줄 수 있다.
};
};
export const minusPL = (payload) => {
//매개변수로 payload를 넣는다
return {
type: MINUS_PL,
payload, //키와 밸류가 같으면 하나만 써줄 수 있다.
};
};
const initialState = {
number: 0,
};
const counter = (state = initialState, action) => {
switch (action.type) {
case PLUS_PL:
// 받아온 payload를 더해준다
return { number: state.number + action.payload };
case MINUS_PL:
//받아온 payload를 빼준다
return { number: state.number - action.payload };
default:
return state;
}
};
export default counter;
App.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { plusPL, minusPL } from "./redux/modules/counter";
import { useState } from "react";
const App = () => {
const totalNumber = useSelector((state) => state.counter.number);
const [number, setNumber] = useState(0);
const dispatch = useDispatch();
//익명함수로 바로 넣어줄 수도 있지만 함수를 만들어서 onChange에 넣어주었다.
const onChangeHandler = (event) => {
//event.target이라는 객체를 value라는 키값으로 구조분해 할당한 것
const { value } = event.target;
// 자동으로 문자열로 설정되어 있는 event target value를 숫자롤 +를 붙여 바꿔준다
setNumber(+value);
};
//익명 함수로 바로 넣어줄 수도 있지만 함수를 만들어서 onclick에 넣어주었다.
const onClickAddNumberHandler = () => {
//payload로 들어갈 값을 인자로 넣어준다
dispatch(plusPL(number));
};
const onClickUnAddNumberHandler = () => {
dispatch(minusPL(number));
};
return (
<div>
<p>현재총합 {totalNumber}</p>
<p>{number}</p>
<input onChange={onChangeHandler} type="number" />
<button onClick={onClickAddNumberHandler}> + </button>
<button onClick={onClickUnAddNumberHandler}> - </button>
</div>
);
};
export default App;
Ducks 패턴 - redux module 작성방법의 정석
협업자들끼리 구성요소를 빨리 파악하기 위해 정한 패턴
우리는 이미 이 패턴을 따르고 있었다. ㅎㄷㄷ
1. reducer 함수를 export default 한다.
2. action creator 함수들을 export 한다
3. action의 type은 app/reducer/ACTION_TYPE 형태로 작성한다.
(우리는 action type을 상수로 PLUS_ONE 하나만 써줬는데 app/counter/PLUS_ONE 이런식으로 써줘야된다는 말인가?)
module 파일 1개에는 action-type, action creator, reducer가 모두 존재하는 작성방식
React- router-dom
SPA 방식 설정
예전에 바닐라.js로 SPA 웹 페이지 만들었던 것과 원리는 비슷하다.
yarn add react-router-dome
yarn add react-router-dome
여러 페이지의 컴포넌트들을 만들고
router.js에 넣어준다.
import React from "react";
// 1. react-router-dom을 사용하기 위해서 아래 API들을 import 합니다.
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";
//모든 페이지 컴포넌트들 임포트
// 2. Router 라는 함수를 만들고 아래와 같이 작성합니다.
//BrowserRouter를 Router로 감싸는 이유는,
//SPA의 장점인 브라우저가 깜빡이지 않고 다른 페이지로 이동할 수 있게 만들어줍니다!
const Router = () => {
return (
<BrowserRouter>
<Routes>
{/* 컴포넌트들의 url을 정해준다. */}
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
App.js에 router를 넣어준다.
import React from "react";
import Router from "./shared/Router"; //라우터 임포트 해준다.
const App = () => {
return <Router />;
// App 컴포넌트 안에 넣어준다.
};
export default App;
App.js는 최상위 컴포넌트로 모든 컴포넌트들이 화면에 띄워지기 전에 거쳐간다.
react-router-dom에서 쓰는 hook
useNavigate(); : 페이지 이동시킬 때 쓴다.
import React from "react";
import { useNavigate } from "react-router-dom";
const Home = () => {
const navigate = useNavigate();
return (
<>
<div>Home</div>
<button
onClick={() => {
navigate("/about");
}}
>
To About
</button>
</>
);
};
useLocation:
-현재 위치하고 있는 페이지의 정보를 얻을 수 있다.
-pathname이 찍힌다
- 조건부 렌더링에 이용할 수 있다)]
Link:
- hook은 아니지만 꼭 알아야하는 api
- HTML의 a 태그를 대체한 것,
- 페이지 이동시킬 때 쓴다.
a 태그를 쓰지 않는 이유
페이지 이동시 브라우저 새로고침 된다.
= 모든 컴포넌트를 다시 렌더링하고, useState와 redux에 메모리상 구축했던 상태값들이 모두 초기화된다.
= 성능에 악영향을 주고, 불필요한 움직임이다.
import React from "react";
import { Link, useLocation } from "react-router-dom";
const About = () => {
const location = useLocation();
console.log(location);
return (
<>
<div>About</div>
<div>{`현재 페이지: ${location.pathname}`}</div>
<Link to="/contact">To Contact</Link>
</>
);
};
export default About;
children을 이용한 layout을 적용해보자
layout 역할을 하는 컴포넌트들에서는 어떤 자식 element들이 들어올지 미리 알 수 없어서 children을 쓴다
컴포지션: 합성이라는 뜻
layout을 만들어서 header와 footer를 합성해보자.
layout 태그가 감싸는 모든 것들이 children이 된다.
항상 header와 footer 중간에 children 들어가도록 지정한다.
router 컴포넌트에서 <BrowserRouter> 태그 바로 아래에 <Layout> 컴포넌트 태그를 넣으면
모든 페이지에 공통으로 layout이 적용된다.
Layout.js
// src/shared/Layout.js
import React from "react";
const HeaderStyles = {
width: "100%",
background: "black",
height: "50px",
display: "flex",
alignItems: "center",
paddingLeft: "20px",
color: "white",
fontWeight: "600",
};
const FooterStyles = {
width: "100%",
height: "50px",
display: "flex",
background: "black",
color: "white",
alignItems: "center",
justifyContent: "center",
fontSize: "12px",
};
const layoutStyles = {
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
minHeight: "90vh",
};
function Header() {
return (
<div style={{ ...HeaderStyles }}>
<span>Sparta Coding Club - Let's learn React</span>
</div>
);
}
function Footer() {
return (
<div style={{ ...FooterStyles }}>
<span>copyright @SCC</span>
</div>
);
}
function Layout({ children }) {
return (
<div>
<Header />
<div style={{ ...layoutStyles }}>{children}</div>
<Footer />
</div>
);
}
export default Layout;
Router.js
import React from "react";
// 1. react-router-dom을 사용하기 위해서 아래 API들을 import 합니다.
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";
//모든 페이지 컴포넌트들 임포트
import Layout from "./Layout";
// 2. Router 라는 함수를 만들고 아래와 같이 작성합니다.
//BrowserRouter를 Router로 감싸는 이유는,
//SPA의 장점인 브라우저가 깜빡이지 않고 다른 페이지로 이동할 수 있게 만들어줍니다!
const Router = () => {
return (
<BrowserRouter>
<Layout>
<Routes>
{/* 컴포넌트들의 url을 정해준다. */}
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
</Routes>
</Layout>
</BrowserRouter>
);
};
export default Router;
동적 라우팅 Dynamic Route
const Router = () => {
return (
<BrowserRouter>
<Layout>
<Routes>
{/* 컴포넌트들의 url을 정해준다. */}
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
//url works 이후로 어떤 값이 들어가던지 차이가 없게 해준다.
<Route path="works/:id" element={<Works />} />
</Routes>
</Layout>
</BrowserRouter>
);
};
useParams: 같은 컴포넌트지만 다른 내용물을 보여줄 때 쓰는 Hook
'개발자 되는 중 > 개발 공부' 카테고리의 다른 글
스파르타 알고리즘 - Python 1주차 (0) | 2022.12.14 |
---|---|
스파르타 리액트 입문 (0) | 2022.12.14 |
스파르타 Python 기초 (0) | 2022.12.14 |
스파르타 Javascript 기초 (0) | 2022.12.14 |
웹 개발 특강 (0) | 2022.12.14 |