typescript oop 렌더링 프로젝트

jeongwon yun·2023년 3월 5일
0

Typescript

목록 보기
15/25

Title, Description, People

위 3개의 인풋을 받는 form 태그를 렌더링하고 서브밋을 할 수 있는 앱을 ts 객체지향으로 만들어보자.

<body>
  <template id="project-input">
    <form>
      <div class="form-control">
        <label for="title">Title</label>
        <input type="text" id="title" />
      </div>
      <div class="form-control">
        <label for="description">Description</label>
        <textarea id="description" rows="3"></textarea>
      </div>
      <div class="form-control">
        <label for="people">People</label>
        <input type="number" id="people" step="1" min="0" max="10" />
      </div>
      <button type="submit">ADD PROJECT</button>
    </form>
  </template>
  <div id="app"></div>
</body>
class ProjectInput {
    templateElement: HTMLTemplateElement;
    hostElement: HTMLDivElement;
    element: HTMLFormElement;
  	titleInputElement: HTMLInputElement;
    descriptionInputElement: HTMLInputElement;
    peopleInputElement: HTMLInputElement;
    
    constructor() {
        this.templateElement = document.getElementById('project-input')! as HTMLTemplateElement;
        this.hostElement = document.getElementById('app')! as HTMLDivElement;

        const importedNode = document.importNode(this.templateElement.content, true);
        this.element = importedNode.firstElementChild as HTMLFormElement;
      
      	this.titleInputElement = this.element.querySelector('#title') as HTMLInputElement;
        this.descriptionInputElement = this.element.querySelector('#description') as HTMLInputElement;
        this.peopleInputElement = this.element.querySelector('#people') as HTMLInputElement;

        this.attach();
    }

    private attach() {
        this.hostElement.insertAdjacentElement('afterbegin', this.element);
    }
}

const prjInput = new ProjectInput();

이렇게 돔을 렌더링 할 수 있다.


이제 서브밋 이벤트를 달아보자.

class ProjectInput {
    templateElement: HTMLTemplateElement;
    hostElement: HTMLDivElement;
    element: HTMLFormElement;
    titleInputElement: HTMLInputElement;
    descriptionInputElement: HTMLInputElement;
    peopleInputElement: HTMLInputElement;
    constructor() {
        this.templateElement = document.getElementById('project-input')! as HTMLTemplateElement;
        this.hostElement = document.getElementById('app')! as HTMLDivElement;

        const importedNode = document.importNode(this.templateElement.content, true);
        this.element = importedNode.firstElementChild as HTMLFormElement;
        this.element.id = 'user-input';

        this.titleInputElement = this.element.querySelector('#title') as HTMLInputElement;
        this.descriptionInputElement = this.element.querySelector('#description') as HTMLInputElement;
        this.peopleInputElement = this.element.querySelector('#people') as HTMLInputElement;

        this.configure();
        this.attach();
    }

    private submitHandler(event: Event) {
        event.preventDefault();
      	console.log(this.titleInputElement.value);
    }

    private configure() {
        this.element.addEventListener('submit', this.submitHandler);
    }

    private attach() {
        this.hostElement.insertAdjacentElement('afterbegin', this.element);
    }
}

const prjInput = new ProjectInput();

서브밋을 하면

console.log(this.titleInputElement.value);

value를 찾을 수 없다는 에러가 발생한다.

