본문 바로가기

Programming/국비학원

220808 - 리액트 - redux, es6

redux

참고:

https://itprogramming119.tistory.com/entry/React-react-redux-%EC%98%88%EC%A0%9C

 

[React] react-redux 정리 및 예제

[Redux란?] 1. Redux 등장 배경 MVC 패턴 형식으로 state가 변화되면 Model, View, Controller에 이벤트가 발생하고 값이 변화하는 구조였습니다. 즉, 양방향 데이터 흐름이었죠. 양방향 데이터 흐름은 복잡하

itprogramming119.tistory.com

https://kyun2da.dev/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/Redux-%EC%A0%95%EB%A6%AC/

 

Redux 정리

Redux란? 리덕스는 리액트에서 가장 많이 사용되는 상태 관리 라이브러리중 하나이다. 리덕스를 사용하면 컴포넌트의 상태 업데이트 관련 로직을 다른 파일로 분리시켜서 효율적으로 관리할 수

kyun2da.dev

https://velog.io/@luck2901/Redux-Concept

 

Redux Concept

리액트에서 상태를 더 효율적으로 관리하는 데 사용하는 상태 관리 라이브러리.지금까지의 상태 관리의 로직은 상태 관리를 쉽게 하기 위해 모두 App 컴포넌트에서 진행해왔음.App에서 모든 로직

velog.io

 

 

리액트에서 많이 사용되는 상태 관리 라이브러리

상태 관리의 로직을 컴포넌트 밖에서 처리하는 것

최근에는 redux toolkit 으로도 사용 가능

 

 

  • 역할

MVC 패턴의 복잡한 양방향 데이터 흐름을 개선하기 위해 단방향 데이터 흐름 적용 (기존에는 App 컴포넌트의 state를 업데이트하면 App 컴포넌트가 리렌더링되고, 하위 컴포넌트도 모두 리렌더링됨)

 

View는 데이터 변경 없이 Action을 Dispatcher에 전달(Dispatcher에서 데이터 값 변경됨)

-> store 통해 view에 변경된 값 전달0

 

=> 컴포넌트끼리 상태 공유해야 할때 여러 컴포넌트 거치지 않고 쉽게 업데이트 가능

 

 

  • 기본 개념
  • Action : 상태변화 일으킬 때 참조하는 객체

{
  type:'INSERT_TODO',
  todo: {
    id:1,
    text: '리액트 배우기',
    done:false
  }
}

 

 

  • Action Creator (액션 생성 함수) : 액션 객체 만들어주는 함수

1. 액션 타입을 상수로 정의

const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

2. 액션 생성 함수

const increment =(diff) =>({
  type:INCREMENT,
  diff:diff
});
const decrement =(diff) =>({
  type:DECREMENT,
  diff:diff
});

3. 함수 호출

increment(1)
decrement(1)

4. dispatch로 액션 전달.

store.dispatch(increment(1));

 

 

  • Reducer : 상태에 변화 일으키는 함수

(현재상태, 액션객체) 2개 파라미터 받음

리듀서가 초기에 사용할 초기 상태 initialState 값 먼저 설정

-> switch문을 사용하여 action.type에 따라 새로운 상태를 만들어서 반환

 

const initialState = {
  number:0,
  foo:'bar',
  baz:'qux'
};
function counter(state = initialState, action){
  switch(action.type){
    case INCREMENT:
      return Object.assign({}, state,
            { number: state.number + action.diff});
    case DECREMENT:
      return Object.assign({}, state,
            { number: state.number - action.diff});
    default:
      return state;
  }
}

 

※ return문에 spread 연산자 사용 가능

return{
  ...state,
  number: state.number + action.diff}

 

 

  • Store : 애플리케이션의 상태 값들을 내장

액션, 리듀서 존재 시 스토어 생성 가능

const {createStore} = Redux;
const store = createStore(counter);

 

import {createStore} from 'redux';

 

 

  • Dispatch : 액션을 스토어에 전달
// 파라미터에는 리듀서 함수를 넣어주어야 함
const store = createStore(reducer);

button.onclick = () => { 
   // dispatch 함수를 사용하여 액션을 스토어에게 전달
   store.dispatch(plus(1));
}

 

 

  • Subscribe : 리덕스 스토어의 상태가 바뀔 때마다 특정 함수를 실행

unsubscribe : 구독 취소

 

 

  • Selector :  react-redux에서 상태 값을 가져올 때 사용 (cf. vanilla.js의 리덕스에서는 getState 사용)

 

 

  • 3가지 규칙

1. 1 애플리케이션 1 스토어

2. 리덕스 상태는 읽기 전용 -> 상태 업데이트 시 기존 객체 건드리지 않고 새로운 객체 생성함

