본문 바로가기
Programming

[Redux] 리덕스 CRUD !

by 개발자 염상진 2022. 6. 1.

 

 

정적인 HTML 소스 구성

 

 

Redux를 활용해서 CRUD 작업을 진행하기 전, 먼저 정적인 웹 페이지를 구성해준다. 기본적인 Header, Nav, Article 3개의 섹션으로 구분이 되어 있는 간단한 웹페이지다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.js"></script>
    <title>Document</title>
</head>
<body>
    <header>
        <h1>Redux CRUD</h1>
        hello redux !
    </header>    
    <nav>
        <ol>
            <li><a href="Javascript.html">Javascript</a></li>
            <li><a href="CSS.html">CSS</a></li>
            <li><a href="React.html">React</a></li>
        </ol>
    </nav>
    <article>
        <ul>
            <li><a href="/create">Create</a></li>
            <li><input type="button" value="Delete"></li>
        </ul>
    </article>

    <h2>Javasciprt</h2>
    Javascirpt is ...
</body>
</html>

 

 

모듈화(Modularity)

 

위의 코드들은 변경하기 위해서 많은 리소스가 필요하다. 만약 코드가 수만줄에 다다른다면 관리가 매우 어려워 질 것이다. 따라서 정적인 웹 페이지 소스를 관리하기 용이하도록 모듈화를 진행한다. 현재 동일한 결과를 가지고 있지만, 각 컴포넌트에 변수와 함수를 사용해서 훨씬 편리하게 컴포넌트 관리가 가능해진다.

<body>
    
    <div id="subject"></div>
    <div id="nav"></div>
    <div id="control"></div>
    <div id="article"></div>
        
        <script>
            subject();
            nav();
            control();
            article();

            function subject(){
                document.querySelector("#subject").innerHTML=`
                <header>
                    <h1>Redux CRUD</h1>
                    hello redux !
                </header>
                `
            }
            

            function nav(){
                document.querySelector("#nav").innerHTML=`
                <nav>
                    <ol>
                        <li><a href="Javascript.html">Javascript</a></li>
                        <li><a href="CSS.html">CSS</a></li>
                        <li><a href="React.html">React</a></li>
                    </ol>
                </nav>
                `
            }

            function control(){
                document.querySelector("#control").innerHTML=`
                <ul>
                    <li><a href="/create">Create</a></li>
                    <li><input type="button" value="Delete"></li>
                </ul>
                `
            }

            function article(){
                document.querySelector("#article").innerHTML=`
                <article>
                    <h2>Javasciprt</h2>
                    Javascirpt is ...
                </article>
                `
            }
        </script>
    </div>
</body>

 

Redux 초기 설정

 

① State로 관리할 대상 선정

이번 예제에서 state로 관리해야 할 객체는 title, description 두가지다. 

 

② store, reducer 생성

function reducer(state, action){
    if(state===undefined){
        return {
            contents:[
                {id:1, title:'Javasciprt', desc:'Javascript is ...'},
                {id:2, title:'CSS', desc:'CSS is ...'},
            ]
        }
    }
}

var store = Redux.createStore(reducer);

 

Redux Read

 

nav 엘리멘트를 클릭하면 해당 엘리멘트에 해당하는 description을 읽어올 수 있다. 

 

먼저 클릭했을 때 state에 보내줄 action을 작성한다. action은 store.dispatch() 함수로 reducer에게 action 데이터를 전송한다.

<li>
    <a onclick="
        let action = {type:'SELECT', id:${state.contents[i].id}}
        store.dispatch(action)
    " href="#">
        ${state.contents[i].title}
    </a>
</li>

 

reducer에서는 action의 type을 파싱해서 SELECT일 경우 state를 복사/업데이트 한 후 새로운 state를 반환한다.

function reducer(state, action){
    if(state===undefined){
        return {
            selectd_id:1,
            contents:[
                {id:1, title:'Javasciprt', desc:'Javascript is ...'},
                {id:2, title:'CSS', desc:'CSS is ...'},
            ]
        }
    }
    let newState;
    if(action.type==='SELECT'){
        newState = Object.assign({}, state, {selectd_id:action.id})
    }
    return newState;
}

 

마지막으로 article 컴포넌트에서 state를 전달받아 자신의 id에 해당하는 contents를 파싱한 후 title과 descript을 Article에 뿌려주면 된다. 마지막으로 state가 변경될 때 마다 컴포넌트를 업데이트 해주기 위해서 store.subscribe()메소드에 article 컴포넌트를 보내주면 클릭할 때 마다 웹 페이지가 변경되면서 article을 READ할 수 있다.

