흔하게 많이 사용되는 간단한 알고리즘(?)을 정리하려한다

 

다음과 같이 객체로 이루어진 리스트가 있다

const transactionList = [
        {
          id: 1,
          transactionId: 99,
          sellDate: "20220801",
          price: 300,
        },
        {
          id: 2,
          transactionId: 99,
          sellDate: "20220701",
          price: -200,
        },
        {
          id: 3,
          transactionId: 99,
          sellDate: "20221001",
          price: 400,
        },
        {
          id: 4,
          transactionId: 99,
          sellDate: "20221101",
          price: -100,
        },
      ];

 

 

[Case1] 여기서 sellDate 가 가장 빠른 날짜를 찾고 싶다

 

1. 리스트 안의 Object 에서 필요한 키(sellDate)에 해당하는 Value 들을 다 뽑아서 리스트로 구성한다

2. Math.min을 이용하여 최솟값을 리턴해준다

function findMin(key) {
	return Math.min(...transactionList.map((obj) => obj[key]));
}
const firstTradeDate = findMin("sellDate")


// result : 20220701

 

 

[Case2] 이번엔 sellDate가 가장 빠른 날짜 객체 통채로를 찾고 싶다.

reduce 메소드를 이용하여 해당 객체의 index를 가져오기로 한다

  const getFirstTargetIndex = (array, target) =>
    array.reduce(
      (prevIdx, value, curIdx, arr) =>
        value[target] >= arr[prevIdx][target] ? prevIdx : curIdx,
      0
    );

  const answer = getFirstTargetIndex(transactionList, "sellDate");
  console.log("answer", answer);
  
  // result : 1

 

reduce 메소드는 매개변수로 콜백함수와 초기값을 받는다

 

콜백함수

(prevIdx, value, curIdx, arr) =>
        value[target] >= arr[prevIdx][target] ? prevIdx : curIdx,

콜백함수는 (이전 index, 현재 object, 현재 index, 리스트) 를 매개변수로 받는다

여기서 이전 Index는 계속 갱신되는 값이다.

누산을 통해서 우리는 index 값을 리턴받기 때문에 index 값이 받아지는 것이다.

 

 

초기값

0

여기서 0은 직접 비교대상이 되는 숫자 0의 역할이 아니고, 초기 index 역할이다

arr[0] 번째의 값을 초기값으로 잡아서 비교를 시작한다

 

 

질문 받았던 내용 중에 머릿 속에서 구름처럼 어렴풋이 알았던 것이 드러나서 구름을 걷어내 보고싶었다.

 

1. 객체 (Object)

객체는 대상, 오브젝트, 한 덩이(?), 하나의 개념 

예를 들어 사람을 하나의 객체라고 할 수 있고, 자동차를 하나의 객체라고 할 수 있다.

프로그래밍 적으로는 커스텀한 ValidException 을 하나의 객체라 할 수 있고,

한가지 로직을 수행하는 SomethindService 도 하나의 객체라고 할 수 있다.

 

 

2. 클래스 (Class)

객체들이 공통적으로 갖는 속성들을 모아서 정의 내린 것을 클래스 라고 한다.

흔히 말하는 객체는 붕어빵, 클래스는 붕어빵 틀

좀더 코드에 가깝게 예시를 들면 class SomethingService 내부에 어쩌구 서비스를 구성하는 요소들을 정의하고,

const somethingService = new SomethingService 로 클래스(빵틀) 에서 그때 그때 필요한 객체(빵) 을 찍어내는 것이다.

 

 

3. 객체지향 프로그래밍 (OOP)

객체를 좋아하는(추구하는) 프로그래밍(개발)이다

다른 개념으로 절차 지향 프로그래밍이 있는데, 절차 지향 프로그래밍은 프로세스가 함수 단위로 순서대로 진행되는 것을 말한다

반면 OOP는 객체들의 유기적인 관계를 통해서 프로세스가 진행된다.

 

어플리케이션을 구성하는 요소들을 객체로 바라보고, 객체끼리 연동, 유기적으로 작동하는 것

 

4. 객체지향 프로그래밍의 특징

1) 추상화

김철수, 이영희, 박훈이, 최짱구 등의 개개인이라는 객체를 공통화하여 사람이라는 클래스로 추상화 하는 것이다.

추상화는 손에 잡히는 형체는 없지만 머릿속에 어떤 개념이 잡히도록 하는 것을 말한다.

