티스토리 뷰

퇴사

 이직을 하겠다고 생각하고 지난 3월 10일 1년 8개월 다닌 회사에서 퇴사를 했다.

기존에 하던 일은 RPA프로그램을 만드는 일이었고, 프로젝트를 진행하며 팀 프로젝트에서의 협업, 타 부서 또는 고객과의 의사소통하는 법에 대하여 많은 것을 배울 수 있었다.

하지만 사용하는 기술은 Automation Anywhere라는 RPA 전용 개발툴과 excel 뿐이었고(프로그래밍 언어는 사용하지 않는 개발툴이었다...), 아무래도 고객사가 대기업이다 보니, 보안상의 이유로 git, github와 같은 형상관리 시스템, tello나 jira와 같은 협업툴을 일절 사용 할수가 없었다.

실무 프로젝트를 진행하며 많은 것을 배웠지만, 반대로 이 일을 계속 할 경우 개발자로써 기술적으로 커리어에 문제가 될 수도 있겠다 생각을 했고, 1년 반 정도의(...) 고민을 해오다, 더 늦출 수 없게 생각을 했다.

결국 28살이 된 2021년 3월 퇴사를 하게되었다.

사실 회사를 다니며 공부할 수 있었겠지만, 회사를 다니며 공부하는게 쉽지가 않았다.

 

28살인 지금 다소 늦을 수도 있는 나이지만 퇴사 후 웹 front-end 개발자로 진로를 바꾸기 위해 React를 공부하고 있다.

 그리고 공부 해야될게 많을 것 이라고는 생각했지만 직접 부딪쳐 보니 앞으로 해야 될 학습량이 ㄹㅇ 방대함을 실감하고 있다. 

ㅠ_____________ㅠ

computer science, html, css, js는 기본이고, ts, npm, Nodejs, React, Next.js, Git, Web Security, Webpack, Docker, Kubernetes 등 (나열하자니 끝이없다..)

 

첫 토이 프로젝트

 이직을 위한 포트폴리오용 프로젝트를 만들기로 했고, 첫 프로젝트는 boiler-plate코드를 제작하는 것이었다.

front-end 개발을 위해 사용할 기술은 React라는 라이브러리이고, 현재 본격적으로 접하기 시작한 후 약 한달정도 시간이 지났다.

단순하게 공부부터 한다면, 어떤 상황에 어떤기술을 사용해야 될 지 모르고, 또 효율이 잘 안날거라고 생각해서 바로 시작한 프로젝트를 시작하기로 했다.

간단하게 유저등록, 회원가입, 유저정보 수정기능을 포함한 Boiler-Plate 코드를 제작해 보기로 했다.

사실 정말로 별 내용이 없는 프로젝트이기도 하다.

기능이 정말 몇개 없지만, LifeCycle, Components, Rendering방식/최적화, 상태관리, Props data flow등 생각해야 될 문제가 굉장히 많았고, 이러한 문제들을 단독으로 부딪히며 해결해 나가기 위해 노력하고 있다.

1번째 리팩토링 하기 전 최초의 프로젝트는 아래의 인프런 강의를 보며 진행했다.

www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%A9%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%85%B8%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B8%B0%EB%B3%B8/dashboard

 

[무료] 따라하며 배우는 노드, 리액트 시리즈 - 기본 강의 - 인프런 | 강의

이 강의를 통해서 리액트와 노드를 어떻게 사용하는지 기본적인 내용들을 배울 수 있습니다., 리액트와 노드의 기본을 학습하세요! 📝 강의 소개 안녕하세요 ^ ^ 이 강의에서는 리액트와 노드

www.inflearn.com

 간단한 프로젝트지만, react js를 사용하며, 프로젝트 소스코드 분할, redux를 이용한 외부 상태관리등 배울 점이 많은 강의 였다. (redux는 정말정말정말 어려웠다...)

 위의 강의를 보며 처음 프로젝트를 진행했고, 강의를 보며 만든 프로젝트를 js가 아닌 ts로 바꾸고 css frame work도 bootstrap을 사용하며 첫번째로 리팩토링을 했다.

랜딩페이지, 회원가입페이지, 로그인 페이지 등 개선 작업을 거쳤지만 UI가 굉장히 조잡하고, 소스코드도 상당히 비효율적 이었다.

 

2번째로 리팩토링 작업을 했다.

이번에는 styled-component라이브러리를 이용하여 화면을 좀더 동적으로 제어하길 원했고, bootstrap이 아닌 직접 css로 디자인하길 원했고, 로그인페이지, 회원가입페이지 뿐만아니라 navbar의 반응형, sidebar의 토글 기능 등 좀더 세세한 작업을 진행했다. 

