문제 페이지를 만들었지만 아직 만들어야할 게 많습니다.

 

하나 하나 만들어 가도록 하겠습니다.

 

일단 첫 번째로, 라디오 버튼으로 눌러놓은 값들을 저장할 공간이 있어야 합니다.

 

라디오 버튼을 기억하고 있다고 하더라도, 그 값을 우리가 알지 못하면 정답을 눌렀는지 어쨌는지 알 수가 없기 때문이죠.

 

라디오 버튼을 눌렀을 때, 그 값을 전달해주는 방법이 뭐가 있을까요?

 

input값과 vue의 데이터가 연동이 된다? 네, v-model입니다.

 

          <input type="radio" v-model="selectedAnswer"  :name="'answer'+index" value='a' id=a> <label for="a">{{ answer.a }}</label>
          <input type="radio" v-model="selectedAnswer" :name="'answer'+index" value='b' id=b> <label for="b">{{ answer.b }} </label>
          <input type="radio" v-model="selectedAnswer" :name="'answer'+index" value='c' id=c> <label for="c">{{ answer.c }} </label>
          <input type="radio" v-model="selectedAnswer" :name="'answer'+index" value='d' id=d> <label for="d">{{ answer.d }} </label>

이렇게 input 버튼에 v-model을 달아주면 이 값들이 선택 될 때마다 값이 value값으로 변한다는걸 알 수 있습니다.

 

테스트를 해보니 값들이 잘 변하고 있네요

 

그럼 이제 이 받아온 값들을 언제 쏴주면 좋을까요?

 

selectedAnswer의 값이 변할 때마다 실행되는 함수가 있으면 참 좋을 것 같습니다.

 

이 selectedAnswer값을 보고 있다가 값이 변하면 실행하는 함수!! watch 속성을 사용하면 어떨까요?

 

watch 속성을 추가해주도록 하겠습니다.

 

  watch :{
    selectedAnswer :{
      handler(){
        console.log(this.selectedAnswer)
      }
    }
  }

watch 속성을 추가하고 selectedAnswer를 주목하도록 합니다.

 

그리고 이 값이 변할 때 handler(){} 함수가 작동을 하게 됩니다.

 

여기서 함수를 직접 정의하기보다는 methods에 함수를 정의해주고 가져다 사용하는 것이 더욱 나은 방법입니다.

 

watch 속성을 주니 값이 잘 들어가는지 테스트 하는 버튼을 누르지 않아도 자동으로 들어가는 걸 확인 할 수가 있네요.

 

자 그럼 이제 이 값을 고른 정답 리스트에 담는 작업을 하도록 합시다.

 

methods를 하나 만들어줄건데 이건 부모컴포넌트의 고른 정답 리스트에 값을 담아주는 역할을 할겁니다.

 

부모 컴포넌트에 값을 전달해주려면 다시 emit을 사용하면 됩니다.

 

    nextMove(){
      this.$emit('next-move')
    },
    prevMove(){
      this.$emit('prev-move')
    },
    saveAnswer(){
      this.$emit('save-answer')
    }
 
  },
  watch :{
    selectedAnswer :{
      handler(){
        this.saveAnswer()
      }
    }
  }

이렇게 methods에 함수를 설정해주고 handler안에서 사용해줍니다.

 

그 다음은 부모 컴포넌트에서 save-answer라는 이벤트로 인덱스와 고른 값을 받은 뒤, 그 값을 이용해 selectedAnswerList에 넣어주면 됩니다.

saveAnswer(){
      this.$emit('save-answer', {index:this.index, selectedAnswer:this.selectedAnswer})
    }
 

saveAnswer를 다음과 같이 수정해준뒤, 부모 컴포넌트에서 받아주도록 합니다.

 

부모 컴포넌트에 selectedAnswerList를 만들고 함수도 실행시켜주고 하면

 

<template>
  <div id="app" class="container fw-bold">
    <QuestionComponent
      v-for="(i, index) in questionAmount"
      :key="index"
      question="세상에서 가장 귀여운 동물은?"
      :answer="{a:'강아지', b:'고양이', c:'토끼', d:'페코'}"
      :index="index"
      v-show="index == showIndex"
      @next-move="nextMove"
      @prev-move="prevMove"
      @save-answer="saveAnswer"
    />
    <button class="submit-button" @click="submitAnswers">Submit</button>
  </div>
</template>

<script>
import _ from 'lodash'
import QuestionComponent from './components/QuestionComponent.vue';

export default {
  name: 'App',
  data(){
    return {
      questionAmount : _.range(30),
      showIndex : 0,
      selectedAnswerList : new Array(30).fill(''),
      
    }
  },
  
  components:{
    QuestionComponent
  },
  methods: {
    nextMove(){
      this.showIndex = this.showIndex+1 > 29 ? 29 : this.showIndex+1
    },
    prevMove(){
      this.showIndex = this.showIndex - 1 < 0 ? 0 : this.showIndex-1
    },
    saveAnswer({index, selectedAnswer}){
      this.selectedAnswerList[index] = selectedAnswer
      console.log(this.selectedAnswerList)
    },
    submitAnswers() {
      // Your logic for submitting answers here
    }
  
  }
  }

</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  margin-top: 60px;
}

.container {
  background-color: #fce4ec;
  border-radius: 10px;
  box-shadow: 0 0 20px 0 rgba(255, 105, 180, 0.5);
  padding: 40px;
}

.question-text {
  font-size: 24px;
  color: #333333;
  margin-bottom: 20px;
}

.answer-label {
  font-size: 20px;
  color: #333333;
  margin-bottom: 10px;
}

.answer-input {
  margin-right: 10px;
}

.submit-button {
  background-color: #ff69b4;
  border: none;
  border-radius: 50px;
  color: white;
  font-size: 18px;
  padding: 10px 30px;
  margin-top: 30px;
  box-shadow: 0 0 20px 0 rgba(255, 105, 180, 0.5);
  transition: all 0.3s ease-in-out;
}