객체지향프로그래밍에서 추상화는 어플리케이션에서 사용되는 객체들의 공통된 특징을 파악하여 클래스로 정의해두는 설계기법을 말한다.

 

2) 캡슐화

추상화를 하면서 객체 내부의 특징들을 private, public으로 정의할 수 있는데,

private 으로 정의된 속성들이 캡슐화이다.

외부에서 건드릴 필요 없는 내부로직들을 캡슐화 함으로써 정보를 은닉하고, 독립적으로 작동할 수 있도록 한다.

변경에 유연한 프로그램을 만들기 위해서 독립시키는 것이다.

 

3) 상속

코드의 중복을 피하기 위함

부모 클래스로 부터 기본적인 속성을 상속 받고, 각각의 개성을 살리는 자식 클래스를 만들어 낼 수 있다.

Exception이 있고, 그 Exception을 상속받아서 ValidException을 개성있게 만들어 낼 수 있다.

 

4) 다형성

부모 클래스로부터 상속받은 속성에 대해서, 이미 상속받았으니까 끝이 아니고, 자식 클래스에서 그 속성을 재정의 할 수 있다.

이것을 오버라이딩이라고 한다.

같은 이름의 속성을 유지할 수 있다.

메서드 이름을 낭비하지 않을 수 있고, 이름을 뭘로 지어야할지 고민하는 시간이 줄어든다.

 

 

OOP 는 코드를 여럿이서 협업하여 작성할 때 룰을 규정해 나가면서, 누가봐도 이해하기 쉽고, 유지보수하기 쉬운 방법으로 발전한 프로그래밍 방법이라고 할 수 있다.

제목을 어떻게 지어야할 지 고민이었다.

 

물론 방법을 찾는 과정에서도 검색어를 뭐라고 해야할지도 고민이었다.

 

일단 하고싶은건, 로컬 변수를 쓰는 페이지나 컴포넌트 내부에 공통으로 자주 쓰이는 함수를 common 화 하는 것이었다.

 

이전에는 흔하게 functions/common.js 이런식으로 자주 사용되는 함수들을 죄다 모아놓던가 했었다.

 

함수 별로 각각 사용되는 매개변수들을 일일히 정의하고 넘겨주는 것은 덤이요,,,

 

 


최종적으로 선택해서 사용한 방법 Mixins)

redirect to 👉 https://tacit.tistory.com/166

 

 

[Nuxt] (mixins 사용법) vue 페이지나 컴포넌트에서 분리된 공통 함수에서 this 사용하기

[Nuxt] Vue 페이지나 컴포넌트에서 분리된 공통 함수 사용하기 [Nuxt] Vue 페이지나 컴포넌트에서 분리된 공통 함수 사용하기 제목을 어떻게 지어야할 지 고민이었다. 물론 방법을 찾는 과정에서도

tacit.tistory.com

 


 

⬇️ 아래는 삽질 history ⬇️

 

예시)

/pages/start.vue : 시작 폼 페이지

<template>
    <form @submit.prevent="submitForm">
        <div class="email-wrapper">
          <label for="email" class="font-15b form-label">이메일</label>
          <input
            v-model="email"
            type="text"
            class="form-input"
            :class="{'input-error': !emailValidation}"
            placeholder="example@domain.com"
            maxlength="100"
            required
            @input="onInputEmail"
          />
          <p v-if="!emailValidation" class="font-15r warning">올바르지 않은 이메일 형식입니다</p>
        </div>

        <div class="phone-wrapper">
          <label for="phone" class="font-15b form-label">연락처</label>
          <input
            v-model="phone"
            class="form-input"
            :class="{'input-error': !phoneValidation}"
            type="text"
            placeholder="010-1234-5678"
            maxlength="20"
            required
            @input="onInputPhone"
          />
          <p v-if="!phoneValidation" class="font-15r warning">올바르지 않은 전화번호 형식입니다</p>
        </div>
        <div class="button-wrapper">
          <button class="font-20b submit-button" type="submit">컨설팅 신청하기</button>
        </div>
    </form>
</template>

 

 

동일한 파일(/pages/start.vue) 함수 부분

