반응형

React는 state의 값의 변화에 따라 UI가 변화하는 특징을 가지고 있다.

그렇다보니 데이터를 가지고 놀때 useState를 통해 값을 처리하고 setState를 통해 변경하고 변경된 UI부분만 재 처리하는 작업을 반복하는데, 해당값을 상위의 컴포넌트부터 최하단의 컴포넌트까지 복잡한 구조를 props를 통해 넘겨야한다면 굉장히 불필요한 데이터 전달처리로 소스도 난잡해지고 중간 중간의 컴포넌트들은 사용하지도 않는 props데이터를 넘기다보니 처음 소스를 받는사람은 이건 뭔데 넘기고 있을까? 라는 생각을 가질 수도 있을 것이다.

 

이럴때 전역 state처리를 해주는 redux나 recoil이 있는데, recoil에 대하여 알아보고자 한다.

 

Recoil

recoil은 state management 라이브러리로 페이스북팀에서 만들었고, 굉장히 간결하고 강력하다.

실제 예시를 보고 개념도 쉽고 그림도 잘 그려져서 너무 좋았다.

 

설치하기

먼저 사용을 해보기 위해 설치를 해야한다.

npm install recoil

 

설치가 끝나면 recoil설정 파일이 필요하다.

recoil에서 특정 전역처리를 하는 영역 덩어리를 atom이라고 부른다.

atoms.ts파일을 열어주고

전역 state를 처리할 값을 입력해주고 필요한 부분들에서 불러서 사용하면 된다.

 

그 전에 전체를 RecoilRoot로 App을 감싸주는걸 잊지 말자!

 

-atom

atoms.ts

import { atom } from "recoil";

export const isDarkAtom = atom({
    key: "isDark",
    default: true,
})

atom에는 key와 default값을 object형태로 넣어준다.

key값은 global state로 사용할 key값이고, default는 기본 값이다. 기본값으로 인해 자료형도 선택이 된다.

 

- useRecoilValue

전역 state값 불러오기

파라미터에는 호출할 atom을 넣어줍니다.

import { isDarkAtom } from "../atoms";
import { useRecoilValue } from "recoil";

const isDark = useRecoilValue(isDarkAtom);

 

- useSetRecoilState

전역 state값 변경하기

파라미터에는 호출할 atom을 넣어줍니다.

import { isDarkAtom } from "../atoms";
import { useSetRecoilState } from "recoil";

const setIsDark = useSetRecoilState(isDarkAtom);
setIsDark(true); //atom의 state값 변경

 

- 값불러오고 변경하기를 useState처럼 처리하기

파라미터에는 호출할 atom을 넣어줍니다.

import { useRecoilState } from "recoil";
import { isDarkAtom } from "../atoms";

const [isDark, setIsDark] = useRecoilState(isDarkAtom);
console.log(isDark);
setIsDark(true);

많이 보던 형태가 아닐까 싶습니다.

바로 useState랑 생김새가 똑같습니다.

값만 불러야하거나 변경만 해야하면 위에서처럼 하나만 import해서 쓰면 되지만, 둘다 필요한 경우에는 해당 방식이 좋아보입니다. :)

 

 

반응형
반응형

react-hook-form을 사용하여 form태그 내의 input태그의 onChange이벤트, 유효성 검사, submit후 데이터 값 확인 등 다양한 작업을 손쉽게 처리 할 수 있습니다.

 

기존의 react를 사용하여 해당 기능을 처리하려면 useState를 통해 처리할 값고 set함수를 선언하고 onChange이벤트에 넣어주고  value값에 state값을 넣어주고, 유효성을 체크하려면 onChange에 걸어둔 함수에서 길이를 체크하거나 정규식으로 패턴을 검사하거나 한개의 input만 해도 아주 복잡한 코드가 만들어지는데, 복잡한 회원가입이나 입력폼에서 이 과정을 실행하려면 상당한 긴 소스가 작성될 것입니다. 이런 문제를 간단하게 해결해 줄 수 있습니다.

 

먼저 설치를 진행합니다.

npm install react-hook-form

 

기본적으로 사용을 위해서 useForm이라는 hook을 항상 import해서 사용합니다.

import { useForm } from "react-hook-form"

 

1. register

register는 input 기능에 특화되어 있는 함수로 onBlur, onChange를 가지고 있는 함수입니다.

처리하고자 하는 input태그 내에 {...register("name")} 하나만으로 모든 처리가 끝납니다.

 

-사용 방법

import { useForm } from "react-hook-form";

interface IForm{
    userId: string;
    email: string;
}

function App(){
    const { register } = useForm();

    return (
        <div>
            <form>
                <input {...register("userId")} placeholder="아이디" />
                <input {...register("email")} placeholder="이메일" />
                <button>추가</button>
            </form>
        </div>
    )
}

export default App;

단지 jsx input 내부에 {...register("이름")}만으로 onChange이벤트에 의해 value값이 처리되는 걸 볼 수있습니다.

 

-유효성 검사

register함수의 2번째 파라미터부터 유효성 검사를 할 요소를 object형태로 넣어주면 됩니다.

{
    required: "아이디를 입력하세요.",
    minLength: {
        value: 5,
        message: "아이디는 최소 5자리 이상입니다."
    },
    maxLength: {
        value: 12,
        message: "아이디는 최대 12자리 이상입니다."
    }    
}

required: true, minLength:5, maxLength: 12 이런식으로 넣어줘도 되지만, 오류메시지 출력을 위해 value값과 message로 나눠서 처리해주었습니다.

 

-유효성 검사 예시

import { useForm } from "react-hook-form";

interface IForm{
    userId: string;
    email: string;
}

function App(){
    const { register } = useForm();

    return (
        <div>
            <form>
                <input {
                    ...register("userId",
                    {
                        required: "아이디를 입력하세요.",
                        minLength: {
                            value: 5,
                            message: "아이디는 최소 5자리 이상입니다."
                        },
                        maxLength: {
                            value: 12,
                            message: "아이디는 최대 12자리 이상입니다."
                        },
                    }
                )} placeholder="아이디" />
                <input {...register("email")} placeholder="이메일" />
                <button>추가</button>
            </form>
        </div>
    )
}

export default App;

 

-pattern(정규식 체크)

