Udemy에서 진행한 '프론트엔드 개발자 성장 가이드(장현석, 임동준)' 영상을 보고 정리한 글입니다.

📄 내용 정리

프론트엔드 개발자란?

  • HTMLWEB으로 연결된 문서브라우저 위에서 동작하는 문서이며, 다양한 기기 위에서 동작한다.
  • FE 개발 범위 : Backend For Fronted, 백오피스, 대시보드, 차트, UI 개발, 퍼블리셔, 코어, 모듈, 라이브러리, 디자인 시스템, 모바일
  • 필수 역량 : 1. HTML 2.javascript 3.CSS 4.브라우저 5.네트워크

학습의 주도권 가져오기

  • 오늘은 JavaScript 호이스팅에 대해서 공부해야지 vs 오늘은 땡땡 자바스크립트 책 1페이지부터 400페이지까지 읽어야지
  • 책이나 강의를 끝내는 것을 목적으로 공부하는 것은 학습의 주도권을 뺏긴 것이다
  • 책이나 강의 : 블로그 글을 쭈루룩.. → 남이 공부한 것을 따라치고, 나열한 것이 진짜 공부인가?
  • 내가 무엇을 공부해야 할지 모르는 상태 ㅠ
  • 채용공고, 책 목차, 강의 목차 등 키워드 단위로 공부하기!
    • 추천하는 방법 온갖 책의 목차를 긁어보기 → 나만의 학습목차를 만들기 → 각 책에서 그 부분들을 읽고 정리하기

Input vs Output의 혼합

  • 오늘은 Debounce, Throttle에 대해서 공부해야지 vs JavaScript를 이용해서 무한 스크롤을 만들어야지! 거기서 Debounce, throttle을 이용해봐야지.
  • 클론코딩은 서비스, 디자인을 클론하는거지 코드를 클론하는 것이 아님
  • Input + Output + Feedback

자기 객관화

  • 함께 자라기 : 스터디, 해커톤, 학습 내역 체계화해서 정리하기
  • 회사 지원하기
    • Top → Down
    • Botton → Up
    • 내가 공부할 키워드!

피드백 주도 성장

  • 내 경험치와 점수를 어디서 얻지?
  • 더 자주, 더 빨리, 더 꾸준히 받을 수 있는 피드백!!
  • 테스트코드, 코드리뷰, 짝 프로그래밍, 커뮤니티 참여
  • 문제 바깥에 있는 사람을 맥락을 파악해 문제를 빨리 해결할 수 있다
  • 써먹을 수 있는 환경에서 공부해야

Q&A

  • 좋은 개발자란? 문제를 정의하고, 문제를 해결하고, 해결을 공유
  • 어떻게와 왜를 방황도 하면서 직접 찾아보기
  • 업무와 연결되어 있는 공부를 하면 업무와 공부 실력이 함께 늘어갈거예요
  • 작은 문제를 얼마나 탁월하게 해결하려 했는지가 중요

👩🏻‍🔧 어떻게 나에게 녹일까?

강의를 듣고 현재 학습 방식의 문제를 파악해봤다.

  • 완강, 완독 목적의 공부
  • 피드백이 부족
  • input의 부족

지금 채우고 싶은 부분은 CS와 JavasScript에 대한 깊은 이해, 그리고 당장 회사일을 잘하고 싶다는 것이다. 사실 계속 방황하는 지점은 이 지점인데, 내가 채우고 싶은 부분과 회사 일의 접점이 지금 당장 눈에 보이지 않는다. 그래도 우선은 input을 늘리고 접점이 생길 때까지 해나갈 수 밖에 없다고 생각한다. 다만 일을 할 때 만난 문제를 탁월하게 해결하는 습관을 꾸준히 길러야겠다고 생각했다.

공부 방법에도 변화를 주려고 한다. 지금까지는 책을 처음부터 끝까지 읽거나, 강의를 끝까지 보는 형식이었는데 꾸역꾸역 해왔다. 하지만 남는게 끝냈을 때의 약간의 성취감뿐이라는 것을 인정해야겠다. 가고 싶은 회사의 채용공고와 면접 문제들을 자주 보면서 가장 궁금한 키워드들 위주로 공부를 하려한다. 목차가 흥미로운 책을 발견했는데, 한 번에 여러 주제가 아닌 하나의 주제로 다양한 자료들을 보면서 공부하고, 내용을 옮겨적는 포스팅이 아닌 공부한 것을 다시 정리하는 포스팅을 하려한다.

그래도 나는 피드백을 받기 좋은 환경에 있는데, 사수님에게 한달에 한번 면담을 하면서 조언을 구하거나 일에 대한 피드백을 받고 있고, 코드리뷰도 하고 있다. 내년에는 내가 해온 공부의 결과와 방향성에 대한 피드백을 주기적으로 인프런 멘토링을 통해 받으려고 한다. 그러려면 해온 결과물이 있어야하기 때문에 그 자체로도 동기부여가 될것이라고 생각한다.

그래서 지금 머리속의 계획을 정리하자면

  1. ‘한 권으로 읽는 컴퓨터 구조와 프로그래밍’ 목차별로 공부하기
  2. 일할 때 만난 문제를 탁월하게 해결하는 습관 기르기
  3. 사수님에게 피드백 적극적으로 요청하기
  4. 한달에 한번 멘토링 받기 - 멘토링을 위한 준비 열심히 하기