<script>
export default {
  name: 'Start',
  data() {
    return {
      email: '',
      phone: '',
      emailValidation: true,
      phoneValidation: true,
      phoneFormat
    }
  },
  methods: {
    onInputEmail() {
      this.email = this.email.replace(/[^a-zA-Z0-9@._]*$/gi, '')
      // 이메일 정규식
      const emailRegex = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/
      // 이메일 정규식 부적합
      if (!emailRegex.test(this.email)) {
        this.emailValidation = false
      } else {
        this.emailValidation = true
      }
    },
    onInputPhone() {
      this.phone = this.phoneFormat(this.phone)
      // 휴대폰 정규식
      const phoneRegex = /^\d{2,3}-\d{3,4}-\d{4}$/
      // 휴대폰 정규식 부적합
      if (!phoneRegex.test(this.phone)) {
        this.phoneValidation = false
      } else {
        this.phoneValidation = true
      }
    }
  }
}
</script>

 

 

phone / email 정규식 체크하고, 포멧 맞춰주는 일을 하는 함수를 공용화 하고 싶었다.

 

방법1) 그냥 평소 쓰던 것 처럼 functions/common.js 에 함수들 정의하고 필요한 인자들을 넘겨줘서 값을 리턴 받기

방법2) functions/common.js 안에 함수를 정의 하지만, 인자를 정의하지 않고, vue 스코프 내부의 this를 전체 전달하기

방법3) plugins/common.js 플러그인으로 빼서 전역함수로 inject 하여 this.$phoneCheck() 로 사용하기

 

 

 

방법1) 예시

 // functions/common.js
 
 export const onInputEmail= (email, emailValidation) => {
  email = email.replace(/[^a-zA-Z0-9@._]*$/gi, '')
  // 이메일 정규식
  const emailRegex = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/
  // 이메일 정규식 부적합
  if (!emailRegex.test(this.email)) {
    emailValidation = false
  } else {
    emailValidation = true
  }
  return email, emailValidation
},

// pages/start.vue

import { onInputEmail } from "~/functions/common.js"

this.phone, this.emailValidation = this.onInputEmail(this.phone, this.emailValidation)

 

- 페이지별로 공통적인 역할을 하는 함수이긴 하지만, 약간씩 달라지는 조건이 있어서, 넘겨줘야할 인자들이 점점 많아지고, 함수 내부에서 분기를 쳐야하는 경우가 생기기도 한다.

- 리턴 받는 값으로 페이지 내부 로컬 변수들에 할당해 주어야한다.

- 참고로 위의 코드가 동작하는지는 모름 ㅋ

 

 

방법2) 지역 this 넘겨주기

// functions/common.js


export const phoneValidate = localThis => {
  localThis.phone = localThis.phoneFormat(localThis.phone)
  // 휴대폰 정규식
  const phoneRegex = /^\d{2,3}-\d{3,4}-\d{4}$/
  // 휴대폰 정규식 부적합
  if (!phoneRegex.test(localThis.phone)) {
    localThis.phoneValidation = false
  } else {
    localThis.phoneValidation = true
  }
}

export const emailValidate = localThis => {
  localThis.email = localThis.email.replace(/[^a-zA-Z0-9@._]*$/gi, '')
  // 이메일 정규식
  const emailRegex = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/
  // 이메일 정규식 부적합
  if (!emailRegex.test(localThis.email)) {
    localThis.emailValidation = false
  } else {
    localThis.emailValidation = true
  }
}

페이지, 컴포넌트 에서 사용하기

// pages/start.vue

<template>
    <form>
        <div class="email-wrapper">
          <label for="email" class="font-15b form-label">담당자 이메일</label>
          <input
            v-model="email"
            type="text"
            class="form-input"
            :class="{'input-error': !emailValidation}"
            placeholder="example@domain.com"
            maxlength="100"
            required
            @input="onInputEmail"
          />
          <p v-if="!emailValidation" class="font-15r warning">올바르지 않은 이메일 형식입니다</p>
        </div>

        <div class="phone-wrapper">
          <label for="phone" class="font-15b form-label">담당자 연락처</label>
          <input
            v-model="phone"
            class="form-input"
            :class="{'input-error': !phoneValidation}"
            type="text"
            placeholder="010-1234-5678"
            maxlength="20"
            required
            @input="onInputPhone"
          />
          <p v-if="!phoneValidation" class="font-15r warning">올바르지 않은 전화번호 형식입니다</p>
        </div>
    </form>
</template>

<script>
import {phoneValidate,emailValidate} from '~/functions/common.js'

