Redux 살펴보기
1. redux 왜 쓰나요

1. redux 왜 쓰나요

React는 '일방향 데이터 흐름'을 가집니다.

이미지 : Redux가 필요하다는 것을 언제 알 수 있나요?

값을 전달해야하는 컴포넌트가 부모-자식 관계가 아니라면..

만들긴 했는데 기능이 점점 많아진다면..

redux를 사용해보자

1. redux 왜 쓰나요

이미지 : https://if.kakao.com/2019/program

1. redux 왜 쓰나요

google trend redux, mobx

2. react 구현 예제

2. react 구현 예제

Todos.js


    import React, { Component } from 'react';

    import Todo from './Todo';
    import AddTodo from './AddTodo';

    class Todos extends Component {

        //Component state with default values
        state = {
            addTodoValue: "",
            todos: [
                {
                    id: 1,
                    value: "todo 1",
                    isDone: false
                },
                {
                    id: 2,
                    value: "todo 2",
                    isDone: true
                },
                {
                    id: 3,
                    value: "todo 3",
                    isDone: false
                }
            ]
        }

        //Local helper method to get date
        getTime() {
            let d = new Date();
            var n = d.getTime();
            return n;
        }

        //method called from Todo component
        handleDelete = todo => {
            const todos = this.state.todos.filter((t) => {
                return t.id !== todo
            });
            this.setState({ todos });
        }

        handleDone = todo => {
            const todos = [...this.state.todos];
            todos.map((t) => {
                if (t.id === todo.id) {
                    t.isDone = !t.isDone;
                }
                return t;
            });
            this.setState({todos});
        }

        //method called from AddTodo component
        addNewTodo = value => {
            if (value) {
                const todos = [...this.state.todos];
                todos.push(
                    {
                        id: this.getTime(),
                        value: value,
                        isDone: false
                    }
                );
                this.setState({ addTodoValue: "", todos })
            } else {
                console.log("Please Add Todo Text");
            }
        }

        render() {
            return (
              <table className="table">
                <tbody>
                    {this.state.todos.map((todo, index) => (
                        <tr key={todo.id}>
                            <Todo index={index+1} todo={todo} fooDelete={this.handleDelete} fooDoneDone={this.handleDone} />
                        </tr>
                    ))}
                    <tr>
                        <td colSpan="4" className="text-center">
                            <AddTodo fooAddTodo={this.addNewTodo} addTodoValue={this.state.addTodoValue} />
                        </td>
                    </tr>
                </tbody>
            </table>
            );
        }
    }

    export default Todos;
  

2. react 구현 예제

Todo.js


    import React, { Component } from 'react';

    class Todo extends Component {

        render() {
            return (
                <React.Fragment >
                    <td style= className="text-center">
                        {this.props.index}
                    </td>
                    <td style= className="text-center">
                        <input type="checkbox" defaultChecked={this.props.todo.isDone}  onChange={() => this.props.fooDoneDone(this.props.todo)} />
                    </td>
                    <td>
                        {
                            this.renderTodo()
                        }
                    </td>
                    <td style= className="text-center">
                        <button onClick={() => this.props.fooDelete(this.props.todo.id)} className="btn btn-danger btn-sm">Delete</button>
                    </td>
                </React.Fragment>
            );
        }

        renderTodo(){
            if(this.props.todo.isDone)
            return <s>{this.props.todo.value}</s>;
            else
            return this.props.todo.value;
        }

    }

    export default Todo;
  

2. react 구현 예제

AddTodo.js


import React, { Component } from 'react';

class AddTodo extends Component {
    state = {
        defaultValue: "",
        value: this.props.addTodoValue
    }

    handleChange = (e) => {
        //Updating local component state
        this.setState({
            value: e.target.value
        });
    }

    handleKeyPress = (e) => {
        if (e.key === "Enter") {
            this.addTodo();
        }
    };

    clearInput = () => {
        //Clear existing value in input
        document.getElementById("todoValue").value = "";
        
        //Updating local component state
        this.setState({value:""});
    }

    addTodo = () => {
        //Call method reference in Todos component using props
        this.props.fooAddTodo(this.state.value);
        this.clearInput();
    }

    render() {
        return (
            <div className="input-group mb-3">
                <input type="text" className="form-control" id="todoValue" placeholder="ToDo" onChange={this.handleChange} onKeyPress={this.handleKeyPress}/>
                <div className="input-group-append">
                    <button onClick={this.addTodo} className="btn btn-outline-secondary" type="button" id="button-addon2">Add New ToDo</button>
                </div>
            </div>
        );
    }
}