<input {
    ...register("email", {
        required: "이메일을 입력하세요.",
        pattern: {
            value: /^[a-zA-Z0-9+-\_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/,
            message: "이메일 형식이 아닙니다."
        },
})} placeholder="이메일" />

 

-validate옵션을 통한 커스텀을 통한 유효성 검사

validate: {
    noAdmin: (value) => value.includes("admin") ? "admin은 포함 시킬 수 없습니다." : true,
    noShin: (value) => value.includes("Shin") ? "Shin은 포함 시킬 수 없습니다." : true,
}

validate의 return값이 false 또는 문자열이 발생하면 유효하지 않는 값으로 인식하기 때문에, 오류로 처리하고자 하는 값이면 false처리하거나 메시지를 입력하면 되고 유효한 데이터라면 true를 처리해주면 됩니다.

 

 

2. watch

form태그의 입력된 값들의 변화를 관찰해주는 함수입니다.

userForm에서 watch함수를 호출하기만 하면 값이 변경될 때 마다 동작하는 것을 확인 할 수 있습니다.

const { register, watch } = useForm<IForm>();

console.log(watch());

 

값을 입력할때마다 console.log에 와치함수가 동작하여 태그 값들이 찍히고 있다.

 

 

3. handleSubmit

onSubmit의 동작을 처리해줄 handleSubmit입니다.

handleSubmit에는 2개의 인자를 받습니다.

onValid와 onInvalid

타입스크립트 설명에서 알 수 있듯이 첫번째 파라미터는 필수값이며, 유효한 경우 동작할 함수를 넣어줍니다.

두번째 파라미터는 선택이며, 유효하지 않은 경우 동작할 함수를 넣어주면 됩니다.

 

사용법은 form태그 onSubmit에 해당 함수를 넣어주면 됩니다.

import { useForm } from "react-hook-form";

interface IForm{
    userId: string;
    email: string;
}

function App(){
    const { register, handleSubmit } = useForm<IForm>();

    const onValid = (data:IForm) => {
        console.log(data);
    }

    return (
        <div>
            <form onSubmit={handleSubmit(onValid)}>
            </form>
        </div>
    )
}

export default App;

onValid라는 함수를 만들었습니다.

IForm interface의 정의를 받는 타입의 object값들을 받아서 console.log에 찍어주는 함수를 생성하였습니다.

form태그 내부에는 onSubmit태그 내의 handleSubmit함수를 생성하였고 첫번째 인자에 onValid를 넣어주었습니다.

 

 

4. formState:erros

useForm내의 formState를 사용하면 유효하지 않는 값들을 걸러내고 어떤 오류의 종류인지 오류메시지는 무엇인지 알 수 있습니다.

formState를 호출하고 formState의 errors메시지를 호출하는 것으로 에러들을 체크 할 수 있습니다.

const { register, handleSubmit, formState } = useForm<IForm>();
console.log(formState.errors);

이것들을 각 항목별로 유효성 오류를 표출시킬 수 있습니다.

사진에서 볼 수 있듯이 register에서 등록한 이름별로 오류메시지들이 출력됩니다.

type에는 어떤 종류인지 나옵니다.

message에는 문자열로 정의한 오류 메시지가 출력됩니다.

해당 정보들을 토대로 아래처럼 각 항목 아래에 오류 메시지를 출력시킬 수 있습니다.

 

errors?.항목이름?.message

오류 데이터가 없는 경우가 있을 수 있으니 꼭 optional(?.)처리를 해주세요.

 

-오류 표출해보기

import { useForm } from "react-hook-form";

interface IForm{
    address: string;
    email: string;
    nickName: string;
    phone: string;
    userId: string;
    userPw: string;
    userPwConfirm: string;
}

function Join(){
    const { register, watch, handleSubmit, formState:{errors} } = useForm<IForm>();

    //handleSubmit은 2개의 인자를 받는데 첫번째는 유효한 데이터인 경우 호출되는 함수다.
    //두번째 인자는 유효하지 않는 경우 호출되는 함수로 첫번째 함수만 필수값이다.
    const onValid = (data:IForm) => {
        console.log(data);
    }
    
    //에러체크는 formState의 error함수를 사용하여 알 수 있다.
    console.log(errors);
    return (
        <div>
            <form 
                style={{display:"flex", flexDirection:"column"}} 
                onSubmit={handleSubmit(onValid)} >
                <input {...register("userId", {required: "회원 아이디를 입력하세요."})} placeholder="아이디"/>
                <span>{errors?.userId?.message}</span>
                <input {...register("userPw", {required: "비밀번호를 입력하세요.", minLength: 10})} placeholder="비밀번호"/>
                <span>{errors?.userPw?.message}</span>
                <input {...register("userPwConfirm", {required: "비밀번호 확인을 입력하세요.", minLength: 10})} placeholder="비밀번호확인"/>
                <span>{errors?.userPwConfirm?.message}</span>
                <input {...register("nickName", {required: "닉네임을 입력하세요.", minLength: 10})} placeholder="닉네임"/>
                <span>{errors?.nickName?.message}</span>
                <input {
                    ...register("email", {
                        required: "이메일을 입력하세요.",
                        pattern: {
                            value: /^[a-zA-Z0-9+-\_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/,
                            message: "이메일 형식이 아닙니다."
                        },
                    })}
                    placeholder="이메일"/>
                <span>{errors?.email?.message}</span>
                <input {...register("address", {required: "주소를 입력하세요."})} placeholder="주소"/>
                <span>{errors?.address?.message}</span>
                <input {...register("phone", {required: "전화번호를 입력하세요."})} placeholder="전화번호"/>
                <span>{errors?.phone?.message}</span>
                <button>회원가입</button>
            </form>
        </div>
    );
}

export default Join;

작성 후 회원가입 버튼을 눌러서 submit동작을 시켜보면 각 오류메시지 타입들이 노출됩니다.

formState의 errors메소들을 주로 많이 사용하기 때문에 아래처럼 바로 가져와서 사용합니다.

const { formState:{errors} } = useForm<IForm>();

 

 

5. defaultValues

defaultValues로 기본값을 처리할 수 있습니다.

useForm의 첫번째 인자에 object형태로 정의해주면 됩니다.

const { register, handleSubmit, formState:{errors} } = useForm<IForm>({
    defaultValues: {
        phone: "010"
    }
});

타입스크립트로 useForm<IForm> 을 처리해둔 덕에 자동완성으로 리스트들이 나옵니다.

defaultValues내부에 원하는 값을 넣고 기본값을 입력해주면 UI에 바로 입력되어서 나오는 걸 볼 수 있습니다.

 

 

6. setError

setError를 통해 강제로 에러를 발생시킬 수도 있습니다.

역시나 타입스크립트가 사용법에 대해서 알려주고 있습니다.

 

첫번째 인자에는 에러를 발생시킬 항목을  두번째에는 에러 옵션을 넣으라고 나옵니다. 세번째는 선택입니다.

 

아래는 비밀번호 확인을 하여 오류가 발생한 경우를 작성했습니다.

const { setError } = useForm<IForm>();

const onValid = (data:IForm) => {
    if(data.userPw !== data.userPwConfirm){
        setError("userPwConfirm", 
                    {message: "비밀번호가 일치하지 않습니다."},
                    {shouldFocus: true}
            );
    }
}

submit동작을 해보면 오류가 발생하는 것을 볼 수 있습니다.

캡처에서는 빨간 밑줄이 그어져있지만 바로 포커싱을 가게 하고 싶다면 shouldFocus옵션을 true로 주면 된다.

 

 

 

또한 onValid나 InValid쪽에서 서버 오류등이 발생한 경우에도 강제로 오류를 발생시켜주면 좋을텐데 이런 경우에도 사용할 수 있습니다.

 

예외 처리에 대한 값이 필요한데 타입스크립트이므로 interface에 form태그에 대한 값을 추가하고, jsx에서 오류처리를 하던 부분에 동일하게 처리를 해주면 됩니다.

다만 interface에서 해당 extra값은 필수값은 아니므로 '?'처리를 해줍니다.

 

-비밀번호와 비밀번호 확인이 다른 경우 오류 발생처리(Full ver_Join.tsx)  & 강제 오류발생으로 메시지 띄우기 소스

import { useForm } from "react-hook-form";

interface IForm{
    userId: string;
    extraError?: string;
}

function Join(){
    const { 
        register, 
        handleSubmit, 
        formState:{errors},
        setError
    } = useForm<IForm>({
        defaultValues: {
            phone: "010"
        }
    });

    //handleSubmit은 2개의 인자를 받는데 첫번째는 유효한 데이터인 경우 호출되는 함수다.
    //두번째 인자는 유효하지 않는 경우 호출되는 함수로 첫번째 함수만 필수값이다.
    const onValid = (data:IForm) => {
        if(data.userPw !== data.userPwConfirm){
            setError("userPwConfirm", {message: "비밀번호가 일치하지 않습니다."});
        }
        setError("extraError", {message: "알 수 없는 오류가 발생했습니다."});
    }
    
    return (
        <div>
            <form 
                style={{display:"flex", flexDirection:"column"}} 
                onSubmit={handleSubmit(onValid)} >
                <input {...register("userId", {required: "회원 아이디를 입력하세요."})} placeholder="아이디"/>
                <span>{errors?.userId?.message}</span>
                <input {...register("userPw", {required: "비밀번호를 입력하세요.", minLength: 10})} placeholder="비밀번호"/>
                <span>{errors?.userPw?.message}</span>
                <input {...register("userPwConfirm", {required: "비밀번호 확인을 입력하세요.", minLength: 10})} placeholder="비밀번호확인"/>
                <span>{errors?.userPwConfirm?.message}</span>
                <input {...register("nickName", {required: "닉네임을 입력하세요.", minLength: 10})} placeholder="닉네임"/>
                <span>{errors?.nickName?.message}</span>
                <input {
                    ...register("email", {
                        required: "이메일을 입력하세요.",
                        pattern: {
                            value: /^[a-zA-Z0-9+-\_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/,
                            message: "이메일 형식이 아닙니다."
                        },
                    })}
                    placeholder="이메일"/>
                <span>{errors?.email?.message}</span>
                <input {...register("address", {required: "주소를 입력하세요."})} placeholder="주소"/>
                <span>{errors?.address?.message}</span>
                <input {...register("phone", {required: "전화번호를 입력하세요."})} placeholder="전화번호"/>
                <span>{errors?.phone?.message}</span>
                <button>회원가입</button>
                <span>{errors?.extraError?.message}</span>
            </form>
        </div>
    );
}

export default Join;

 

submit동작 후 강제 에러가 발생된 것을 볼 수 있습니다.

 

 

7. setValue

setValue를 통해 강제로 값을 변경할 수 있다.

입력 후 특정 값을 비워야하거나 자동 맞춤을 해주거나 할 경우 필요한 기능으로 2개의 필수 파라미터와 세번째는 옵션값을 갖는다.

첫번째 파라미터는 대상(항목)이며, 두번째 파라미터는 처리할 값이다.

처리할 값은 타입스크립트라면 정의해놓은 값으로 넣어주면 된다. string > string, number > number

사용 예시

setValue("toDo", ""); //toDo항목의 값을 비워준다.
반응형
반응형

테스트 자동화에 대해 알아보겠습니다.

 

개발을 진행하다보면 다양한 함수를 작성하고 적용하는 과정에서 다양한 케이스별 테스트를 진행하게 됩니다.

단순한 코드라면 테스트도 크게 어렵지 않고 실수나 잘못된 연산 있더라도 쉽게 수정이 가능하지만, 복잡하고 케이스별로 오류가 발생할수 있는 함수를 작성 중이라면, 아래와 같은 경우를 많이 만나 볼 수 있습니다.

 

1. test( params ); 라는 함수를 작성하였다.

2. test(1)은 정상 동작한다.

3. test(2)는 오류가 발생한다.

4. test(2)에 대한 오류를 수정하고, test(2)가 정상 동작한다.

> 수정을 했으니 이제 정상 동작하는 소스일까? ===> 아니오

수정으로 인해 test(2)는 정상이 되었지만, 정상동작하던 test(1)이 오류가 발생할 수도 있고 다른 케이스별 오류가 발생할 수 있다.

 

개발을 하다보면 생각보다 굉장히 자주 만나는 현상입니다.

그럼 개발자는 무엇을 어떻게 할까요?

정상 동작을 하는지 유형 별로 정리를 하고 작성한 코드를 테스트 > 수정 > 테스트를 반복합니다.

 

이렇게 수동으로 테스트를 진행하다보면 에러가 발생할 여지가 있습니다.

 

 

 

 

 

 

BDD방법론

BDD는 테스트(test), 문서(documentation), 예시(example)를 한데 모아놓은 개념으로 실제 개발사례를 통해 BDD에 대해 알아보겠습니다.

 

 

 

 

거듭제곱 함수를 통해 자동화 테스트 적용해보기

pow라는 이름의 함수를 만들고 BDD에 적용해보겠습니다.
(자바스크립트 자체에 ** 연산자나 Math.pow 메소드를 활용해도 되지만 BDD 학습을 위해 작성합니다.)
 
describe("거듭제곱 출력 함수", function() {
	it("매개변수의 n제곱", function() {
		assert.equal(pow(2, 3), 8);
	});
});
 

describe("title", function(){...})

구현하고자 하는 기능에 대해 설명이 들어갑니다. 여기선 pow함수에 대한 설명을 입력합니다.

 

it("유형별 설명", function(){...})

it의 첫번째 인수엔 특정 유스 케이스에 대한 설명이 들어갑니다. 이곳에는 누가 읽고 이해할 수 있도록 자연어로 작성합니다. 두번째 인수엔 테스트 할 함수가 들어갑니다.

 

assert.equal(value1, value2)

기능을 제대로 작성했다면 해당 구문이 오류없이 실행됩니다.

value1과 value2가 일치하지 않는다면 오류가 발생합니다.

value1에 pow(3, 3)를 value2에는 81을 입력하면 일치할 것이고 value2에 다른값을 입력하면 에러가 발생합니다.

 

 

사용되는 라이브러리

3가지의 라이브러리를 활용합니다.

Mocha - describe, it와 같은 테스팅함수와 테스트 관련 주요함수를 제공합니다.

Chai - 다양한 assertion을 제공하는 라이브러리입니다. (assert.equal)

Sison - 함수의 정보를 캐내는 라이브러리로 내장 함수를 모방합니다.

 

 

 

 

 

자동화 테스트해보기

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css"> <!-- Mocha css -->
</head>
<body>
    <div id="mocha"></div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script> <!-- Mocha -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script> <!-- chai -->
    <script>
        let assert = chai.assert;
        window.onload = function(){
            mocha.setup('bdd'); // mocha setup

            describe("pow함수 검증하기", function(){
                it("주어진 숫자의 n 제곱", function(){
                    assert.equal(pow(3, 3), 27);
                    assert.equal(pow(3, 4), 81);
                    assert.equal(pow(3, 5), 243);
                });

            });

            mocha.run();
        };
        
        //pow 작성
        function pow(x, n) {
            if(n < 2){
                return x;
            }
            return x * pow(x, --n);
        }
    </script>
</body>
</html>

거듭제곱 메소드를 재귀함수를 통해 작성하였습니다.

작성한 pow를 테스트화 시킨 코드입니다. 아래에서 테스트 결과를 보겠습니다.

 

결과

테스트 정상 화면

title과 테스트 결과가 정상적인경우 위 사진처럼 발생합니다.

 

 

 

 

 

오류 발생시키기

describe("pow함수 검증하기", function(){
    it("주어진 숫자의 n 제곱", function(){
        assert.equal(pow(3, 3), 27);
        assert.equal(pow(3, 4), 81);
        assert.equal(pow(3, 5), 243);
        assert.equal(pow(2, 2), 222);
    });
});

일부러 마지막에 오류를 내면 어떻게 될까요?

 

오류 결과

정상동작하는 3개에 대해선 문제가 없지만 오류가 발생중인 부분에 대해서 오류 메시지를 출력해줍니다.

오류메시지를 세분화 해보겟습니다.

 

 

 

 

 

오류메시지 분기하기

describe("pow함수 검증하기", function(){
    it("주어진 숫자의 n 제곱 3^3 = 27", function(){
        assert.equal(pow(3, 3), 27);
    });
    it("주어진 숫자의 n 제곱 3^4 = 81", function(){
        assert.equal(pow(3, 4), 81);
    });
    it("주어진 숫자의 n 제곱 3^5 = 243", function(){
        assert.equal(pow(3, 5), 243);
    });
    it("주어진 숫자의 n 제곱 2^2 = 4", function(){
        assert.equal(pow(2, 2), 222);
    });
});

각 유형별로 it로 나누고 각각의 설명을 기재하였습니다.

아래는 오류 메시지 결과입니다.

 

분기처리한 오류 메시지 결과

체크박스 형태로 성공은 체크, 실패한 부분은 x와 함께 메시지를 출력해주고 있네요.

유형별 결과를 한눈에 보기엔 분기처리 한 것이 보기 좋은것 같습니다.

 

 

 

 

 

자료 출처: https://ko.javascript.info/testing-mocha

 

테스트 자동화와 Mocha

 

ko.javascript.info

반응형
반응형

신규 고도화 사업을 진행하게되어 기존 소스를 받았는데, 구조가 뭔가 이상하다.

 

분명히 전자정부프레임워크라고 했는데, egov관련된 정보는 어디에도 안보이고 pom.xml부터 에러가 발생한다.

pom.xml에 parent라는 태그가 보인다.

찾아보니 메이븐을 부모, 자식형태로 구성해서 공통화를 할 수 있다고 한다.

오... 같은 프로젝트에 공통으로 쓰는 라이브러리가 많을때 쓰면 좋을 것 같다. (근데 전자정부인데 이렇게 마음대로 구조를 나눠도 되나...?😨)

 

pom.xml parent, modules구조로 나누기

먼저 조상(부모)가 될 프로젝트를 구성한다. (egov기준으로 예시를 작성하겠습니다.)

해당 프로젝트에선 딱히 뭔가를 만들지 않고, pom.xml만 작성한다.

 

Parent Project

1. 신규 프로젝트 생성

 

2. Project name, Group id를 작성한다.

egov-prj-parent 프로젝트 생성

 

3. egov구조 sample데이터도 전부 체크해준다.

 

4. 프로젝트가 생성되면 프로젝트 우클릭 > Properties > Java Build Path로 갑니다.

각 항목들을 모두 제거해서 프로젝트를 비워줍니다.

 

제거하면 다 사라지고 껍데기만 남는데 필요없는 src폴더까지 지워줍니다.

 

5. pom.xml을 수정합니다.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.psw</groupId>
	<artifactId>egov-prj-parent</artifactId>
	<version>1.0.0</version>
	<packaging>pom</packaging>
	<name>egov-prj-parent</name>
	<url>http://www.egovframe.go.kr</url>

	<licenses>
        <license>
            <name>The Apache Software License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
        </license>
    </licenses>

	<properties>
	    <spring.maven.artifact.version>4.3.25.RELEASE</spring.maven.artifact.version>
		<egovframework.rte.version>3.10.0</egovframework.rte.version>
	</properties>
	
	<modules>
        <module>egov-prj-child1</module>
        <module>egov-prj-child2</module>
    </modules>

	<repositories>
        <repository>
            <id>mvn2s</id>
            <url>https://repo1.maven.org/maven2/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
			<id>egovframe</id>
			<url>http://maven.egovframe.go.kr/maven</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
        <repository>
            <id>egovframe_old1</id>
            <url>http://maven.egovframe.kr:8080/maven/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>egovframe_old2</id>
            <url>http://www.egovframe.go.kr/maven/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

	<dependencies>
		<!-- 표준프레임워크 실행환경 -->
        <dependency>
		    <groupId>egovframework.rte</groupId>
		    <artifactId>egovframework.rte.ptl.mvc</artifactId>
		    <version>${egovframework.rte.version}</version>
		    <exclusions>
		    	<exclusion>
		    		<artifactId>commons-logging</artifactId>
		    		<groupId>commons-logging</groupId>
		    	</exclusion>
		    </exclusions>
        </dependency>
        <dependency>
		    <groupId>egovframework.rte</groupId>
		    <artifactId>egovframework.rte.psl.dataaccess</artifactId>
		    <version>${egovframework.rte.version}</version>
        </dependency>
        <dependency>
			<groupId>egovframework.rte</groupId>
			<artifactId>egovframework.rte.fdl.idgnr</artifactId>
			<version>${egovframework.rte.version}</version>
		</dependency>
       	<dependency>
			<groupId>egovframework.rte</groupId>
			<artifactId>egovframework.rte.fdl.property</artifactId>
			<version>${egovframework.rte.version}</version>
		</dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
		    <groupId>javax.servlet</groupId>
		    <artifactId>jstl</artifactId>
		    <version>1.2</version>
        </dependency>

        <dependency>
		    <groupId>taglibs</groupId>
		    <artifactId>standard</artifactId>
		    <version>1.1.2</version>
        </dependency>

		<dependency>
	        <groupId>org.antlr</groupId>
	        <artifactId>antlr</artifactId>
	        <version>3.5</version>
   		</dependency>

		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.5.0</version>
		</dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.tiles</groupId>
            <artifactId>tiles-jsp</artifactId>
            <version>3.0.8</version>
        </dependency>
	</dependencies>

	<build>
        <defaultGoal>install</defaultGoal>
        <directory>${basedir}/target</directory>
        <finalName>${artifactId}-${version}</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
	                <groupId>org.apache.tomcat.maven</groupId>
	                <artifactId>tomcat7-maven-plugin</artifactId>
	                <version>2.2</version>
	                <configuration>
	                    <port>80</port>
	                    <path>/</path>
	                    <systemProperties>
	                        <JAVA_OPTS>-Xms256m -Xmx768m -XX:MaxPermSize=256m</JAVA_OPTS>
	                    </systemProperties>
	                </configuration>
	            </plugin>
                <plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-compiler-plugin</artifactId>
					<version>3.1</version>
					<configuration>
						<source>1.8</source>
						<target>1.8</target>
						<encoding>UTF-8</encoding>
						<maxmem>1024m</maxmem>
					</configuration>
				</plugin>
				<plugin>
                	<groupId>org.apache.maven.plugins</groupId>
                	<artifactId>maven-war-plugin</artifactId>
                	<version>2.4</version>
                	<configuration>
                		<failOnMissingWebXml>false</failOnMissingWebXml>
                	</configuration>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>hibernate3-maven-plugin</artifactId>
                    <version>3.0</version>
                    <configuration>
                        <components>
                            <component>
                                <name>hbm2ddl</name>
                                <implementation>annotationconfiguration</implementation>
                            </component>
                        </components>
                    </configuration>
                    <dependencies>
                        <dependency>
                            <groupId>org.hsqldb</groupId>
                            <artifactId>hsqldb</artifactId>
                            <version>2.5.0</version>
                        </dependency>
                    </dependencies>
                </plugin>
                <!-- EMMA -->
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>emma-maven-plugin</artifactId>
                    <version>1.0-alpha-3</version>
                </plugin>
                <!-- PMD manven plugin -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-pmd-plugin</artifactId>
                    <version>3.12.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <!-- EMMA -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                    <forkMode>once</forkMode>
                    <reportFormat>xml</reportFormat>
                    <excludes>
                        <exclude>**/Abstract*.java</exclude>
                        <exclude>**/*Suite.java</exclude>
                    </excludes>
                    <includes>
                        <include>**/*Test.java</include>
                    </includes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>emma-maven-plugin</artifactId>
                <inherited>true</inherited>
            </plugin>
            <!-- JavaDoc -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>3.1.1</version>
            </plugin>
        </plugins>
    </build>
    <reporting>
        <outputDirectory>${basedir}/target/site</outputDirectory>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-project-info-reports-plugin</artifactId>
                <version>3.0.0</version>
                <reportSets>
                    <reportSet>
                        <id>sunlink</id>
                        <reports>
                            <report>javadoc</report>
                        </reports>
                        <inherited>true</inherited>
                        <configuration>
                            <links>
                                <link>http://docs.oracle.com/javase/6/docs/api/</link>
                            </links>
                        </configuration>
                    </reportSet>
                </reportSets>
            </plugin>
            <!-- JUnit Test Results & EMMA Coverage Reporting -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>emma-maven-plugin</artifactId>
                <inherited>true</inherited>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>surefire-report-maven-plugin</artifactId>
                <inherited>true</inherited>
                <reportSets>
                    <reportSet>
                        <reports>
                            <report>report-only</report>
                        </reports>
                    </reportSet>
                </reportSets>
            </plugin>
            <!-- Generating JavaDoc Report -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <configuration>
                    <minmemory>128m</minmemory>
                    <maxmemory>512m</maxmemory>
                    <encoding>${encoding}</encoding>
                    <docencoding>${encoding}</docencoding>
                    <charset>${encoding}</charset>
                </configuration>
            </plugin>
            <!-- Generating Java Source in HTML -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jxr-plugin</artifactId>
                <configuration>
                    <inputEncoding>${encoding}</inputEncoding>
                    <outputEncoding>${encoding}</outputEncoding>
                    <linkJavadoc>true</linkJavadoc>
                    <javadocDir>apidocs</javadocDir>
                </configuration>
            </plugin>
        </plugins>
    </reporting>
</project>

너무나도 기네요...

공통으로 사용할 메이븐은 parent에 넣고 사용할 예정이고 여기서 테스트를 위해 tiles만 추가해두겠습니다.

 

modules부분에서 하위 프로젝트를 지정합니다.

 

Child1 Project

1. child1 프로젝트 생성하기

 

2. egov 프로젝트를 생성하고 pom.xml을 바로 수정합니다.

(설정이나 삭제 과정은 없습니다.)

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.psw</groupId>
		<artifactId>egov-prj-parent</artifactId>
		<version>1.0.0</version>
	</parent>
	<artifactId>egov-prj-child1</artifactId>
	<packaging>war</packaging>
	<version>1.0.0</version>
	<name>egov-prj-child1</name>
	<url>http://www.egovframe.go.kr</url>

	<licenses>
		<license>
			<name>The Apache Software License, Version 2.0</name>
			<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
		</license>
	</licenses>

	<properties>
		<spring.maven.artifact.version>4.3.25.RELEASE</spring.maven.artifact.version>
		<egovframework.rte.version>3.10.0</egovframework.rte.version>
	</properties>

	<repositories>
		<repository>
			<id>mvn2s</id>
			<url>https://repo1.maven.org/maven2/</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>egovframe</id>
			<url>http://maven.egovframe.go.kr/maven</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>egovframe_old1</id>
			<url>http://maven.egovframe.kr:8080/maven/</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>egovframe_old2</id>
			<url>http://www.egovframe.go.kr/maven/</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

	<dependencies>

	</dependencies>

	<build>
		<defaultGoal>install</defaultGoal>
		<directory>${basedir}/target</directory>
		<finalName>${artifactId}-${version}</finalName>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.tomcat.maven</groupId>
					<artifactId>tomcat7-maven-plugin</artifactId>
					<version>2.2</version>
					<configuration>
						<port>80</port>
						<path>/</path>
						<systemProperties>
							<JAVA_OPTS>-Xms256m -Xmx768m -XX:MaxPermSize=256m</JAVA_OPTS>
						</systemProperties>
					</configuration>
				</plugin>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-compiler-plugin</artifactId>
					<version>3.1</version>
					<configuration>
						<source>1.8</source>
						<target>1.8</target>
						<encoding>UTF-8</encoding>
						<maxmem>1024m</maxmem>
					</configuration>
				</plugin>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-war-plugin</artifactId>
					<version>2.4</version>
					<configuration>
						<failOnMissingWebXml>false</failOnMissingWebXml>
					</configuration>
				</plugin>
				<plugin>
					<groupId>org.codehaus.mojo</groupId>
					<artifactId>hibernate3-maven-plugin</artifactId>
					<version>3.0</version>
					<configuration>
						<components>
							<component>
								<name>hbm2ddl</name>
								<implementation>annotationconfiguration</implementation>
							</component>
						</components>
					</configuration>
					<dependencies>
						<dependency>
							<groupId>org.hsqldb</groupId>
							<artifactId>hsqldb</artifactId>
							<version>2.5.0</version>
						</dependency>
					</dependencies>
				</plugin>
				<!-- EMMA -->
				<plugin>
					<groupId>org.codehaus.mojo</groupId>
					<artifactId>emma-maven-plugin</artifactId>
					<version>1.0-alpha-3</version>
				</plugin>
				<!-- PMD manven plugin -->
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-pmd-plugin</artifactId>
					<version>3.12.0</version>
				</plugin>
			</plugins>
		</pluginManagement>
		<plugins>
			<!-- EMMA -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<configuration>
					<skipTests>true</skipTests>
					<forkMode>once</forkMode>
					<reportFormat>xml</reportFormat>
					<excludes>
						<exclude>**/Abstract*.java</exclude>
						<exclude>**/*Suite.java</exclude>
					</excludes>
					<includes>
						<include>**/*Test.java</include>
					</includes>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>emma-maven-plugin</artifactId>
				<inherited>true</inherited>
			</plugin>
			<!-- JavaDoc -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-javadoc-plugin</artifactId>
				<version>3.1.1</version>
			</plugin>
		</plugins>
	</build>
	<reporting>
		<outputDirectory>${basedir}/target/site</outputDirectory>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-project-info-reports-plugin</artifactId>
				<version>3.0.0</version>
				<reportSets>
					<reportSet>
						<id>sunlink</id>
						<reports>
							<report>javadoc</report>
						</reports>
						<inherited>true</inherited>
						<configuration>
							<links>
								<link>http://docs.oracle.com/javase/6/docs/api/</link>
							</links>
						</configuration>
					</reportSet>
				</reportSets>
			</plugin>
			<!-- JUnit Test Results & EMMA Coverage Reporting -->
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>emma-maven-plugin</artifactId>
				<inherited>true</inherited>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>surefire-report-maven-plugin</artifactId>
				<inherited>true</inherited>
				<reportSets>
					<reportSet>
						<reports>
							<report>report-only</report>
						</reports>
					</reportSet>
				</reportSets>
			</plugin>
			<!-- Generating JavaDoc Report -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-javadoc-plugin</artifactId>
				<configuration>
					<minmemory>128m</minmemory>
					<maxmemory>512m</maxmemory>
					<encoding>${encoding}</encoding>
					<docencoding>${encoding}</docencoding>
					<charset>${encoding}</charset>
				</configuration>
			</plugin>
			<!-- Generating Java Source in HTML -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jxr-plugin</artifactId>
				<configuration>
					<inputEncoding>${encoding}</inputEncoding>
					<outputEncoding>${encoding}</outputEncoding>
					<linkJavadoc>true</linkJavadoc>
					<javadocDir>apidocs</javadocDir>
				</configuration>
			</plugin>
		</plugins>
	</reporting>
</project>

parent부분에 부모로 받을  groupId와 artifactId를 매칭해줍니다.

<dependencies>부분은 비웁니다. 부모에서 상속받은 메이븐만 사용할 예정입니다.

 

3. 해당 워크스페이스가 동작하고 있는 경로로가서 child1프로젝트를 parent프로젝트로 넣습니다.

parent프로젝트 복사한다.

나중에 child2도 생성해서 똑같이 복사하면 됩니다. 지금은 1만 복사합니다.

 

4. eclipse로 돌아와서 프로젝트를 새로고침 해보면, child1이 들어와있는걸 볼 수 있습니다.

parent 안에 child1

 

5. 필요없어진 egov-prj-child1은 지웁니다.

 

6. 이제 개발해야할 child1의 소스를 import하겠습니다.

parent안에 있는 child1를 해야합니다.

 

Package Explorer - 우클릭 - import
Maven - Exsiting Maven Projects
child1 선택

 

그럼 정상적으로 import가 된다.

지웠던 egov-prj-child1가 생성되었다.

 

7. child1프로젝트에 타일즈 세팅후 톰캣 구동을 해보니 정상적으로 페이지가 뜬다. 😆

tiles 적용 완료

 

Child2 Project

child1 프로젝트와 마찬가지로 동일하게 설정하고 import후 개발을 진행하면 된다.

반응형
반응형

웹 개발을 하면서 HTML 태그 요소에 가장 많이 사용하는건, ID와 class 속성일 겁니다.

제가 알고있는 지식으로는 id는 숫자로 시작하면 안되고, 문자열로 시작해야한다로 알고 있었는데, 일반 정수형 데이터를 넣어도 잘 적용이 되고, js를 통해 제어가 가능했습니다.

 

그 예제는 아래와 같습니다.

 

HTML id속성이 정수형이면 어떻게 될까?

id값에 정수형으로 태그 생성하기

const h1 = document.createElement("h1");
h1.id = 11223344;
h1.innerText = "TEST";
document.body.append(h1);

정수형으로 id값을 생성하고 js를 통해 h1태그의 텍스트 값을 변경해보겠습니다.

생성된 태그를 확인해보면 정수형으로 생성했지만 문자열형태로 id값까지 h1태그가 잘 생성된 걸 볼 수 있습니다.

 

document.getElementById("11223344").innerText = "변경";

숫자로 시작하는 id값을 호출하여 text값을 변경해도 문제없이 동작합니다.

 

숫자 ID값에 css적용하기?

<style>
  #11223344{color: yellowgreen}
</style>

다만 style태그에 정의한 css는 동작하지 않습니다.

 

 

 

HTML - id란? 특징

HTML - ID속성

ID는 document 전체에서 유일한 고유식별자로 유일해야합니다.

요소를 구분하고 스타일(css), 스크립트(js)에서 특정 요소를 식별하고 제어하기 위해 주로 사용됩니다.

 

ID의 특징

- 요소마다 한개의 ID만 가질 수 있습니다. 

- 공백(스페이스, 탭)의 값을 포함하면 안됩니다. 혹시 공백을 넣게되면 공백까지 ID로 취급됩니다.

- 대소문자를 구분합니다.

 

 

 

HTML버전별 ID 동작 차이

HTML4

id는 A-Z, a-z로 시작해야하며, 이후 문자, 숫자, '-', '_', '.'이 올 수 있습니다.

 

HTML5

HTML5로 올라오면서 공백을 제외하고 대부분의 값을 받아들입니다.

숫자로만 구성되거나, 밑줄로 시작하거나 점만으로 구성되어도 가능합니다.

 

4, 5는 이러한 차이점이 발생하고 있는데, 5로 올라오면서 허용된 대부분의 값이 문제가 되고 있습니다.

특히나 css에서 .과 :는 선택자로서 특별한 의미를 지니는데, id값에 .과 :가 있게되면 css측에서는 혼돈이 발생됩니다.

 

 

"."이 있는 id값에 css적용하기

<style>
  #abc.def{color: yellowgreen} //abc라는 id를 가지면서 def라는 클래스를 가진 요소에 적용
  #abc\.def{color: yellowgreen} //\역슬래쉬를 통해 처리
</style>

<h1 id="abc.def">TEST</h1>

안그래도 복잡한 구조에 역슬래쉬까지 추가해서 제어를 해야합니다.

저라면 정말 어쩔수 없는 특별한 상황이 아니라면 사용하고 싶지 않군요.

 

다른 스택오버플로우나 블로그 글들에서도 볼 수 있듯 숫자로 시작하는 id값 생성과 제어는 js까진 사용 가능하지만

html4처럼 기존의 양식을 지키는 형태로 작성하며, 문자[A-Za-z]로 시작하고, ':', '.'와 같은 특수문자보단 '-', '_'를 사용하거나 카멜표기법을 따르는것을 권장합니다.

 

 

 

 

자료출처: https://stackoverflow.com/questions/70579/what-are-valid-values-for-the-id-attribute-in-html

자료출처: https://www.w3.org/TR/CSS21/syndata.html#characters

 

반응형
반응형

자료 출처 :
- https://medium.com/@gaurav.pandvia/understanding-javascript-function-executions-tasks-event-loop-call-stack-more-part-1-5683dea1f5ec
- https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
- https://new93helloworld.tistory.com/358
- https://velog.io/@jakeseo_me/2019-03-15-2303-%EC%9E%91%EC%84%B1%EB%90%A8-rmjta5a3xh
- https://meetup.toast.com/posts/89

웹 개발자로 년차가 쌓이고 있는 현 시점에서 자바스크립트의 내부 동작을 이해할 필요가 있었다.
자바스크립트의 내부에서 어떤식으로 돌아가는지에 대한 내용들을 파악해보고 참고한 내용들을 참고하여 작성하는 글이다.


🪄자바스크립트의 변화

과거 웹 페이지의 동작만을 위해 동작하던 자바스크립트는 현대에 와서 많은 변화가 있었는데, 더 이상 웹 페이지만을 위해 존재하지 않는다.
2008년 구글에서 V8엔진을 사용하여 크롬을 출시한다. 이를 시작으로 2009년 노드가 탄생하였고, 서버와 클라이언트, 어플리케이션 개발 등 다양한 분야에서 사용되게 된다.

🔥자바스크립트 엔진

V8은 자바스크립트의 엔진이다. 크롬 브라우저의 엔진이면서, Node.js의 기반이다.
싱글스레드로 동작하면서 메모리힙콜스택으로 구성되어 있다.

메모리 힙(Memory Heap): 원시타입(Primitive type), 객체(Array, Object, Function 등)타입이 선언되면 메모리힙에 할당된다. 사용이 끝나면 자동으로 해제(Garbage collection)된다.
콜스택(Call Stack): 실행해야 할 코드가 쌓인다.

🧙Call Stack

실행할 코드가 쌓인다고 가볍게 설명했는데, 더 자세히 알아보자.
일단 여기서 이름 자체에 Stack이라는 키워드로 어느정도 동작을 유추할 수 있다.
후입선출를 가진 스택은 마지막에 쌓인 데이터가 먼저 출력되는 방식이다.
요청이 들어올 때마다 이 콜스택이라는곳에 쌓아두는데, 아래와 같은 소스가 있다면 어떻게 쌓이고 동작할까?

function tenSum(x){
    return x + 10;
}

function printSum(x){
    const sum = tenSum(x);
    console.log(sum);
}
printSum(5);

동작 처리 설명

  • Stack1: printSum(5)가 push된다.
  • Stack2: printSum(5) 내부에 있는 tenSum(x)가 push된다.
  • Stack3: tenSum(x)가 연산되면서 15를 출력하고 sum이라는 변수에 담아주고 pop이 된다. console.log(sum)이 push된다.
  • Stack4: console.log(sum)이 출력되면서 pop된다.
  • Stack5: printSum(5)가 출력되면서 pop된다.

🔁Event Loop & Task Queue

아래와 같은 코드를 만나면 어떻게 될까?

console.log("start", new Date())
setTimeout(()=>{console.log("setTimeout", new Date())}, 0);
const wakeUpTime = Date.now() + 1000;
while (Date.now() < wakeUpTime) {}
console.log("test1");
console.log("test2");
console.log("test3");
console.log("end", new Date())

위 소스에 대해 설명하자면 아래와 같다

  • 단순 console.log를 실행한다.
  • setTimeout이 동작한다. 동작시간은 0초로 설정하였다.
  • 이후 현재시간의 1초뒤의 시간을 wakeUpTime에 작성하여 while문에서 시간으로 체크한다.
  • 1초동안 강제로 동기화를 시켜서 페이지가 살짝 멈추게 된다.
  • 이후 단순한 console.log들을 출력한다.

예상한 결과가 나오길 빌면서 아래 출력 결과를 공유한다.

setTimeout메소드가 2번째에 있고 동작시간을 0으로 처리했음에도 콜백안에 있는 console.log는 가장 나중에 출력되고 있는걸 볼 수 있다.
심지어 4번째 while문에서 강제로 블로킹 처리를 해서 페이지가 멈추는 현상까지 발생는데 어떻게 이런 결과가 나왔을까?
이런 현상에 대해서는 이벤트 루프, 테스트 큐에 대해 알아야한다.

먼저 테스크 큐콜백 함수들이 대기하는 큐 형태이다.
Queue는 선입선출 구조로 먼저 들어온 것들이 먼저 나간다.
위 예제의 실제 동작을 설명하자면 아래와 같다.

  • 콜스택에 console,log("start")가 push되고 출력되면서 pop된다.
  • setTimeout이 콜스택에 push되고 콜백 내용이 테스크 큐에 등록되면서 pop된다.
  • 0ms가 지나고 이벤트 큐는 콜 스택이 비워질때까지 기다리면서 콜백을 실행시킬 준비를 한다.
  • wakeUpTime의 변수가 메모리 힙에 올라가고 이후부턴 콜스택들이 동작된다...

위와 같은 비동기 처리나 I/O의 작업이 자바스크립트에서 발생하면 콜백함수가 콜스택에서 처리되는게 아니라 별도의 테스크 큐라는 곳에 쌓였다가 이벤트 루프에 의해 실행되기 때문에 뜻밖의 결과를 얻을 수 있다.

⭐브라우저의 내부 환경

자바스크립트의 엔진을 구동하는 환경의 브라우저의 내부 모습은 아래와 같다.

그림에서도 볼 수 있듯이 개발하면서 자주 사용했던 Ajax, setTimeout과 같은 비동기 처리들은 자바스크립트가 해주는게 아니라 Web API영역에 따로 정의되어 있다. Node.JS에서는 동시성을 위해 libuv라이브러리를 도입했는데, 이 libuv라이브러리가 이벤트 루프를 제공한다.

정리하자면 비동기 작업을 하게되면 Node.js의 API가 호출되고, 그러면서 작성된 콜백 함수들은 이벤트 루프에 의해 관리가 되고 실행이 된다.

반응형
반응형

데이터 시각화 작업을 하다보면 다양한 오픈소스와 차트 라이브러리를 사용하게 되는데, 그 중 오픈소스이며 MIT 라이선스를 따르는 chart.js로 세계지도 만들기 예제를 알아보겠습니다.🚀

공식 홈페이지를 가도 샘플페이지등을 확인해봐도 지도형 차트는 보이지 않는데, github 지도 관련하여 배포중인 사이트가 있습니다.

 

 

https://github.com/sgratzl/chartjs-chart-geo

 

GitHub - sgratzl/chartjs-chart-geo: Chart.js Choropleth and Bubble Maps

Chart.js Choropleth and Bubble Maps. Contribute to sgratzl/chartjs-chart-geo development by creating an account on GitHub.

github.com

위 페이지를 들어가보면 다양한 모양의 지도차트를 볼 수 있습니다. 제공중인 샘플소스를 CodePen페이지를 통해 제공중이니 적용해보셔도 좋습니다.

 

 

Chart.js 세계지도 데이터 시각화하기

필요한 리소스

- chart.js 지도 geo처리 > https://unpkg.com/chartjs-chart-geo@3.5.2/build/index.umd.min.js

- 지도를 그리기 위한 위 경도  json정보 > https://unpkg.com/world-atlas@2.0.2/countries-50m.json

 
world-atlas는 세계 지리 위 경도 지도정보를 제공하고 있습니다.
해당 url로 fetch함수를 통해 json데이터 받고, chart.js geo에 맞도록 가공하여 세팅합니다.
 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chart.js 세계지도</title>
</head>
<body>
    <div>
        <canvas id="myChart"></canvas>
    </div>

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script type="text/javascript" src="https://unpkg.com/chartjs-chart-geo@3.5.2/build/index.umd.min.js"></script>
<script>
const url = "https://unpkg.com/world-atlas@2.0.2/countries-50m.json"; //세계지도를 그리기 위한 json
fetch(url).then(result => result.json()).then((datapoint) => {
    //나라 뽑아내기
    const countries = ChartGeo.topojson
                        .feature(datapoint, datapoint.objects.countries).features;

    //차트를 그리기 위한 데이터셋
    const data = {
        labels: countries.map(country => country.properties.name),
        datasets: [{
            label: 'Countries',
            data: countries.map(country => ({feature: country, value: Math.random()*100})) //노출데이터는 랜덤값으로 표기
        }]
    };
    
    //차트 옵션
    const config = {
        type: 'choropleth', //차트 모양 > 지구본 타입 설정
        data,
        options: {
            showOutline: true, //지구본 원형라인 잡아주기
            showGraticule: true, //지구본 위, 경도선 그려주기
            scales: {
                xy: {
                    projection: 'equalEarth' //지구본 모양으로 변경
                }
            },
            plugins: {
                legend: {
                    display: false //chart.js차트 범례 숨기기(별도 표기가 됨)
                }
            }
        }

    };

    const myChart = new Chart(
        document.getElementById('myChart'),
        config
    );  
});
</script>
</body>
</html>

상세 옵션정보는 주석 또는 chart.js geo github페이지를 확인해주세요.

 

결과물

chart.js를 활용한 세계지도 차트

값은 랜덤으로 설정하여 페이지 로드시 매번 바뀌는걸 볼 수 있습니다.

반응형
반응형

프로젝트 기능을 수행하다보면 각종 다양한 접근 방식이나 데이터 처리로 인해 오류를 만나게 됩니다.

이런 경우 하나의 서비스를 처리하면서 데이터에 대한 일관적인 상태를 유지하고 문제가 없게 관리를 해야합니다.

 

트랜잭션

정보처리기사 공부를 하다보면 트랜잭션 개념을 만나게 되는데, 여기서 나왔던 설명이 괜찮게 느껴져서 인용해겠습니다.

 

1. A라는 사람이 B의 중고 물건이 마음에 들어서 거래를 진행하게 되었습니다.

2. A는 B에게 물건에 해당하는 금액을 어플을 통해 송금하였습니다.

3. 송금을 했기때문에 A계좌에서는 해당하는 금액만큼 돈이 빠져나갔습니다.

4. 그사이 B계좌로 돈이 입금되어야하지만 알 수 없는 오류가 발생합니다.

5. A는 계좌에서 금액이 빠져나갔지만 B는 돈을 못받는 현상을 막기 위해 트랙잭션이 발생하고, A의 계좌는 원래대로 돌아옵니다.


이런식으로 데이터를 처리하는데 오류나 다양한 상황에 대하여 안정성을 확보하고 성공한 경우에만 반영을 해주는 것을 트랜잭션이라고 합니다.

 

 

 

트랜잭션의 특징

트랜잭션은 4가지의 특징을 지닙니다.

-원자성, 일관성, 독립성, 지속성

 

1. 원자성(Atomicity)

DB에 모두 반영하거나 반영되지 않는 것을 말합니다. 즉, 모두 성공으로 처리하거나 모두 실패로 처리해야합니다.

 

2. 일관성(Consistency)

트랜잭션 작업 처리의 결과가 항상 일관되어야 함을 말합니다.(데이터 타입이 반환 후와 전이 동일해야 함)

 

3. 독립성(Isolation)

동시 발생하는 트랜잭션들이 서로에게 영향을 미치지 않아야합니다.

 

4. 영속성(Durability)

트랜잭션을 성공적으로 마치면 결과가 영구적으로 저장되어야 합니다.

 

 

스프링 프로젝트 트랜잭션 설정하기

1. 어노테이션을 통한 설정하기

트랜잭션 설정을 위해서 다양한 방법이 있지만 그 중 어노테이션을 통한 설정 방법을 알아보겠습니다.

먼저 컨텍스트부분에 아래의 한 줄을 추가해주면 간단하게 적용이 가능합니다.

<tx:annotation-driven transaction-manager="txManager"/>

 

 

context-transaction.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
    	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

	<tx:annotation-driven transaction-manager="txManager"/>
	
	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
</beans>

이후 트랜잭션을 처리하고 싶은 메소드나 클래스, 인터페이스 등에 @Transactional 어노테이션을 걸어주면 된다.

 

 

XXXService.java

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE)
public HashMap<Object, Object> insertUser(HashMap<Object, Object> params) {
	int result = loginMapper.insertUser(params);
	params.clear();
	params.put("result", result);
	return params;
}

inserUser라는 매퍼를 통해 insert쿼리를 수행하는 도중 오류가 발생하면 트랜잭션이 발생한다.

 

반응형