3. Reducer 함수는 순수 함수 (이전상태,액션객체를 파라미터로 받음 / 파라미터 외 값에 의존 X / 이전상태는 건드리지 않고, 변화를 준 새로운 상태의 객체 만들어 반환 / 똑같은 파라미터-> 똑같은 반환값 )

 

 

 

redux 사용하지 않는 컴포넌트 흐름

Addnum -> Addnumroot -> App -> Displaynumroot -> Displaynum 순으로 매개변수 전달됨
=> 과정 복잡

 

  •  
  • App

import { Component } from 'react';
import './App.css';
import AddNumberRoot from './Components/AddNumberRoot';
import DisplayNumberRoot from './Components/DisplayNumberRoot';

function App() {
  return (
    <div className="App">
      <h1>Root</h1>
      <AddNumberRoot></AddNumberRoot>
      <DisplayNumberRoot></DisplayNumberRoot>
    </div>
  );
}

export default App;

 

 

  • AddNumber

import { Component } from "react";

class AddNumber extends Component{
    render(){
      return(
        <div>
          <h1>숫자 더하기</h1>
          <input type='button' value='+'></input>
          <input type='text' value='0'></input>
        </div>
      );
    }
}

export default AddNumber;

 

 

  • AddNumberRoot

import { Component } from "react";
import AddNumber from "./AddNumber";

class AddNumberRoot extends Component{
    render(){
      return(
        <div>
          <h1>숫자 더하기 Root</h1>
          <AddNumber></AddNumber>
        </div>
      );
    }
}

export default AddNumberRoot;

 

 

  • DisplayNumber 

import { Component } from "react";

class DisplayNumber extends Component{
    render(){
      return(
        <div>
          <h1>결과 보기</h1>
          <input type='text' value='0' readOnly></input> {/*readonly: 수정 불가*/}
        </div>
      );
    }
}

export default DisplayNumber;

 

 

  • DisplayNumberRoot

import { Component } from "react";
import DisplayNumber from "./DisplayNumber";

class DisplayNumberRoot extends Component{
    render(){
      return(
        <div>
          <h1>결과 보기</h1>
          <DisplayNumber></DisplayNumber>
        </div>
      );
    }
}

export default DisplayNumberRoot;

 

 

  • number 속성 부여 (display)
  • App

import { Component } from 'react';
import './App.css';
import AddNumberRoot from './Components/AddNumberRoot';
import DisplayNumberRoot from './Components/DisplayNumberRoot';

class App extends Component {

  state={number:0}

  render(){
    return (
      <div className="App">
        <h1>Root</h1>
        <AddNumberRoot></AddNumberRoot>
        <DisplayNumberRoot number={this.state.number}></DisplayNumberRoot>
      </div>
    );
  }
}

export default App;

 

 

  • DisplayNumberRoot

import { Component } from "react";
import DisplayNumber from "./DisplayNumber";

class DisplayNumberRoot extends Component{
    render(){
      return(
        <div>
          <h1>결과 보기</h1>
          <DisplayNumber number={this.props.number}></DisplayNumber>
        </div>
      );
    }
}

export default DisplayNumberRoot;

 

 

  • DisplayNumber

import { Component } from "react";

class DisplayNumber extends Component{
    render(){
      return(
        <div>
          <h1>결과 보기</h1>
          <input type='text' value={this.props.number} readOnly></input> {/*readonly: 수정 불가*/}
        </div>
      );
    }
}

export default DisplayNumber;

 

 

  • +버튼 계산 기능 (add)
  • App

import { Component } from 'react';
import './App.css';
import AddNumberRoot from './Components/AddNumberRoot';
import DisplayNumberRoot from './Components/DisplayNumberRoot';

class App extends Component {

  state={number:0}

  render(){
    return (
      <div className="App">
        <h1>Root</h1>
        <AddNumberRoot onClick={(size)=>{this.setState({number:this.state.number+size});}}></AddNumberRoot>
        <DisplayNumberRoot number={this.state.number}></DisplayNumberRoot>
      </div>
    );
  }
}

export default App;

 

 

  • AddNumberRoot

import { Component } from "react";
import AddNumber from "./AddNumber";

class AddNumberRoot extends Component{
    render(){
      return(
        <div>
          <h1>숫자 더하기 Root</h1>
          <AddNumber onClick={(size)=>{this.props.onClick(size);}}></AddNumber>
          {/*상위 컴포넌트 props 중 onClick에 size 값 전달*/}
        </div>
      );
    }
}

export default AddNumberRoot;

 

 

  • AddNumber

import { Component } from "react";

class AddNumber extends Component{

  state={size:1}

  render(){
    return(
      <div>
        <h1>숫자 더하기</h1>
        <input type='button' value='+'
        onClick={()=>{this.props.onClick(this.state.size)}}></input> //윗 컴포넌트 onClick 속성에 size 전달
        <input type='text' value={this.state.size}
        onChange={(e)=>{this.setState({size:Number(e.target.value)});}}></input> //더할 숫자 변경
      </div>
    );
  }
}