.submit-button:hover {
  background-color: #ff94c2;
  box-shadow: 0 0 20px 0 rgba(255, 105, 180, 0.8);
  cursor: pointer;
}
</style>

 

값을 선택할 때 마다 해당 위치에 선택한 값들이 잘 들어가는 것을 확인할 수 있습니다.

 

이제 correctAnswerList를 하나 만들어서 대조시켜주면 내가 무슨 문제를 틀렸는지도 바로 알 수 있겠네요!!!

 

값들이 차곡차곡 잘 쌓이는 것을 알 수 있습니다!!

 

이제 Submit버튼을 누르면 correctAnswerList와 값을 비교하고 틀린 문제들을 체크해주면 될 것 같습니다.

 

자 이제 여기까지 왔으니 슬슬 진짜 문제들을 만들어 보도록 합니다.

 

지금은 문제가 바껴도 매번 같은 문제들만 나오고 있는데, 문제를 옮기면 당연히 문제도 달라지고 답도 달라져야하죠

 

이제 문제들을 각각 Object로 바꿔주도록 하겠습니다.

 

 <QuestionComponent
      v-for="(i, index) in questionAmount"
      :key="index"
      question="세상에서 가장 귀여운 동물은?"
      :answer="{a:'강아지', b:'고양이', c:'토끼', d:'페코'}"
      :index="index"
      v-show="index == showIndex"
      @next-move="nextMove"
      @prev-move="prevMove"
      @save-answer="saveAnswer"
    />

여기서 questionAmount로 그냥 문제 개수만큼 반복문을 돌리는 것이 아니라 문제 Object를 순회하면서

 

question과 answer에 각각 다른 값들을 쏴주겠다 이말입니다

 

그럼 일단 questionAmount를 questions로 바꾸어야 하겠습니다.

 

questions로 하나 하나 만들려고 하다 보니 갑자기 30개라는 문제양이 너무 많아보입니다.

 

5개로 줄이도록 하겠습니다.

 

data(){
    return {
      questionAmount : _.range(30),
      showIndex : 0,
      selectedAnswerList : new Array(30).fill(''),     
    }
  },

data부분을 수정해주도록 하겠습니다.

 

 data(){
    return {
      questions :[{
        question : '다음 중 세상에서 가장 귀여운 동물은?',
        answer : {
          a:'강아지',
          b:'고양이',
          c:'김나리',
          d:'토끼',
        },
      }],
      showIndex : 0,
      selectedAnswerList : new Array(30).fill(''),
     
     
    }
  },

 

이런식으로 말이죠. 그리고 컴포넌트에 쏴줄 props도 바꿔주도록 합니다.

 

 <QuestionComponent
      v-for="(question, index) in questions"
      :key="index"
      :question="question.question"
      :answer="question.answer"
      :index="index"
      v-show="index == showIndex"
      @next-move="nextMove"
      @prev-move="prevMove"
      @save-answer="saveAnswer"
    />

이렇게 QuestionComponent도 바꿔줍니다. questionAmount를 도는게 아니라 questions를 돌고

 

question과 answer는 이제 question 안에 있는 question과 answer를 전달해줄겁니다.

question도 이제 dynmaic props로 바꿔주어야겠죠.

 

문제 5개를 만들어 놓고 테스트를 해보도록 합니다.