export default {
  name: 'Start',
  data() {
    return {
      phoneValidate,
      emailValidate,
      email: '',
      phone: '',
      emailValidation: true,
      phoneValidation: true,
      phoneFormat
    }
  },
  methods: {
    onInputEmail() {
      this.emailValicate(this)
    },

    onInputPhone() {
      this.phoneValidate(this)
    },
  }
}
</script>

 

 

- 냅다 로컬 this를 넘겨버림

- 리턴 할 필요 없이 분리된 함수에서 바로 this.phone = '1231313' 이런식으로 바로 값을 할당 가능

- 분리된 함수 페이지 (functions/common.js) 내부 함수를 화살표함수로 정의하면 함수가 호출된 부모의 this를 참조하므로 지역 this를 인자로 안넘겨줘도 작동할 것이라고 예상했으나, 그렇지 않았다.

- 넘겨주는 인자 없이 functions/common.js 내부의 phoneValidate() 함수 안에서 console.log(this) 를 하면 undefined 가 떴다.

 

 

 

방법3) plugins

전체적으로 2번과 동일하지만 구현 방법의 약간의 차이가 있을 뿐

플러그인을 사용할 뿐,,

 

플러그인 파일 생성 (그냥 functions/common.js 같은 역할)

 

// plugins/common.js

export default (context, inject) => {
  const phoneValidate = localThis => {
    localThis.phone = localThis.phoneFormat(localThis.phone)
    // 휴대폰 정규식
    const phoneRegex = /^\d{2,3}-\d{3,4}-\d{4}$/
    // 휴대폰 정규식 부적합
    if (!phoneRegex.test(localThis.phone)) {
      localThis.phoneValidation = false
    } else {
      localThis.phoneValidation = true
    }
  }
  inject('phoneValidate', phoneValidate)
}

 

 

nuxt.config.js에 plugins 등록

기존에 등록되어있는것 그대로 두고, 리스트 형태로 추가 추가 추가

 

// nuxt.config.js


  // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
  plugins: [
    '~/plugins/axios/interceptors.js',
    '~/plugins/vuex-persistedstate',
    '~/plugins/common.js'
  ],

 

페이지나 컴포넌트에서 사용하기

 

// pages/start.vue

<template>
    <form>
        <div class="phone-wrapper">
          <label for="phone" class="font-15b form-label">담당자 연락처</label>
          <input
            v-model="phone"
            class="form-input"
            :class="{'input-error': !phoneValidation}"
            type="text"
            placeholder="010-1234-5678"
            maxlength="20"
            required
            @input="onInputPhone"
          />
          <p v-if="!phoneValidation" class="font-15r warning">올바르지 않은 전화번호 형식입니다</p>
        </div>
    </form>
</template>

<script>
export default {
  name: 'Start',
  data() {
    return {
      phoneValidate,
      phone: '',
      phoneValidation: true,
      phoneFormat
    }
  },
  methods: {
    onInputPhone() {
      this.$phoneValidate(this)
    },
  }
}
</script>

 

- 별도의 import 없이 this.$함수명으로 접근하여 사용할 수 있다.

- input 태그에서 @input=$phoneValidate(this) 로 바로 사용은 불가능 했다.

 

 

 

결론적으로 localThis를 통째로 넘겨주는 방법으로 구현을 할 수 있다는걸 알게 되었으나,,

왜 화살표 함수를 썼는데 호출부의 this 를 읽지 못하는지,

왜 @input 함수에서 바로 사용하지 못하는지, 

 

해결해야할 의문이 남아있다

 

to be...continue...(?)

input 에 required를 주고는 조건(v-if) 에 따라서 display none이 되도록 설정되어있음

 

 

 

required로 되어 있는데… div에서 display:none으로 보이지 않도록 했으니깐…  submit를 누르면 에러표시를 할 수가 없어서 그런거다.

 

공존할 수 없는 몇가지가 있다

readonly & requried 불가

'programming > error' 카테고리의 다른 글

[RangeError] : Incorrect locale information provided  (0) 2025.05.13

 

1. GET -> 파라미터로 받아온 id로 해당되는 Cat 객체 찾기


router.get('/cats/:id', (req, res) => {
    try {
        const params = req.params;
        const cats = Cat.find((cat)=> { 
            return cat.id === params.id
        })
        res.status(200).send({
            success : true,
            data: {
                cats
            }
        })       
    }catch(error) {
        res.status(400).send({
            success: false,
            error : error.message
        })
    }
})

 

 