export default AddTodo;
  

2. react 구현 예제

2. react 구현 예제

2. react 구현 예제

$ git checkout add_component

2. react 구현 예제

Todo.js


import React, { Component } from 'react';

import MixTodo from './MixTodo';
import NumberTodo from './NumberTodo';
import TextTodo from './TextTodo';

class Todo extends Component {

    render() {
        return (
            <React.Fragment >
                <td style= className="text-center">
                    {this.props.index}
                </td>
                <td style= className="text-center">
                    <input type="checkbox" defaultChecked={this.props.todo.isDone}  onChange={() => this.props.fooDoneDone(this.props.todo)} />
                </td>
                <td>
                    {
                        this.renderTodo()
                    }
                </td>
                <td style= className="text-center">
                    <button onClick={() => this.props.fooDelete(this.props.todo.id)} className="btn btn-danger btn-sm">Delete</button>
                </td>
            </React.Fragment>
        );
    }

    renderTodo(){
        if(this.props.todo.isDone){
          return <s>{this.props.todo.value}</s>;
        }else{
          let str = this.props.todo.value;
          let pattern_num = /[0-9]/;	// 숫자 
          let pattern_eng = /[a-zA-Z]/;	// 문자 
          if( pattern_num.test(str) && pattern_eng.test(str)){
            return <MixTodo value={str}/>
          } else if( pattern_num.test(str)){
            return <NumberTodo value={str}/>
          } else {
            return <TextTodo value={str}/>
          }
        }
    }

}

export default Todo;
  

2. react 구현 예제

MixTodo.js


import React from 'react';

const todoStyle = {
  color: 'red',
}

const MixTodo = ({
  value
}) => {
  return (
    <div style={todoStyle}>
      { value }
    </div>
  )
};

export default MixTodo;
  

2. react 구현 예제

NumberTodo.js


    import React from 'react';

    const todoStyle = {
      color: 'blue',
    }
    
    const NumberTodo = ({
      value
    }) => {
      return (
        <div style={todoStyle}>
          { value }
        </div>
      )
    };
    
    export default NumberTodo;
  

2. react 구현 예제

TextTodo.js


    import React from 'react';

    const todoStyle = {
      color: 'aqua',
    }
    
    const TextTodo = ({
      value
    }) => {
      return (
        <div style={todoStyle}>
          { value }
        </div>
      )
    };
    
    export default TextTodo;
  

2. react 구현 예제

새로운 요구사항

2. react 구현 예제

2. react 구현 예제

2. react 구현 예제

$ git checkout lift

2. react 구현 예제

Todos.js


    import React, { Component } from 'react';

    import Todo from './Todo';
    import AddTodo from './AddTodo';
    
    class Todos extends Component {
    
        //Component state with default values
        state = {
            addTodoValue: "",
            todos: [
                {
                    id: 1,
                    value: "todo 1",
                    isDone: false
                },
                {
                    id: 2,
                    value: "todo 2",
                    isDone: true
                },
                {
                    id: 3,
                    value: "todo 3",
                    isDone: false
                }
            ],
            isBackgroundColorChange: false
        }
    
        //Local helper method to get date
        getTime() {
            let d = new Date();
            var n = d.getTime();
            return n;
        }
    
        //method called from Todo component
        handleDelete = todo => {
            const todos = this.state.todos.filter((t) => {
                return t.id !== todo
            });
            this.setState({ todos });
            this.defaultBackgroundColor();
        }
    
        handleDone = todo => {
            const todos = [...this.state.todos];
            todos.map((t) => {
                if (t.id === todo.id) {
                    t.isDone = !t.isDone;
                }
                return t;
            });
            this.setState({todos});
            this.defaultBackgroundColor();
        }
    
        //method called from AddTodo component
        addNewTodo = value => {
            if (value) {
                const todos = [...this.state.todos];
                todos.push(
                    {
                        id: this.getTime(),
                        value: value,
                        isDone: false
                    }
                );
                this.setState({ addTodoValue: "", todos })
                this.defaultBackgroundColor();
            } else {
                console.log("Please Add Todo Text");
            }
        }
    
        handleChangeBockground = (value) => {
          let pattern_num = /[0-9]/;	// 숫자 
          let pattern_eng = /[a-zA-Z]/;	// 문자 
          if( !pattern_num.test(value) && pattern_eng.test(value)){
            this.setState({isBackgroundColorChange: true});
          }else {
            this.setState({isBackgroundColorChange: false});
          }
        }
    
        defaultBackgroundColor = () => {
          this.setState({isBackgroundColorChange: false});
        }
    
        render() {
            return (
                <table className="table">
                    <tbody>
                        {this.state.todos.map((todo, index) => (
                            <tr key={todo.id}>
                                <Todo isBackgroundColorChange={this.state.isBackgroundColorChange} index={index+1} todo={todo} fooDelete={this.handleDelete} fooDoneDone={this.handleDone} />
                            </tr>
                        ))}
                        <tr>
                            <td colSpan="4" className="text-center">
                                <AddTodo handleChangeBockground={this.handleChangeBockground} fooAddTodo={this.addNewTodo} addTodoValue={this.state.addTodoValue} />
                            </td>
                        </tr>
                    </tbody>
                </table>
            );
        }
    }
    
    export default Todos;
  