questions :[{
        question : '다음 중 세상에서 가장 귀여운 동물은?',
        answer : {
          a:'강아지',
          b:'고양이',
          c:'김나리',
          d:'토끼',
        },
      },
      {
        question : '다음 중 가장 맛있는 음료수는??',
        answer : {
          a:'펩시',
          b:'코카콜라',
          c:'웰치스',
          d:'환타',
        },
      },

이런식으로 하나 하나 문제들을 수정해주도록 합니다.

 

매우 만족스러운 결과가 나왔습니다.

 

이제 정말 문제를 하나하나 풀어볼 수 있는 페이지가 완성이 되었습니다.

 

이왕 문제 오브젝트를 만든 김에 각 오브젝트들이 correctAnswer도 가지고 있게 해줍시다.

 questions :[{
        question : '다음 중 세상에서 가장 귀여운 동물은?',
        answer : {
          a:'강아지',
          b:'고양이',
          c:'김나리',
          d:'토끼',
        },
        correctAnswer : 'c'
      },
      {
        question : '다음 중 가장 맛있는 음료수는??',
        answer : {
          a:'펩시',
          b:'코카콜라',
          c:'웰치스',
          d:'환타',
          correctAnswer : 'a'
        },
      },
      {
        question : '다음 중 가장 재미있는 프로그래밍 언어는?',
        answer : {
          a:'파이썬',
          b:'C',
          c:'Java',
          d:'HTML',
        },
        correctAnswer : 'd'
      },

이런식으로 각자가 correctAnswer까지 가지고 있으면 이후에 정답을 체크하기에 좀 더 수월해질 수 있을 것 같습니다.

selectedAnswerList도 이제 굳이 Array로 여러개 만들지 않고 문제의 개수만큼만 만들어주면 되겠죠

 

next버튼과 prev버튼을 누를 때 문제 길이 이상으로 넘어가지 못하게 하는 것도 수정해주고 말이죠.

 

추가로 문제가 바꼈을 때 문제별 정답버튼의 id를 고정시켜버려서 

 

다음 문제부터는 label을 눌러도 라디오 버튼이 클릭이 되지 않기에,  :id와 :for으로 동적으로 받아주어 문제를 해결했습니다.

<template>
  <div id="app" class="container fw-bold">
    <QuestionComponent
      v-for="(question, index) in questions"
      :key="index"
      :question="question.question"
      :answer="question.answer"
      :index="index"
      v-show="index == showIndex"
      @next-move="nextMove"
      @prev-move="prevMove"
      @save-answer="saveAnswer"
    />
    <button class="submit-button" @click="submitAnswers">Submit</button>
  </div>
</template>

<script>
import QuestionComponent from './components/QuestionComponent.vue';

export default {
  name: 'App',
  data(){
    return {
      questions :[{
        question : '다음 중 세상에서 가장 귀여운 동물은?',
        answer : {
          a:'강아지',
          b:'고양이',
          c:'김나리',
          d:'토끼',
        },
        correctAnswer : 'c'
      },
      {
        question : '다음 중 가장 맛있는 음료수는??',
        answer : {
          a:'펩시',
          b:'코카콜라',
          c:'웰치스',
          d:'환타',
        },
          correctAnswer : 'a'
      },
      {
        question : '다음 중 가장 재미있는 프로그래밍 언어는?',
        answer : {
          a:'파이썬',
          b:'C',
          c:'Java',
          d:'HTML',
        },
        correctAnswer : 'd'
      },
      {
        question : '다음 중 가장 좋은 계절은?',
        answer : {
          a:'봄',
          b:'여름',
          c:'가을',
          d:'겨울',
        },
        correctAnswer:'b'
      },
      {
        question : '다음 중 가장 맛있는 야식은?',
        answer : {
          a:'치킨',
          b:'피자',
          c:'족발',
          d:'햄버거',
        },
          correctAnswer:'a'
      },
      ],
      showIndex : 0,
      selectedAnswerList : [],
     
      
    }
  },
  
  
  components:{
    QuestionComponent
  },
  methods: {
    nextMove(){
      this.showIndex = this.showIndex+1 > this.questions.length -1 ? this.questions.length -1 : this.showIndex+1
    },
    prevMove(){
      this.showIndex = this.showIndex - 1 < 0 ? 0 : this.showIndex-1
    },
    saveAnswer({index, selectedAnswer}){
      this.selectedAnswerList[index] = selectedAnswer
      console.log(this.selectedAnswerList)
    },
    submitAnswers() {
      // Your logic for submitting answers here
    }
  },
  created(){
    this.selectedAnswerList = new Array(this.questions.length).fill('')
  }
  }

</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  margin-top: 60px;
}

.container {
  background-color: #fce4ec;
  border-radius: 10px;
  box-shadow: 0 0 20px 0 rgba(255, 105, 180, 0.5);
  padding: 40px;
}

.question-text {
  font-size: 24px;
  color: #333333;
  margin-bottom: 20px;
}

.answer-label {
  font-size: 20px;
  color: #333333;
  margin-bottom: 10px;
}

.answer-input {
  margin-right: 10px;
}

.submit-button {
  background-color: #ff69b4;
  border: none;
  border-radius: 50px;
  color: white;
  font-size: 18px;
  padding: 10px 30px;
  margin-top: 30px;
  box-shadow: 0 0 20px 0 rgba(255, 105, 180, 0.5);
  transition: all 0.3s ease-in-out;
}

.submit-button:hover {
  background-color: #ff94c2;
  box-shadow: 0 0 20px 0 rgba(255, 105, 180, 0.8);
  cursor: pointer;
}
</style>
<template>
  <div>
    <div>
      <h3 class="question-number">문제 {{index+1}}</h3> 
      <p class="question-text">{{question}}</p>
    </div>
    <div class="container d-flex justify-content-center">
      <div class="options">
        <div class="answerBox d-flex align-items-center justify-content-evenly">
          <input type="radio" v-model="selectedAnswer"  :name="'answer'+index" value='a' :id="'a'+index"> <label :for="'a'+index">{{ answer.a }}</label> 
          <input type="radio" v-model="selectedAnswer" :name="'answer'+index" value='b' :id="'b'+index"> <label :for="'b'+index">{{ answer.b }} </label> 
          <input type="radio" v-model="selectedAnswer" :name="'answer'+index" value='c' :id="'c'+index"> <label :for="'c'+index">{{ answer.c }} </label> 
          <input type="radio" v-model="selectedAnswer" :name="'answer'+index" value='d' :id="'d'+index"> <label :for="'d'+index">{{ answer.d }} </label> 
        </div>
        <div class="d-flex justify-content-between" style="width:500px;">
          <button @click="prevMove" class="btn next-btn">이전 문제</button>
          <button @click="nextMove" class="btn prev-btn">다음 문제</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data(){
    return {
      selectedAnswer:null,
    }
  },
  props:{
    question:String,
    answer:Object,
    index:Number,
  },
  methods:{
    nextMove(){
      this.$emit('next-move')
    },
    prevMove(){
      this.$emit('prev-move')
    },
    saveAnswer(){
      this.$emit('save-answer', {index:this.index, selectedAnswer:this.selectedAnswer})
    }
  
  },
  watch :{
    selectedAnswer :{
      handler(){
        this.saveAnswer()
      }
    }
  }
}
</script>

<style>
.question-number {
  color: #FF69B4;
  font-size: 24px;
  margin-bottom: 10px;
}

.question-text {
  color: #4B0082;
  font-size: 20px;
  margin-bottom: 20px;
  text-shadow: 2px 2px 3px #FFB6C1;
}

.options {
  background-color: #FFFACD;
  border-radius: 10px;
  box-shadow: 2px 2px 10px #D2691E;
  padding: 20px;
}

.answerBox {
  border: 1px solid black;
  width: 500px;
  height: 100px;
}

.btn {
  background-color: #FF69B4;
  border: none;
  border-radius: 10px;
  color: #FFF;
  font-size: 16px;
  font-weight: bold;
  margin-top: 20px;
  padding: 10px 20px;
  text-shadow: 2px 2px 3px #FFB6C1;
  transition: all 0.3s ease-in-out;
}

.btn:hover {
  box-shadow: 2px 2px 5px #D2691E;
  cursor: pointer;
  transform: scale(1.1);
}
</style>