'성장' 카테고리의 다른 글

Input  (0) 2022.01.13
요즘 고민  (0) 2021.12.01
탈잉 월간 코드리뷰 ver1 리뷰  (0) 2021.10.29

원문링크 : Build own your react
원문을 보시면 글의 흐름에 따라 추가되고, 수정되는 코드를 한 눈에 확인할 수 있습니다.

미흡한 실력으로 읽어 내려가고, 실습해보면서 이해 안 가는 부분은 우선 넘어가고,학습 보조 도구로 작성한 글이라 해당 글에 대한 정보를 얻기에는 부족한 글입니다.

들어가기 전에

  • 왜? 자바스크립트로 리액트의 원리를 알고 싶어서
  • 결과물? 자바스크립트로 작성된 리액트 코드
  • 어떻게 ? ‘Build your own React’를 보고 따라한다, 더 알고싶은 부분은 공부해서 정리한다

💡목차
Step I: The `createElement` Function
Step II: The `render` Function
Step II: Concurrent Mode
Step IV: Fibers
Step V: Render and Commit Phases
Step VI: Reconciliation
Step VII: Function Components
Step VIII: Hooks

1. createElement Function

JSX —babel—> React.createElement

React의 element는 React.createElement 함수를 이용해 만들어지지만 가독성이 떨어지기 때문에 JSX 문법을 사용한다. JSX 문법은 babel이 JS로 변환한다.

React.createElement

function createElement(type,props,...children){ // (type,props,[children])
    return {
        type,// tag name
        props:{
            ...props,
            children
        }
    }
}
// text를 처리하기 위한 함수
function createTextElement(text) {
  return {
    type: 'TEXT_ELEMENT',
    props: {
      nodeValue: text,
      children: []
    }
  };
}

💡 뜬금없이 스프레드 연산자!

...children은 children이라는 배열의 요소를 스프레드 한 것. 따라서 type,props,[children]이 됨

2. render Function

ReactDOM.render

  1. DOM node 생성
  2. props 추가
  3. 생성한 node를 container에 추가
function render(element,container){
    const dom = 
                element.type == "TEXT_ELEMENT"
                ? document.createTextNode("") // text node
                : document.createElement(element.type) // element type으로 DOM노드 생성하기

  const isProperty = key => key !== 'children'
  // element에 props 추가
    Object.keys(element.props)
            .filter(isProperty)
            .forEach(name => {
                dom[name] = element.props[name]
            })                
    // children을 재귀적으로 
    element.props.children.forEach(child=>
        render(child,dom)    
    )
    container.appendChild(dom) // 생성한 node container에 추가
}

3. Concurrent Mode(동시성 모드)

render 함수에서 재귀호출부분의 경우 트리 끝까지 렌더가 될 때까지 동작을 멈출 수가 없다. 트리가 커지는 경우 긴 시간 동안 메인 스레드가 멈추게 된다. 브라우저가 높은 우선 순위의 작업을 처리하기 위해서 렌더를 멈출 필요가 있다.

function render(element, container) {
... 
 element.props.children.forEach((child) => render(child, dom)); 

  container.appendChild(dom);
}

그래서 작업을 작은 단위로 나누고, 각 단위가 끝나면 브라우저가 렌더링을 중단하도록 한다.

let nextUnitOfWork = null

function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }
  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(nextUnitOfWork) {
  // TODO
}

requestIdleCallback : loop를 만들기 위해 requestIdleCallback를 사용한다. 브라우저의 메인 스레드가 idle 상태가 되면 requestIdleCallback을 호출한다.

4. Fibers

fibertree

각 작업 단위를 구조화 하기 위해 fiber tree 라는 자료 구조가 필요하다.

위와 같은 작업을 수행한다고 할 때 render 함수 내부에서 root fiber를 만들고, 이것을 nextUnitOfWork로 설정한다. 나머지 작업들은 performUnitOfWork function에서 일어난다.

각 fiber는 세가지 일을 한다.

  1. element를 DOM에 추가한다
  2. element의 children에 추가할 fiber를 만든다
  3. 다음 작업 단위를 선택한다.

fiber - children - ling - parent

fiber tree의 목적은 다음 작업단위를 쉽게 찾는데에 있다.

한 fiber의 작업이 끝났을 때, children이 있을 경우에는 children이 없을 때는 sibling이 다음 작업 단위가 된다.

sibling도 없을 경우 parent로 올라가고, root로 갈때까지 render 된다.

5. Render and Commit

지금의 코드에서 새로운 node가 DOM에 추가될 때 브라우저는 렌더 트리가 완성되기 전에 작업을 중단할 수 있다. 이 경우 우리는 미완성된 UI를 보게된다.

이를 위해 DOM을 제거하는 로직 대신, fiber 루트를 추적할 것이다. 이를 wipRoot(work in progress rot)라고 한다.

일단 모든 작업이 끝나고 나면 (더 이상 다음 작업이 없는 경우), 전체 fiber 트리를 돔에 커밋한다.

이 과정은 commitRoot 함수에서 이루어집니다. 여기서 모든 노드를 재귀적으로 dom에 추가한다.

6. Reconciliation(재조정)

node의 update와 delete는 render 함수로 얻은 element를 마지막으로 커밋한 fiber 트리와 비교한다.

이를 위해 커밋을 한 후 마지막 fiber tree를 저장한다. 이를 current root라고 부른다.