2. POST -> 신규 객체 추가

router.post('/cats', (req, res) => {
    try {
        const data = req.body
        console.log(data)
        Cat.push(data)
        res.status(200).send({
            success: true,
            data : {}
        })
    }
    catch(error){
        res.status(400).send({
            success: false,
            error: error.message
        })
    }
})

 

 

3. PUT -> 기존 객체 전체 갈아끼우기

router.put('/cats/:id', (req, res) => {
    try {
        const params = req.params;
        const data = req.body;
        let result
        Cat.forEach((cat) => {
            if(cat.id === params.id) {
                cat = data;
                result = cat;
            }
        })
        res.status(200).send({
            success : true,
            data: {
                cat : result
            }
        })       
    }catch(error) {
        res.status(400).send({
            success: false,
            error : error.message
        })
    }
})

 

 

4. PATCH -> 기존 객체 부분 수정

구조분해 할당 모르면 난리 브루스 펼쳐지는 부분

cat = { ...cat : 기존에 있던 객체들을 펼쳐서 오브젝트에 담기 => ...data : 새로 변경할 키-속성 값을 덮어 쓰기 }

 

router.patch('/cats/:id', (req, res) => {
    try {
        const params = req.params;
        const data = req.body;
        let result
        Cat.forEach((cat) => {
            if(cat.id === params.id) {
                cat = { ...cat, ...data}
                result = cat;
            }
        })
        res.status(200).send({
            success : true,
            data: {
                cat : result
            }
        })       
    }catch(error) {
        res.status(400).send({
            success: false,
            error : error.message
        })
    }
})

 

express 설치 후

 

post로 JSON 객체를 보내려는데, undefined 가 뜬다

 

post 라우터

app.post('/cats', (req: express.Request, res: express.Response) => {
    try {
        const data = req.body
        console.log(data)
        res.status(200).send({
            success: true,
            data : {}
        })
    }
    catch(error){
        res.status(400).send({
            success: false,
            error: error.message
        })
    }
})

 

포스트맨에서 테스트한 내용

 

백엔드 라우터에서 콘솔로그로 받은 req.body -> undefined

 

 

이는 아주 간단하게 해결된다

express가 json 데이터를 읽을 수 있도록 내장 미들웨어를 설정해 주기만 하면 된다

 

app.use(express.json())

 

express 라우터를 구성할 때 주의점은 순서가 중요하다는 것이다.

먼저 실행되어야 할 미들웨어들을 위로 올리고,

차례로 라우터들을 배치하고,

마지막에 not found 에러 처리를 위한 미들웨어 등 후순위 미들웨어를 배치한다

 

json 미들웨어를 추가하고 나서 정상적으로 req.body 가 찍히는 모습 확인

 

잘 디자인 된 웹 API 라면

1. 플랫폼 독립성 : 내부에서 어떤 방식으로 구현되는지와 관계없이 API를 호출할 수 있어야 한다. 표준 프로토콜을 사용

2. 서비스 진화 : API가 수정되어도(진화) 기존의 어플리케이션은 수정 없이 계속 작동할 수 있어야 한다.

 

기본 디자인 원칙

1. 리소스 중심 디자인, 클라이언트에서 접근할 수 있는 모든 종류의 개체, 데이터, 서비스가 리소스에 포함된다.

https://tacit.com/orders

2. 리소스마다 해당 리소스를 고유하게 식별하는 URI인 식별자가 있다.

https://tecit.com/orders/5

3. HTTP 요청은 독립적, 상태를 저장하지 않는다.

4. 표준 HTTP 동사 수행 : GET, POST, PUT, PATCH

5. 리소스 URI는 명사를 기반으로 해야한다

https://tecit.com/orders  // Good
https://tecit.com/add-order  // Bad

6. 여러 관계 수준을 탐색하기 위한 URI 제공시 -> 컬랙션/항목/컬렉션 보다 더 복잡하게 설계하지 않는 것이 좋다

https://tecit.com/customers/1/orders/99/products  // Bad

// Good : 2가지로 나누어서 제공
https://tecit.com/customers/1/orders
https://tecit.com/orders/99/products

7. API 요청이 많을 수록 웹서버의 부하를 높인다