자 다음에는 이제 submit버튼을 눌러 맞춘 정답이 몇개나 되는지를 만들어보도록 하겠습니다

나만의 작고 귀여운 사이트가 하나 만들어졌습니다.

 

하지만 문제가 1개밖에 없다는 게 너무 아쉽습니다

 

문제를 30개 정도는 만들어 줘야 문제풀이 사이트라고 할 수 있을텐데 말이죠.

 

문제를 30개 만들어보도록 하겠습니다.

 

<template>
  <div id="app" class="fw-bold">
    <QuestionComponent 
    question="세상에서 가장 귀여운 동물은?"
    :answer="{a:'강아지', b:'고양이', c:'토끼', d:'페코'}"
    />
  </div>
</template>

 

우리가 이럴려고 문제와 정답을 App.vue에서 QuestionComponent에 뿌려준거 아니겠습니까

 

v-for 을 사용하여 30개의 문제를 아주 간단하게 만들 수 있습니다.

 

<script>
import _ from 'lodash'
import QuestionComponent from './components/QuestionComponent.vue';

export default {
  name: 'App',
  data(){
    return {
      questionAmount : _.range(30)
    }
  },
  components:{
    QuestionComponent
  }

}
</script>

이렇게 data안에 길이 30의 리스트를 만들고 그 리스트를 for 돌려주면 30개가 만들어질테니까 말이죠.

 

오 문제 30개가 생겼습니다. 하지만 문제가 전부 1이라고 나오는게 굉장히 거슬리네요. 

 

문제의 인덱스도 props로 넘겨줘서 문제별 숫자를 다르게 만들어주도록 하겠습니다.

<template>
  <div id="app" class="fw-bold">
    <QuestionComponent v-for="(i, index) in questionAmount" :key="index"
    question="세상에서 가장 귀여운 동물은?"
    :answer="{a:'강아지', b:'고양이', c:'토끼', d:'페코'}"
    :index="index"
    />
  </div>
</template>

 

for문을 돌릴 때, index값도 같이 받아준 뒤 index값을 props로 넘겨주도록 합니다.

 

 props:{
    question:String,
    answer:Object,
    index:Number,
  }
<template>
  <div>
    <div>
    <h3>문제 {{index+1}}</h3> 
    <p>{{question}}</p>
    </div>
    <div class="container d-flex justify-content-center">

    <div> &lt;보기&gt;
    <div class="answerBox d-flex align-items-center justify-content-evenly">
      <input type="radio" name="answer" value='a' id=a> <label for="a">{{ answer.a }}</label> 
      <input type="radio" name="answer" value='b' id=b> <label for="b">{{ answer.b }} </label> 
      <input type="radio" name="answer" value='c' id=c> <label for="c">{{ answer.c }} </label> 
      <input type="radio" name="answer" value='d' id=d> <label for="d">{{ answer.d }} </label> 

    </div>
    </div>
    </div>
  </div>
</template>

1번부터 차근차근 잘 나오는걸 확인할 수 있습니다.

 

근데 이렇게 문제를 한 페이지에 모두 다 보여주는게 또 너무 아쉽습니다...

 

뭔가 버튼을 눌러서 한 문제씩 보여줄 수 있다면 참 좋을것 같은데 말이죠...

 

우리가 index를 만들어냈으니 index를 활용해보면 참 좋을 것 같습니다.

 

인덱스 값이 1인것만 보여줘, 2인것만 보여줘 이런식으로 하면 필요한 문제만 볼 수 있을테니까 말이죠!!

 

여기에 v-if까지 넣어서 수정을 해주면 참 좋을것 같지만, Vue는 v-for과 v-if를 같이 사용하면 이렇게 에러가 터져버립니다.

 

 

대충 v-if랑 v-for 같이 쓰지 말라는 내용인데요...

 

우리는 v-if를 사용하면 그 페이지를 제외한 다른 것들을 사라지게 되니, if문을 쓸거면 애초에 그냥 computed를 쓰던 뭘 쓰던 해서 필터링을 먼저 한 뒤 v-for문 안에 필터링 된 오브젝트를 써라 뭐 할라고 굳이 v-for문으로 다 뿌려놓고 v-if문으로 걸르냐 라는 소리입니다.

 

하지만 우리는 문제를 필터링 시키면 안됩니다. 문제를 무조건 다 for문으로 가져오긴 해야하는데 보고 싶은것만 보고 싶다 이겁니다!!

 

그럴 때 사용할 수 있는게 v-show입니다. v-if 와 비슷한 작용을 하지만 html태그가 사라지지 않고 남아있으면서 display만 none으로 바꿔주는 녀석입니다.

 

그렇기에 v-for문과 같이 써도 에러를 터뜨리지 않고 잘 작동해줍니다. 기특한 녀석

 

자 이제 v-if를 없애버리고 v-show를 사용해줍니다.

 

컴포넌트에 이렇게 추가를 해봅니다. index가 1번이니 문제 2번 하나만 나와야겠죠?

  <QuestionComponent v-for="(i, index) in questionAmount" :key="index"
    question="세상에서 가장 귀여운 동물은?"
    :answer="{a:'강아지', b:'고양이', c:'토끼', d:'페코'}"
    :index="index"
    v-show="index==1"
    />

 

완벽합니다 

 

자 이제 문제1을 보고 싶은데요...

 

....v-show를 고정시켜버렸으니 우리는 문제1을 볼 수가 없습니다.

 

v-show 안에 변수를 넣어서 index === (변수) 로 설정해주고 버튼을 누를때마다 변수 값을 바꿔서 보여주는 인덱스를 컨트롤 하면 참 좋을 것 같습니다.

 

바로 해보도록 합시다.

 

    <QuestionComponent v-for="(i, index) in questionAmount" :key="index"
    question="세상에서 가장 귀여운 동물은?"
    :answer="{a:'강아지', b:'고양이', c:'토끼', d:'페코'}"
    :index="index"
    v-show="index==showIndex"
    />

 