그리고 모든 fiber 속성에 alternate를 추가한다. 이것은 이전의 단계에서 커밋한 fiber다.

그런 다음 performUnitOfWork에서 새로운 fiber를 생성하는 로직을 추출해 reconcileChildren 함수를 만든다. 예전 fiber의 자식들과 재조정해야하는 element를 비교하기 위에 동시에 순회한다.

element와 oldFiber의 type 비교

  • 타입이 같으면 props만 바꾼다 - update
  • 타입이 다르면 새로운 element이므로 새로운 DOM node를 생성한다 - placement
  • 타입이 다르고 예전 fiber가 있으면 예전 node를 지운다 - deletion

effectTag 속성을 추가한다.

제거하고 싶은 노드를 추적하기 위해 delection 배열을 만든다.

commitRoot에서 delection 배열을 순회해 commitWork를 실행한다. commitWork 함수에서 effectTag를 이용해 append하거나 removeChild하거나 update한다.

갱신을 위해 updateDom 함수를 만든다.

예전 fiber의 props를 새로운 fiber의 props와 비교해 사라진 props는 제거하고, 달라진 props를 설정한다. 이 때 on으로 시작하는 props(event)가 있다면 eventListner를 추가한다.

7. 함수형 컴포넌트

함수형 컴포넌트로 만들어진 fiber에는 DOM node가 없다. 그래서 children을 props로 가져오는 대신 함수를 호출한다.

fiber 타입이 함수인지 체크한다음 updateFunctionComponent 에서 지금까지와 같은 역할을 하게끔 수정한다.

8. Hook

함수형 컴포넌트에 상태를 추가하기.

const Myact = {
  createElement,
  render,
  useState,
}

/** @jsx Didact.createElement */
function Counter() {
  const [state, setState] = Didact.useState(1)
  return (
    <h1 onClick={() => setState(c => c + 1)}>
    Count: {state}
    </h1>
    )
}
const element = <Counter />
const container = document.getElementById("root")
Didact.render(element, container)
let wipFiber = null
let hookIndex = null

function updateFunctionComponent(fiber) {
  wipFiber = fiber
  hookIndex = 0
  wipFiber.hooks = []
  const children = [fiber.type(fiber.props)]
  reconcileChildren(fiber, children)
}

함수형 컴포넌트를 호출하기 전에 useState 함수의 내부에서 사용하기 위한 몇몇 전역 변수들을 초기화해야 한다.

먼저 작업중인 fiber를 설정한다. 또한 그 fiber에 hook 배열을 추가함으로서 동일한 컴포넌트에서 여러 번 useState 함수를 호출 할 수 있도록 한다.

function useState(initial) {
  const oldHook =
        wipFiber.alternate &&
        wipFiber.alternate.hooks &&
        wipFiber.alternate.hooks[hookIndex]
  const hook = {
    state: oldHook ? oldHook.state : initial,
  }

  wipFiber.hooks.push(hook)
  hookIndex++
  return [hook.state]
}

함수형 컴포넌트가 useState를 호출할 때 이것이 오래된 hook인지를 체크하는데, 이때 훅 인덱스를 사용해 fiber의 alternate를 체크한다.

만약 우리가 가지고 있는 것이 오래된 hook이라면 상태를 초기화하지 않았을 경우 이 훅의 상태를 새로운 훅으로 복사한다.

그리고 새로운 훅을 fiber에 추가한 뒤 훅 인덱스 값을 증가시킨 다음 state를 반환한다.

const hook = {
  state: oldHook ? oldHook.state : initial,
  queue: [],
}

    const setState = action => {
      hook.queue.push(action)
      wipRoot = {
        dom: currentRoot.dom,
        props: currentRoot.props,
        alternate: currentRoot,
      }
      nextUnitOfWork = wipRoot
      deletions = []
    }

wipFiber.hooks.push(hook)
hookIndex++
return [hook.state, setState]
}

또한 useState는 상태를 갱신하는 함수 역시 리턴해야 하므로, 액션을 받는 setState 함수를 정의한다. 이 액션을 우리가 훅에 추가한 큐에 넣는다.

그리고 렌더 함수에서 했던 것과 비슷한 작업을 하는데, 새로운 작업중(wip)인 루트를 다음 작업할 단위로 설정하여 반복문에서 새로운 렌더 단계를 시작할 수 있도록 한다.

const actions = oldHook ? oldHook.queue : []
actions.forEach(action => {
  hook.state = action(hook.state)
})

아직 액션을 실행하지는 않았다. 이는 컴포넌트 렌더링 다음에 수행하는데, 오래된 훅의 큐에서 모든 액션을 가져온 다음 이를 새로운 훅 state에 하나씩 적용하면 갱신된 state를 얻을 수 있게 된다.

새로 알게된 것

  • React의 element는 React.createElement 함수를 이용해 만들어지지만 가독성이 떨어지기 때문에 JSX 문법을 사용한다. JSX 문법은 babel이 JS로 변환한다.
  • react는 작업단위를 구조화하기 위해 fiber tree 구조를 사용한다.
  • virture DOM은 render 함수로 얻은 element 즉, javascript 객체다. 리액트는 이전의 tree와 새로운 tree를 동시에 순회해 비교한다.

'React' 카테고리의 다른 글

리액트에서 Variable과 State의 차이가 뭘까?  (0) 2022.01.13
presentational + container 패턴  (0) 2021.10.30
React Query  (0) 2021.07.09

