드디어 Redux의 구조에 대해 알아보는 시간이었다. 이 과정을 듣기 전에는 그냥 다른 사람의 코드를 무조건 가져와 사용하다보니 뭐가뭔지 어떻게 데이터를 움직이는지 알지 못했는데 조금이나마 이해가 되는 것 같고 기록해놓고자 학습일지에 적어놓았다.
과정은 8주로 마무리 되지만 좀더 공부해보면서 학습일지를 수정해서 기록해놓고자 한다.
-글로벌 변수를 지양하는 건, 인지하기 어려워질 수 있기 때문이다.
-안쓰는 CSS가 남아있을 수 있다.전체 불필요한 사이즈를 키우는 것 좋지않다.
-CSS파일을 어떤 것을 먼저 썼냐에 따라 로드 타이밍이 바뀌어 원하지 않는 스타일이 적용되기도 한다.
이 안에 스타일 주면 됨
`# with npm
npm install --save styled-components
import 'App.css';
import StyledComponentExample from './comonents/StyledComponentsExample/StyledComponentExample'
export default function styledComponentExample() {
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
render(
<Wrapper>
<Title>
Hello World!
</Title>
</Wrapper>
);
}
import 'App.css';
import StyledComponentExample from './comonents/StyledComponentsExample/StyledComponentExample'
export default function styledComponentExample() {
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
// Create a Wrapper component that'll render a <section> tag with some styles
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
const Button = styled.button`
/* Adapt the colors based on primary prop */
background: ${props => props.primary ? "palevioletred" : "white"};
color: ${props => props.primary ? "white" : "palevioletred"};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// Use Title and Wrapper like any other React component – except they're styled!
render(
<>
<Wrapper>
<Title>
Hello World!
</Title>
</Wrapper>
<Button onClick={() => alert("normal")}>Normal</Button>
<Button onClick={() => alert("primary")} primary>Primaty</Button>
<TomatoButton>Tomato</TomatoButton>
</>
);
}
정리해보면
export defalt function StyledComponentExaple() {
return (
<>
<>
<Thing>Hello world!</Thing>
<Thing>How ya doing?</Thing>
<Thing className="something">The sun is shining...</Thing>
<div>Pretty nice day today.</div>
<Thing>Don't you think?</Thing>
<div className="something-else">
<Thing>Splendid.</Thing>
</div>
</>
</>
);
}
-& : 자신에게 줄 style (나를 특정할 수 있다!!!!)
-& ~ & : 바로 옆에 있지 않아도 동위 요소들에게 줄 style
-& + & : 자신 바로 옆에 있는 값의 style
-&.something : 자신인데, classname이 something인 값의 styled 지정
-.something-else& : something인 클래스 외에 줄 style
const Input = styled.input.attrs(props => ({
type: "text",
size: props.size || "1em",
}))`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
/* here we use the dynamically computed prop */
margin: ${props => props.size};
padding: ${props => props.size};
`;
return (
<div>
<Input placeholder="A small text input" />
<br />
<Input placeholder="A bigger text input" size="2em" />
</div>
);
(1) 단일 스토어를 만드는 법
액션
을 정의액션
을 사용하는, 리듀서
를 만듬.리듀서
들을 합침리듀서
를 인자로, 단일 스토어를 만들어 사용.(2) React에서 스토어를 사용하는 법
# 프로젝트 설치
npx create-react-app redux-start
# 폴더 이동
cd redux-start
# 리덕스 설치
npm i redux
type은 문자열이다.
function 액션생성자(...args) { return 액션; }
액션 객체
를 리턴해줌const ADD_TODO = "ADD_TODO";
function addTodo(todo) {
return {
type: ADD_TODO,
todo,
};
}
형식
function 리듀서(previousState, action) {
return newState;
}
ㄴ 액션을 받아서 스테이트를 리턴하는 구조로 되어있다.
ㄴ 인자로 들어오는 previousState와 리턴되는 newState는 다른 참조를 다른 참조를 가지도록 해야함(각각 Immutable 해야한다는 뜻.)
즉, 리듀서는 previousState와 action을 이용해서 newState를 만들어내는 것
액션 사용할 것이므로 export 형태로 변경
reducer.js
import {ADD_TODO} from "./actions";
// state의 모습 생각
// ['코딩', '점심먹기'...]; 이런식의 결과가 들어오도록 해보자.
function todoApp(previousState, action) {
// 초기값을 설정해주는 부분
if (previousState === undefined) {
return [];
}
if (action.type === ADD_TODO) {
return [... previousState, action.todo];
}
return previousState;
}
reducer.js
import {ADD_TODO} from "./actions";
// state의 모습 생각
// ['코딩', '점심먹기'...]; 이런식의 결과가 들어오도록 해보자.
const initialState = [];
function todoApp(previousState = initialState, action) {
// 초기값을 설정해주는 부분
//if (previousState === undefined) {
// return [];
//}
if (action.type === ADD_TODO) {
return [... previousState, action.todo];
}
return previousState;
}
const store = createStore(리듀서);
인자들 확인
createStore<S>(
reducer: Reducer<S>,
preloadedState: S,
enhancer?: StoreEnhancer<S>
):Store<S>;
사용할 리듀서 export 형태만들기
store.js
import { createStore } from 'redux';
import { todoApp } from './reducers';
const store = createStore(todoApp);
export default store;
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
console.log(store); // 콘솔에 store 모습 확인해보기 위해
console.log(store.getState()); // 현재 state상태 확인
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
[ ] 랑 동일
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import {addTodo} from './redux/actions';
console.log(store);
console.log(store.getState());
store.dispatch(addTodo('코딩'));
console.log(store.getState());
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
index.js 수정
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import {addTodo} from './redux/actions';
store.subscribe(() => { // 구독 -> 상태가 변하면 콘솔 출력
console.log(store.getState());
});
//console.log(store); // store 모양 확인
//console.log(store.getState()); // store 상태 확인
store.dispatch(addTodo('코딩')); // store state 변경
store.dispatch(addTodo('책읽기'));
store.dispatch(addTodo('먹기'));
//console.log(store.getState()); // 변경된 상태 확인
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import {addTodo} from './redux/actions';
const unsubscribe = store.subscribe(() => { // 구독 -> 상태가 변하면 콘솔 출력
console.log(store.getState());
});
//console.log(store); // store 모양 확인
//console.log(store.getState()); // store 상태 확인
store.dispatch(addTodo('코딩')); // store state 변경
store.dispatch(addTodo('책읽기'));
store.dispatch(addTodo('먹기'));
unsubscribe();
store.dispatch(addTodo('코딩2')); // unsubscribe 이후론 콘솔에 안찍힘
store.dispatch(addTodo('책읽기2')); // 콘솔엔 안찍히지만 추가는 되었을 것!
store.dispatch(addTodo('먹기2'));
//console.log(store.getState()); // 변경된 상태 확인
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
store.getState();
store.dispatch(액션); // 액션을 직접 넣거나?
store.dispatch(액션생성자()); // 생성자통해 액션만드는게 더 일반적임.
const unsubscribe = store.subscribe(() => { });
unsubscribe
라는 것 기억!store.replaceReducer(다른리듀서); -> 실무에서 잘 쓰이진 않음
action.js -> addTodo 수정
export const ADD_TODO = "ADD_TODO";
// {type: ADD_TODO, text: '할일'}
export function addTodo(text) {
return {
type: ADD_TODO,
text,
};
}
reducers.js
import {ADD_TODO} from "./actions";
// state 구상
// [{text: '코딩', done: false},{text: '점심먹기', done: false}];
const initialState = [];
export function todoApp(previousState = initialState, action) {
if (action.type === ADD_TODO) {
return [...previousState, { text: action.text, done: false }];
}
return previousState;
}
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import {addTodo} from './redux/actions';
store.subscribe(() => { // 구독 -> 상태가 변하면 콘솔 출력
console.log(store.getState());
});
store.dispatch(addTodo('할일'));
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
actions.js
export const ADD_TODO = "ADD_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";
// {type: ADD_TODO, text: '할일'}
export function addTodo(text) {
return {
type: ADD_TODO,
text,
};
}
// 최종형태 {type: COMPLETE_TODO, index: 3}
export function completeTodo(index) {
return {
type: COMPLETE_TODO,
index,
};
}
reducers.js
import {ADD_TODO, COMPLETE_TODO} from "./actions";
// state 구상
// [{text: '코딩', done: false},{text: '점심먹기', done: false}];
const initialState = [];
export function todoApp(previousState = initialState, action) {
if (action.type === ADD_TODO) {
return [...previousState, { text: action.text, done: false }];
}
if (action.type === COMPLETE_TODO) {
return previousState.map((todo, index) => {
if (index === action.index) {
return {... todo, done: true }
}
return todo;
})
}
return previousState;
}
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import {addTodo, completeTodo} from './redux/actions';
store.subscribe(() => { // 구독 -> 상태가 변하면 콘솔 출력
console.log(store.getState());
});
store.dispatch(addTodo('할일'));
store.dispatch(completeTodo(0));
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
import {ADD_TODO, COMPLETE_TODO} from "./actions";
// state 구상
// [{text: '코딩', done: false},{text: '점심먹기', done: false}];
// {todos: [{text: '코딩', done: false},{text: '점심먹기', done: false}], filter:'ALL'}
const initialState = { todos: [], filter: 'ALL' };
export function todoApp(previousState = initialState, action) {
if (action.type === ADD_TODO) {
return {
...previousState,
todos: [...previousState.todos, { text: action.text, done: false }]
};
}
if (action.type === COMPLETE_TODO) {
return {
...previousState,
todos: previousState.todos.map((todo, index) => {
if (index === action.index) {
return {... todo, done: true }
}
return todo;
}),
};
}
return previousState;
}
export const ADD_TODO = "ADD_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";
// {type: ADD_TODO, text: '할일'}
export function addTodo(text) {
return {
type: ADD_TODO,
text,
};
}
// 최종형태 {type: COMPLETE_TODO, index: 3}
export function completeTodo(index) {
return {
type: COMPLETE_TODO,
index,
};
}
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_COMPLETE = 'SHOW_COMPLETE';
export function showALL() {
return {type: SHOW_ALL};
}
export function showComplete() {
return {type: SHOW_COMPLETE};
}
reducers.js
import {ADD_TODO, COMPLETE_TODO, SHOW_COMPLETE} from "./actions";
// state 구상
// [{text: '코딩', done: false},{text: '점심먹기', done: false}];
// {todos: [{text: '코딩', done: false},{text: '점심먹기', done: false}], filter:'ALL'}
const initialState = { todos: [], filter: 'ALL' };
export function todoApp(previousState = initialState, action) {
if (action.type === ADD_TODO) {
return {
...previousState,
todos: [...previousState.todos, { text: action.text, done: false }]
};
}
if (action.type === COMPLETE_TODO) {
return {
...previousState,
todos: previousState.todos.map((todo, index) => {
if (index === action.index) {
return {... todo, done: true }
}
return todo;
}),
};
}
if (action.type === SHOW_COMPLETE) {
return {
... previousState,
filter: "COMPLETE",
};
}
if (action.type === SHOW_ALL) {
return {
... previousState,
filter: "ALL",
};
}
return previousState;
}
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import {addTodo, completeTodo, showComplete} from './redux/actions';
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch(addTodo('할일'));
store.dispatch(completeTodo(0));
store.dispatch(showComplete()); //filter 값 변경진행
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
reducers.js
import {ADD_TODO, COMPLETE_TODO, SHOW_COMPLETE, SHOW_ALL} from "./actions";
import { combineReducers } from "redux";
// state 구상
// [{text: '코딩', done: false},{text: '점심먹기', done: false}];
// {todos: [{text: '코딩', done: false},{text: '점심먹기', done: false}], filter:'ALL'}
const initialState = { todos: [], filter: 'ALL' };
// [{text: '코딩', done: false},{text: '점심먹기', done: false}]
const todosInitialState = initialState.todos;
const filterInitialState = initialState.filter;
const reducer = combineReducers({
todos : todosReducer,
filter : filterReducer,
});
export default reducer;
function todosReducer(previousState = todosInitialState, action) {
if (action.type === ADD_TODO) {
return [...previousState, { text: action.text, done: false }];
}
if (action.type === COMPLETE_TODO) {
return previousState.map((todo, index) => {
if (index === action.index) {
return {... todo, done: true }
}
return todo;
});
}
return previousState;
}
function filterReducer(previousState = filterInitialState, action) {
if (action.type === SHOW_COMPLETE) {
return "COMPLETE";
}
if (action.type === SHOW_ALL) {
return "ALL";
}
return previousState;
}
store.js <- 바꾼 리듀서로 교체
import { createStore } from 'redux';
import reducer from './reducers';
const store = createStore(reducer);
export default store;
단일 store를 만들고, subscribe와 getState를 이용하여,
변경되는 state데이터를 얻어, props로 계속 아래로 전달
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App store={store}/>
</React.StrictMode>
);
reportWebVitals();
App.js
import logo from './logo.svg';
import './App.css';
import { useEffect, useState } from 'react';
function App({store}) {
const [state, setState] = useState(store.getState());
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState());
})
return () => {
unsubscribe();
}
}, [store]);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{JSON.stringify(state)}
</header>
</div>
);
}
export default App;
import logo from './logo.svg';
import './App.css';
import { useEffect, useState } from 'react';
import { addTodo } from "./redux/actions";
function App({store}) {
const [state, setState] = useState(store.getState());
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState());
})
return () => {
unsubscribe();
}
}, [store]);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{JSON.stringify(state)}
<button onClick={click}>추가</button>
</header>
</div>
);
function click() {
store.dispatch(addTodo("todo"));
}
}
export default App;
새로운 폴더에 새파일 작성
ReduxContext.js
import { createContext } from "react";
const ReduxContext = createContext();
export default ReduxContext;
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import ReactContext from './contexts/ReduxContext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<ReactContext.Provider value={store}>
<App />
</ReactContext.Provider>
</React.StrictMode>
);
reportWebVitals();
App.js
import logo from './logo.svg';
import './App.css';
import { useContext, useEffect, useState } from 'react';
import { addTodo } from "./redux/actions";
import ReduxContext from './contexts/ReduxContext';
function App() {
const store = useContext(ReduxContext);
const [state, setState] = useState(store.getState());
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState());
})
return () => {
unsubscribe();
}
}, [store]);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{JSON.stringify(state)}
<button onClick={click}>추가</button>
</header>
</div>
);
function click() {
store.dispatch(addTodo("todo"));
}
}
export default App;
잘 나오는 것을 확인 할 수 있다
import logo from './logo.svg';
import './App.css';
import { useContext, useEffect, useState } from 'react';
import { addTodo } from "./redux/actions";
import ReduxContext from './contexts/ReduxContext';
function useReduxState() {
const store = useContext(ReduxContext);
const [state, setState] = useState(store.getState());
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState());
})
return () => {
unsubscribe();
}
}, [store]);
return state;
}
function useReduxDispatch() {
const store = useContext(ReduxContext);
return store.dispatch;
}
function App() {
const state = useReduxState();
const dispatch = useReduxDispatch();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{JSON.stringify(state)}
<button onClick={click}>추가</button>
</header>
</div>
);
function click() {
dispatch(addTodo("todo"));
}
}
export default App;
역시 동일하게 작성 잘된다.
다 따로 쪼개 진행
TodoList.jsx
import useReduxState from "../hooks/useReduxState";
export default function TodoList() {
const state = useReduxState();
return (
<ul>
{state.todos.map((todo) => {
return <li>{todo.text}</li>;
})}
</ul>
);
}
TodoForm.jsx
import { useRef } from "react";
import useReduxDispatch from "../hooks/useReduxDispatch";
import { addTodo } from "../redux/actions";
export default function TodoForm() {
const inputRef = useRef();
const dispatch = useReduxDispatch();
return (
<div>
<input ref={inputRef} />
<button onClick={click}>추가</button>
</div>
);
function click() {
dispatch(addTodo(inputRef.current.value));
}
}
App,js
import logo from './logo.svg';
import './App.css';
import TodoList from './components/TodoList';
import TodoForm from './components/TodoForm';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<TodoList />
<TodoForm />
</header>
</div>
);
}
export default App;
잘 정리 되었다.
npm i react-redux
출처 : fastcampus_React & Redux로 시작하는 웹 프로그래밍