8. Web API와 기본데이터 원본 사이에 종속성이 발생하지 않도록 해야한다. -> 관계형 데이터베이스 구조를 그대로 Web API 리소스로 사용할 필요가 없다.

예를 들어, premium_order, basic_order 이렇게 2가지 오더 테이블이 존재한다고 해서 /premium-orders, /basic-orders 와 같이 따라갈 필요가 없다. /orders처럼 관계형 데이터베이스를 추상화 한다.

9. 일부 작업을 특정 리소스에 매핑하지 못하는 경우 -> 의사 리소스로 표시하고 쿼리 문자열을 사용하여 필요한 매개 변수를 지정하는 URI를 제공한다

/add?operand1=99&operand2=1

 

HTTP 메서드 별 작업 정의

- GET : 리소스의 표현 검색, 응답메시지 : 리소스의 세부 정보

- POST : 새로운 리소스 생성, 요청메시지 : 새 리소스의 세부 정보

- PUT : 리소스를 만들거나 대체, 요청메시지 : 만들거나 업데이트할 리소스 지정

- PATCH : 리소스의 부분 업데이트, 요청메시지 : 리소스에 변경할 내용

- DELETE : 리소스 제거

 

 

HTTP 메서드 별 응답코드

메서드 응답코드 상태
GET 200 정상
  404 리소스를 찾을 수 없음
  204 요청이 처리 되었지만 응답 본문이 없음(콘텐츠 없음)
POST 201 새 리소스를 만드는 경우(만들어짐)
  200 일부 처리를 수행하지만 새 리소스를 만들지 않는 경우
  204 요청이 처리 되었지만 응답 본문이 없음(콘텐츠 없음)
  400 클라이언트가 잘못된 데이터를 요청에 배치한 경우 (잘못된 요청)
PUT 201 새 리소스를 만드는 경우(만들어짐)
  200 / 204 기존 리소스를 업데이트 할 경우(정상/내용없음)
  409 기존 리소스를 업데이트 할 수 없는 경우
PATCH    
     
     

 

비동기작업

POST, PATCH, PUT, DELETE와 같은 작업은 시간이 오래 걸리는 경우가 있다.

처리 작업이 완료될 때 까지 기다렸다가 클라이언트에게 응답을 보내는 경우, 클라이언트가 기다리다 지쳐 타임아웃을 낼 수가 있다.

이를 방지하기 위해, 요청이 수락되었지만 아직 완료되지 않았음을 나타내는 202(수락됨) 코드가 있다.

202 코드와 함께 location 헤더에 폴링할 엔드포인트 URI를 포함하여 응답한다.

HTTP/1.1 202 Accepted
Location: /api/status/12345

 

페이지네이션

쿼리 문자열로 개수를 서버쪽에서 필터링하여 응답한다.

https://tacit.com/orders?limit=25&offset=50

 

[TODO]

- page/test.vue

 

test 페이지 안에서 form 데이터들(name, phone, email 등등) 을 변수로 관리하고,

그 데이터들을 모아서 백엔드로 전달

 

input 태그 가 특정 컴포넌트로 쌓여서 template에 바로 쓰이지 않고, 하위 컴포넌트 안에 들어가야함

 

 

[SOLUTION#1]

- emit으로 하위 컴포넌트 데이터(e.target.value) 를 상위 컴포넌트(page/test.vue) 로 전달

# 상위 컴포넌트 (pages/test.vue)

<template>
	<SearchInput
	  :search-keyword="searchKeyword"
	  @input="updateSearchKeyword"
	></SearchInput>
</template>


<script>
methods: {
  updateSearchKeyword(keyword) {
    this.searchKeyword = keyword
  }
}
</script>
# 하위 컴포넌트 (components/SearchInput.vue)

<template>
  <div>
    <input
      type="text"
      :value="searchKeyword"
      @input="$emit('input', $event.target.value)"
    />
    <button type="button" @click="$emit('search')">search</button>
  </div>
</template>

<script>
export default {
  props: {
    searchKeyword: {
      type: String,
      default: () => '',
    },
  },
}
</script>

 

 

[SOLUTION#2]

- v-model 사용

# 상위 컴포넌트 (pages/test.vue)

<template>
	<SearchInput
		v-model="searchKeyword"
	  :search-keyword="searchKeyword"
	></SearchInput>
</template>


<script>
</script>
# 하위 컴포넌트 (components/SearchInput.vue)

<template>
  <div>
    <input
      type="text"
      :value="value"
      @input="$emit('input', $event.target.value)"
    />
    <button type="button" @click="$emit('search')">search</button>
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: () => '',
    },
  },
}
</script>

