드디어 Redux의 구조에 대해 알아보는 시간이었다. 이 과정을 듣기 전에는 그냥 다른 사람의 코드를 무조건 가져와 사용하다보니 뭐가뭔지 어떻게 데이터를 움직이는지 알지 못했는데 조금이나마 이해가 되는 것 같고 기록해놓고자 학습일지에 적어놓았다.
과정은 8주로 마무리 되지만 좀더 공부해보면서 학습일지를 수정해서 기록해놓고자 한다.


스타일링 라이브러리

styled_components

  • CSS의 문제점 -> 사이즈를 큰 사이트를 다루다 보니 문제가 생겼다.
    • Global Namespace : 글로벌 변수를 지양해야하는 JS와 대치
    • Dependencies : CSS 간의 의존관리
    • Dead Code Elimination : 안쓰는 CSS 인지 어려움
    • Minification : 클래스 이름 최소화
    • Sharing Constants : JS의 코드와 값을 공유하고 싶음
    • Non-deterministic Resolution : CSS 파일 로드 타이밍 이슈
    • Isolation : 격리

-글로벌 변수를 지양하는 건, 인지하기 어려워질 수 있기 때문이다.

-안쓰는 CSS가 남아있을 수 있다.전체 불필요한 사이즈를 키우는 것 좋지않다.

-CSS파일을 어떤 것을 먼저 썼냐에 따라 로드 타이밍이 바뀌어 원하지 않는 스타일이 적용되기도 한다.

  • 스타일을 style 태그로 분리시켜 적어주어 해결했다.
  • 사용법 : styled.{element}`이 안에 스타일 주면 됨 `
    • 상속 유사점..

styled componont

공식문서 살펴보자

  • 설치