이게 왜 궁금해?

data.index.status === Status.Approval

or

const isAppoval = data.index.status === Status.Approval;

이라는 조건을 여러 곳에서 반복적으로 사용하게 되었다.

딱 봐도 길고, 반복적으로 사용하고, 여러 조건들과 함께 쓰일 경우에는 더 의미를 직관적으로 알 수 없었다.

그래서 isApproval 이라는 무언가로 만들어 쓰려고 하는데 data가 변경될 때마다 isApproval 상태도 변경되어야 했다.

그래서 그냥 변수(variable)로 선언해도 될지, setState는 사용하지 않지만, state로 선언해야할지 궁금해서 찾아보았다.

결론부터

class variable은 변경했을 때 React가 알아채지 못한다.

state는 setState로 변경했을 때 react가 변화를 감지해 re-rendering된다.

내 코드의 경우 isApproval을 내가 직접 변경하지 않고, data에 따라 변하기 때문에 변화를 감지해 리렌더링 시켜야할 일은 없다. data가 변경되었을 때 렌더링되면서 isApproval의 값도 바뀌므로 변수로 사용해도 됨!

🤷‍♀️State가 뭐지?

// class variable
export default class Test extends Component {
  render(props) {
        super(props);
    const isMe = this.props.name === 'Gisele';
    return (
      <div>
        {isMe ? '나다' : '나 아니다'}
      </div>
    );
  }
}

// state
export default class Test extends Component {
  constructor(props) {
        super(props)
    this.state = { isMe : false };
  }

  render() {
    if(this.props.name === 'Gisele') {
      this.setState({isMe: true});
    }
    return (
      <div>
        {isMe ? '나다' : '나 아니다'}
      </div>
    );
  }
}

state와 variable의 차이는 class형 컴포넌트에서 더 알기 쉬워서 가져옴.

State 는 컴포넌트의 현재 상황에 대한 정보를 나타내는 일반 자바스크립트 객체다(클래스형 컴포넌트에서). 일반 변수는 함수가 종료될 때 사라지지만, state는 값이 보존된다.

컴포넌트의 역할은 raw data를 HTML로 변환하는 것이기 때문에 이 raw data는 props와 state 객체로 구성되어 있다고 생각할 수 있다. props와 state가 render() 함수의 입력 데이터라고 할 수도 있다. 따라서 state는 구성 요소의 동작을 제어하는 일부 속성을 나타낸다.

state는 상태 변수에 변경 사항을 직접 적용하지 않는다. 이는 React에서 변경사항으로 간주되지 않기 때문이다. 대신 state의 상태는 setState() 를 사용해 변경한다. 그리고 setState는 비동기식이다. 한 줄에서 setState를 호출하고 다음 줄에서 상태가 업데이트가 된다는 보장은 없다. setState가 상태를 업데이트하라는 명령이 아니고 상태 업데이트에 대한 요청이기 때문이다. 이 문제를 해결하지 위해 업데이트가 적용된 후 실행되도록 보장되는 setState() 콜백 함수를 사용할 수 있다.

함수형 컴포넌트에서는 useState() 훅을 사용해 state를 관리한다. 클래스형 컴포넌트에서 state는 항상 객체지만, 함수형 컴포넌트에서는 모든 type이 state가 될 수 있다. 각 상태는 단일 값을 보유한다.

초기 값은 초기 렌더링에만 할당된다. 이후 렌더링에서 useState의 인수는 무시되고 현재 값이 검색된 값이 된다.

const Avartar = (user) => {
    const [userState, setUserState] = useState(user);

    // 해결
    useEffect(()=>{
        setUserState(user)
    },[user])

    return user.avartar && (<img src={user.avartar}>)
}

useState만 사용하면 해당 인수가 prop이 변경될 때마다가 아니라 처음에만 사용되기 때문에 작동하지 않는다. 이 문제를 해결하려면 들어오는 객체를 state로 사용할 때는 새 객체를 만들어야 한다.

그렇지 않으면 리액트는 객체의 변경을 감지하지 못한다.(객체는 참조형 data이기 때문에 객체 내부 값이 변해도 객체 자체의 주소값이 변하기 않기 때문)

const Message = ()=>{
    const [messageObj,setMessageObj] = useState({message:''})

    return (
        <>
           <input type='text'
                  value={messageObj.message}
                  onChange={e=>{
                  // 동작하지 않는 예시
                     messageObj.message = {e.target.value};
                     setMessage(messageObj) 

                 // 동작하려면 새로운 객체를 만들어야 한다.
                      const new = {message:e.target.value};
                      setMessage(newMessageObj)

                 // 또는 setState의 첫 번째 인수인 prevState와 스프레드를 이용할 수 있다
                       setMessage(prevState=>{
                           return {...prevState,message:e.target.value}
                       })

                }} />
        </>
    )
}

위 내용으로 state의 특징을 정리하면 다음과 같다.

  1. state는 컴포넌트의 어떤 정보를 갖고 있는 데이터다.
  2. state가 변경되면 React는 이를 감지해 render가 일어난다.
  3. state는 바로 할당하는 방식으로 변경하면 react가 변화를 감지하지 못하므로 setState를 이용해 변경한다.
  4. setState는 비동기로 처리되므로, setState 바로 다음 명령에서 새로운 값이 반영되지 않을 수 있음
  5. state를 객체로 사용할 경우 값을 할당할 때 스프레드 연산자 등을 이용해 새 객체를 만들어줘야함(객체의 data type이 참조형이기 때문에 내부값이 변해도 객체 자체의 주소값이 변하지 않기때문)
  6. setState는 첫 번째 인자로 이전 상태값을 갖는다.