function article(){
    let state = store.getState();
    let articleTitle, articleDesc;
    for(let i=0; i<state.contents.length; i++){
        if(state.contents[i].id === state.selectd_id){
            articleTitle = state.contents[i].title;
            articleDesc = state.contents[i].desc;
            break;
        }
    }

    document.querySelector("#article").innerHTML=`
    <article>
        <h2>${articleTitle}</h2>
        ${articleDesc}
    </article>
    `
}
store.subscribe(article);

 

 

Redux Create

 

element를 생성하기 위해서는 state에 mode 속성을 추가해준다. create Button을 클릭할 때 마다 mode가 변경되고, 새로운 title과 description을 담은 배열을 추가하여 state을 업데이트를 진행한다.

 

function reducer(state, action){
    if(state===undefined){
        return {
            max_id:2,
            mode:'create',
            selectd_id:1,
            contents:[
                {id:1, title:'Javasciprt', desc:'Javascript is ...'},
                {id:2, title:'CSS', desc:'CSS is ...'},
            ]
        }
    }
    let newState;
    if(action.type==='SELECT'){
        newState = Object.assign({}, state, {selectd_id:action.id})
    }else if(action.type === 'CREATE'){
        let newMaxId = state.max_id + 1;
        let newContents = state.contents.concat();
        newContents.push({id: newMaxId,title:action.title, desc:action.desc});
        newState = Object.assign({}, state, {
            max_id:newMaxId,
            contents:newContents,
            mode:'read',
        })
    }
    return newState;
}

 

article 컴포넌트에서 state.mode에 따라서 두가지 로직으로 구분된다. 먼저 state.mode가 create인 경우 <form> 태그를 생성하고 title, description 데이터를 담은 action을 dispatch() 해준다. 

function article(){
    let state = store.getState();


    if(state.mode === 'create'){
        document.querySelector("#article").innerHTML=`
        <article>
            <form onsubmit="
                event.preventDefault();
                let _title = this.title.value;
                let _desc = this.desc.value;
                store.dispatch({type:'CREATE', title:_title, desc:_desc})
            ">
                <p>
                    <input type="text" name="title" placeholder="title"/>
                </p>
                <p>
                    <textarea name="desc" placeholder="description"></textarea>
                    </p>
                    <p>
                        <input type="submit">
                        </p>
            </form>
        </article>
        `
    }else if(state.mode === 'read'){
        let articleTitle, articleDesc;
        for(let i=0; i<state.contents.length; i++){
            if(state.contents[i].id === state.selectd_id){
                articleTitle = state.contents[i].title;
                articleDesc = state.contents[i].desc;
                break;
            }
        }
        document.querySelector("#article").innerHTML=`
        <article>
            <h2>${articleTitle}</h2>
            ${articleDesc}
        </article>
        `
    }
}

 

마지막으로 nav() 컴포넌트를 subscribe에 등록해주면 <form> 태그를 완성할 때 마다 nav의 element가 자동으로 업데이트 된다. 

store.subscribe(nav);

 

CREATE 로직에서는 read/ create 모드를 관리할 수 있는 Boolean 타입의 속성을 state로 관리해줘야 한다. 또한 action의 type도 CREATE로 설정해서 dispatch 해주면 로직을 간편하게 관리할 수 있다.

 

Redux Delete

 

삭제 기능을 구현하기 위해서는 삭제 Button을 관리하는 control 컴포넌트의 delete Button에 onclick 이벤트를 걸어준다. action은 DELETE만 담아 dispatch()를 호출한다.

<li><input onclick="
    let action = {type:'DELETE'}
    store.dispatch(action)
" type="button" value="Delete"></li>

 

nav에서 컴포넌트를 클릭하면 state.selected_id가 변경되기 때문에 이를 토대로 contents 속성을 filter 해준다. 새로운 배열과 mode는 welcome으로 속성을 변경해준다음 새로운 state를 반환한다.

else if(action.type === 'DELETE'){
    let newContents = state.contents.filter(item=>item.id !== state.selectd_id)
    newState = Object.assign({}, state, {
        mode:'welcome',
        contents : newContents,
    })
}

return newState;

 

마지막으로 article 컴포넌트에서 state.mode가  welcome이 되었을 경우 렌더링 해줄 HTML 소스를 작성해준다.