# with npm
npm install --save styled-components
  • app 구조
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>
  );
}  

  • 컴포넌트들을 직접 만들지 않아도 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;
  `;

  // 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>
    </>
  );
}  

정리해보면

  • 스타일드 컴포넌트는?
    • Automatic critical CSS: 자동 style injects & 코드 스플릿함
    • 클래스 이름 문제가 생기지 않게함 : 고유 / 오버랩X / misspellings X
    • CSS 삭제가 쉽게 해준다.
    • 관리에 좋다.
    • 벤더도 자동으로. standard만 주면 됨.

문서를 통해 좀 더 알아보자

  • 계속 랜더링 하게 놓지 말고 밖으로 빼놔라
  • & 알아보기
 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

  • attr
  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>
  );

emotion / sass 는 좀 더 정리해서 올려보겠다.

emotion

sass

Redux

01. Redux 개요

  • 컴포넌트 간의 커뮤니케이션이 쉽지 않다.
    • 최상위에서 props를 설정하고 계층 연결된 컴포넌트들에게 전달,전달해주면서, 진행은 가능하나,불편..
    • context를 사용하면 조금 더 편하게 가능하나 "전역데이터" 효과적 관리가 중요하며, Redux를 사용하면 효율적 관리가 가능하다.
  • Redux는 store를 통해서 데이터 관리를 한다. 수정되면 자동 랜더 진행됨

[익히게 될 것들]

(1) 단일 스토어를 만드는 법

  • import redux
  • 액션을 정의
  • 액션을 사용하는, 리듀서를 만듬.
  • 리듀서들을 합침
  • 최종 합쳐진 리듀서를 인자로, 단일 스토어를 만들어 사용.

(2) React에서 스토어를 사용하는 법

  • import react-redux
  • connect 함수를 이용해서 컴포넌트에 연결

설치

# 프로젝트 설치
npx create-react-app redux-start

# 폴더 이동
cd redux-start

# 리덕스 설치
npm i redux

02. Action

리덕스의 액션?

  • 액션은 사실 그냥 객체(object)입니다.
    • 스토어의 상태를 변경하는 용도로 사용!
  • 두 가지 종류의 Action이 존재!
    • { type: 'TEST' } // payload 없는 액션
    • { type: 'TEST', params:'hello' } // payload 있는 액션
  • 필수 Property는 type 만으로 유일하며, type은 문자열이다.

리덕스의 액션생성자?

function 액션생성자(...args) { return 액션; }

  • 액션을 생성하는 함수를 "액션생성자(Action Creator)"라고 함.
  • 함수를 통해 액션을 생성해 액션 객체를 리턴해줌
  • createTest('hello'); // {type: 'TEST', params: 'hello'}를 리턴

Redux의 Action의 역할?

  • 액션 생성자를 통해서 액션을 만듬.
  • 만들어진 액션 객체를 Redux store에 보냄.
  • Redux store가 액션객체를 받으면 스토어가 갖고있는 원래 상태값이 변경됨.
  • 변경된 상태값에 의해 그 상태를 이용하고 있는 컴포넌트들이 변경됨.
  • 액션은, 즉 store에 보내는 일종의 input값으로 생각할 수 있음.

Action을 준비하는 방법?

  • 액션의 타입을 정의하여 변수로 빼는 단계
    • 안해도 되긴 함(강제사항 아님)
    • 타입을 단순 문자열로 넣기에는 실수를 유발할 가능성이 크다.
    • 미리 정의한 변수로 사용하면, 스펠링 등 주의를 덜 기울여도 됨.
  • 액션객체를 만들어 내는 함수를 만드는 단계
    • 하나의 액션 객체를 만들기 위해 하나의 함수를 만들어서 진행.
    • 위와 같이 액션의 타입은 미리 정의한 타입변수로 부터 가져와 사용.

Todo action 만들어 보기

const ADD_TODO = "ADD_TODO";

function addTodo(todo) {
    return {
        type: ADD_TODO,
        todo,
    };
}

03. Reducers

리덕스의 리듀서?

  • 액션을 주면, 그 액션이 적용되어 결과를 만들어줌(변경될수도,아닐수도..)
  • 그냥 함수임.
    • Pure Function - 항상 같은 인풋을 넣으면 같은 결과가 나오는 것.
      (시간에 따라 결과값이 변하는 등의 진행이 되는 것은 안맞음)
    • Immutable - 이전 state와 변경되는 state가 별도의 객체로 진행되어야함.
      • why?
        • Reducer를 통해 state가 달라졌음을 Redux가 인지하기 때문.

형식

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;
}

04. createStore

스토어를 만드는 함수

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();
  • store 모습 확인

    _
  • getstate통해 현재 state 상태 확인
    [ ] 랑 동일
    .
  • action생성자 가져와서 사용!

    .
  • dispatch로 state 변경해보기
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();

  • subscript 사용

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();

  • 구독의 리턴값은 unsubscribe이므로 어느 시점 구독 풀어야 함.
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라는 것 기억!
    • unsubscribe(); 선언하면 삭제됨.
  • store.replaceReducer(다른리듀서); -> 실무에서 잘 쓰이진 않음

05. combineReducers

todo state 형식부터 싹 바꿀 것

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();

  • done 유무 변경 위한 과정

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();

  • 더 구조화해보자
    reducers.js -> 구조형태만 바꿔보는 것..
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;
}

  • filter 값을 바꿀 수 있는 것 구현
    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,
    };
}

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();

App의 state가 커지고 복잡해질수록 어려워질 수 있음을 알 수 있다.

  • 리덕스는 단일스토어라 리듀서를 쪼개는 것을 잘 해야 한다.
  • todo에만 관련있는 앞 2개와 filter에만 관련있는 아래 2개를 따로 리듀서로 쪼개서 합쳐보겠다.

이와 관련된 기능이 combineReducers

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;

  • 이대로 해도 되지만 한 파일에 다 리듀서 넣기보다 따로 파일 분리시키는 것이 관리하기 좋음.



06. Redux를 React에 연결(react-redux를 안쓰고 연결해보기)

단일 store를 만들고, subscribe와 getState를 이용하여,
변경되는 state데이터를 얻어, props로 계속 아래로 전달

  • componentDidMount - subscribe
  • componentWillUnmount - unsubscribe

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';

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;

  • 추가 버튼 누르면 todo 추가되도록 구조만들어보기
    App.js
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;

모든 컴포넌트에 전달하는 방법으로 context 써보겠다.

새로운 폴더에 새파일 작성

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;


잘 나오는 것을 확인 할 수 있다

  • 공통된 hook부분을 커스텀 훅으로 빼보자.
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;


잘 정리 되었다.

07. Redux를 React에 연결(react-redux를 쓰고 연결해보기)

react-redux?

  • Provider 컴포넌트를 제공해준다.
  • connect 함수를 통해 "컨테이너"를 만들어준다.
    • 컨테이너는 store의 상태와 디스패치(액션)를 연결한 컴포넌트에 props로 넣어주는 역할을 함
    • 그렇다면 어떤 것이 필요한가?
      • 어떤 state를 어떤 props에 연결할 것인지에 대한 정의 필요
      • 어떤 dispatch(액션)을 어떤 props에 연결할 것인지에 대한 정의 필요
      • 그 props를 보낼 컴포넌트를 정의 필요

react-redux 설치

npm i react-redux

동일하게 제공해주는 provider 컴포넌트 이용해서 사용하면 된다.

출처 : fastcampus_React & Redux로 시작하는 웹 프로그래밍

profile
이것저것 합니다.

0개의 댓글