Redux : 리덕스 프로젝트 연습

이영광·2021년 8월 21일
0

리액트

목록 보기
7/9
post-thumbnail

ui준비하기

리액트 프로젝트에서 리덕스 를 사용할때
1.프레젠테이셔녈 컴포넌트 와
2.컨테이너 컴포넌트
로 나눠서 관리를 하는데 프레젠테이셔널 콤포는트는 프롭스로 받아와서 UI를 보여주는 형태로만 관리하고

컨테이너 컴포넌트는 Redux로부터 상태를 받아오거나 Redux store의 action을 dispatch하기도 한다

이런식의 관리는 보통의 관례로서 무조건은 아니지만 좀더 효율적이다고 볼수있다

카운터컴포넌트 만들기

import React from 'react';

const Counter = ({number,onIncrease,onDecrease}) => {
    return (
        <div>
            <h1>{number}</h1>
            <div>
                <button onClick={onIncrease}>+1</button>
                <button onClick={onDecrease}>-1</button>
            </div>
            
        </div>
    );
};

export default Counter;

const App = () => {
  return (
    <div>
      <Counter number={0}/>
      <hr/>
      <Todos/>
    </div>

  );
};
앱에 삽입

할일 목록 컴포넌트 만들기

import React from 'react';


const TodoItem = ({todo,onToggle,onRemove}) => {
    return <div>
    <inpu type = 'checkbox'></input>
    <spa>예제 텍스트</span>
    <butto>삭제</button>
    </div>
}



const Todos = ({input,todos,onChangeInput,onInsert,onToggle,onRemove}) => {
     
    const onSubmit =(e) =>{
        e.preventDefault()
    }

    return (
        <div>
            <form onSubmit={onSubmit}>
                <input/>
                <button type="submit">등록</button>
            </form>
            <div>
                <TodoIte/>
                <TodoIte/>
                <TodoIte/>
                <TodoIte/>
                <TodoIte/>

            </div>
         
        </div>
            
    );
};

 Todos;


리덕스 사용하기

리덕스를 사용할때는 액션타입, 액션생성함수,리듀서코드작성 을 해야한다

보통적인 구조로 action,constants,reducers 3개의 디렉토리를 만들고 기능별로 파일을 하나씩 만드는 방식을 사용한다 (리덕스 공식문서에서의 사용법)

사지은 Ducks패턴의 방식인데 액션타입 리듀서 함수 액션함수를 한곳에 물아넣어서 하는 방식을 말한다 기존 리듀서에 불편함을 느끼는 개발자들이 사용

ducks패턴으로 코드작성

counter모듈작성

1.액션 타입정의하기
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

2.액션 생성함수 만들기



const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

export const increase=() =>({ type:INCREASE})
export const decrease=() =>({type:DECREASE})

const initialState ={
    number:0
}

3.리듀서 만들기

const counter=(state=initialState,action)=>{

    switch(action.type){
        case INCRESE:
            return {
                number:state.number+1
            }
        case DEREASE:
            return {
                number:state.num-1
            }
            default: return state
    }

}

expo default counter

//익스포트는 여러개 내보낼수있고
// 익스포트 디폴트는 단한개만 내보낼수있다

4액션타입 정의하기

todos 모듈

모듈 만들기

const CHANGE_INPUT = 'todos/CHANGE_INPUT';//인풋변경
const INSERT = 'todos/INSERT';//새로 todo등록
const TOGGLE = 'todos/TOGGLE';//todo 체크변경
const REMOVE = 'todos/REMOVE';//todo 삭제

액션 함수만들기


export const changeInput = (input) =>({
    type: CHANGE_INPUT
    input
})

let id = 3;

export const insert = (text) =>({
    type:INSERT
    todo:{
        id:id++
        text,
        done:false
    }
})

export const toggle = id =>({
    type:TOGGLE
    id
})

export const remove = () =>({
    type:REMOVE
    id
})

리듀서 함수만들기