자 이제 우리는 이 showIndex를 컨트롤할 수 있는 버튼을 만들어 주어야 합니다.

 

App.vue에 만들지 QuestionComponent에 만들지는 자유입니다.

 

QuestionComponent에 만드는게 더 맞는거 같다는 생각이 들긴 하지만... App.vue에 만들어도 딱히 상관은 없을 것 같습니다.

 

App.vue에 넣어보도록 합니다.

 

어차피 showIndex만 컨트롤해주면 되는거라...딱히 상관은 없지만... 문제 박스 안에 보기 밑에 딱 버튼을 고것의 밑으로 딱 배치하고 싶은디 버튼을 자식컴포넌트 안의 div안으로 넣지를 못하니 이거... 참 귀찮아집니다.

 

? 그럼 저 QuestionComponent와 버튼을 하나의 div로 묶으면 되는거 아니냐! 라고 하실 수도 있습니다.

 

머리를 잘 굴리면 굳이 안 넣고도 배치를 잘 맞출 수 있을 것 같지만 저는 굳이 자식 컴포넌트의 보기 아래에 버튼을 배치하고 싶습니다.

 

사실 QuestionComponent에 버튼을 넣고 emit으로 넘겨주는거 하고 싶어 빌드업 한거였습니다.

 

그냥 QuestionComponent에 넣어주도록 합니다.

 

<template>
  <div>
    <div>
    <h3>문제 {{index+1}}</h3> 
    <p>{{question}}</p>
    </div>
    <div class="container d-flex justify-content-center">

    <div> &lt;보기&gt;
    <div class="answerBox d-flex align-items-center justify-content-evenly">
      <input type="radio" name="answer" value='a' id=a> <label for="a">{{ answer.a }}</label> 
      <input type="radio" name="answer" value='b' id=b> <label for="b">{{ answer.b }} </label> 
      <input type="radio" name="answer" value='c' id=c> <label for="c">{{ answer.c }} </label> 
      <input type="radio" name="answer" value='d' id=d> <label for="d">{{ answer.d }} </label> 

    </div>
    <div class="d-flex justify-content-between" style="width:500px;">

    <button>다음 문제</button>
    <button>이전 문제</button>
    </div>
    </div>
    </div>
  </div>
</template>

<script>
export default {
  props:{
    question:String,
    answer:Object,
    index:Number,
  }
}
</script>

<style>
.answerBox{
  border: 1px solid black;
  width: 500px;
  height: 100px;
}
</style>

이거 보세요 캬... 이럴려고 Component별로 나누는거 아니겠습니까

 

같은 컴포넌트 안에 넣어주자마자 바로 레이아웃이 완벽하게 딱 들어맞습니다

 

다음 문제와 이전 문제의 버튼 위치가 왜 이런다냐...

 

하여튼 이제 우리는 버튼들을 잘 배치했으니 문제 버튼을 누를 때마다 문제를 샥 샥 이동시켜주면 되는겁니다.

 

자 그럼 자식 컴포넌트에서 버튼을 눌러 부모 컴포넌트의 showIndex를 조작해주면 되겠군요!!

 

일단 CSS를 살짝 넣고 다시 돌아오겠습니다.

 

각자 CSS를 넣어보도록 합니다.

 

CSS로 이쁘게 만드는건 너무 어려운 작업입니다.

 

ChatGPT한테 해달라고 합시다.

 

자 CSS를 넣고 왔습니다 

 

개인적인 취향입니다. 버튼을 누르면 shadow넣고 transform:scale 커지게 만드는건 제가 정말 애용하는 기능 중 하나입니다.

 

submit버튼도 지멋대로 만들어놓고, hover에 pink-shadow까지 넣어줬네요 아주.. 맘에 듭니다.

 

transition ease-in-out도 걸어서 애니메이션 효과까지 넣어줬네요

 

뭐.. CSS기능들 이해 하고 나중에 누가 만들어달라고 하면 만들 수만 있으면 되는거 아니겠습니까...

 

다 할 줄은 아는 기능인데 내가 하면 안 이뻐서 그렇지....

 

갓GPT입니다 하여튼...

 

 

 

자 그럼 이제 다음 문제를 눌렀을 때 문제의 번호가 바뀌도록 해줍니다.

 

다음 문제 버튼을 클릭하면 emit 이벤트가 발생하도록 해야합니다.

 <button @click="prevMove" class="btn next-btn">이전 문제</button>
 <button @click="nextMove" class="btn prev-btn">다음 문제</button>

각 버튼에 methods를 넣어준 뒤,

 

  methods:{
    nextMove(){
      this.$emit('next-move')
    },
    prevMove(){
      this.$emit('prev-move')
    }
  
  }

각 메소드에서는 emit 이벤트를 발생시키도록 합니다. 

 

   <QuestionComponent
      v-for="(i, index) in questionAmount"
      :key="index"
      question="세상에서 가장 귀여운 동물은?"
      :answer="{a:'강아지', b:'고양이', c:'토끼', d:'페코'}"
      :index="index"
      v-show="index == showIndex"
      @next-move="nextMove"
      @prev-move="prevMove"
    />
 
 

와우... QuestionComponent에 속성들이 계속해서 늘어나고 있습니다... v-for , v-show, static-props, dynamic-props, 거기에 emit이벤트까지 다 쓰여지고 있네요

 

