Vanilla JavaScript에 redux 도입,필터링 구현

chaewon Im·2022년 2월 14일
0

트러블슈팅

목록 보기
3/10

팀원들이 react까지 배우기엔 러닝커브가 많이 높을 것으로 보여서 프론트엔드는 장고 template과 자바스크립트,제이쿼리만을 이용해 개발을 하고있다. 필터링 기능을 구현하던 중, 평소라면 react의 state와 useEffect를 이용해 구현했을 필터링을 자바스크립트만으로 구현하려고 하니 생소하고 어려움을 느꼈다.

기존 코드

let category='all';
let color='all';

function setOption(e,tone){
	switch(e.target.className){
        case 'category':
            category = e.target.id;
            break;
        case 'color':
            color = e.target.id;
            break;
    }
    requestFilteringObjects(category,color,tone);
}

function requestFilteringObjects(category,color,tone){
    $.ajax({
        url:`/shopping/list/${tone}/?category=${category}&color=${color}`,
        type:'GET',
        data:{
        },
        success:function(response){
            $("body").html(response)
}

setOption(e) 함수는 사용자가 필터링 요소를 눌렀을 때 호출되는 함수이다. catecory, color 변수 각각의 값이 따로따로 유지되어 있어야 한다. react를 썼다면 각각을 state변수로 만들어서 클릭될 때 마다 setState로 값을 변경하고, useEffect의 두번째 인자로 두 요소를 전달해서 값이 변경될 때 마다 requestFilteringObjects()함수가 실행되도록 구현했을 것이다. 그치만 바닐라 자바스크립트에는 이런 기능이 없다.

처음에는 방법이 바로 떠오르지 않았다. 그래서 차선책을 썼다. 전역변수로 만들어서 함수 내에서 조건에 따라 해당하는 값을 변경하도록 만들었다. 그치만 이렇게 했을 때 이미 변수가 선언되어있다는 에러가 떴다.

Uncaught SyntaxError: Identifier 'category' has already been declared

다시 문제에 봉착했다. 어떻게 하면 바닐라 자바스크립트에서 useState,useEffect와 같은 효과를 낼 수 있을까 고민하던 중, redux는 바닐라 자바스크립트에서도 사용할 수 있다는 사실이 떠올랐다. 이참에 아직 실제 프로젝트에 사용해보진 않았던 redux를 도입해 보았다.

Redux 도입

store 생성

function reducer(state,action){
    if(state === undefined){
        return {category:'all',color:'all'}
    }

    let newState;
    if(action.type === 'CHANGE_COLOR'){
        newState = Object.assign({},state,{color:action.color});
    }
    if(action.type === 'CHANGE_CATEGORY'){
        newState = Object.assign({},state,{category:action.category});
    }
    console.log(action.type,action,state,newState);
    return newState;
}

let store = Redux.createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
console.log(store.getState());

일단 배운대로 reducer를 만들고, store를 생성했다. color와 category를 각각 업데이트 하기 위해 두 개의 action type을 생성했고, 각 액션에 따라 state를 변경하도록 만들었다. 초기값은 전체 데이터를 보여주기 위해 'all'로 설정했다.

setOption() 동작 변경

function setOption(e,tone){
    let category='all';
    let color='all';

    switch(e.target.className){
        case 'category':
            category = e.target.id;
            store.dispatch({type:'CHANGE_CATEGORY',category:category});
            break;
        case 'color':
            color = e.target.id;
            store.dispatch({type:'CHANGE_COLOR',color:color});
            console.log(color)
            break;
    }

    requestFilteringObjects(tone);
}

이에 따라 전역변수로 선언했던 color와 category는 함수 내로 들어왔고, 새로운 state값으로 만들 값을 저장하는 변수가 되었다. redux를 사용하기 전에는 이 두 변수의 값을 바로 데이터를 요청하는 함수로 넘겨주고, url에 담아 서버로 전송했었다. 그러나 도입 후에는 dispatch를 통해 store의 state값을 변경하는 작업만을 한다. 그리고 변경이 모두 끝난 후 requestFilteringObjects() 함수를 실행한다.

requestFilteringObjects() 변경&보완

function requestFilteringObjects(tone){
    let category = store.getState().category;
    let color = store.getState().color;
    $.ajax({
        url:`/shopping/list/${tone}/`,
        type:'GET',
        data:{
            category:category,
            color:color
        },
        success:function(response){
            let clothes_list = document.createElement('div');
            clothes_list.className = 'clothes-list';
            response = JSON.parse(response);

            if(!response.length){
                console.log('No product')
                $('#clothes-list').html(`
                    <div class="no-product">해당하는 상품이 존재하지 않습니다.</div>
                `)
            }else{
                response.map((item,index)=>{
                    let id = item.pk;
                    let url = item.fields.url;
                    let thumbnail = item.fields.thumbnail;
                    let brand = item.fields.brand;
                    let name = item.fields.name;
                    let category = item.fields.category;
                    let color = item.fields.color;
                    let tone = item.fields.tone;
                    let price = item.fields.price;
                    let discount_price = item.fields.discount_price;
                    ...
                    //상품 item DOM을 생성,추가.

              })
              //기존 HTML 변경.
              $('#clothes-container').html(clothes_list)
            }
        }
    })
}

category와 color는 store에서 getState를 통해 읽어오도록 바꿨다. 이를 data로 넘겨주고, 요청 성공 시 화면을 업데이트 하도록 변경했다. 이 때 react에서 map을 이용해 객체 리스트를 렌더링하던 방식처럼 구현했다. 그리고 필터링에 걸리는 상품이 없는 경우는 상품이 없다는 안내메세지까지 띄우도록 처리했다.

필터링 상품 목록 업데이트 구현

//변경 전
$("body").html(response)
//변경 후
$('#clothes-container').html(clothes_list)

기존의 이 코드를 새로운 DOM을 생성하고 업데이트하는 긴 코드로 바꿨다. 이유는 views.py에서 응답할 때 html 템플릿을 전달하기 때문이다. 그동안 했던 대로 백엔드에서는 데이터만 전달하는 방식이였다면 변경 전 방식도 문제 없었겠지만, 우리는 기본적인 장고 사용법대로 html을 전달했기 때문에, 필터링 요청 시 전체 페이지가 전부 reload 되어버려서 state가 유지되지 않는다는 문제가 있었다.

따라서 필터링 요청 시에는 반환하는 데이터를 Json형태로 주도록 처리했고, react에서 리스트 컴포넌트의 데이터를 업데이트 하듯이 DOM을 업데이트 해서 리스트 컨테이너 안에 추가한 것이다.

//views.py, tone=spring인 경우

def spring_list(request):
    clothes = Clothes.objects.filter(tone="spring")

    category = request.GET.get('category',None)
    color = request.GET.get('color',None)

    if category != None or color != None:
        
        q = Q(tone='spring') # 쿼리스트링
        if category and category != 'all':
            q &= Q(category=category)
            print(category)
        if color and color != 'all':
            q &= Q(color=color)
            print(color)
    
        clothes = Clothes.objects.filter(q).distinct()
        clothes_serialized = serializers.serialize('json',clothes)
        
        //필터링 데이터는 Json으로 return
        return JsonResponse(clothes_serialized,safe=False)
        
	//기본적으로는 템플릿 return
    return render(request,'fashion/spring_list.html',{
        'clothes':clothes
    })

변경 사항들에 맞게 수정을 진행하니 필터링 기능이 잘 동작했다! 이렇게 react 없이 react에서 쓰던 방법과 나름 유사하게 필터링을 구현해보았다.

profile
빙글빙글 돌아가는 주니어 프론트엔드 개발자

0개의 댓글