ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • A non-serializable value was detected in the state 에러 해결.
    문제 해결 기록 2022. 10. 24. 12:57

    redux-toolkit으로 전역 상태 객체를 사용하다가 발생한 이슈입니다.

     

    문제의 발생 배경은 rtk 라이브러리로 특정 객체를 전역 상태로서 사용하고 있었고 프로그램이 시작될 때,

    해당 객체의 초기값에 존재하는 속성(빈 배열 내부)에 class와 new 연산자로 다른 타입의 객체를 만들어 초기 상태를 만들어주고 있었습니다.

    export const surveySlice = createSlice({
        name: 'test',
        initialState: {
            value: {
                head: {
                    title: 'test',
                    desc: ''
                },
                body: [
                    new Test()
                ]
            }
        },
        reducers: {}
    }

    그리고 그 클래스의 정의는 아래의 형식으로 제작했습니다.

    class Test {
        title: string;
        type: SurveyTypeEnum;
        options: Array<string>;
        isRequired: boolean;
        constructor() {
            this.title = '';
            this.type = SurveyTypeEnum.multipleChoices;
            this.options = ['옵션 1'];
            this.isRequired = false;
        }
    }

    문제는 이런식으로 초기값을 만들어주면 콘솔에서

    A non-serializable value was detected in the state

    이러한 에러를 보여준다는 것이었습니다. (프로그램 작동 상의 문제는 없었습니다.)

     

    또한, 사용자가 화면의 특정 버튼을 클릭할 때마다 new 연산자를 사용해 전역 상태의 배열에 클래스의 인스턴스를 추가해주는 상황에도 똑같은 에러가 발생했습니다.

     

    문제를 해결하기 위해 먼저 콘솔창 에러메세지가 안내하는 redux 공식 문서 페이지로 이동했습니다.

    바로 문제에 대한 해답을 주는 것을 볼 수 있습니다.

     

    '함수, 프로미스 객체, 또는 직렬화가 불가능한 요소를 전역상태로 지정할 수 있느냐'에 대한 질문의 답으로

    'redux는 직렬화가 가능한 일반 객체, 배열, 원시 타입 변수들만 지정하는 것을 추천하며, 그렇지 않을 경우 프로그램의 동작과 디버깅에 문제가 생길 수 있다.'라고 명확한 답을 보여줍니다.

     

    여기서 직렬화란 JSON.stringify 등의 함수로 해당 값의 정보를 모두 온전하게 JSON, 또는 string값으로 옮길 수 있느냐를 뜻합니다. 자바스크립트에서는 함수도 객체이지만 내부적으로 다른 슬롯과 속성을 가지다 보니 redux에서는 일반 객체로 취급하지 않고 비직렬화의 속성을 가진 요소라고 판단하는것 같습니다.

     

    그러나 new 연산자를 통해 생성된 인스턴스들은 일반 객체이므로 명백하게 직렬화가 가능한 요소라고 생각했습니다.

    따라서, 전역상태의 배열에 추가되는데 왜 에러가 나는지 위의 문서만으로 파악하기 쉽지 않았습니다.

     

    그래서 나름의 필살기 console.log함수로 일단 해당 인스턴스를 출력해보았습니다.

     

    값이 잘 출력됩니다!

    또한, 해당 인스턴스의 모든 속성들은 직렬화가 가능한 값으로 이루어져 전혀 문제가 없어보입니다.

     

    그러나 미심쩍은 부분이 있었습니다.

    바로 맨 앞에, 인스턴스의 클래스 이름까지 함께 출력이 되고 있는 것이 디버깅 경험상 어색했습니다.

    그래서 바로 해당 인스턴스를 생성하는 클래스와 동일한 역할을 하는 함수를 만들어 보았습니다.

    const testElemConstructor = () => {
        return {
            title: '',
            type: SurveyTypeEnum.multipleChoices,
            options: ['옵션 1'],
            isRequired: false
        }
    }

    이 함수는 위의 클래스가 생성하는 인스턴스와 동일한 값을 가진 객체를 반환하는 역할을 합니다.

    이 함수로 객체를 생성해 콘솔에 출력해 보았습니다.

     

    추측이 맞은 것을 볼 수 있었습니다!

    이 함수로 반환한 객체는 클래스로 생성한 인스턴스와 동일한 속성과 값을 갖지만,

    콘솔에 출력한 객체의 정보 앞에 아무런 내용도 없다는 것이 차이였습니다.

     

    그렇다면 이 함수로 반환한 객체를 전역상태에 적용할 수 있는지 여부가 에러를 제거하는데 중점이 되었고,

    initialState: {
        value: {
            head: {
                title: 'test',
                desc: ''
            },
            body: [
                testElemConstructor()
            ]
        }
    },

    실제로 위의 코드대로 수정을 하니까 정말 직렬화에러 문제가 사라졌습니다.

     

    이렇게 문제를 해결할 수 있었지만 두 경우의 차이를 좀 더 파악해보고 싶었습니다.

     

    new 연산자를 사용해 생성한 객체와 함수를 통해 반환된 객체 모두 일반 객체이기 때문에,

    먼저 JSON.stringify함수를 통해 강제 직렬화를 시키면 두 객체가 동일한 값을 갖게될지 궁금했습니다.

     

    그 결과 두 객체는 모두 직렬화 되었을 때 동일한 JSON형태가 되는 것을 확인할 수 있었습니다.

    그렇다면 명백히 class가 new 연산자로 반환하는 인스턴스의 형식이 redux의 전역상태와 호환되지 않다는 것이 더욱 명백해졌습니다.

     

    그리고 이 두 JSON객체들을 다시 JSON.parse함수를 통해 역직렬화해보았습니다.

     

    그렇게 되면 두 객체의 출력문이 완전히 동일해집니다.

    즉, new 연산자로 생성한 인스턴스가 처음에 보여주었던 class 이름이 사라지고 두 객체의 출력 형식 또한 완전히 동일해지는 것을 알 수 있습니다.

     

    이러면 new 연산자로 생성한 인스턴스 객체도 직렬화 -> 역직렬화의 작업을 거쳐 전역상태에 적용하게 되면 처음의 에러가 완전히 나지 않을 것임을 추측해볼 수 있습니다.

    initialState: {
        value: {
            head: {
                title: '',
                desc: ''
            },
            body: [
                JSON.parse(JSON.stringify(new Test()))
            ]
        }
    },

     

    이 코드를 전역 상태의 초기값으로 적용하고 프로그램을 실행시켰습니다.

     

    그 결과는 에러없는 깨끗한 콘솔창을 볼 수 있었습니다!

     

    앞으로는 redux를 사용할 때 class와 new를 사용하는데 주의해야겠습니다.

Designed by Tistory.