function todos(state = initialState,action){
    switch(action.type){
        case CHANGE_INPUT:
            return {
                ...state,
                input:action.input
            }
            case INSERT:
                return{
                    ...state,
        todos:state.todos.concat(action.todo)
                }
                case TOGGLE:
                    return {
                        ...state,
                        todos:state.todos.map((todo)=>todo.id === action.id?{...todo,done: !todo.done}:todo)
                    }
                    case REMOVE:
                        return {
                            ...state,
                            todos: state.todos.filter((todo)=>todo.id !== action.id)
                        }
                        default:
                            return state;
    }
}

루트리듀서 만들기

const rootReducer  = combineReducers({
  counter,
  todos,
});

export default rootReducer

리덕스적용 in React

스토어 만들기

index.js보통 리택트앱을 깔면 초기상태로 있는 파일인데
여기서 스토어를 만들고 리덕스 적용이 이루어진다

const store = createStore(rootReducer)


ReactDOM.render(
  <Provider store={store}>
    <App />
    </Provider>,
  document.getElementById('root')
);
리액트 리덕스에서 제공하는 Provider로 감싸주고 여기에 프롭스로 store전달

Redux DevTools 적용

Redux DevTools는 리덕스 개발자 도구 이다 크롬에서 다운가능
설치도 가능
yarn add redux-devtools-extension 으로 설치

컨테이너 컴포넌트 만들기

컴포넌트 리덕스 스토어에 잡근해 상태를 받아오고 액션을 디스패치해줘야한다
리덕스 스토어와 연동된 컴포는트를 컨테이너 컴포넌트라고 부른다

CounterContainer 만들기

const CounterContainer = () => {
    return (
        <div>
            <Counter/>
        </div>
    );
};

이컴포넌트와 리덕스 연결은 connect(react-redux제공)

connect(mapStatetoProps,mapDispatchToProps)(연동할 컴포넌트)

mapStatetoProps = 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수

mapDispatchToProps = 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위한함수

connect 호출하면 또 다른 함수 반환 반환된 함수에 컴포넌트를 파라미터로 넣어주면 리덕스와 연동된 컴포넌트 만들어짐

 const makeContainer = connect(mapStateToProps,mapDispatchToProps)
makeContainer(taget component)
_

connect 사용

const CounterContainer = ({number, increase,decrease}) => {
    return (
        <div>
            <Counter number={number} onIncrease={increase} onDecrease={decrease}/>
        </div>
    );
};

const mapStateToProps = state = ({
    number : state.counter.number,
})

const mapDispatchToProps = dispatch =>({
    increase:() =>{
        console.log('increase')
    },
    decrease:()=>{
        console.log('decrease')
    }
})

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(CounterContainer)

mapStateToProps와 mapDispatchProps 에서 반환하는 객체 내부의 값들은 컴포넌트의 props로 전달 mapStateToPorps는 state를 파라미터로 받아오고, 이값은 현재 스토어의 상태를 가르킨다 , mapDispatchProps는 dispatch를 파라미터로 받아온다.

dispatch 사용해보기

1

import React from 'react';
import { connect } from 'react-redux'
import Counter from '../component/Counter' //nmber,increase,decrease 프롭
import {increase,decrease} from '../modules/counter'

const CounterContainer = ({number, increase,decrease}) => {
    return (
        <div>
            <Counter number={number} onIncrease={increase} onDecrease={decrease}/>
        </div>
    );
};

const mapStateToProps = state => ({
    number : state.counter.number,
})

const mapDispatchToProps = (dispatch) =>({
    increase:() =>{
        dispatch(increase())
    },
    decrease:()=>{
        dispatch(decrease())
    }
})

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(CounterContainer)

tip : 1번의 형태로 해본적이 없어서
tip : 2번의 형태로 고쳐보왓는데 결국 무명의 함수였다

1 const mapStateToProps = state => ({

number : state.counter.number,

})

2 function(state){

return({
number:state.counter.number,}
)}

