// Counter.tsx
import styled from 'styled-components';
import { connect } from 'react-redux';
// Action 타입 정의
enum ActionType {
Increment = 'INCREMENT',
Decrement = 'DECREMENT'
}
// Action 객체 정의
interface IncrementAction {
type: ActionType.Increment;
}
interface DecrementAction {
type: ActionType.Decrement;
}
// Action 타입을 하나로 묶음
type Action = IncrementAction | DecrementAction;
// Increment Action 생성 함수
const Increment = (): IncrementAction => {
return { type: ActionType.Increment };
}
// Decrement Action 생성 함수
const Decrement = (): DecrementAction => {
return { type: ActionType.Decrement };
}
// 상태 타입 정의
interface State {
count: number;
}
// 초기 상태값 정의
const initialState: State = {
count: 0
};
// 리듀서 함수 정의
export const reducer = (state = initialState, action: Action): State => {
switch (action.type) {
case ActionType.Increment:
return { ...state, count: state.count + 1}
case ActionType.Decrement:
return { ...state, count: state.count - 1}
default:
return state;
}
}
// 스토어에서 사용할 mapStateToProps 함수 정의
const mapStateToProps = (state: State) => {
return {
count: state.count,
}
}
// 스토어에서 사용할 mapDispatchToProps 함수 정의
const mapDispatchProps = {
Increment,
Decrement,
}
// 스타일드 컴포넌트 정의
const Container = styled.div`
display: flex;
align-items: center;
`;
const Button = styled.button`
margin: 0 10px;
`
const Circle = styled.div<{count: number}>`
width: 100px;
height: 100px;
border-radius: 50%;
background-color: ${({ count }) => ( count % 2 === 0 ? 'blue' : 'red')};
color: white;
font-size: 24px;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
`
// 컴포넌트에서 사용할 props 타입 정의
interface Props {
count: number;
Increment: typeof Increment;
Decrement: typeof Decrement;
}
// 컴포넌트 정의
function Counter({count, Increment, Decrement}: Props) {
// 증가 버튼 클릭 핸들러
const handleIncrement = () => {
Increment();
};
// 감소 버튼 클릭 핸들러
const handleDecrement = () => {
Decrement();
};
return (
<Container>
<Button onClick={handleIncrement}>+</Button>
<Circle count={count}>{count}</Circle>
<Button onClick={handleDecrement}>-</Button>
</Container>
);
}
// connect 함수를 이용하여 컴포넌트와 스토어 연결
export default connect(mapStateToProps, mapDispatchProps)(Counter);
//App.tsx
import Counter from './components/Counter';
function App() {
return (
<div>
<Counter />
</div>
);
}
export default App;
//index.tsx
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { reducer } from './components/Counter';
const store = createStore(reducer);
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<App />
</Provider>
);
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.14",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-redux": "^7.1.25",
"@types/redux": "^3.6.0",
"@types/styled-components": "^5.1.26",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-scripts": "5.0.1",
"styled-components": "^5.3.8",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
별 것도 없는게 코드가 엄청 길다... 프로젝트 할때 폴더 구조를 잘 짜야할 것 같다.