그리고 내가 예전에 몰랐거나 실수를 범했던 것들의 이유를 알 수 있었다.

❓ 이 함수에서 setState를 여러번하면 렌더링이 매번 일어나나? -> 아니다. 해당 블럭이 모두 실행된 후! 렌더링이 일어난다.
❓ 객체 state가 왜 안바뀌었지? 새로운 객체를 생성하지 않고, 객체에 값을 바로 할당했기 때문
❓ 바로 위에서 setState를 했는데 왜 이전 상태값으로 처리가 됐지? 같은 함수 블럭 안에서 setState한 직후 실행한 것은 아직 state에 새로운 값이 반영되기 전이므로 그렇다. 코드 블럭이 다 실행된 후 state에 값이 반영된다.

State의 불변성


referece

'React' 카테고리의 다른 글

나만의 React 라이브러리 만들기  (0) 2022.01.18
presentational + container 패턴  (0) 2021.10.30
React Query  (0) 2021.07.09

내가 확실히 이해하고, 실천하고 있는 클린코드 원칙만!

  • 하나의 function은 하나의 기능만
  • 명확한 이름
  • 매개변수는 객체로
  • 높은 응집도, 낮은 결합도
  • 컴포넌트는 모델 스키마를 기준으로

 

https://gisele-dev.tistory.com/47

 

리팩터링 2판

리팩터링 원칙 리팩터링이란 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법이다. 좋은 설계, 쉬운 이해, 버그 찾기, 프로그래밍

gisele-dev.tistory.com

https://gisele-dev.tistory.com/48

 

클린코드

깨끗한 코드는 한가지를 제대로 한다. 1. 깨끗한 코드 "깨끗한 코드는 단순하고 직접적이다 .깨끗한 코드는 잘 쓴 문장처럼 읽힌다. 깨끗한 코드는 결코 설계자의 의도를 숨기지 않는다. 오히려

gisele-dev.tistory.com

 

  • URL에 #등의 특수문자는 인코딩해주기! - # → %23
  • amplify 자동 배포가 안될 때 웹훅추가, reconnect
  • typescript static readonly - readonly를 붙인 class 내부 property나 interface 구현체는 값 변경을 못함
  • https://www.tutorialsteacher.com/typescript/typescript-readonly
  • react-query refetch는 enabled와 상관없이 동작한다. useEffect에 있는 경우 주의
  • 같은 의미에는 같은 단어를 쓰기
  • npm과 npx의 차이?npx는 npm 레지스트리에 올라가있는 패키지를 쉽게 설치하고 관리할 수 있도록 도와줌. npm을 통해 설치하는 모든 종류의 Node.js 기반 파일을 간단하게 설치하고 실행할 수 있게 도와줌.npx를 사용하는 경우
    • npm run script를 사용하지 않고 로컬에 설치된 패키지를 사용할 경우
    • 일회성 명령으로 패키지를 실행할 경우
    • gist-based script를 실행할 경우
    npx란 무엇일까? 그리고 npm이랑 어떤 차이점이 있을까?
  • 즉, npm = Package Manager(관리), npx = Pacakage Runner(실행)
  • npx는 npm의 npm을 좀 더 편하게 사용하기 위해서 5.2.0 버전부터 새로 추가된 도구!
  • useQuery의 결과값으로 useEffect 안에서 setState를 하면 무한루프를 돈다
    • 원인 ? setState ↔  useQuery 호출의 무한반복
    • 해결 ? useEffect array에 date가 아니라 isSuccess를 넣는다
  • 상수는 곳곳에서 쓰이는 값을 한번에 바꾸기 위해서 쓰는 것!
  • 타입 캐스트를 어디서 할지에 대해
  • antd popconfirm에서 form submit을 하고싶을 때?
    • *okButtonProps*={{ htmlType: 'submit', form: formName }}

'성장' 카테고리의 다른 글

Udemy 프론트엔드 개발자 성장 가이드  (0) 2022.01.18
요즘 고민  (0) 2021.12.01
탈잉 월간 코드리뷰 ver1 리뷰  (0) 2021.10.29

벌써 1년 6개월정도 경력의 2년차 개발자다. 두둥. 1년은 SI 개발을 했고, 지금은 스타트업에서 프론트엔트 개발자로 일하고 있다. 29살에 경력을 시작해, 첫 5개월은 개발을 한다는 자체가 기뻤다. 학원 출신이라 CS에 대한 갈증이 있었기 때문에 강의와 책을 통해 CS 공부를 조금씩했다. 그 때까지도 지금 개발 세계에서 펼쳐지고 있는 일들을 거의 몰랐다. 첫 프로젝트는 나름 만족스러웠다. SI는 악명이 높다지만 나는 운이 좋았는지 적당한 업무 강도, 적당한 환경이었기 때문이다.

