[프론트엔드_개발] axios와 student 앱

혯승·2023년 6월 5일
0

프론트엔드_개발

목록 보기
9/9
post-thumbnail

💻 student2 프로젝트

vue create student2

cd student2

vue add router
npm install axios

src/students-db.json

강의 자료에서 json 소스 코드를 작성하자


📝 학생 목록 화면 구현

1) StudentListView.vue

src/views/StudentListView.vue

<template>
  <div id="StudentListView">
    <h1>학생 목록</h1>
    <table>
      <tr><td>id</td><td>학번</td><td>이름</td><td>전화</td><td>성별</td>
          <td>이메일</td><td>학과</td></tr>
      <tr v-for="student in students" v-bind:key="student.id">
        <td>{{ student.id }}</td>
        <td>{{ student.studentNo }}</td>
        <td>{{ student.name }}</td>
        <td>{{ student.phone }}</td>
        <td>{{ student.sex }}</td>
        <td>{{ student.email }}</td>
        <td>{{ student.departmentId }}</td>
      </tr>
    </table>

  </div>
</template>

<script>
import axios from 'axios';

axios.defaults.baseURL = "http://localhost:3000";
    // 백엔드 API URL 앞 부분을 미리 설정한다

export default {
  name: "StudentListView",
  data() {
    return {
      students: [ ]
    }
  },
  mounted() { // 컴포넌트가 처음 화면에 보이게 될 때, 자동으로 호출되는 메소드
    this.reloadStudents(); // 데이터를 로드한다
  },
  methods: {
    async reloadStudents() { // 백엔드 API를 호출하여 데이터를 로드한다
      try {
        const response = await axios.get("/students");
        this.students = response.data;
      } catch (error) {
        alert('조회 에러: ' + (error instanceof Error ? error.message : error));
      }
    }
  }
}
</script>

<style scoped>
h1 { border-bottom: 1px solid gray; }
table { border-collapse: collapse; margin: 20px 0; width: 100%; }
tr:nth-child(1) { background-color: #eee; text-align: center; }
td { border: 1px solid gray; padding: 6px; }
td:nth-child(1) { text-align: center; width: 30px; }
</style>
async reloadStudents() { // 백엔드 API를 호출하여 데이터를 로드한다
      try {
        const response = await axios.get("/students");
        this.students = response.data;
      } catch (error) {
        alert('조회 에러: ' + (error instanceof Error ? error.message : error));
      }
    }

axios 메소드는 비동기(asynchronous) 메소드 (async 키워드 필요)
비동기 메소드/함수를 호출하여 리턴 값을 받을 때는 await 키워드가 필요

✏️ 동기 vs 비동기

  • 동기
명령1()
명령2()
명령3()

명령1을 호출하고 완료될 때까지 기다린다. 완료 후에 명령2를 호출한다.
명령2가 완료될 때까지 기다린다. 완료 후에 명령3을 호출한다.
명령3이 완료될 때까지 기다린다.

  • 비동기
명령1()
명령2()
명령3()

명령1을 호출한다. 명령1이 완료될 때까지 기다리지 않고 명령2를 호출한다.
명령2가 완료될 때까지 기다리지 않고 명령3을 호출한다.

✏️ 동기방식은 호출을 하고 완료될 때까지 기다리기 때문에 리턴 값을 받는 것이 쉽다. 하지만, 비동기 방식은 기다리지 않으므로 리턴 값을 받는 것이 어렵다.

  • 비동기의 리턴 값
    비동기의 리턴 값을 받을 때는 await 키워드를 붙여야 한다.
    본문에 await 키워드를 포함하는 함수는 비동기 함수이며, 앞에 asynx 키워드를 붙여야 한다.

2) router/index.js 수정

src/router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import StudentListView from '../views/StudentListView.vue'

const routes = [
  {
    path: '/',
    name: 'StudentListView',
    component: StudentListView
  },
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

4) App.vue

<template>
  <div id="App">
    <router-view/>
  </div>
</template>

<style>
div#App { padding: 30px; margin: 30px auto; width: 800px;
  border: 1px solid #ccc; box-shadow: 3px 3px 3px #aaa; }
</style>
  • 실행
json-server --watch src/students-db.json
npm run serve

👤 학생 수정 화면 구현 #1 화면 이동

1) StudentListView.vue 수정

<template>
  <div id="StudentListView">
    <h1>학생 목록</h1>
    <table>
      <tr><td>id</td><td>학번</td><td>이름</td><td>전화</td><td>성별</td>
          <td>이메일</td><td>학과</td></tr>
      <tr v-for="student in students" v-bind:key="student.id" v-on:click="goEdit(student.id)">
        <td>{{ student.id }}</td>
        <td>{{ student.studentNo }}</td>
        <td>{{ student.name }}</td>
        <td>{{ student.phone }}</td>
        <td>{{ student.sex }}</td>
        <td>{{ student.email }}</td>
        <td>{{ student.departmentId }}</td>
      </tr>
    </table>

  </div>
</template>

