React
styled-components
Context API
styled-components
를 통한 컴포넌트 스타일링Context API
를 사용한 전역 상태 관리<body>
스타일을 어떻게 지정할까?특정 컴포넌트를 만들어서 스타일링 하는게 아니라
<body>
와 같은 글로벌 스타일을 추가하고 싶으면 createGlobalStyle사용
import styled, { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
body {
background-color: #e9ecef;
}
`
return (
<>
<GlobalStyle />
<TodoListBlock/>
const TodoListBlock = styled.div`
padding-top: 48px;
h1 {
margin: 0;
}
.day {
margin-top: 4px;
}
`
cf1. 조건부 스타일링을 할 필요가 없고, 기능적으로 중요하지 않은 내용이라면 CSS Selector 를 사용하고, 기능적으로 중요한 내용은 별도의 컴포넌트로 만들자
<input type=”text">
에 텍스트 입력할 때 마다 포커스를 잃는 문제contextAPI를 사용해서 state가 변경될 때 마다 rerender 되어서 발생하는 문제
const Remove = styled.div`
display: flex;
align-items: center;
justify-content: center;
display: none;
`
const TodoItemBlock = styled.div`
&:hover {
${Remove} {
display: initial;
}
}
`;
cf1. Remove
가 TodoItemBlock
보다 위에 위치해야함
import styled, { css } from 'styled-components';
${props =>
props.done &&
css`
color:#ced4da;
`}
react-icons 설치
npm install react-icons --save
react-icons 사용방법
import { FaBeer } from 'react-icons/fa';
<FaBeer />
cf1. ‘react-icons/fa’ import시 fa로 시작하는 아이콘만 사용 가능
autoFocus는 HTML과 React에서 사용되는 속성으로 페이지 로드 후 자동으로 포커스 지정
<input type="text" autoFocus />
나
📦src
┣ 📂components
┃ ┣ 📜TodoBottom.js
┃ ┣ 📜TodoList.js
┃ ┗ 📜TodoTop.js
┣ 📜App.css
┣ 📜App.js
┣ 📜data.js
┣ 📜index.css
┣ 📜index.js
┗ 📜styles.js
// 생략
<Todo>
<Context.Provider
value={{ todoData, setTodoData }}>
<TodoTop />
<TodoList />
<TodoBottom />
</Context.Provider>
</Todo>
완성코드
📦src
┣ 📂components
┃ ┣ 📜TodoCreate.js
┃ ┣ 📜TodoHead.js
┃ ┣ 📜TodoItem.js
┃ ┣ 📜TodoList.js
┃ ┗ 📜TodoTemplate.js
┣ 📜App.css
┣ 📜App.js
┣ 📜index.css
┣ 📜index.js
┗ 📜TodoContext.js
<>
<TodoProvider>
<GlobalStyle />
<TodoTemplate>
<TodoHead />
<TodoList />
<TodoCreate />
</TodoTemplate>
</TodoProvider>
</>
cf1. 완성코드는 App 컴포넌트에 TodoTemplate.js라는 템플릿 컴포넌트를 만들고
안에 TodoHead, TodoItem,TodoCreate 등 컴포넌트를 props.child로 받음
ex)
const dataString = today.toLocaleString('ko-kr',{
year:'numeric',
month:'long',
day:'numeric',
}) // 2023년 4월 19일
const dayName = today.toLocaleString('ko-kr', { weekday : 'long'}) // 화요일
const dataString = today.toLocaleString('ko-kr',{
year:'numeric',
month:'long',
day:'numeric',
}) // 2023.4. 19
참고 코드
import React, { useReducer, createContext, useContext, useRef } from 'react';
const initialTodos = [
{
id: 1,
text: '프로젝트 생성하기',
done: true
},
{
id: 2,
text: '컴포넌트 스타일링하기',
done: true
},
{
id: 3,
text: 'Context 만들기',
done: false
},
{
id: 4,
text: '기능 구현하기',
done: false
}
];
function todoReducer(state, action) {
switch (action.type) {
case 'CREATE':
return state.concat(action.todo);
case 'TOGGLE':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
);
case 'REMOVE':
return state.filter(todo => todo.id !== action.id);
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
// context 분리
const TodoStateContext = createContext();
const TodoDispatchContext = createContext();
const TodoNextIdContext = createContext();
export function TodoProvider({ children }) {
const [state, dispatch] = useReducer(todoReducer, initialTodos);
const nextId = useRef(5);
return (
<TodoStateContext.Provider value={state}>
<TodoDispatchContext.Provider value={dispatch}>
<TodoNextIdContext.Provider value={nextId}>
{children}
</TodoNextIdContext.Provider>
</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
);
}
// 커스텀 hook
export function useTodoState() {
const context = useContext(TodoStateContext);
// 커스텀 Hook 에러 처리
if (!context) {
throw new Error('Cannot find TodoProvider');
}
return context;
}
export function useTodoDispatch() {
const context = useContext(TodoDispatchContext);
if (!context) {
throw new Error('Cannot find TodoProvider');
}
return context;
}
export function useTodoNextId() {
const context = useContext(TodoNextIdContext);
if (!context) {
throw new Error('Cannot find TodoProvider');
}
return context;
}
cf1. throw new Error()
는 자바스크립트에서 예외를 발생시키는 방법
예외가 발생되면 예외를 처리할 수 있는 try…catch
블록이 있는 곳까지 전파되어 예외를 처리할 수 있음
cf2. 1개의 Context 를 만들어서 state
와 dispatch
를 함께 넣는 것이 아닌, 2개의 Context로 분리하기
⇒ dispatch
만 필요한 컴포넌트에서 불필요한 렌더링 방지 및 사용 편리
state
와 dispatch
를 하나의 context로 만들었다면, TodoCreate를 open한 상태에서 이미 존재하는 todo를 toggle한다면 TodoCreate는 리렌더링이 일어난다.
왜냐하면 TodoCreate가 state도 useContext로 받아오고 있기 때문이다.
TodoCreate입장에선 TodoState는 불필요하기 때문에, 굳이 받아올 필요가 없고 더군다나 리렌더링이 일어나게 한다면 더더욱 분리하는 것이 좋다.
cf3. 커스텀 hook 사용방법
import React from 'react';
import { useTodoState, useTodoDispatch } from '../TodoContext';
function Sample() {
const state = useTodoState();
const dispatch = useTodoDispatch();
return <div>Sample</div>;
}
cf4. 커스텀 Hook 에러 처리
만든 useTodoState
, useTodoDispatch
, useTodoNextId
Hook 을 사용하려면, 해당 컴포넌트가 TodoProvider 컴포넌트 내부에 렌더링되어 있어야 함
(예: App 컴포넌트에서 모든 내용을 TodoProvider 로 감싸기)
만약 TodoProvider 로 감싸져있지 않다면 에러를 발생시키도록 커스텀 Hook 을 수정
⇒ 추후 실수 방지
dom 요소에 직접 접근해서 값을 변경해야할 때 사용하는 리액트 hook (예 : input 요소에 포커스)
값이 변경되어도 재렌더링 되지 않음
TodoContext 에서 관리하고 있는 state
가 바뀔 때 때 TodoCreate 의 불필요한 리렌더링을 방지 하기
export default React.memo(TodoItem);
export default React.memo(TodoCreate);