그 다음으로 알만한 IT 회사의 외주 프로젝트를 했다. 그 때 엄청난 자극을 받았다. 나는 들어본적도 없는 들어본 도커나 AWS를 쓰고, 좋은 코드를 짜기 위해 노력하고, 스프린트를 진행하고, 회사 사옥도 좋았다. 지나다니는 사람들도 다 개발자고, 누군가의 책상 위에 GoLang 책이 놓여있었다. 내가 얼마나 뒤처져있는지 알았고, 내가 원하던 개발자는 이런거였구나 싶었다. 그래서 '프론트엔드 개발자로 회사에 취직하기'를 목표로 했다. 그 때부터 깃헙을 관리하고, 포트폴리오를 만들고, 프론트엔드 관련된 강의를 들으며 준비했고 다행히 지금 회사에 취직을 하게 되었다.

처음 3달동안은 팍팍 성장하는게 느껴졌다. 좋은 개발문화를 만들기 위해 노력하고, 코드리뷰도 받고, 회사 도메인에 대해 이해도 하고, 프로젝트 구조에 대해 고민하고, 어떤 기술 스택을 쓸지 공부하고... 그러면서 취업준비+재택근무 오래하다가 오랜만에 출근+통근 왕복 3시간+회사적응등등의 이유로 착실히 쌓인 피로가 터지면서 퍼져버렸다. 공부를 하지 않으면 불안한 마음이 늘 있었는데 그럴 마음 조차들지 않을만큼 피곤에 절어서 퇴근하면 자기 바빴고, 성장이라는 단어에 신물이 났다. 그렇게 일만하면서 회복하기를 또 한 두달.

그리고 지금이다. 퇴근하고 컴퓨터 앞에 앉을만큼 체력과 정신력이 생겼고, 성장하고 싶다는 욕구가 퐁퐁 솟아오르는데 이제 어떻게 해야할지 모르겠다.
지금 모르는 것을 나열해보면

  • 성장이 뭐지
  • 어떻게 해야하지
  • 나는 지금 회사에서 일을 잘하고 있나
  • 일을 잘한다는건 뭐지
  • 일을 잘하려면 뭘해야하나
  • 곧 3년차가 되는데 이대로 괜찮나
  • 약속의 3년에 쓸만한 개발자는 뭘 어떻게 해야하지
  • 연봉협상을 위해서 어필할게 지금 있나
  • 이직을 한다고 했을 때 과연 가능한 수준인가
    ....이다.


뭐이렇게 모르는게 많아?

다시 정리해보면 나의 핵심 욕구는 성장하고 싶다와 일을 잘하고 싶다.
그런데 개발자로 성장하는 것과 일을 잘하는 것이 어떤 건지 모르겠다.
그래서 어떻게할지 모르겠다.

자바스크립트 강의를 쭉 들었고, 또 강의를 끊어는 놨는데 이게 맞나 싶어서 집중을 못하고 있다. 그래서 우선을 방향과 목표를 구체적으로 정하는게 좋을 것 같아 개발자 성장에 대한 글이나 영상을 보고 정리하려고 시작한 글이었는데...이렇게 글이 길어졌네.

덕분에 생각을 정리했으니 이제 몸을 움직일 차례다. 무브무브.

'성장' 카테고리의 다른 글

Udemy 프론트엔드 개발자 성장 가이드  (0) 2022.01.18
Input  (0) 2022.01.13
탈잉 월간 코드리뷰 ver1 리뷰  (0) 2021.10.29

프로젝트에서 파일 업로드와 다운로드를 구현하면서 사용한 코드들의 의미를 알아보기 위해 정리한 글입니다.

파일 업로드

flow :
file input -> 파일 선택 -> formdata 객체 만들기 -> multipart로 전송

Input type=file

<input type="file"
       id="avatar" name="avatar"
       accept="image/png, image/jpeg">

file type은 input 요소의 공용 특성 외에도 추가적인 특성을 지원한다.

accept는 선택할 수 있는 파일 유형을 지정할 수 있다. 확장자(.jpg,.pdf,.doc)나 MIME 유형의 문자열을 값으로 입력하면 해당 유형의 파일만 선택할 수 있다.

아래처럼 입력하면 엑셀파일만 선택할 수 있다.
accept='application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'

capture는 이미지 또는 비디오 데이터를 캡처할 때 사용할 방법이고, files는 선택한 파일을 나열하는 FileList다. multipletrue로 지정하면 여러 개의 파일을 선택할 수 있다.

선택한 파일에는 아래와 같이 접근할 수 있고,

const selectedFile = document.getElementById('input').files[0];

onChange event를 이용해 e.target.files[0]로도 접근할 수 있다.

Formdata

FormData 인터페이스는 form 필드와 그 값을 나타내는 일련의 key/value 쌍을 쉽게 생성할 수 있는 방법을 제공한다. 또한 XMLHttpRequest.send() 메서드를 사용해 쉽게 전송할 수 있다.

리액트에서 formData로 파일을 보내는 코드를 아래처럼 짰다.

  const [targetFile, setTargetFile] = useState<RcFile[]>([]);

const handleFileChange = (e)=>{
    const file = e.target.files[0]
    setTargetFile(file)
}

const onSubmit = ()=>{
    const formData = new FormData();

      formData.append('fileKey',targetFile[0])

      axios.post("api/uploadfile", formData)
              .then(res => {
                console.log(res);
              })
              .catch(err => {
                console.log(err);
              });
}


...
<input type="file" name="file" onChange={e => this.handleFileChange(e)}/>

Multipart

Multipart란 웹 클라이언트가 요청을 보낼 때 http 프로토콜의 바디 부분에 데이터를 여러 부분으로 나눠서 보내는 것을 의미한다. 여러 부분을 하나의 복합 메세지로 보낸다.