<script>
import axios from 'axios';

axios.defaults.baseURL = "http://localhost:3000";
    // 백엔드 API URL 앞 부분을 미리 설정한다

export default {
  name: "StudentListView",
  data() {
    return {
      students: [ ]
    }
  },
  mounted() { // 컴포넌트가 처음 화면에 보이게 될 때, 자동으로 호출되는 메소드
    this.reloadStudents(); // 데이터를 로드한다
  },
  methods: {
    async reloadStudents() { // 백엔드 API를 호출하여 데이터를 로드한다
      try {
        const response = await axios.get("/students");
        this.students = response.data;
      } catch (error) {
        alert('조회 에러: ' + (error instanceof Error ? error.message : error));
      }
    },
    goEdit(id) {
      this.$router.push("/edit/" + id);
    }
  }
}
</script>

<style scoped>
h1 { border-bottom: 1px solid gray; }
table { border-collapse: collapse; margin: 20px 0; width: 100%; }
tr:nth-child(1) { background-color: #eee; text-align: center; }
td { border: 1px solid gray; padding: 6px; }
td:nth-child(1) { text-align: center; width: 30px; }
tr:hover { background-color: #ffd; cursor: pointer }
</style>
goEdit(id) {
      this.$router.push("/edit/" + id);
    }

예를 들어 id가 3이라면, "/edit/3" URL로 넘어감. 3 값은 router parameter
▶︎ 파라미터에 주어진 값으로 이동

2) StudentEditView.vue

src/views/StudentEditView.vue

<template>
  <div id="StudentEdit">
    {{ $route.params.id }} 
    <!-- route 이름 주의!! -->
  </div>
</template>

<script>
export default {
  name: "StudentEditView",
}
</script>

<style>
</style>

{{ $route.params.id }} : router parameter 데이터 중에서, 이름이 id인 값을 출력

3) router/index.js 수정

import { createRouter, createWebHistory } from 'vue-router'
import StudentListView from '../views/StudentListView.vue'
import StudentEditView from '../views/StudentEditView.vue'

const routes = [
  {
    path: '/',
    name: 'StudentListView',
    component: StudentListView
  },
  {
    path: '/edit/:id',
    name: 'StudentEditView',
    component: StudentEditView
  },
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

path: '/edit/:id' 이 부분에서 :id 부분은 router parameter이고, 이름은 id


id가 3인 학생을 클릭하면 http://localhost:8080/edit/4 로 이동하여 router parameter id 값이 출력됨


👤 학생 수정 화면 구현 #2

1) StudentEditView.vue

<template>
  <div id="StudentEdit">
    <h1>학생 수정</h1>
    <div>
      <input type="text" v-model="student.studentNo" />
    </div>
    <div>
      <input type="text" v-model="student.name" />
    </div>
    <div>
      <input type="phone" v-model="student.phone" />
    </div>
    <div>
      <label><input type="radio" value="" v-model="student.sex" /> 남자</label>
      <label><input type="radio" value="" v-model="student.sex" /> 여자</label>
    </div>
    <div>
      <input type="email" v-model="student.email" />
    </div>
    <div>
      <select v-model="student.departmentId">
        <option value="1">소프</option>
        <option value="2">컴공</option>
        <option value="3">정통</option>
        <option value="4">글티</option>
      </select>
    </div>
    <div>
      <button type="button" v-on:click="save">저장</button>
      <button type="button" v-on:click="goList">취소</button>
    </div>
  </div>
</template>

<script>
import axios from 'axios';

axios.defaults.baseURL = "http://localhost:3000";

export default {
  name: "StudentEditView",
  data() {
    return {
      student: { }
    }
  },  
  mounted() {
    const id = this.$route.params.id;
    this.loadStudent(id);
  },
  methods: {
    async loadStudent(id) {
      try {
        const response = await axios.get("/students/" + id);
        this.student = response.data;
      } catch (error) {
        alert('조회 에러: ' + (error instanceof Error ? error.message : error));
      }
    },
    async saveStudent(student) {
      try {
        await axios.put("/students/" + student.id, student);
      } catch (error) {
        alert('저장 에러: ' + (error instanceof Error ? error.message : error));
      }
    },
    async save() {
      await this.saveStudent(this.student);
      this.goList();
    },
    goList() {
      this.$router.push("/");
    }
  }
}
</script>

<style>
input[type=text], input[type=phone], input[type=email] { padding: 6px; width: 200px; }
select { padding: 6px; width: 150px }
div { margin-bottom: 15px; }
label { margin-right: 15px; }
button { padding: 5px 20px; margin-right: 10px; }
</style>
 mounted() {
    const id = this.$route.params.id;
    this.loadStudent(id);
  }

컴포넌트가 처음에 보일 때 학생 한명의 정보가 보여야 하므로 router parmeter을 받아와서 이동해야함

async saveStudent(student) {
      try {
        await axios.put("/students/" + student.id, student);
      } catch (error) {
        alert('저장 에러: ' + (error instanceof Error ? error.message : error));
      }
    }