else if(state.mode === 'welcome'){
    document.querySelector("#article").innerHTML=`
    <article>
        <h2>Welcome</h2>
        Welcome Redux ~!
    </article>
    `
}

 

 

Redux Update

 

Update 로직은 state mode를 변경한 후 <form> 태그에서 title, description을 변경해준다. submit을 클릭하면 action에 UPDATE type이 dispatch() 되고 전달받은 title과 desc로 새로운 contents 배열을 생성해서 state를 업데이트 해준다. state mode는 'read'로 다시 변경해준다.

 

먼저 nav 컴포넌트를 변경해준다. 업데이트를 위한 버튼을 새로 추가했다.

 <li>
    <a onclick="
        let action = {type:'SELECT', id:${state.contents[i].id}, mode:'read'}
        store.dispatch(action)
    " href="#">
        ${state.contents[i].title}
    </a>
    <input onclick="
    let update_action = {type:'SELECT',id:${state.contents[i].id} ,mode:'update'}
    store.dispatch(update_action)
" type="button" value="update">
</li>

 

article 컴포넌트에 update 경우를 새로 추가했다. 기본적인 HTML 구조는 create와 동일하다. 다만 action에 태워 보내지는 type이 UPDATE로 변경되었을 뿐이다.

else if(state.mode==='update'){
    document.querySelector("#article").innerHTML=`
    <article>
        <form onsubmit="
            event.preventDefault();
            let _title = this.title.value;
            let _desc = this.desc.value;
            store.dispatch({type:'UPDATE', title:_title, desc:_desc})
        ">
            <p>
                <input type="text" name="title" placeholder="title"/>
            </p>
            <p>
                <textarea name="desc" placeholder="description"></textarea>
                </p>
                <p>
                    <input type="submit">
                    </p>
        </form>
    </article>
    `
}

 

마지막으로 reducer 함수에서 UPDATE에 대한 로직을 구성해준다. 배열의 경우 state에 바로 접근해서 변경하는 것 보다, concat() 함수를 써서 복사본을 수정해 state 객체를 업데이트 해준다. 업데이트가 완료된 후 state.mode는 다시 read로 변경해준다.

 

else if(action.type === 'UPDATE'){
    let newContents = [];
    newContents = state.contents.concat();
    let {title, desc} = action;
    for(let i=0; i<newContents.length; i++){

        if(newContents[i].id === state.selectd_id){

            newContents[i].title = title;
            newContents[i].desc = desc;
            break;
        }
    }
    newState = Object.assign({}, state, {
        mode : 'read',
        contents : newContents,
    })

}

return newState;

 

 

Redux CRUD 정리

 

Redux를 활용해서 간단한 CRUD 기능을 구현했다. HTML을 사용해서 다소 코드가 복잡하게 보이지만 React를 활용한 실무에서는 한 파일에서 모든 Redux 소스를 관리하지 않는다. store.js / reducer.js / actions.js 등 파일을 구분하고, 좀더 명시적으로 코드를 작성하는게 암묵적인 룰이다.

 

CRUD를 구현할 때 가장 중요한 건 state로 관리할 대상을 명확하게 설계하는 것이다. 관계형 데이터베이스 설계 단계에서 외부 스키마, 개념 스키마, 내부 스키마를 단계적으로 체계적인 설계과정을 거치는 것 처럼 Redux의 state도 각 컴포넌트들이 어떤 state가 필요한지 미리 규정한 후 어플리케이션을 설계하는 습관을 들여야 나중에 편하다.

 

 

 

Source Code

 

Reference

 

 

 

 

[Redux] Redux란? 리덕스 사용법

What is Redux? Redux란? Redux는 Javascript로 빌드된 앱을 위한 예측 가능한 상태 컨테이너다. Redux는 일관적으로 동작하고, 서로 다른 환경(서버, 클라이언트, 네이티브) 등 모든 환경에서 작동한다. 또한

about-tech.tistory.com

 

 

[React] Effect HOOK API useEffect 사용하는 방법

React 데이터 흐름? React 데이터 흐름 React의 개발 방식의 가장 큰 특징은 컴포넌트 단위로 페이지를 구성하는 것이다. 한개의 웹 페이지를 여러 컴포넌트로 잘라서 구성한 뒤 조립하는 형식으로

about-tech.tistory.com

 

 

[React] props vs state

React props vs state 기본 개념 React Functional Component props, state state hooks? what is props, state, state hook in react and How can I use them? 리액트 컴포넌트 사이에 데이터 교환을 하기 위한..

about-tech.tistory.com

 

댓글