export default AddNumber;

 

 

  • DisplayNumberRoot

import { Component } from "react";
import DisplayNumber from "./DisplayNumber";

class DisplayNumberRoot extends Component{
    render(){
      return(
        <div>
          <h1>결과 보기</h1>
          <DisplayNumber number={this.props.number}></DisplayNumber>
        </div>
      );
    }
}

export default DisplayNumberRoot;

 

 

  • DisplayNumber

import { Component } from "react";

class DisplayNumber extends Component{
    render(){
      return(
        <div>
          <h1>결과 보기</h1>
          <input type='text' value={this.props.number} readOnly></input> {/*readonly: 수정 불가*/}
        </div>
      );
    }
}

export default DisplayNumber;

 

 

 

redux 라이브러리 활용

터미널: npm install redux

 

+ Redux DevTools 크롬 확장프로그램 설치

 

 

  •  
  • AddNumber

import { Component } from "react";
import store from "../store";

export default class AddNumber extends Component{ 

  state={size:1}

  render(){
    return(
      <div>
        <h1>숫자 더하기</h1>
        <input type='button' value='+'
        onClick={()=>{
          store.dispatch({type:'INCREMENT',size:this.state.size});  //더하기 기능, 더할 숫자를 스토어에 전달
          }}></input>
        <input type='text' value={this.state.size}
        onChange={(e)=>{this.setState({size:Number(e.target.value)});}}></input>
      </div>
    );
  }
}

 

 

  • store.js

import { createStore } from "redux";

export default ((state, action)=>{
    if (state===undefined){
        return{
            number:0
        }
    }

    if (action.type==='INCREMENT'){
        return{...state, number:state.number+action.size} 
        //state 기존값 복제 (2번째 파라미터 반영)
    }
    return state;
}, window._REDUX_DEVTOOLS_EXTENSION__&&window._REDUX_DEVTOOLS_EXTENSION__())
//윈도우의 크롬확장프로그램에 추가

 

 

  • 기능 구현
  • displaynum

import { Component } from "react";
import store from "../store";

export default class DisplayNumber extends Component{
  state={number:store.getState().number};

  constructor(props){
    super(props);
    store.subscribe(()=>{  //state 바뀔 때마다 실행됨
      this.setState({number:store.getState().number});  
    });
  }

    render(){
      return(
        <div>
          <h1>결과 보기</h1>
          <input type='text' value={this.state.number} readOnly></input> {/*readonly: 수정 불가*/}
        </div>
      );
    }
}

 

 

  • app

import { Component } from 'react';
import './App.css';
import AddNumberRoot from './Components/AddNumberRoot';
import DisplayNumberRoot from './Components/DisplayNumberRoot';

class App extends Component {

  state={number:0}

  render(){
    return (
      <div className="App">
        <h1>Root</h1>
        <AddNumberRoot></AddNumberRoot>
        <DisplayNumberRoot></DisplayNumberRoot>
      </div>
    );
  }
}

export default App;

 

 

  • addnumroot => 재사용 컴포넌트로 사용 불가, store에 종속됨

import { Component } from "react";
import AddNumber from "./AddNumber";

class AddNumberRoot extends Component{
    render(){
      return(
        <div>
          <h1>숫자 더하기 Root</h1>
          <AddNumber></AddNumber>
        </div>
      );
    }
}

export default AddNumberRoot;

 

 

  • displaynumroot => 재사용 컴포넌트로 사용 불가, store에 종속됨

import { Component } from "react";
import DisplayNumber from "./DisplayNumber";

class DisplayNumberRoot extends Component{
    render(){
      return(
        <div>
          <h1>결과 보기</h1>
          <DisplayNumber></DisplayNumber>
        </div>
      );
    }
}

export default DisplayNumberRoot;

 

 

 

  • 종속성 제거

=> store 사용하면서 다른 컴포넌트에도 속성 전달할수 있도록 함

 

기존 컴포넌트 포함하는 container 컴포넌트 새로 생성해 해결
=> container 컴포넌트는 store 연결, 기존 컴포넌트는 컴포넌트끼리 연결

 

 

  • store.js

import { createStore } from "redux";

export default ((state, action)=>{
    if (state===undefined){
        return{
            number:0
        }
    }

    if (action.type==='INCREMENT'){
        return{...state, number:state.number+action.size} 
        //state 기존값 복제 (2번째 파라미터 반영)
    }
    return state;
}, window._REDUX_DEVTOOLS_EXTENSION__&&window._REDUX_DEVTOOLS_EXTENSION__())
//윈도우의 크롬확장프로그램에 추가

 

 

  • addnumroot

import { Component } from "react";
import AddNumber from "../containers/AddNumber"; 