2. react 구현 예제

AddTodo.js


    import React, { Component } from 'react';

    class AddTodo extends Component {
        state = {
            defaultValue: "",
            value: this.props.addTodoValue
        }
    
        handleChange = (e) => {
            const { handleChangeBockground } = this.props;
            handleChangeBockground(e.target.value);
            //Updating local component state
            this.setState({
                value: e.target.value
            });
        }
    
        handleKeyPress = (e) => {
            if (e.key === "Enter") {
                this.addTodo();
            }
        };
    
        clearInput = () => {
            //Clear existing value in input
            document.getElementById("todoValue").value = "";
            
            //Updating local component state
            this.setState({value:""});
        }
    
        addTodo = () => {
            //Call method reference in Todos component using props
            this.props.fooAddTodo(this.state.value);
            this.clearInput();
        }
    
        render() {
            return (
                <div className="input-group mb-3">
                    <input type="text" className="form-control" id="todoValue" placeholder="ToDo" onChange={this.handleChange} onKeyPress={this.handleKeyPress}/>
                    <div className="input-group-append">
                        <button onClick={this.addTodo} className="btn btn-outline-secondary" type="button" id="button-addon2">Add New ToDo</button>
                    </div>
                </div>
            );
        }
    }
    
    export default AddTodo;
  

2. react 구현 예제

Todo.js


    import React, { Component } from 'react';

    import MixTodo from './MixTodo';
    import NumberTodo from './NumberTodo';
    import TextTodo from './TextTodo';
    
    class Todo extends Component {
    
        render() {
            return (
                <React.Fragment >
                    <td style= className="text-center">
                        {this.props.index}
                    </td>
                    <td style= className="text-center">
                        <input type="checkbox" defaultChecked={this.props.todo.isDone}  onChange={() => this.props.fooDoneDone(this.props.todo)} />
                    </td>
                    <td>
                        {
                            this.renderTodo()
                        }
                    </td>
                    <td style= className="text-center">
                        <button onClick={() => this.props.fooDelete(this.props.todo.id)} className="btn btn-danger btn-sm">Delete</button>
                    </td>
                </React.Fragment>
            );
        }
    
        renderTodo(){
            if(this.props.todo.isDone){
              return <s>{this.props.todo.value}</s>;
            }else{
              let str = this.props.todo.value;
              let pattern_num = /[0-9]/;	// 숫자 
              let pattern_eng = /[a-zA-Z]/;	// 문자 
              if( pattern_num.test(str) && pattern_eng.test(str)){
                return <MixTodo value={str}/>
              } else if( pattern_num.test(str)){
                return <NumberTodo value={str}/>
              } else {
                return <TextTodo isBackgroundColorChange={this.props.isBackgroundColorChange} value={str} />
              }
            }
        }
    
    }
    
    export default Todo;
  

2. react 구현 예제

TextTodos.js


    import React, {useState, useEffect} from 'react';

    // const todoStyle = {
    //   color: 'aqua',
    // }
    
    const TextTodo = ({
      value
      ,isBackgroundColorChange
    }) => {
      const [todoStyle, setTodoStyle] = useState({});
    
      useEffect(() => {
        let styled = {
          color: 'aqua'
        }
        if(isBackgroundColorChange){
          styled.backgroundColor = 'gray';
          styled.color = 'white';
        }
        setTodoStyle(styled);
      }, [value, isBackgroundColorChange]);
      return (
        <div style={todoStyle}>
          { value }
        </div>
      )
    };
    
    export default TextTodo;
  
3. redux

3. redux

3. redux

3. redux