그리고 유저상태를 Redux에서 관리할 수 있도록 했다.

 

 

3번째로 리팩토링을 해야겠다고 생각한 이유는 여전히 조잡한 UI,

styled-component의 남발로 이게 function 컴포넌트인지 styled컴포넌트인지 구분이 안가고 가독성이 떨어지는 소스코드, 회원가입 페이지에서 form 데이터의 복잡성 등등

좋은 소스코드가 어떤건지는 알수 없지만 딱봐도 이게 안좋은 소스코드인건 확실히 알 수 있다.

import React, { useState } from 'react';
import axios from 'axios';
import { MainLogo } from '../../../../image/Images';
import { login_page, main_page } from '../../../../info_manage/page_url';
import { 
    UserRegisterMainScreen, 
    UserRegisterForm, 
    Form,
    UserImagePlace,
    UserImageLabel,
    InputDelimiter, 
    ConfirmDupButton,
    InputPassword,
    WarnText,
    UserRegisterButton,
    MoveLoginPage,
    MoveMainPage
} from './UserRegisterPageStyle'
import './UserRegisterPage.css';
import { Link, Redirect } from 'react-router-dom';
import { ConfirmUserForm } from '../../../../function_module/UserForm';
import check from './check.svg';

const formData: FormData = new FormData();
function UserRegisterPage() {
    const [userimgBase64, setUserimgBase64] = useState("");
    const [redirect, setRedirect] = useState(false);
    //server로 보낼 유저 정보관리
    const [userData, setUserData] = useState({
        useremail: "",
        nickname: "",
        password: "",
        password_confirm: ""
    });
    //카테고리별 조건과 유저가 양식에 맞췄는지에 대한 정보 관리
    const [warnText, setWarnText] = useState({
        useremail : ["", false],
        nickname : ["", false],
        password: ["", false],
        password_comfirm: ["", false]
    });
    //유저 정보가 중복되었는지에 대한 정보 관리
    const [dupData, setDupData] = useState({
        useremail: false,
        nickname: false,
    })
    //유저 정보 중복 확인 후 다시 바뀔 경우 대비하여 관리
    const [userDataTmp, setUserDataTmp] = useState({
        useremail: "",
        nickname: ""
    });
    //========input box blur 처리=========
    let warn: string ="";
    let blurText: boolean = false;
    const emailWarnText = () => {
        if(userData.useremail === "") warn = "* 이메일을 입력해주세요.";
        else if(ConfirmUserForm(userData.useremail, 0) === false) warn = "* 이메일 양식으로 입력해주세요.";
        else {
            blurText = true;
            warn = "* 중복 확인을 해주세요.";
        }
        setWarnText({
            ...warnText,
            useremail: [warn, blurText]
        });
    }
    const nicknameWarnText = () => {
        if(userData.nickname === "") warn = "* 별명을 입력해주세요.";
        else {
            blurText = true;
            warn = "* 중복 확인을 해주세요.";
        }
        setWarnText({
            ...warnText,
            nickname: [warn, blurText]
        });
    }
    const passwordWarnText = () => {
        if(userData.password === "") warn = "* 비밀번호를 입력해주세요."
        else if(ConfirmUserForm(userData.password, 1) === false) warn = "* 비밀번호양식은 8~25자리 숫자,영문자 혼합입니다."
        else{
            blurText = true;
            warn = "비밀번호가 입력 완료되었습니다.";
        } 
        setWarnText({
            ...warnText,
            password: [warn, blurText]
        });
    }
    const passwordConfirmWarnText = () => {
        if(userData.password === "") warn = "* 비밀번호를 입력해주세요.";
        else{
            if(userData.password_confirm === "") warn = "* 비밀번호 확인을 입력해주세요.";
            else if(userData.password !== userData.password_confirm) warn = "* 비밀번호와 비밀번호 확인이 다릅니다.";
            else{
                blurText = true;
                warn = "비밀번호와 비밀번호 확인이 일치합니다.";
            } 
        }
        setWarnText({
            ...warnText,
            password_comfirm: [warn, blurText]
        });
    }
    //========input box blur 처리=========

    //========Image 미리보기 Script=========
    const fileChangeHandler = (e : React.ChangeEvent<HTMLInputElement>) => {
        const reader: FileReader = new FileReader();
        reader.onloadend = () =>{
            if(reader.result){
                setUserimgBase64(String(reader.result));
            }
        }
        if(e.target.files){
            reader.readAsDataURL(e.target.files[0]);
            formData.set("user_image", e.target.files[0]);
        }
    };
    const imgRemoveHandler = () => {
        setUserimgBase64("");
        formData.delete("user_image");
    };
    //========Image 미리보기 Script=========

    //########### Script that sends a reqeust to the server ###########

    //======== Duplicate check email, password ==========
    const confirmEmail = async () => {
        //email칸이 비어있지 않고, email양식대로 입력이 되었을 경우 중복확인 진행
        if(userData.useremail === '') alert("* 이메일을 입력해주세요.");
        else if(warnText.useremail[1] === false) alert(warnText.useremail[0]);
        else{
            await axios.post('/email_confirm',{
                email : userData.useremail
            }).then(response => {
                if(response.data["result"] === 0){
                    setUserDataTmp({
                                ...userDataTmp,
                                useremail: userData.useremail
                            });
                             setDupData({
                                ...dupData,
                                useremail: true,
                            });
                }else if(response.data["result"] === 1){
                    setWarnText({
                        ...warnText,
                        useremail: ["* 중복된 이메일입니다.", true]
                    });
                    setDupData({
                        ...dupData,
                        useremail : false
                    });
                }else{
                    alert("알수 없는 오류");
                }
            });
        }
    }
    const confirmNickname = async () => {
        //별명 칸이 빈칸이 아닐경우 중복 확인 진행
        if(userData.nickname === '') alert("* 별명을 입력해주세요.");
        else{
            await axios.post('/nickname_confirm', {
                nickname: userData.nickname
            }).then(response => {
                if(response.data["result"] === 0){
                    //Dose not duplicated nickname
                    setUserDataTmp({
                        ...userDataTmp,
                        nickname: userData.nickname
                    });
                    setDupData({
                        ...dupData,
                        nickname: true,
                    });
                }else if(response.data["result"] === 1){
                    //Duplicated nickname
                    setWarnText({
                        ...warnText,
                        nickname: ["* 중복된 별명입니다.", true]
                    });
                    setDupData({
                        ...dupData,
                        nickname: false,
                    });
                }else{
                    alert("알 수 없는 오류");
                }
            })
        }
    }
    //------------- Detecting data change after duplication check ---------------
    if(userData.useremail !== userDataTmp.useremail && dupData.useremail === true){
        setDupData({
            ...dupData,
            useremail: false
        });
    }
    if(userData.nickname !== userDataTmp.nickname && dupData.nickname === true){
        setDupData({
            ...dupData,
            nickname: false
        });
    }
    //------------- Detecting data change after duplication check ---------------

    //======== Duplicate check email, password ==========

    //======== User register Request to server ==========
    const submit = async (e : React.FormEvent<HTMLFormElement>) => {
        //formData에 입력받은 데이터들 정렬
        e.preventDefault();
        if(dupData.useremail === false) alert("* 이메일 확인해주세요.");
        else if(dupData.nickname === false) alert("* 별명 확인해주세요.");
        else if(warnText.password[1] === false) alert("* 비밀번호를 확인해주세요.");
        else if(warnText.password_comfirm[1] === false) alert("* 비밀번호 확인을 확인해주세요.");
        else{
            formData.set("email", userData.useremail);
            formData.set("nickname", userData.nickname);
            formData.set("password", userData.password);
            formData.set("user_rol", "0");
            formData.set("created_by", userData.nickname);
            formData.set("updated_by", userData.nickname);
            await axios.post("/register_user", formData)
                .then((response) => {
                    if(response.data["registered"] === true){
                        alert("회원가입이 완료되었습니다.");
                        setRedirect(true);
                    }else{
                        alert("알 수 없는 오류가 발생하였습니다. 다시 시도해주세요");
                    }
                });
        }
    };
    //======== User register Request to server ==========

    //########### Script that sends a reqeust to the server ###########
    
    //회원가입 완료 시 로그인페이지로 이동
    if(redirect === true){
        return <Redirect to={login_page}/>
    }

    return (
        <UserRegisterMainScreen>
            <UserRegisterForm>
                <Form onSubmit={submit}>
                    <a href={main_page}>
                        <img src={MainLogo} alt="Home" className="logo-img"/>
                    </a>
                    <div className="user-register-text">
                        회원가입
                    </div>
                    <div>대표사진설정</div>
                    {userimgBase64 ? 
                        <UserImagePlace src={userimgBase64} onClick={imgRemoveHandler}/>
                        : 
                        <div/>
                    }
                    <br/>
                    <UserImageLabel htmlFor="userimage-button">
                        내 PC에서 찾기
                    </UserImageLabel>
                    <br/>
                    <input id="userimage-button" type="file" onChange={fileChangeHandler} hidden/>
                    <br/>
                    <InputDelimiter onBlur={emailWarnText} placeholder="이메일 입력" autoFocus
                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => setUserData({...userData, useremail: e.target.value})}
                    />
                    <label>
                        <ConfirmDupButton onClick={confirmEmail} type="button">중복확인</ConfirmDupButton>
                    </label>
                    <WarnText font={dupData.useremail} {...dupData.useremail}>
                        {dupData.useremail && <img src={check} alt="confirm"/>}
                        {dupData.useremail ? "사용할 수 있는 이메일 입니다." : warnText.useremail[0]}
                    </WarnText>
                    <InputDelimiter onBlur={nicknameWarnText} placeholder="닉네임 입력"
                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => setUserData({...userData, nickname: e.target.value})}
                    />
                    <label>
                        <ConfirmDupButton onClick={confirmNickname} type="button">중복확인</ConfirmDupButton>
                    </label>
                    <WarnText font={dupData.nickname} {...dupData.nickname}>
                        {dupData.nickname && <img src={check} alt="confirm"/>}
                        {dupData.nickname ? "사용할 수 있는 닉네임 입니다." : warnText.nickname[0]}
                    </WarnText>
                    <InputPassword onBlur={passwordWarnText} type="password" placeholder="비밀번호 입력"
                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => setUserData({...userData, password: e.target.value})}
                    />
                    <WarnText font={warnText.password[1]} {...warnText.password[1]}>
                        {warnText.password[1] && <img src={check} alt="confirm"/>}
                        {warnText.password[0]}
                    </WarnText>
                    <InputPassword onBlur={passwordConfirmWarnText} type="password" placeholder="비밀번호 확인"
                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => setUserData({...userData, password_confirm: e.target.value})}
                    />
                    <WarnText font={warnText.password_comfirm[1]} {...warnText.password_comfirm[1]}>
                        {warnText.password_comfirm[1] && <img src={check} alt="confirm"/>}
                        {warnText.password_comfirm[0]}
                    </WarnText>
                    <UserRegisterButton type="submit">가입하기</UserRegisterButton>
                    <Link to={login_page}>
                        <MoveLoginPage>로그인</MoveLoginPage>
                    </Link>
                    <Link to={main_page}>
                        <MoveMainPage>홈으로</MoveMainPage>
                    </Link>
                </Form>
            </UserRegisterForm>
        </UserRegisterMainScreen>
    );
}