컴포넌트에서 액션을 디스패치하기 위해 액션 생성함수를 호출하고 dispatch로 일일이 감싸는 작업은 나주엥 조금 번거러울수잇다 액션 생성 함수가 많아진다면 더 번거러울것이다

이것을 리덕스 에서 제공하는 bindActionCreators 함수를 사용하면 간편하다
2

이런식으로 변경
   dispatch => bindActionCreators(
        {
            increase,
            decrease,
        },
        dispatch,),
    )(CounterContainer)

더간편한방법은
3

export default connect(
    function(state){return({number:state.counter.number,})},
 
   
        {
            increase,
            decrease,
        },
      
    )(CounterContainer)

3단계로 간편의 변화버전^^

TodosContainer 만들기

import React from 'react';
import {connect} from 'react-redux'
import {changeInput, insert, toggle, remove} from '../modules/todos'
import Todos from '../component/Todos'


const TodosContainer = ({input,todos,changeInput,insert,toggle,remove}) => {
    return (
        <Todos
        input ={input}
        todos ={todos}
        onChangeInput={changeInput}
        onInsert={insert}
        onToggle={toggle}
        onRemove={remove}
        />
    );
};

export default connect(
    ({todos})=>({
        input:todos.input,
        todos:todos.todos,
    }),
    {
        changeInput,
        insert,
        toggle,
        remove
        

    },
)(TodosContainer)

Todos 컴포넌트에서 props를 받아 사용


const TodoItem = ({todo,onToggle,onRemove}) => { //사용해보자 프롭스받아온걸
    return <div>
    <input type = 'checkbox'
     onClick = {()=>{onToggle(todo.id)}}
     checked={todo.done}
     readOnly={true}
    />
    <span style={{textDecoration:todo.done? 'line-throught' : 'none'}}>{todo.text}</span>
    <button onClick ={()=>onRemove(todo.id)}>삭제</button>
    </div>
}



const Todos = ({input,todos,onChangeInput,onInsert,onToggle,onRemove}) => {
    
    const onSubmit =(e) =>{
        e.preventDefault()
        onInsert(input)
        onChangeInput('')
    }
    const onChange = ((e)=>{
        onChangeInput(e.target.value)
    })
    

    return (
        <div>
            <form onSubmit={onSubmit}>
                <input value ={input}
                       onChange={onChange}
                />
                <button type="submit">등록</button>
            </form>
            <div>
             {todos.map(todo=>(
             <TodoItem
                key = {todo.id}
                todo = {todo}
                onToggle={onToggle}
                onRemove={onRemove}
             />
             ))}
            </div>
         </div>
            
    );
};

이것을 하면서 마지막에 Cannot read property map of undefined 가나왔는데 이런 실수 는 아직은 자주하지만.. 에러찾으면서 항상 제일 큰문제는 컴포넌트 연결이 잘되지않아서 렌더링이 되지않았던거같다

현재까지 구현!

리덕스 편하게 사용하기

redux-actions

swich 문이 아닌 handleActions 함수를 사용해 각 액션에 업데이트 함수 설정 형식

yarn add redux - actions


 export const increase=() =>({type:INCREASE})-->
 export const increase = createAction(INCREASE)
 
 
 export const decrease=() =>({type:DECREASE})--->
 export const decrease = createAction(DECREASE)

handleActions 함수를 사용해 변경


const counter=(state = initialState,action)=>{

    switch(action.type){
        case INCREASE:
            return {
                number:state.number+1
            }
        case DECREASE:
            return {
                number:state.number-1
            }
            default: return state
    }

}

--->
  const counter = handleAction({
    [INCREASE]:(state,action) =>{return({number:state.number +1})},
    [DECREASE]:(state,action) =>{return({number:state.number -1})},
    initialState,
       
})
리턴을 감싸고있는 중괄호는 뺴도된다 이해하기 위해서 따로작성

Todos.js 도 이런식으로 변경