hell


    const Component = ({
      id,
      title,
      name,
      isLoading,
      isRefresh,
      isExpanded,
      isPagination,
      isVertical,
      isSmall,
      label,
      size,
      renderIcon,
      handleClick,
      handleSize,
      ...
      ...
      ...
      ...others
    }) => {

3. redux

3. redux

4. redux ducks pattern

4. redux ducks pattern

Ducks: Redux Reducer Bundles

저는 Redux 어플리케이션을 구축하는 과정에서, 한 번에 기능 한 부분을 만들 때,

각각의 use case에 따라 {actionTypes, actions, reducer} 한 벌을 계속 추가해야 된다는 것을 깨달았습니다.

저는 각각을 별도의 파일 심지어 폴더로 나누어두었지만, 95%의 경우에는 reducer/actions 한 쌍만이 서로 연관된 동작을 필요로 합니다.

제가 봤을 땐, 이러한 조각들이 포함된 하나의 독립된 모듈로 묶는 것이 더 의미가 있고, 라이브러리로 패키징하기에도 쉽습니다.

4. redux ducks pattern

액션 타입 정의하기

액션 생성 함수 만들기

초기 상태 정의하기

리듀서 정의하기

5. redux 구현 예제 ( ducks pattern )

5. redux 구현 예제 ( ducks pattern )

base.js


      import { createAction, handleActions } from 'redux-actions';
      import { fromJS } from 'immutable';
      
      //액션 타입 정의하기
      const SHOW_BACKGROUND_COLOR = 'base/SHOW_BACKGROUND_COLOR';
      const HIDE_BACKGROUND_COLOR = 'base/HIDE_BACKGROUND_COLOR';
      
      //액션 생성 함수 만들기
      export const showBackgroundColor = createAction(SHOW_BACKGROUND_COLOR);
      export const hideBackgroundColor = createAction(HIDE_BACKGROUND_COLOR);
      
      //초기상태 정의하기
      const initialState = fromJS({
        isBackgroundColorChange: false,
      });
      
      //리듀서 정의하기
      export default handleActions({
        [SHOW_BACKGROUND_COLOR]: (state, action) => state
          .set('isBackgroundColorChange',true),
        [HIDE_BACKGROUND_COLOR]: (state, action) => state
          .set('isBackgroundColorChange',false)
      }, initialState);
    

5. redux 구현 예제 ( ducks pattern )

index.js


      import React from 'react';
      import { render } from 'react-dom';
      import './index.css';
      import App from './App';
      import 'bootstrap/dist/css/bootstrap.css';
      
      import { createStore, combineReducers } from 'redux';
      import { Provider } from 'react-redux';
      import * as modules from './store/modules';
      
      const reducers = combineReducers(modules);
      const store = createStore(reducers);
      render(
        <Provider store={store}>
          <App />
        </Provider>,
        document.getElementById('root')
      );
    

5. redux 구현 예제 ( ducks pattern )

AddTodo.js


      import React, { Component } from 'react';
      import { connect } from 'react-redux';
      import { bindActionCreators } from 'redux';
      import * as baseActions from '../store/modules/base';
      
      class AddTodo extends Component {
          state = {
              defaultValue: "",
              value: this.props.addTodoValue
          }
          
          handleChangeBackground = (value) =>{
            const { BaseActions } = this.props;
              let pattern_num = /[0-9]/;	// 숫자 
              let pattern_eng = /[a-zA-Z]/;	// 문자 
              if( !pattern_num.test(value) && pattern_eng.test(value)){
                BaseActions.showBackgroundColor();
              }else {
                BaseActions.hideBackgroundColor();
              }
          }
      
          handleChange = (e) => {
              this.handleChangeBackground(e.target.value);
              //Updating local component state
              this.setState({
                  value: e.target.value
              });
          }
      
          handleKeyPress = (e) => {
              if (e.key === "Enter") {
                  this.addTodo();
              }
          };
      
          clearInput = () => {
              //Clear existing value in input
              document.getElementById("todoValue").value = "";
              
              //Updating local component state
              this.setState({value:""});
              this.handleChangeBackground("");
          }
      
          addTodo = () => {
              //Call method reference in Todos component using props
              this.props.fooAddTodo(this.state.value);
              this.clearInput();
          }
      
          render() {
              return (
                  <div className="input-group mb-3">
                      <input type="text" className="form-control" id="todoValue" placeholder="ToDo" onChange={this.handleChange} onKeyPress={this.handleKeyPress}/>
                      <div className="input-group-append">
                          <button onClick={this.addTodo} className="btn btn-outline-secondary" type="button" id="button-addon2">Add New ToDo</button>
                      </div>
                  </div>
              );
          }
      }
      
      export default connect(
        null,
        dispatch => ({
          BaseActions: bindActionCreators(baseActions, dispatch),
        }),
      )(AddTodo);
    

5. redux 구현 예제 ( ducks pattern )

TextTodo.js


      import React, {useState, useEffect} from 'react';
      import { connect } from 'react-redux';
      
      const TextTodo = ({
        value
        ,isBackgroundColorChange
      }) => {
        const [todoStyle, setTodoStyle] = useState({});
      
        useEffect(() => {
          let styled = {
            color: 'aqua'
          }
          if(isBackgroundColorChange){
            styled.backgroundColor = 'gray';
            styled.color = 'white';
          }
          setTodoStyle(styled);
        }, [value, isBackgroundColorChange]);
        return (
          <div style={todoStyle}>
            { value }
          </div>
        )
      };
      
      export default connect(
        state => ({
          isBackgroundColorChange: state.base.get('isBackgroundColorChange'),
        }),
        null,
      )(TextTodo);
      
    

5. redux 구현 예제 ( ducks pattern )

TextTodo.js


      import React, {useState, useEffect} from 'react';
      import { useSelector } from 'react-redux'
      
      const TextTodo = ({
        value
      }) => {
        const [todoStyle, setTodoStyle] = useState({});
        const isBackgroundColorChange = useSelector(state => state.base.get('isBackgroundColorChange'))
      
        useEffect(() => {
          let styled = {
            color: 'aqua'
          }
          if(isBackgroundColorChange){
            styled.backgroundColor = 'gray';
            styled.color = 'white';
          }
          setTodoStyle(styled);
        }, [value, isBackgroundColorChange]);
        return (
          <div style={todoStyle}>
            { value }
          </div>
        )
      };
      
      export default TextTodo;
    

5. redux 구현 예제 ( ducks pattern )

Todo.js / Todos.js

는 원상복구 시킵니다.


기능이 정상적으로 작동하는지 확인

5. redux 구현 예제 ( ducks pattern )

immutable

https://immutable-js.github.io/immutable-js/

      // base.js
          import { fromJS } from 'immutable';
          
          //초기상태 정의하기
          const initialState = fromJS({
            isBackgroundColorChange: false,
          });
          
          //리듀서 정의하기
          export default handleActions({
            [SHOW_BACKGROUND_COLOR]: (state, action) => state
              .set('isBackgroundColorChange',true),
      
      // TextTodo.js
          const isBackgroundColorChange = useSelector(state => state.base.get('isBackgroundColorChange'))
    

5. redux 구현 예제 ( ducks pattern )

불변성


      var obj = {
        name: 'foo',
      };
      
      var newObj = obj;
      console.log(newObj.name); // foo

      obj.name = 'bar';
      
      console.log(obj.name); // bar
      console.log(newObj.name);  // bar
      console.log(obj === newObj); // true

      // 불변성
      var object = {
        foo: 'hello',
        bar: 'world'
      };
      var differentObject = {
        ...object,
        baz: 'bye'
      };
      console.log(differentObject === object); // false

    

5. redux 구현 예제 ( ducks pattern )

React에서는 왜 상태값이 불변성을 유지해야 할까요

의도하지 않은 특정 객체가 변경되면 참조하고 있던 객체에서도 변경이 일어난다.

이 상태를 유지할 경우 side-effect가 발생할 확률과 프로그램의 복잡도가 높아진다.

변경이 일어난 객체의 프로퍼티만 비교함으로써 React에서 최적화가 가능하다.

6. redux 실습

6. redux 실습

요구사항

App.js 파일 하단에 새로운 컴포넌트가 생성되었습니다 ( ToastContainer )

현재 toast컴포넌트는 value 값이 들어오면 2초간 toast 팝업이 노출되고 자동으로 사라집니다.

AddTodo에서 새로운 Todo 등록시 toast 팝업에 메세지를 입력된 값으로 변경하여 노출시켜주세요.

(기존 toast에 들어오는 value 값은 무시해주세요.)


새로운 redux 모듈을 만들어 주세요 (base는 사용하지 않습니다.)

새로운 모듈 이름은 'test.js' 입니다.

7. redux 어디에 쓰나요

7. redux 어디에 쓰나요

7. redux 어디에 쓰나요

7. redux 어디에 쓰나요

redux 로 전부 쓴다면 어떻게 될까

7. redux 어디에 쓰나요

추가적으로 반드시 알아야 할 것