[TODO]

입력 Form 에서 사용자가 Yes / No 중 Yes 라고 답하면 상세 내역을 적기 위한 textarea 를 보여주어야 한다

 

 

[WHAT I DONE]

- "YES" 버튼을 클릭시 setAnswerY 메소드를 실행하여 this.answer_y = true 로 세팅

- this.answer_y 값이 true 이면 <textarea v-if="answer_y">

- textarea 요소가 보이고 focus 도 자동으로 이동

 

 

[첫번째 시도]

setAnswerY() {
    this.answer_n = false
    this.answer_y = true
    // 마우스 커서 이동
    this.setFocus('inputText')
},

setFocus(item) {
	this.$refs[item].focus()
},

 

✔️ 결과

answer_y=true 로 바꾸는 즉시 DOM이 업데이트 되지 않아서 $ref 요소를 찾을 수 없음

Because Vue is trying to optimize and batch changes, it won't immediately update the DOM when we set isEditing to false. So when we call focusOnEdit(), the "Edit" Button has not been rendered yet.

 

 

[두번째 시도]

- setTimeout을 이용한 시간차

setAnswerY() {
    this.answer_n = false
    this.answer_y = true
    // 마우스 커서 바로 이동
    setTimeout(() => {
    	this.setFocus('inputText')
    }, 200)
    this.setFocus('inputText')
},

setFocus(item) {
    this.$refs[item].focus()
    this.$nextTick(() => {
    const itemRef = this.$refs[item]
    	itemRef.focus()
    })
},

 

✔️ 결과

구현은 되었으나 임시방편같은 느낌,,

 

 

[세번째 시도] -> BEST❣️

- Vue의 nextTick() 메소드 사용

setAnswerY() {
  this.answer_n = false
  this.answer_y = true
  // 마우스 커서 바로 이동
  this.setFocus('inputText')
},

setFocus(item) {
  this.$nextTick(() => {
    const itemRef = this.$refs[item]
    itemRef.focus()
  })
},

 

✔️ 결과 

Vue의 nextTick() 메소드를 사용하여 DOM이 재렌더링 된 이후 $ref.focus 잡도록 처리

Instead, we need to wait until after Vue undergoes the next DOM update cycle. To do that, Vue components have a special method called $nextTick(). This method accepts a callback function, which then executes after the DOM updates.

 

 

[추가 시도]

 

v-show & v-if 의 차이점

 

v-show는 일단 내부적으로 렌더링은 하되 display : none / block 으로 조정

v-if 상태값에 따라 그때그때 렌더링 여부가 결정됨

하지만, v-show를 써도 마찬가지로 DOM이 업데이트 될 때까지 nextTick() 메소드로 기다려야함

 

1. migrations 파일 & model 생성

sequelize model:generate --name Member --attributes name:string, password:string
  • DBMS에 적용하기 위한 migrations 파일이 만들어지고, ORM에서 객체로 사용할 memer.js 파일이 만들어진다

2. 생성된 migrations 파일에 필요한 컬럼 추가

"use strict";
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable(
      "members",
      {
        id: {
          allowNull: false,
          autoIncrement: true,
          primaryKey: true,
          type: Sequelize.INTEGER,
        },
        name: {
          type: Sequelize.STRING(50),
          comment: "실명",
        },
        password: {
          type: Sequelize.STRING(512),
          comment: "비밀번호",
        },
        email: {
          type: Sequelize.STRING(100),
          allowNull: false,
          comment: "이메일",
        },
        is_deleted: {
          type: Sequelize.TINYINT.UNSIGNED,
          defaultValue: 0,
          comment: "삭제여부(0:아니오, 1:예)",
        },
        phone: {
          type: Sequelize.STRING(256),
          allowNull: false,
          comment: "휴대폰(010-XXXX-XXXX)",
        },
        created_at: {
          type: "TIMESTAMP",
          defaultValue: Sequelize.fn("NOW"),
          allowNull: false,
          comment: "등록일시",
        },
        updated_at: {
          type: "TIMESTAMP",
          defaultValue: Sequelize.literal(
            "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
          ),
          comment: "수정일시",
        },
      },
      {
        uniqueKeys: {
          phone_unique: {
            fields: ["phone"],
          },
        },
        comment: "회원",
      }
    );
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable("members");
  },
};

 