form을 통해서 파일을 등록하고 전송할 때 웹 브라우저가 보내는 HTTP 메시지는 Content-Type 속성이 multipart/form-data로 지정되며, 정해진 형식에 따라 메시지를 인코딩하여 전송한다. 이를 처리하기 위해 서버는 멀티파트 메시지에 대해서 각 파트별로 분리하여 개별 파일의 정보를 얻게 된다.

HTTP는 위 이미지와 같이 4개의 파트로 나뉘고
Header의 Content-Type 필드로 message body에 들어가는 데이터 타입을 명시한다.

multipart 메시지는

  • 서로 붙어있는 여러개의 메시지를 포함하여 하나의 복합 메시지로 보내진다.
  • MIME multipart 메세지는 Content-type 헤더에 boundary 파라미터를 포함한다
  • boundary는 메시지 파트를 구분하는 역할을 하며, 메세지의 시작과 끝 부분도 나타낸다
  • boundary를 선택하는 것은 클라이언트의 몫이다. 보통 무작위의 문자(UUID등)을 선택해 메세지의 본문과 충돌을 피한다
  • HTTP form을 채워서 제출하면, 가변 길이 텍스트 필드와 업로드 될 객체는 각각 멀티파트 본문을 구성하는 하나의 파트가 되어 보내진다. 멀티 파트 본문은 여러 다른 종류의 길이의 값으로 채워진 form을 허용한다.
  • multipart/form-data : 사용자가 양식을 작성한 결과 값의 집합을 번들로 만드는데 사용한다.

대용량 업로드에서 난 에러처리

파일 업로드를 진행하던 중

504 Gateway Time-out

에러가 발생했다. 파일 용량이 커서 nginx의 기본 timeout을 넘어갔기 때문에 발생하는 에러였다. 서버와 클라이언트간 connection을 유지하는 시간이 길기 때문이고 nginx의 기본 timout 시간을 바꿔주면 에러는 해결된다.

우리는 서버에서 bull library를 이용해 queue를 만들어 파일부터 전송하고, 업로드 과정은 클라이언트와의 connection과는 상관없이 처리하는 방식으로 구현했다.

파일 다운로드

flow :
api 요청 -> response를 blob으로 받아옴 -> 브라우저에서 다운로드

Blob

Blob객체는 파일류의 불변하는 미가공 데이터를 나타낸다. 텍스트와 이진 데이터의 형태로 읽을 수 있으며, ReadableStream 으로 변환한 후 그 메서드를 사용해 데이터를 처리할 수도 있다.

Javascript에서 File 인터페이스는 사용자 시스템의 파일을 지원하기 위해 Blob 인터페이스를 확장한 것이므로, 모든 Blob 기능을 상속한다.

생성

const blob = new Blob(array,options)

const blob = new Blob([response.data], { type: response.headers['content-type'] });
  • array : ArrayBuffer, ArayBufferView,Blob(file),DOMString 객체 또는 이러한 객체가 혼합된 Array를 사용할 수 있다.

  • option

    • type : 데이터의 MIME 타입, 기본값은 ""
      • endings : \n을 포함하는 문자열 처리를 'transparent'와 'native'로 지정. 기본값은 transparent

Property는 size type를 가지고, slice(start,end,type)를 사용해 지정된 바이트 범위의 데이터를 포함하는 새로운 Blob 객체를 만들 수 있다. 이를 Chunks 객체라고 한다.

Blob URL

  • Blob 객체를 가리키는 URL 생성
  • createObjectURL : Blob 객체를 나타내는 URL을 포함한 다음과 같은 DOMString을 생성. 이 Blob URL은 생성된 window의 document(브라우저)에서만 유요하다
  • revokeObjectURL() : URL.createObjectURL()을 통해 생성한 기존 URL을 폐기한다. revokeObjectURL을 통해 해제하지 않으면 자바스크립트 엔진에서 GC되지 않는다. 메모리 누수를 방지하기 위해 생성된 URL을 DOM과 바인딩한 후에는 해제하는 것이 좋다.
  • revokeObjectURL
export const downloadFile = (response: AxiosResponse<Blob>) => {
  const blob = new Blob([response.data], { type: response.headers['content-type'] });
  const url = window.URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;

  const filename = response.headers['content-disposition']
    .split('filename=')[1]
    .split(';')[0];

  link.setAttribute('download', filename);
  document.body.appendChild(link);
  link.click();
  link.remove();
  window.URL.revokeObjectURL(url);
};

reference

'Javascript' 카테고리의 다른 글

this  (0) 2021.11.28
실행 컨텍스트  (0) 2021.11.28
자바스크립트 런타임환경  (0) 2021.09.11
이벤트  (0) 2021.08.28
CRP(Critical render path, 브라우저 렌더링 순서)  (0) 2021.08.21

Fact

정산 시스템 - 델타

지난 달부터 하고 있는 정산 시스템이 베타를 지나 감마를 지나..델타 버전을 하고 있다. 이번달 중순까지 감마 버전 작업을 하고 있었는데 중간에 기획이 아예 바뀌었다.

