- [개발자의품격][부트캠프][1기][23차시] Vue.js #16 | Vuex2022년 03월 19일 03시 26분 04초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
Vuex
vuex 라이브러리를 반드시 써야 하는 것은 아니다. (한 번도 안 쓰는 프로젝트도 있다.)
Vue 프로젝트 생성 시 Vuex를 사용하는 것으로 선택하면 src 폴더 내에 store폴더와 index.js 파일이 자동으로 생성되고 main.js에 store 폴더가 import 된다.
Vuex는 상태를 관리하기 위한 패턴 라이브러리이다. 애플리케이션 내 모든 컴포넌트의 중앙 집중식 데이터 저장소로서 역할을 한다.
각 컴포넌트에는 그 컴포넌트에서만 사용할 수 있는 data()를 정의할 수 있다. 이때 어떤 데이터는 다른 컴포넌트들이 그 데이터에 빈번하게 접근해야 하는 경우가 있다. 이런 데이터는 중앙에서 관리하는 것이 효율적이다. Vuex는 이런 데이터를 효율적으로 관리한다. 예를 들어 로그인 정보의 경우 각 컴포넌트는 로그인된 사용자 데이터를 공유하여 알맞은 화면을 보여주어야 한다.
상태는 예측 가능한 방식(규칙)으로만 변경될 수 있고 변경된 경우에는 알려준다.
Vuex는 사용자 정보, 장바구니 데이터, Todo 리스트 등에서 사용하며, 모든 컴포넌트에서 접근이 가능하다.
Vuex는 컴포넌트에서 data()라고 쓰지 않고 상태 관리를 강조하기 위해 state()라고 쓴다.
// src/store/index.js import { createStore } from 'vuex' export default createStore({ state() { return { } },
Vuex를 이용한 Todo list 실습
할 일 목록 데이터(todos)를 state에 넣는다.
// src/store/index.js import { createStore } from 'vuex' export default createStore({ state() { return { todos: [ { id: 1, title: 'todo 1', done: true }, { id: 2, title: 'todo 2', done: false }, { id: 3, title: 'todo 3', done: false } ] } },
mutations
mutations 함수에 todos 데이터 변경을 위한 함수를 선언한다. state의 변경은 mutations에서만 가능하다. 변경이 일어나면 state의 데이터를 사용하고 있는 다른 컴포넌트에 변경사항을 알려준다.
// src/store/index.js import { createStore } from 'vuex' export default createStore({ ... mutations: { // 함수를 정의할 때 첫 번째 파라미터는 반드시 state이다. add(state, item){ state.todos.push(item) }, done(state, id){ state.todos.filter(todo => todo.id === id)[0].done = true } },
actions
actions 함수는 states 내 데이터를 수정할 수 있다는 점에서 mutations 함수와 매우 유사하다. 이때 직접 수정할 수도 있지만 일반적으로는 mutations를 통해서 수정한다. mutations와의 가장 큰 차이점은 actions가 비동기 처리를 할 수 있다는 점이다. 데이터의 영속성을 위해서는 DB에 저장을 해야 하는데 mutations에서는 할 수 없는 일이다. 이런 경우 비동기 처리가 가능한 actions에 함수를 정의해 사용한다.
// src/store/index.js import { createStore } from 'vuex' export default createStore({ ... // 비동기 처리 가능 actions: { add: ({ commit }, item) => { // 서버에 new todo 저장 후 성공하면 commit함수를 통해 mutations의 해당 함수를 호출해 state의 데이터를 변경한다. commit('add', item) // 'add'는 mutations 내 함수 이름, item은 전달할 파라미터 } },
setTimeout() 함수로 1초 뒤 commit() 함수를 호출하는 비동기 처리를 해보겠다.
// src/store/index.js import { createStore } from 'vuex' export default createStore({ ... // 비동기 처리 가능 actions: { add: ({ commit }, item) => { // 서버에 new todo 저장 setTimeout(() => { commit('add', item) }, 1000) }, done: ({ commit }, item) => { // 서버에 할일 처리여부 done 저장 setTimeout(() => { commit('done', item) }, 1000) } ...
TodoView.vue에 아래 코드를 추가하면 [그림 1]과 같이 state의 todos 데이터가 조회된다.
// src/views/6_vuex/TodoView.vue <template> <div> <div>{{ $store.state.todos }}</div> </div>
"추가" 버튼과 item을 추가하는 addItem() 함수를 선언한다. "추가" 버튼을 클릭하면 "id:4" item이 추가된다. 영속적인 추가가 아니기 때문에 새로고침 하면 값이 사라진다.
<!-- src/views/6_vuex/TodoView.vue --> <template> <div> ... <button @click="addItem">추가</button> </div> </template> <script> export default { ... methods: { addItem() { // commit함수를 통해서만 mutations의 함수를 호출할 수 있다. this.$store.commit('add', { id: 4, title: 'todo 4', done: false }) } } } </script>
computed
computed를 이용하여 $store.state.todos를 todos라는 새로운 데이터를 만들었다. 실무에서는 코드 길이를 짧게 사용하기 위해 이렇게 새롭게 정의하여 사용한다.
<!-- src/views/6_vuex/TodoView.vue --> <template> <div> <!-- <div>{{ $store.state.todos }}</div> --> <div>{{ todos }}</div> ... <script> export default { ... // computed는 다른 데이터를 참조하여 데이터에 정의되지 않은 새로운 데이터를 만든다. computed: { todos() { return this.$store.state.todos } }, ...
addItem() 함수는 mutations에 정의된 함수를 호출하고, addItem2() 함수는 actions에 정의된 함수를 호출하도록 코딩한다. 해당 버튼도 추가한다. addItem2() 함수는 actions의 비동기 함수를 호출하여 설정된 1초 후에 값이 표시된다. [그림 3]은 "추가(actions)" 버튼 클릭 후 "추가(mutations)" 버튼 클릭하였으나 mutations가 actions 보다 먼저 표시되었다.
<!-- src/views/6_vuex/TodoView.vue --> <template> <div> ... <button @click="addItem">추가(mutations)</button> <button @click="addItem2">추가(actions)</button> </div> </template> <script> export default { ... methods: { addItem() { // mutations에 정의된 함수를 호출할 때는 commit 사용 this.$store.commit('add', { id: 4, title: 'mutations', done: false }) }, addItem2() { // actions에 정의된 함수 호출할 때는 dispatch 사용 this.$store.dispatch('add', { id: 5, title: 'actions', done: false }) } } } </script>
getters
getters를 이용하면 항목수를 편리하게 간단하게 표시할 수 있다. getters에 정의된 함수의 값은 vuex의 데이터가 변경될 때 자동으로 반영된다.
// src/store/index.js import { createStore } from 'vuex' export default createStore({ ... getters: { todosCount(state) { return state.todos.length }, doneTodosCount(state) { return state.todos.filter((todo) => todo.done).length }, notDoneTodosCount(state) { return state.todos.filter((todo) => !todo.done).length } }, ...
TodoView.vue 컴포넌트에 "전체 항목수, 완료된 항목수, 미완료 항목수"를 각각 출력되게 한다. <template>에 동적으로 출력되는 값들은 coputed에서 새롭게 정의된 데이터를 받아오는 것이다.
<!-- src/views/6_vuex/TodoView.vue --> <template> <div> ... <div>전체 항목수: {{ todosCount }}</div> <div>완료된 항목수: {{ doneTodosCount }}</div> <div>미완료 항목수: {{ notDoneTodosCount }}</div> ... </div> </template> <script> export default { ... computed: { ... todosCount() { return this.$store.getters.todosCount }, doneTodosCount() { return this.$store.getters.doneTodosCount }, notDoneTodosCount() { return this.$store.getters.notDoneTodosCount } }, ...
modules
module 방식은 가독성이 좋고 협업에서도 유리하기 때문에 실무에서 주로 사용한다. store 폴더에서 todo.js를 생성하고 index.js에서 state() 함수가 포함된 오브젝트를 export const todo에 넣는다.
// src/store/todo.js export const todo = { state() { return { todos: [ { id: 1, title: 'todo 1', done: true }, { id: 2, title: 'todo 2', done: false }, { id: 3, title: 'todo 3', done: false } ] } }, getters: { todosCount(state) { return state.todos.length }, doneTodosCount(state) { return state.todos.filter((todo) => todo.done).length }, notDoneTodosCount(state) { return state.todos.filter((todo) => !todo.done).length } }, mutations: { // 함수를 정의할 때 첫 번째 파라미터는 반드시 state이다. add(state, item) { state.todos.push(item) }, done(state, id) { state.todos.filter((todo) => todo.id === id)[0].done = true } }, // 비동기 처리 가능 actions: { add: ({ commit }, item) => { // 서버에 new todo 저장 setTimeout(() => { commit('add', item) }, 1000) }, done: ({ commit }, item) => { // 서버에 할일 처리여부 done 저장 setTimeout(() => { commit('done', item) }, 1000) } } }
index.js에 앞서 만든 todo.js를 import 하고, modules에 todo를 추가한다. 그러면 [그럼 5]와 같이 출력되는데 state 데이터가 보이지 않는다.
// src/store/index.js ... import { todo } from './todo' export default createStore({ modules: { todo // {todo:todo} } })
modules 사용법
state 데이터에 접근하기 위해 todo.js에 namespaced를 추가한다.
// src/store/todo.js export const todo = { namespaced: true, ...
TodoView.vue 컴포넌트의 computed의 코드를 수정하면 [그림 4]와 동일하게 출력된다. 하지만 "추가" 버튼은 작동하지 않는다.
// src/views/6_vuex/TodoView.vue ... <script> export default { ... computed: { todos() { // 데이터 가져올 때 return this.$store.state.todo.todos }, todosCount() { // return 값 가져올 때 return this.$store.getters['todo/todosCount'] }, doneTodosCount() { return this.$store.getters['todo/doneTodosCount'] }, notDoneTodosCount() { return this.$store.getters['todo/notDoneTodosCount'] } },
TodoView.vue 컴포넌트의 methods의 addItem(), addItem2() 함수의 'add'를 'todo/add'로 수정하면 "추가" 버튼이 작동한다. module의 키 todo로 접근하는 것이다.
// src/views/6_vuex/TodoView.vue ... <script> export default { ... methods: { addItem() { // mutations에 정의된 함수를 호출할 때는 commit 사용 this.$store.commit('todo/add', { id: 4, title: 'mutations', done: false }) }, addItem2() { // actions에 정의된 함수 호출할 때는 dispatch 사용 this.$store.dispatch('todo/add', { id: 5, title: 'actions', done: false }) } ...
login 기능 추가
user module을 추가하기 위해 store 폴더에 user.js를 생성한다.
// src/store/user.js export const user = { namespaced: true, state() { return { userInfo: { name: 'Sewol', email: 'sewol@gmail.com', tel: '010-0000-0000' } } }, getters: {}, mutations: {}, actions: {} }
index.js에 user.js를 import 하고 module을 추가한다.
// src/store/index.js ... import { user } from './user' // 추가 export default createStore({ modules: { todo: todo, user: user // 추가 } })
TodoView.vue 컴포넌트에 사용자 이름을 추가한다.
// src/views/6_vuex/TodoView.vue <template> <div> ... <div>사용자 이름: {{ userInfo.name }}</div> </div> ... <script> export default { ... computed: { ... userInfo() { return this.$store.state.user.userInfo }
user.js의 state() 함수 내 userInfo를 빈 오브젝트로 만든다. mutations에 setUser() 함수를 생성한다.
// src/store/user.js export const user = { namespaced: true, state() { return { userInfo: {} // userInfo 내 데이터 삭제 } }, ... mutations: { setUser(state, userInfo) { state.userInfo = userInfo } }, ... }
TodoView.vue 컴포넌트에 "로그인" 버튼을 만든다. 로그인 버튼을 누르면 login() 함수를 호출한다.
// src/views/6_vuex/TodoView.vue <template> <div> ... <div><button @click="login">로그인</button></div> <div v-if="userInfo.name">{{ userInfo.name }}님 환영합니다.</div> </div> </template> <script> export default { ... methods: { ... login() { this.$store.commit('user/setUser', { name: 'Sewol', email: 'sewol@gmail.com' }) } } } </script>
로그인된 경우에만 페이지 보이기
router폴더의 index.js에 아래의 코드를 추가한다. beforeEach는 경로 이동 직전에 수행하는 매서드로 사용자가 어떤 메뉴에서 어디로 이동하는지 캐치할 수 있다. 실무에서는 로그인된 사용자에게만 페이지를 보여줘야 하는 경우를 위한 경로 관리에 사용한다. 아래 코드는 로그인이 된 경우에는 next() 함수를 실행하고 그렇지 않으면 로그인 화면으로 redirect 시킨다. 이 기능으로 navigation control을 할 수 있다.
// src/router/index.js ... router.beforeEach((to, from, next) => { console.log('to', to) console.log('from', from) next() }) ...
user.js의 getters에 isLogin() 함수를 선언한다. userInfo.name 값이 있으면(로그인된 경우) true, 없으면 false를 반환한다.
// src/store/user.js export const user = { ... getters: { isLogin(state) { if (state.userInfo.name) { return true } else { return false } } }, ...
router의 index.js에서 로그인 정보를 사용하기 위해 store를 import 하고 router.beforeEach() 함수를 아래와 같이 수정한다.
// src/router/index.js ... import store from '../store' ... router.beforeEach((to, from, next) => { // 홈화면 if (to.path === '/') { next() // 로그인 화면 } else if (to.path === '/vuex/todo') { next() } else { // 로그인된 경우(store.getters['user/isLogin']이 true인 경우) if (store.getters['user/isLogin']) { next() // 로그인이 안된 경우, 로그인 화면으로 Redirect } else { next('/vuex/todo') } } })
728x90반응형'영광의 시대! > 2022 개발자의 품격 부트캠프 1기' 카테고리의 다른 글
다음글이 없습니다.이전글이 없습니다.댓글