3-1. (수작업) 생성된 models/member.js 파일도 동일하게 column 작업

  • 생성한 migrations 파일과 동일하게 넣어준다

 

3-2. (반자동?) migrations 파일로 먼저 DB에 반영을 하고, 반영된 내용을 그대로 ORM Model로 가져온다 

 

  • models/member.js → sequelize-cli 명령어로 생성된 member 모델 파일 삭제
  • 작성한 migrations 파일 DBMS에 반영
npx sequeilize-cli db:migrate
  • sequelize-auto 설치
npm install -g -d sequelize-auto
  • sequelize-auto 실행 파일 작성
    • 프로젝트 루트 디렉토리에 /orm.js
const SequelizeAuto = require("sequelize-auto");

const auto = new SequelizeAuto("DB-SCHEMA", "root", "db-password", {
  host: "localhost",
  dialect: "mysql",
  directory: "./models",
  port: "db-port",
  caseModel: "c",
  caseFile: "c",
});

auto.run((err) => {
  if (err) throw err;
});
  1.  

 

  • node orm 파일 실행

 

  • orm - model을 자동으로 만들어준다 조금만 수정해서 사용하자
    • init-models.js 삭제
    • member.js → Member.js
      • 객체로 사용할 거라서 이름 변경하고,
      • 파일 수정
      • index에서 정의한 sequelize 객체 import
      • underscored : true 추가
import sequelize from "../index.js";
import { DataTypes } from "sequelize";

const Member = sequelize.define(
  "Member",
  {
    id: {
      autoIncrement: true,
      type: DataTypes.INTEGER,
      allowNull: false,
      primaryKey: true,
    },
    type: {
      type: DataTypes.TINYINT.UNSIGNED,
      allowNull: false,
      comment: "회원 유형(0:관리자, 1:일반)",
    },
    name: {
      type: DataTypes.STRING(50),
      allowNull: true,
      comment: "실명",
    },
    password: {
      type: DataTypes.STRING(512),
      allowNull: true,
      comment: "비밀번호",
    },
    email: {
      type: DataTypes.STRING(100),
      allowNull: false,
      comment: "이메일",
    },
    phone: {
      type: DataTypes.STRING(256),
      allowNull: false,
      comment: "휴대폰(010-XXXX-XXXX)",
      unique: "phone_unique",
    },
    nickname: {
      type: DataTypes.STRING(80),
      allowNull: true,
      comment: "닉네임",
    },
    is_deleted: {
      type: DataTypes.TINYINT.UNSIGNED,
      allowNull: true,
      defaultValue: 0,
      comment: "삭제여부(0:아니오, 1:예)",
    },
    portrait_url: {
      type: DataTypes.STRING(256),
      allowNull: true,
      comment: "프로필 사진 S3 url",
    },
    marketing_agree: {
      type: DataTypes.TINYINT.UNSIGNED,
      allowNull: true,
      defaultValue: 0,
      comment: "마케팅 수신 동의여부(0: 아니오, 1: 예)",
    },
    last_login_at: {
      type: DataTypes.DATE,
      allowNull: false,
      defaultValue: sequelize.literal("CURRENT_TIMESTAMP"),
      comment: "마지막 로그인 일시",
    },
    login_fail_count: {
      type: DataTypes.INTEGER,
      allowNull: false,
      defaultValue: 0,
      comment: "로그인 실패 횟수",
    },
    password_reset_code: {
      type: DataTypes.STRING(256),
      allowNull: true,
      comment: "패스워드 재설정 인증코드",
    },
    withdraw_at: {
      type: DataTypes.DATE,
      allowNull: true,
      comment: "탈퇴일시",
    },
  },
  {
    sequelize,
    tableName: "member",
    timestamps: true,
    indexes: [
      {
        name: "PRIMARY",
        unique: true,
        using: "BTREE",
        fields: [{ name: "id" }],
      },
      {
        name: "phone_unique",
        unique: true,
        using: "BTREE",
        fields: [{ name: "phone" }],
      },
    ],
    underscored: true,
  }
);

export default Member;

 

 

 

Node-Sequelize-Mysql (5) Jest 사용해서 만든 ORM 객체 테스트하기

 

 

 

 

+ Recent posts