기획이 바뀐 가장 큰 원인은 베트남측의 작업 플로우를 제대로 이해하지 못했기 때문이다. MISA라는 시스템을 어떻게 사용하고, 어떤 범위까지 업무에 활용하는지를 파악을 못하고, Misa를 배제하는 방향으로 개발을 하고 있었는데 Misa를 배제할 수 없는 상황에 Misa까지 우리 시스템에 녹이기에는 볼륨이 너무 커져서 한국의 정산 담당자의 플로우를 자동화하는 방향으로 기획을 수정하게 됐다.

새 기획에서는

  • 파일 업로드 기능
  • 파일 export 기능
  • 업로드한 데이터 확인
  • 데이터 확정 기능 을 구현했다.

그리고 정산 시스템을 개발하는 내내 도메인 이해를 하기 위해 노력을 많이 했다. 백엔드가 구현하는 비즈니스 로직이라도 전체 플로우를 이해하려고 했고, 회의하는 과정에서 질문도 많이 하고 의견도 많이 냈다.

백엔드와 프론트엔드 어떻게 개발하는지 서로 보기

스프린트 액션 아이템이었다. 프론트로 넘어오는 데이터가 어떤 과정을 통해서 넘어오고, 백엔드에서 보낸 데이터를 프론트에서 어떤 식으로 사용하는지를 이해하자는 취지였다.

현재 벡엔드는 nestJs라는 프레임워크를 사용하는데 타입스크립트를 기본으로 제공하고, express와 달리 아키텍처의 정의를 프레임워크가 제공하기 때문에 프로젝트의 구조나 코드가 비슷하다고 한다.

인상적이었던 부분은 세세한 구현은 implement layer에서 처리하고 service layer에서는 로직이 처리되는 흐름이 한 눈에 보이게끔 구현을 한 부분이었다.

Auth 리팩토링

처음에는 공통상태관리라이브러리를 사용하지 않다가 zustand를 쓰다가 unstatedNext를 도입하면서 auth 부분이 zustand 로직으로 구현되어 있었다. 그 부분을 unstatedNext로 리팩토링을 하면서 토큰 리프레쉬나 권한처리 부분을 같이 리팩토링했다.

자바스크립트 공부와 알고리즘 문제풀이

프로그래머스 lv1 문제를 끝내고 lv2 단계 문제를 일주일에 2~3개 정도 javascript로 풀고있다.

바닐라 자바스크립트로 간단한 게임을 만들었다. 구현을 끝내고 브라우저 101 강의를 들으며 강의를 보며 리팩토링을 했다. class와 contruct, 생성자를 사용해 리팩토링을 했고, this 바인딩과 컨텍스트에 대해 다시 공부했다.

실전 리액트 프로그래밍 속독 중

하루에 50 바닥씩 읽고 있다. 한번 훑고 정독하며 읽을 예정이다. 리액트 작동원리에 대해서 조금씩 배우고 있다.

Finding

  • 컴포넌트에 대한 논의 -> container를 어느 레벨에서 부를 것인가? depth는? 데이터 전달은?
    1. Page level에서 container를 호출하고 모든 하위 컴포넌트에 props로 전달하는 방법.
       - 왜? 데이터를 한 방향으로 내려줘 단방향 흐름을 유지한다
       - 좋지 못함. 왜? 컴포넌트 레벨에서 컨테이너를 호출해도 단방향 흐름을 깨는건 아님, 데이터 수정이 있을 때마다 수정작업이 많아짐. 
    2. Page level에서 container를 호출하고, depth를 최대 2depth로 유지한다.
       - 왜? 수정과 관리가 편함
       - 좋지 못함. jsx파일이 너무 커져 찾기가 힘들어짐 -- JSX파일이 길어지는건 구조적으로 문제는 없지만, 가독성이 떨어짐
    3. 컴포넌트 랩핑은 최소한으로 하되 각 컴포넌트 레벨에서 container를 호출
       - 연관성이 높은 컴포넌트끼리 래핑해 가독성을 높이고, 컴포넌트 수정도 최소한으로 할 수 있다

Feeling

this 바인딩을 이론은 많이 들어봤는데 게임을 만들면서 처음 해당 에러를 만났다. 역시 글로만 공부하는 것보다 직접 부딪히면서 배우는게 기억에 오래 남는다. 봐도봐도 막연했던 this와 실행 컨텍스트를 다시 공부하면서 좀 더 이해할 수 있었다.

좋은 코드를 짜는게 뭘까 고민을 하는데 팀원분을 따라해보기로 했다. 중복되는 로직이나 너무 긴 로직은 hook을 만들어 사용하시는데 재사용을 하지 않는 hook을 만들 필요가 있나 싶긴하지만 반복작업을 hook으로 처리하는 것을 실천하기로 했다.

가정 환경이 변화면서 일찍 퇴근하기 위해 일찍 출근을 한다. 아침 루틴이었던 강아지 산책이 저녁 루틴으로 들어갔다. 그덕에 일주일에 네세번은 하루에 3km 이상씩 걷고 있다. 발목을 다쳐서 달리기를 못하니까 걷기라도 열심히..컨디션과 체력에 도움이 되길 바라며!

12월에도 자바스크립트 공부를 계속 할 예정이다. 유데미에서 자바스크립트 강의를 사뒀다. 12월도 화이팅

'성장 > 월간 회고' 카테고리의 다른 글

2021년 회고  (0) 2022.01.18
2021년 12월 회고  (0) 2022.01.18
2021년 10월 회고  (0) 2021.11.06
9월 회고  (0) 2021.10.14
8월 회고  (0) 2021.09.06

+ Recent posts