자 그럼 이제 emit이벤트가 발생했을대 실행시킬 method까지 정의를 해주도록 합니다.

 

 methods: {
    nextMove(){
      this.showIndex = this.showIndex+1 > 29 ? 29 : this.showIndex+1
    },
    prevMove(){
      this.showIndex = this.showIndex - 1 < 0 ? 0 : this.showIndex-1
    },

29 넘어가면 29로 바꿔주고, 0보다 작아지면 0으로 바꿔주는 그런 로직을 추가했습니다. 

 

if문으로 처리하셔도 됩니다.

완벽합니다

 

근데 문제가 하나 더 발생하고 있습니다.

 

문제를 넘기고나서 라디오버튼을 누르면 이전 라디오 버튼이 사라지는 문제입니다.

사라진 라디오 버튼

 

같은 name을 공유하고 있어서 그런 것 같습니다.

 

name값에도 해당 인덱스 값을 추가로 넣어주도록 합니다. 

 

name값에 변수를 넣어주어야 하니 name을 v-bind시켜주도록 하구요

 

<input type="radio" :name="'answer'+index" value='a' id=a> <label for="a">{{ answer.a }}</label> 
<input type="radio" :name="'answer'+index" value='b' id=b> <label for="b">{{ answer.b }} </label> 
<input type="radio" :name="'answer'+index" value='c' id=c> <label for="c">{{ answer.c }} </label> 
<input type="radio" :name="'answer'+index" value='d' id=d> <label for="d">{{ answer.d }} </label>

대충 이렇게 input에 name을 바인딩 시켜주도록 하자 이겁니다.

 

그럼 각 문제별 다른 라디오 버튼이 나올테니까 말이죠

이제 다른 문제를 가서 다른 답변을 선택하더라도

기존의 문제는 변하지 않은 것을 알 수 있습니다!!

 

이제 문제를 만들 준비가 점차 되어가고 있는 것 같습니다.

 

 

Vue 를 배운지 벌써 1주일이 지나가는 것 같은데 뭐라도 안 만들어보면 금방 또 까먹게 될 것 같아 

 

웹 페이지 하나를 만들어보려고 합니다.

 

node는 웹페이지에서 다운로드 받으시고ㅡ,

 

npm install -g @vue/cli 를 이용하시면 vue/cli를 다운로드 받을 수 있습니다.

 

node.js와 vue/cli를 잘 깔아 뒀으니 이제 mypjt 뷰 프로젝트를 하나 만들겠습니다.\

 

vue create mypjt 로 뷰 프로젝틀르 만들어줍니다.

 

cd mypjt -> npm run serve 로 

 

뷰 프로젝트가 잘 완성된 것을 확인할 수 있습니다.

 

이제 쓸데없는 HelloWorld.vue 컴포넌트를 없애준뒤 시작해보겠습니다.

 

자 HelloWorld 대신 QustionComponent를 만들고 components에도 잘 추가해주었습니다.

 

자 이제, App.vue에서 Question Component에 문제들을 전달해 줄겁니다.

 

여러개의 Question Component를 App vue에 만들건데 각자 다른 정보들을 가지고 있어야 합니다.

 

Question Component안에 문제를 만들어 버리면 거기서 컨트롤을 하기가 좀 어려울 것 같아 App.vue에서 props로 전달해 주도록 하겠습니다.

 

일단 잘 작동을 하는지 테스트용으로 question과 answer을 넘겨보도록 하겠습니다.

 

<template>
  <div id="app">
    <QuestionComponent 
    question="세상에서 가장 귀여운 동물은?"
    :answer="{a:'강아지', b:'고양이', c:'토끼', d:'페코'}"
    />
  </div>
</template>

 

자 QuestionComponent에서 값을 잘 받을 수 있도록 QuestionComponent의 props에서 정의를 해주도록 합시다.

 

 

<script>
export default {
  props:{

    question:String,
    answer:Object,
  }
}
</script>

props에서 받아주어야 한다는걸 까먹으면 안되겠습니다.

 

그리고 html에 적절하게 넣어서 출력을 해보도록 하겠습니다.

<template>
  <div>
    <div>
    <h3>문제 1</h3>
    <p>{{question}}</p>
    </div>
    <div>
    <p> &lt;보기&gt;</p>
    <p>{{answer}}</p>
    </div>
  </div>
</template>

아주 좋습니다. 이제 이것들을 가공해서 이뻐보이게 만들면 되겠군요

 

좀 이뻐보이게 만들기 위해서 bootstrap을 깝니다. npm install bootstrap 해주면 됩니다.

 

그런 뒤 main.js에서 

import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'

이 2개를 css에 추가해주면 됩니다.

 

보기가 훨씬 나아졌습니다.

 

오브젝트 타입에 있는 아이들을 하나씩 꺼내주고 라디오 버튼을 달아놓았습니다

 

 

<template>
  <div>
    <div>
    <h3>문제 1</h3> 
    <p>{{question}}</p>
    </div>
    <div class="container d-flex justify-content-center">

    <div> &lt;보기&gt;
    <div class="answerBox d-flex align-items-center justify-content-evenly">
      <input type="radio" name="answer" value='a' id=a> <label for="a">{{ answer.a }}</label> 
      <input type="radio" name="answer" value='b' id=b> <label for="b">{{ answer.b }} </label> 
      <input type="radio" name="answer" value='c' id=c> <label for="c">{{ answer.c }} </label> 
      <input type="radio" name="answer" value='d' id=d> <label for="d">{{ answer.d }} </label> 

    </div>
    </div>
    </div>
  </div>
</template>

<script>
export default {
  props:{
    question:String,
    answer:Object,
  }
}
</script>

<style>
.answerBox{
  border: 1px solid black;
  width: 500px;
  height: 100px;
}
</style>

 

 

Vuex.store에 들어있는 vue의 상태 state 와 이를 다루는 Mutations(동기)와 Actions(비동기)를 컴포넌트에서 사용할 때 간단하게 구현해주는 좋은 헬퍼 메소드가 있습니다


웬만하면 actions에서 mutations를 commit해서 사용하는게 일반적이라고 하는데 예시로 바로 사용해봅니다

Store에 있는 애들을 바로 컴포넌트로 가지고 올 수 있습니다.

가지고 왔으니 바로 사용도 가능합니다


이렇게 바로 가지고 와서 컴포넌트의 템플릿에 쏴주는게 가능합니다!!

이게 엄청 편한건데 이걸 처음부터 무작정 쓰면 편한지 모르거든요...

mapState안에 오브젝트를 써서 사용할 변수(사실 함수)의 이름을 바꿔줄 수도 있고,

mapMutations의 함수들을 그대로 가지고 와 콜백함수로 재사용도 가능합니다


그럼 이게 도대체 왜 편한거냐!

원래는 이렇게 짜야 하거든요


count가 사실 함수라고 했던 이유는 이런식으로 로직이 작동하기 때문입니다. State값을 반횐하는 함수인거죠

Mutations도 commit으로 불러와야하는데 저 작업을 mapMutations가 미리 다 해서 우리는 함수를 바로 갖다 쓰는 것 처럼 활용할 수 있는겁니다

Vuex의 맵핑 헬퍼 메소드 아주 꿀이에요

https://school.programmers.co.kr/learn/courses/30/lessons/181188

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


버스타고 집에 돌아오는 길에 일단 알고리즘을 어떻게 짤지 고민을 좀 해보게 되는 문제였습니다.

그래서, 기초 알고리즘이라고 하기에는... 좀 그렇지 않나 싶긴 하지만 그래도 Arrays.sort를 구현해볼 수 있는 문제였기에 넣었습니다!


일단 간단하게 로직을 생각해 보았습니다.

시작 지점으로 정렬을 하면 가장 빠르게 시작하는 타겟은 무조건 총을 쏴야할거고, 가능한 그 지점과 겹치는 애가 많게 쏴야합니다.

하지만, 선택한 타겟의 시작지점에서 끝 지점까지의 구간 중 겹치는게 아무리 많더라도, 겹치는 타겟의 구간이 종료되면 같이 거기서 타겟을 쏴야합니다.

왜냐면, 시작 지점으로 정렬을 했기 때문에, 겹쳤던 타겟의 구간을 지나버리면 어차피 다시 돌아와 한번 더 쏴야하기 때문이죠


그래서 선택한 구간 사이에 있는 타겟들을 차례대로 선택하고, 동시에 선택한 구간보다 더 빨리 끝나는 애가 있으면 가능한 구간을 좁혀준다! 가 포인트가 되겠습니다.

그럼 이제 배열을 정렬을 해야 할텐데, 아직 자바에 익숙하지 않아 고생 좀 했습니다...

import java.util.*;
class Solution {
    public int solution(int[][] targets) {
        int answer = 0;
        
        Arrays.sort(targets, (el1, el2)->{
            if(el1[0] == el2[0]){
                return Integer.compare(el1[1], el2[1]);
            } else {
                return Integer.compare(el1[0], el2[0]);
            }
        });
        boolean check = true;
        int start_num = targets[0][0];
        int end_num = targets[0][1];
        if(targets.length == 1){
            answer = 1;
        }
        for(int i=1; i<targets.length; i++){
            if (check == true){              
                check = false;
            } 
                if (targets[i][0] < end_num){
                    end_num = end_num > targets[i][1] ? targets[i][1] : end_num;
                } else{
                    answer += 1;
                    start_num = targets[i][0];
                    end_num = targets[i][1];
                    check = true;
                    if(i == targets.length-1){
                        check = false;
                    }
                    
                    
                    
                }
            }
        if (check == false){
            answer++;
        }
            
        
        
        return answer;
    }
}

https://school.programmers.co.kr/learn/courses/30/lessons/172928

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 
N, S, E, W 값을 받아 해당 방향으로 주어진 숫자만큼 이동하는 문제입니다.
 
4 방향으로 이동을 하는 간단한 문제지만, 왠지 쉽게 풀리지 않았습니다.
 
방향과 이동 횟수가 String으로 주어지는데, 그걸 split 하는 과정에서 [N, S, E, W] 값이 String으로 들어오기 때문에,
 
그걸 direction == "S" 와 같이 비교를 해주니 값이 계속 false가 나와 풀리지 않았습니다.
 
문자열과 문자열을 비교할 때는 == 연산을 쓰는 것이 아니라 equals로 비교를 해야 제대로 된 답이 나옵니다.
 
문제를 풀고 나서 다른 사람들은 자바로 어떻게 문제를 풀었는지 확인해보니 charAt(0) 을 이용하여 char로 바꿔준 뒤 값을 비교하는 방법을 사용하기도 하였습니다.

 

class Solution {
    public int[] solution(String[] park, String[] routes) {
        int[] answer = {};
        int si = 0;
        int sj = 0;
        int[] di = {-1, 1, 0, 0};
        int[] dj = {0, 0, -1, 1};
        int d = 0;
        for(int i = 0; i < park.length; i++){
            String row = park[i];
            for(int j = 0; j < row.length(); j++){
                if (row.charAt(j) == 'S'){
                    si = i;
                    sj = j;
                }
            }
        }
        int ci = si;
        int cj = sj;
        for(String op : routes){
            String direction = op.split(" ")[0];
            System.out.println(direction);
            int num = Integer.valueOf(op.split(" ")[1]);
            int ni = ci;
            int nj = cj;
            if (direction.equals("N")){
                d = 0;
            } else if(direction.equals("S")){
                d = 1;
            } else if (direction.equals("W")){
                d = 2;
            } else if (direction.equals("E")){
                d = 3;
            }
            for(int i=0; i<num; i++){
                ni += di[d];
                nj += dj[d];
                if (ni < 0 || ni >= park.length || nj < 0 || nj >=park[0].length()){
                    break;
                } else if(park[ni].charAt(nj) == 'X') {
                    break;
                }
                if (i == num-1){
                    ci = ni;
                    cj = nj;
                }
            }
        }
        answer = new int[2];
        answer[0] = ci;
        answer[1] = cj;
        return answer;
    }
}

 

카카오 코딩 테스트 문제라고 해서 꽤나 어려울 줄 알았는데, 비교적 로직 자체는 간단해서 그냥 제출했다가 틀렸습니다를 보고 나서야 반례를 부랴부랴 넣은 문제였습니다.

파이썬으로 했다면 좀 더 반례를 세밀하게 찾았을 것 같은데 자바 언어 자체가 아직 익숙하지 않다보니 좀 더 놓치는 부분이 많게 되는 것 같아요..

개인정보 수집 만료일은 개인정보 수집 일자에서 유효기간을 더한 날 - 1일 이라는 것만 체크하고 그걸 오늘 날짜와 비교하기만 하면 되는데,

문제는 오늘 날짜의 연, 월, 일을 어떻게 분리해 낼 것인가 하는거였습니다.

다행히 파이썬과 마찬가지로 자바에서도 문자열에 split을 해주면 문자열을 쪼개어 배열로 만들 수 있었습니다.

정말 많이 사용하게 되는게 공백을 기준으로 자르는 문자열.split(" "); 인데요



terms와 privacies 배열을 보면 값들이 공백으로 나누어져있는 걸 알 수 있습니다.

이런 값들을 가져올 때 공백을 기준으로 split해주면 손 쉽게 값들을 가져올 수 있습니다.

문제는 today와 같이 문자열에서 “ . ” 으로 구분되어있는 문자열을 쪼개는건데요

파이썬에는 그냥 split(".") 으로도 됐던 것 같은데... 아닌가..

split안에는 정규식이 들어가기 때문에 . 하나만 넣으면 문자 하나 로 인식을 해버리기 때문에 split("[.]") 으로 구분자를 설정해주어야 제대로 분해하는 걸 볼 수 있습니다.


import java.util.*;

class Solution {
    public int[] solution(String today, String[] terms, String[] privacies) {
        int[] answer = {};
        List<Integer> result = new ArrayList<Integer>();
        // 1. today를 year, month, day로 나누기
        String[] today_date = today.split("[.]");
        int today_year = Integer.valueOf(today_date[0]);
        int today_month = Integer.valueOf(today_date[1]);
        int today_day = Integer.valueOf(today_date[2]);
        
        Map<String, Integer> expire_map = new HashMap();
        
        // 2. 영어별로 만료 기간 받아오기
        for(int i=0; i<terms.length; i++){
            String alpha = terms[i].split(" ")[0];
            int expire_date = Integer.valueOf(terms[i].split(" ")[1]);
            expire_map.put(alpha, expire_date);
        }
        
        // 3. privacies 만료되는 날짜 찾기
        
        for(int i=0; i<privacies.length; i++){
            String[] target_date = privacies[i].split(" ")[0].split("[.]");
            String target_aplha = privacies[i].split(" ")[1];
            
            int target_year = Integer.valueOf(target_date[0]);
            int target_month = Integer.valueOf(target_date[1]);
            int target_day = Integer.valueOf(target_date[2]);
            target_month += expire_map.get(target_aplha);
            target_day -= 1;
            // 만약 day를 줄였는데 0이 된다면 28로 바꾼뒤 month를 -1 추가로 시켜야함
            if (target_day ==0){
                target_day = 28;
                target_month -= 1;
            }
            if (target_month==0){
                target_month = 12;
                target_year -= 1;
            }
            // 만약 month가 12를 넘었으면 12의 나머지를 month로!
            if (target_month > 12){
            target_year += target_month/12;
            target_month %= 12;
            if(target_month == 0){
                target_month = 12;
                target_year -= 1;
            }
            }
            System.out.printf("%d, %d, %d\n", target_year, target_month, target_day);
            
            // 이제 오늘과 값 비교하기 (오늘보다 만료날짜가 작다면 answer에 넣어주기)
            if (target_year < today_year){
                result.add(i+1);
            }else if( target_year == today_year && target_month < today_month ){
                result.add(i+1);
            }else if( target_year == today_year && target_month == today_month && target_day < today_day){
                result.add(i+1);
            }
        }
        answer = new int[result.size()];
        for(int i=0; i<result.size(); i++){
            answer[i] = result.get(i);
        }
            return answer;
    }
}

이렇게 주어진 문자열에서 연, 월, 일을 잘 분리해서 가져올 수 있느냐!

그리고 연 월 일을 적절하게 증가, 감소시킬 수 있느냐를 묻는 문제였습니다

https://school.programmers.co.kr/learn/courses/30/lessons/176963?language=java

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

파이썬에서는 리스트 값으로 인덱스를 매우 쉽게 구할 수 있습니다.

시간복잡도가 O(n)이기 때문에 자주 사용하지는 않지만, 그래도 반복문을 돌리고 if문을 걸어가며 찾는 것보다 수월하게 찾을 수 있죠.

그렇게 많이 사용할 것 같진 않지만 파이썬으로 할 수 있는걸 자바에서도 하고 싶어서 찾아봤는데

일반 배열에서는 사용하지 못하고 List로는 사용할 수 있다고 합니다.

그렇기에 asList를 이용하여 형변환을 해준 뒤 indexOf를 사용하여 간단하게 문제를 풀 수 있었습니다.

또한 파이썬의 반복문은 객체를 순회하면서 그 안의 객체를 받아오는게 가능한데, 자바에서도  ‘ : ’ 을 활용하여 구현할 수 있었습니다.

String[] names = [“duljji”, “duljji2”] 가 있으면 for(int i=0; i<names.length; i++)  가 아니라  for(String name : names)로 바로 값들을 가져올 수 있는거죠

물론 인덱스가 필요할 때에는 사용할 수 없지만 파이썬에서도 꽤 유용하게 자주 사용하던 방식이라 익혀두었습니다.


For 문으로 photo의 각 배열들을 가져오고 그 각 배열들 안의 String 객체를 for문을 다시 한번 돌려 가져와주었습니다.

그리고 그 값의 인덱스를 찾아서 점수를 더해주어 짧게 풀 수 있었습니다.

+ Recent posts