async save() {
      await this.saveStudent(this.student);
      this.goList();
    },
    goList() {
      this.$router.push("/");
    }

저장 버튼은 누르면 save() 메소드가 호출되는데 return 값이 없음에도 비동기 방식으로 작성한 이유는 saveStudent 메소드가 종료될 때까지 기다려야 하기 때문이다. 또한, saveStudent() 메소드에 await 키워드를 반드시 작성해야 한다. 붙이지 않는다면 save 메소드는 기다리지 않고 바로 goList() 메소드를 호출할 것이다. (저장이 완료된 후에 목록으로 넘어가야함)

저장이 완료되면 처음 목록 화면으로 넘어간다.


✏️ axios 호출 코드 분리

StudentListViewStudentEditView 두 컴포넌트에 axios 호출 코드가 필요
따라서 axios 호출 코드를 studentService.js 파일로 분리하자

1) studentService.js

import axios from 'axios';

axios.defaults.baseURL = "http://localhost:3000";

export async function loadStudents() {		// 목록조회
  try {
    const response = await axios.get("/students");
    return response.data;
  } catch (error) {
    alert('조회 에러: ' + (error instanceof Error ? error.message : error));
  }
}

export async function loadStudent(id) {		// 학생 한명 조회
  try {
    const response = await axios.get("/students/" + id);
    return response.data;
  } catch (error) {
    alert('조회 에러: ' + (error instanceof Error ? error.message : error));
  }
}

export async function saveStudent(student) {	// 학생 저장
  try {
    await axios.put("/students/" + student.id, student);
  } catch (error) {
    alert('저장 에러: ' + (error instanceof Error ? error.message : error));
  }
}

✏️ 이 파일에 구현한 함수들을 다른 파일에서 import하여 사용하려면 export 키워드가 필요!!
또한, 컴포넌트 속성에 바로 대입할 수 없으므로 리턴!

2) StudentListView.vue 수정

<template>
  <div id="StudentListView">
    <h1>학생 목록</h1>
    <table>
      <tr><td>id</td><td>학번</td><td>이름</td><td>전화</td><td>성별</td>
          <td>이메일</td><td>학과</td></tr>
      <tr v-for="student in students" v-bind:key="student.id" v-on:click="goEdit(student.id)">
        <td>{{ student.id }}</td>
        <td>{{ student.studentNo }}</td>
        <td>{{ student.name }}</td>
        <td>{{ student.phone }}</td>
        <td>{{ student.sex }}</td>
        <td>{{ student.email }}</td>
        <td>{{ student.departmentId }}</td>
      </tr>
    </table>

  </div>
</template>

<script>
import { loadStudents } from '../studentService';

export default {
  name: "StudentListView",
  data() {
    return {
      students: [ ]
    }
  },
  async mounted() {
   this.students = await loadStudents();
  },
  methods: {
    goEdit(id) {
      this.$router.push("/edit/" + id);
    }
  }
}
</script>

<style scoped>
h1 { border-bottom: 1px solid gray; }
table { border-collapse: collapse; margin: 20px 0; width: 100%; }
tr:nth-child(1) { background-color: #eee; text-align: center; }
td { border: 1px solid gray; padding: 6px; }
td:nth-child(1) { text-align: center; width: 30px; }
tr:hover { background-color: #ffd; cursor: pointer }
</style>

3) StudentEditView.vue 수정

<template>
  <div id="StudentEdit">
    <h1>학생 수정</h1>
    <div>
      <input type="text" v-model="student.studentNo" />
    </div>
    <div>
      <input type="text" v-model="student.name" />
    </div>
    <div>
      <input type="phone" v-model="student.phone" />
    </div>
    <div>
      <label><input type="radio" value="" v-model="student.sex" /> 남자</label>
      <label><input type="radio" value="" v-model="student.sex" /> 여자</label>
    </div>
    <div>
      <input type="email" v-model="student.email" />
    </div>
    <div>
      <select v-model="student.departmentId">
        <option value="1">소프</option>
        <option value="2">컴공</option>
        <option value="3">정통</option>
        <option value="4">글티</option>
      </select>
    </div>
    <div>
      <button type="button" v-on:click="save">저장</button>
      <button type="button" v-on:click="goList">취소</button>
    </div>
  </div>
</template>

<script>
import { loadStudent, saveStudent } from '../studentService';

export default {
  name: "StudentEditView",
  data() {
    return {
      student: { }
    }
  },
  async mounted() {
    const id = this.$route.params.id;
    this.student = await loadStudent(id);
  },
  methods: {
    async save() {
      await saveStudent(this.student);
      this.goList();
    },
    goList() {
      this.$router.push("/");
    }
  }
}
</script>

<style>
input[type=text], input[type=phone], input[type=email] { padding: 6px; width: 200px; }
select { padding: 6px; width: 150px }
div { margin-bottom: 15px; }
label { margin-right: 15px; }
button { padding: 5px 20px; margin-right: 10px; }
</style>

0개의 댓글

관련 채용 정보