export const changeInput = (input) =>({
    type: CHANGE_INPUT,
    input
}) --->export const changeInput = createAction(CHANGE_INPUT,input=>input)
function todos(state = initialState,action){
    switch(action.type){
        case CHANGE_INPUT:
            return {
                ...state,
                input:action.input
            }
            case INSERT:
                return{
                    ...state,
                    todos:state.todos.concat(action.todo)
                }
                case TOGGLE:
                    return {
                        ...state,
                        todos:state.todos.map((todo)=>todo.id === action.id?{...todo,done: !todo.done}:todo)
                    }
                    case REMOVE:
                        return {
                            ...state,
                            todos: state.todos.filter((todo)=>todo.id !== action.id)
                        }
                        default:
                            return state;
    }
}

--->
  const todos = handleActions(
    {
        [CHANGE_INPUT]:(state,action) =>({...state,
        input:action.payload}),
        [INSERT]:(state,action)=>({
            ...state,
            todos:state.todos.concat(action.payload)
        }),
        [TOGGLE]:(state,action)=>({
            ...state,
            todos:state.todos.map((todo)=>
            todo.id===action.payload?{...todo,done: !todo.done}:todo
            ),
        }),
        [REMOVE]:(state,action)=>({
            ...state,
            todos:state.todos.filter((todo)=>todo.id !== action.payload)
        }),
        
    },
    initialState,
)

export default todos

이렇게 변경

좀더 가독성좋게


const todos = handleActions(
    {
        [CHANGE_INPUT]:(state,{payload:input}) =>({...state,
        input}),
        [INSERT]:(state,{payload:todo})=>({
            ...state,
            todos:state.todos.concat(todo)
        }),
        [TOGGLE]:(state,{payload:id})=>({
            ...state,
            todos:state.todos.map((todo)=>
            todo.id===id?{...todo,done: !todo.done}:todo
            ),
        }),
        [REMOVE]:(state,{payload:id})=>({
            ...state,
            todos:state.todos.filter((todo)=>todo.id !==id)
        }),
        
    },
    initialState,
)

객체비구조화 할당으로 action값의 payload 이름을 새로설정 하면 페이로드의 값이 무엇인지 더 잘보인다

useSelector

유즈셀렉터 를 사용하면 connect를 사용하지 않고 리덕스 상태를 조회할수있다

컨테이너/카운터컨테이너.js

import React ,{useCallback} from 'react';
import { useDispatch, useSelector } from 'react-redux'

import Counter from '../component/Counter' //nmber,increase,decrease 프롭
import {increase,decrease} from '../modules/counter'

const CounterContainer = () => {
    const number = useSelector(state => state.counter.number)
    const dispatch = useDispatch()
    const onIncrease = useCallback(()=>dispatch(increase()),[dispatch])
    const onDecrease = useCallback(()=>dispatch(decrease()),[dispatch])
   
    return (
        <div>
            <Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease}/>
        </div>
    );
};

useDispatch 는 useCallback 과 항상 같이 사용하기

텍스트

useStore훅은 정말가끔 스토어에 직접접근할때만 사용
하지만 상황은 흔치않음

useActions 훅은 리액트 리덕스에 내장된 상태로 릴리즈될 계획이었으나 리덕스 개발팀에서 꼭 필요하지않다고 판단..제외

하지만 공식문서에서 복사하여사용할수있다

이훅은 여러개의 액션을 사용해야 하는 경우 코드를 깔끔하게 정리할수있다

마지막으로 connect 를 사용하거나 useSeletor와 useDispatch를 사용하는건 사용자 마음이나

connect함수는 props가 바뀌지 않았다면 (리렌더링될때)
리렌더링이 자동으로 방지되어 성능 최적화가 된다

useSeletor는 최적화가 되지 않는다 하지만

컨테이너 컴포넌트에 익스포트할때 React.memo(container)로 감싸주면 최적화가 된다

profile
《REACT》《JAVASCRIPT 》 만지고있어욤

0개의 댓글