export default UserRegisterPage;

실제로 사용한 회원가입 페이지의 소스코드와 유저 정보 수정페이지의 소스코드이다.

데이터 흐름이 굉장히 복잡할 뿐만 아니라, function components와 styled-components가 구분이 안간다.

어떻게 해야 좋은 소스코드인지는 앞으로 경험을 많이 해봐야 알 수 있을 것 같다. (험난하다..)

이러한 데이터 흐름은 formik이나 yup을 이용하여 처리가 가능하다고 들었는데, 실제로 어떻게 적용해야 될 지 공부한 후 가독성있게 소스코드를 작성해야겠다.

그리고 웹 디자인 쪽은 styled-components가 아닌 scss를 공부하여 적용할 생각이며, 화면구성은 직접 디자인 할게 아닌, 주제가 비슷한 웹 사이트의 form을 참고하며 적용할 생각이다. (조잡한 UI가 상당히 발목을 잡는다.)

사실 다른부분보다 css로 화면 구성할 때 시간이 너무 많이 걸린다. ㅜ_ㅜ 시간 단축할 방법도 찾아봐야 될것 같다.

 

느낀점

이번에 3번째 리팩토링을 진행하며 느낀점은 정말 기초가 중요하다고 생각이 들었다.

React에 대한 공부를 한 상태로 진행한 것이 아닌, 프로젝트를 진행하면서 공부하다 보니, 좋은 코드가 작성되지 않고, 계속해서 리팩토링 해야 될 상황이 생기고 있다.

 

이렇게 쓰고 보니 정말 구현 된게 별로 없다,,, 하지만 요정도의 구현에만 해도 고려해야 될 것이 많았다는 점(Rendering최적화, ReRendering문제, 소스코드 가독성, data flow 복잡성 등) 그리고 고려해야 될 점이 앞으로 더 많을 거란 점(메모리 누수확인, 메모리 최적화 등 정교한 프로그래밍이 필요하다) 내가 가진 기술이 정말 부족한 점을 느끼고 있다. 

어느정도의 조급함을 가지고 계속해서 임해야겠다.

 

또한 나의 최고 약점인 알고리즘에 대한 준비도 하고 있다.

프로그래머스 level 1~2문제를 며칠내내 붙잡고 있는 경우가 있을 정도로 약점이다.

너무 알고리즘문제에 매달려 있지 말고, 시간도 어느정도 조절해 가면서 열심히 풀어야겠다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함