class AddNumberRoot extends Component{
    render(){
      return(
        <div>
          <h1>숫자 더하기 Root</h1>
          <AddNumber></AddNumber>
        </div>
      );
    }
}

export default AddNumberRoot;

 

 

  • addnum (container)

import { Component } from "react";
import AddNumber from "../Components/AddNumber";
import store from "../store";

export default class extends Component{ //익명클래스
    render(){
        return <AddNumber onClick={(size)=>{
            store.dispatch({type:'INCREMENT',size:size});
        }}></AddNumber>;
    }
}

 

 

  • addnum

import React, { Component } from "react";

export default class AddNumber extends Component{

  state={size:1}

  render(){
    return(
      <div>
        <h1>숫자 더하기</h1>
        <input type='button' value='+'
        onClick={()=>{
          this.props.onClick(this.state.size); 
        }}></input>
        <input type='text' value={this.state.size}
        onChange={(e)=>{this.setState({size:Number(e.target.value)});}}></input>
      </div>
    );
  }
}

 

 

  • displaynumroot

import { Component } from "react";
import DisplayNumber from "../containers/DisplayNumber";

class DisplayNumberRoot extends Component{
    render(){
      return(
        <div>
          <h1>결과 보기</h1>
          <DisplayNumber></DisplayNumber>
        </div>
      );
    }
}

export default DisplayNumberRoot;

 

 

  • displaynum (container)

import { Component } from "react";
import DisplayNumber from "../Components/DisplayNumber";
import store from "../store";

export default class extends Component{


    constructor(props){
      super(props);
      store.subscribe(()=>{
        this.setState({number:store.getState().number});  //스토어에서 변경된 값 전달받고 반환한 계산값
      });
    }

    render(){
        return(
            <DisplayNumber number={this.state.number}></DisplayNumber>
        );
    }

}

 

 

  • displaynum

import { Component } from "react";

class DisplayNumber extends Component{

    render(){
      return(
        <div>
          <h1>결과 보기</h1>
          <input type='text' value={this.props.number} readOnly></input> //props {/*readonly: 수정 불가*/}
        </div>
      );
    }
}

export default DisplayNumber;

 

 

 

 

ES6
  • 화살표 함수

//es5 스타일
function hello5(){
    console.log('안녕하세요 es5');
}
//es6 스타일
const hello6 =()=>{
    console.log('안녕하세요 es6');
}
hello5();
hello6();


  • 화살표 함수2

//es5 스타일
function hello5(name){
    console.log(name+'안녕하세요 es5');
}
//es6 스타일
const hello6 =(name)=> //상수(수정 불가) => 이름이 바뀌면 안되므로 함수명으로도 자주 쓰임
    console.log(name+'안녕하세요 es6');
}
hello5('김철수');
hello6('홍길동');

 

//

김철수안녕하세요 es5
홍길동안녕하세요 es6


  • 문자열 연결

console.log('안녕하세요','잘가세요');
console.log('안녕하세요'+'잘가세요');

//
안녕하세요 잘가세요
안녕하세요잘가세요

 

  • 변수

//es5=var
var na='홍길동';
var na='김철수';
console.log(na); //김철수

=> 변수 반복 선언 가능 
-> 코드 후반부에 잊어버리고 같은 이름 변수 또 선언하더라도 오류 알아차릴 수 없음

//es6=let
let na='홍길동';
let na='김철수'; //오류 발생 => 변수 반복 선언 불가
console.log(na);


  • const (상수)

const pi=3.14;
pi=4;  //오류 => 새로운 값으로 변경 불가
console.log(pi);


  • bactic(``) 

let na='홍길동';
let age=50;
console.log(`${na}의 나이는 ${age}세입니다.`); //홍길동의 나이는 50세입니다.

let hello='안녕하세요';
let statement=`${hello} 내 이름은 ${na}입니다`;
console.log(statement); //안녕하세요 내 이름은 홍길동입니다

 

=> 연결 연산자 필요 없음

 

  • 포맷팅 문자

//Formatting 해서 출력하기
let na='김철수';
console.log('이름 : %s', na);  //이름 : 김철수

let age=22;
console.log('나이 : %d',age);  //나이 : 22
console.log('pi : %d', 3.14);  //pi : 3.14
console.log('수학:%d 영어:%d',78,85);  //수학:78 영어:85
console.log('내 이름은 %s이고 나이는 %d세입니다',na,age); //내 이름은 김철수이고 나이는 22세입니다

let height=175;
let weight=70;
console.log('내 몸무게는 %dkg이고 키는 %dcm입니다',weight,height); //내 몸무게는 70kg이고 키는 175cm입니다

console.log('국어:%d 영어:%d 수학:%d',80,79);  //국어:80 영어:79 수학:%d

=>%s는 문자뿐만 아니라 숫자에도 적용 가능