React 공부할끄니까! State 고급 스킬?!

React 공부할끄니까! State 고급 스킬?!

·

3 min read

본 글은 인프런 강의 ≪React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지≫를 기반으로 작성 되었습니다. 😉


객체 업데이트 하기

⛔️ React State가 가진 객체를 직접 복사하면 안된다.

객체를 업데이트하고 싶을 때는 반드시 새로운 객체를 생성해 복사본을 사용해야 한다.

  • 새로운 객체를 생성할 때 전개(...) 구문을 활용해야 함
  • 전개(...) 구문은 한 레벨 깊이만 복사함에 주의
    • 중첩된 프로퍼티를 업데이트하고 싶다면 한 번 이상 사용해야 한다는 의미

만약에 form을 업데이트 해야 한다면, 아래와 같이 전개 구문을 활용해 복사본을 생성해 업데이트 한다.

const [form, setForm] = useState({ 
    title: 'title',
    desc: 'desc'
})

const handleChange = (e) => { 
    setForm({
        ...form,
        [e.target.name]: e.target.value
    })
}

중첩 객체 업데이트(with Immer)

const [form, setForm] = useState({
    title: 'title',
    desc: 'desc',
    info: { 
        level: 1,
        skill: 'skill'
    }
})

위와 같이 중첩 객체를 이용하는 State가 있다면, form를 한 번 이상 사용해야만 중첩 객체를 업데이트 할 수 있을 것이다.

const handleSkillChange = (e) => { 
    setForm({
        ...form,
        info: { 
            ...form.info,  // 여기서 한 번 이상 사용됨
            skill: e.target.value  // 그래야 업데이트할 수 있음
        }
    })
}

이를 user-immer 라이브러리를 통해 간결하게 리팩토링할 수 있다.

npm i user-immer
const [form, updateForm] = useImmer({
    title: 'title',
    desc: 'desc',
    info: { 
        level: 1,
        skill: 'skill'
    } 
})

const handleChange = (e) => {
    updateForm(draft => {
        draft[e.target.name] = e.target.value
    })
}

const handleSkillChange = (e) => { 
    updateForm(draft => { 
        draft.info.skill = e.target.value
    })
}

배열 업데이트 하기

배열은 변경 가능하지만, State로 저장할 때에는 변경할 수 없어야 한다. 객체와 마찬가지로, 업데이트할 때는 새 배열을 생성(혹은 기존 배열의 복사본을 생성)한 뒤, 새 배열을 State로 두어 업데이트해야 한다.

  • (2)에서 원본 배열을 수정하지 않는 Array API를 알아야 함
const [todos, setTodos] = useState([
    { id: 0, label: 'Hello React!' }
])
const [todoText, setTodoText] = useState('')

const handleChangeTodoText = (e) => { 
    setTodoText(e.target.value)
}

cosnt handleAddTodo = (e) => { 
    const nextId = todos.length
    todos.push({ id: nextId, label: todoText })
}

위 코드는 동작할 것이다. 그러나 핸들러가 todos 배열의 상태를 직접적으로 변경하기 때문에 동일 기능을 하는 새로운 컴포넌트에서도 이 변경을 감지해 리렌더링될 것이다. 이는 각각 컴포넌트의 상태가 불변하지 않음을 의미한다.

즉, todos에 아이템을 추가하는 A, B 두 개의 컴포넌트가 있다면 B에서 일어난 변경이 A에도 영향을 준다는 것이다.  컴포넌트는 항상 순수해야 하기 때문에, 같은 컴포넌트여도 독립적인 상태를 갖고 있어야 불변하다. 따라서 배열의 상태를 업데이트 할 때는 위에서 말했듯 새 배열을 생성해야만 한다.

const handleAddTodo = (e) => { 
    const nextId = todos.length
    setTodos([
        ...todos, // 전개 구문으로 새 배열을 생성
        { id: nextId, text: todoText } // 그 뒤에 새 요소를 추가
    ])
}

최신 Array API로 업데이트 하기

💡 2023년 7월부터 이 기능은 최신 기기 및 브라우저 버전에서 작동한다.

  • Array.prototype.toSorted
  • Array.prototype.toReversed
  • Array.prototype.toSpliced
  • Array.prototype.with

위 기능들은 모두 원본 배열을 변경하지 않고 새로운 배열을 반환하는 점에서 불변성을 유지하는 코드를 작성할 때 유용하다.(4)

References.