private configure() {
  this.element.addEventListener('submit', this.submitHandler.bind(this);
}

configure 함수를 위와 같이 수정하면 해결이 되지만
데코레이터를 이용해서 해결해보자.

function autobind(
    target: any,
    methodName: string,
    descriptor: PropertyDescriptor
) {
    const originalMethod = descriptor.value;
    const adjDescriptor: PropertyDescriptor = {
        configurable: true,
        get() {
            const boundFn = originalMethod.bind(this);
            return boundFn;
        }
    };
    return adjDescriptor;
}

class ProjectInput {
  // ...
  
  	@autobind
    private submitHandler(event: Event) {
        event.preventDefault();
        console.log(this.titleInputElement.value);
    }
}

다음은 사용자 입력을 가져오자.

class ProjectInput {
  	// ...
  
	private gatherUserInput(): [string, string, number] | void {
        const enteredTitle = this.titleInputElement.value;
        const enteredDescription = this.descriptionInputElement.value;
        const enteredPeople = this.peopleInputElement.value;

        if (
        	// 여기서 validation 체크
        ) {
            alert('Invalid input, please try again!');
            return;
        } else {
            return [enteredTitle, enteredDescription, +enteredPeople];
        }
    }
    
    private clearInput() {
        this.titleInputElement.value = '';
        this.descriptionInputElement.value = '';
        this.peopleInputElement.value = '';
    }
    
    @autobind
    private submitHandler(event: Event) {
        event.preventDefault();
        const userInput = this.gatherUserInput();
        if (Array.isArray(userInput)) {
            const [title, desc, people] = userInput;
            console.log(title, desc, people);
            this.clearInput();
        }
    }
}

gatherUserInput 함수는 유효검 검사를 통과하면 사용자 인풋 값인 튜플을 반환하고 그렇지 않을 때에는 빈 값을 반환한다.

사용자 인풋 값이 튜플인지를 알기 위해서는 배열인지를 확인하는 것으로 대체했다.

서브밋했을 때 인풋을 비워주는 clearInput도 추가했다.


유효성 검사를 위한 함수를 만들자.

interface Validatable {
    value: string | number;
    required?: boolean;
    minLength?: number;
    maxLength?: number;
    min?: number;
    max?: number;
}

function validate(validatableInput: Validatable) {
    let isValid = true;
    if (validatableInput.required) {
        isValid = isValid && validatableInput.value.toString().trim().length !== 0;
    }
    if (validatableInput.minLength != null && typeof validatableInput.value === 'string') {
        isValid = isValid && validatableInput.value.length > validatableInput.minLength;
    }
    return isValid;
}

class ProjectInput {
  	// ...
  	
  	private gatherUserInput(): [string, string, number] | void {
        const enteredTitle = this.titleInputElement.value;
        const enteredDescription = this.descriptionInputElement.value;
        const enteredPeople = this.peopleInputElement.value;

        if (
            validate({ value: enteredTitle, required: true, minLength: 5 }) &&
            validate({ value: enteredDescription, required: true, minLength: 5 }) &&
            validate({ value: enteredPeople, required: true, minLength: 5 })
        ) {
            alert('Invalid input, please try again!');
            return;
        } else {
            return [enteredTitle, enteredDescription, +enteredPeople];
        }
    }
}

minLength가 0이면 유효성 검사가 false가 되는 것을 막아보자.

validate 함수에서

validatableInput.minLength != null

위는 =가 하나로, null과 undefined를 모두 포함한다.

이렇게 하면 0으로 설정할 때 원하는 동작을 할 수 있다.

조금 더 정리를 하면

class ProjectInput {
  	// ...
  
  	private gatherUserInput(): [string, string, number] | void {
        const enteredTitle = this.titleInputElement.value;
        const enteredDescription = this.descriptionInputElement.value;
        const enteredPeople = this.peopleInputElement.value;

        const titleValidatable: Validatable = {
            value: enteredTitle,
            required: true
        };
        const descriptionValidatable: Validatable = {
            value: enteredDescription,
            required: true,
            minLength: 5
        };
        const peopleValidatable: Validatable = {
            value: +enteredPeople,
            required: true,
            min: 1,
            max: 5
        };

        if (
            !validate(titleValidatable) ||
            !validate(descriptionValidatable) ||
            !validate(peopleValidatable)
        ) {
            alert('Invalid input, please try again!');
            return;
        } else {
            return [enteredTitle, enteredDescription, +enteredPeople];
        }
    }
}

0개의 댓글