쾌락코딩

destructuring(디스트럭처링)

|

디스트럭처링이란?

디스트럭처링이란, 객체의 구조(structure)를 제거(de)한다는 의미이다.

before destructuring

디스트럭처링은 es6에서 도입된 문법이다. 코드로 보는 것이 가장 효과적인 방법이다.

// es5  
const obj = { name: 'yongjun', age: 100 };

const name = obj.name;
const age = obj.age;

console.log(name, age); // yongjun 100

es5에서 obj의 name 속성과 age 속성을 가져오려면 이렇게 했어야 했다. 그러나 es6에서는 더 이쁘고 쉽게, 더 다양한 옵션을 주면서 이와 같은 작업을 할 수 있다.

const obj = { name: 'yongjun', age: 100 };

const { name, age } = obj;

console.log(name, age); // yongjun 100

이렇게 간단하게 할 수 있을 뿐만아니라 여러가지 팁이 더 존재한다.

// 디스트럭처링 할당 시 할당받을 속성이 없다면 새롭게 변수를 초기화 할 수 있다.
const { name, nationality="korea" } = { name:"yongjun" };
console.log(name, nationality); // yongjun korea

// 새로운 변수명으로 할당 받기
const { name: newName, nationality: newNationality } = { name: "yongjun", nationality: "korea" };
console.log(newName, newNationality); // youngjun korea

함수의 파라미터에서도 활용할 수 있다.

const showProfile = ({ name, nationality="none"}) => {
  console.log(name);
  console.log(nationality);
}

// showProfile은 꼭 객체가 할당되야 한다.
showProfile({name="elecoder" nationality="korea"}); // elecoder korea
showProfile({name="elecoder"}); // elecoder none

배열에서도 디스트럭처링이 가능하다.

let numbers = ["one", "two", "three", "four", "five"];

let [num1, num2] = numbers;
console.log(num1, num2); // one two

// 세번째 네번째 요소를 받고 싶다면 아래와 같이 하자
let [,,num3, num4] = numbers;
console.log(num3,num4); // three four

// spread 연산자와 함께하면 이런것도 가능하다
let [val1, ...vals] = numbers;
console.log(val1); // one
console.log(vals); // two three four five

idpiframe_initialization_failed 에러

|

증상

구글 개발자 콘솔에서 앱 등록을 마친 후 react-google-login로 구글 로그인 연동을 시도하던 중, 로그인 버튼을 클릭하면 idpiframe_initialization_failed 에러가 떳다.

원인

원인을 찾아 구글링을 해보니 공식적으로 에러 원인이 아래와 같이 적혀있었다. reason

따라서 이 자료를 따라 third party cookies enabled를 설정하러 갔는데 이미 설정이 되어 있는 상황이었다…. 당황했다. 혹시나 해서 파이어폭스에서 실행해보니 정상 작동했다. 아무튼 구글 콘솔 설정이 잘못된건 아니란 뜻.

그래서 더 열심히 열심히 구글링했다.

해결방법

구글 크롬 설정 -> 고급 -> 개인정보 및 보안 -> 인터넷 사용 기록 삭제 -> 모든 쿠키 와 캐시된 이미지 또는 파일을 삭제!

이 생각은 스택 오버플로우 Google API: Not a valid origin for the client: url has not been whitelisted for client ID “ID” 에서 얻었다.

React Lifecycle 정리

|

나는 개인적으로 개발할 때 마다 모든 라이프사이클 api를 사용하진 않는다. 아마 나의 무지함 때문에 필요할때도 올바를 라이프 사이클을 선택하지 못할 때도 있었을 것이고, 정말로 내 프로젝트에서는 필요 없는 api들이 존재 하기 떄문일 수도 있다. 아무튼 lifecycle api를 한번 정리해둠으로써 프로젝트 진행시 틈틈히 참조해야겠다.

컴포넌트가 처음 실행되는 Mount관련 api

constructor(props)

컴포넌트가 새로 만들어 질 때마다 호출된다. 프로젝트 구동시 여러 이벤트로 인해 한 컴포넌트가 여러번 생성되고 삭제됨을 반복하기도 하는데 새로 생성될 때 마다 constructor가 실행된다.

return {showInputBox ? (
          <SearchInput
            onChange={this.onChangeInputValue}
            onKeyPress={this.onClickEnterInSearch}
          />
        ) : null}

이렇게 부모의 props에 따라서 showInputBox가 false일 경우 react dom에서는 SearchInput가 사라지기 때문에 componentWillUnmount() 함수가 실행되고, 다시 나타나게 되면 constructor가 실행됨

componentWillMount

신경쓰지 말자. deprecated되었다. UNSAFE_componentWillReceiveProps로 여전히 사용할 수 있긴 하지만 권장되진 않는다고 한다. 이 api에서 사용하던 것들은 constructor와 componentDidMount에서 사용하면 된다고 한다.

render()

컴포넌트를 DOM에 부착하는 함수.

componentDidMount

가상 DOM에서 실제 DOM으로 반영된 이후에 실행된다. 주로 실제 dom을 사용해야 하는 외부 라이브러리를 사용할 때나 네트워크 자원을 요청하는 로직을 수행한다.

Update 관련 api

컴포넌트는 부모로부터 Props가 변경되거나, 자신의 State가 변경될 때 업데이트 된다.

componentWillReceiveProps(nextProps)

deprecate 된다. UNSAFE_componentWillReceiveProps()로 사용할 수는 있지만, 새로운 api인 static getDerivedStateFromProps()를 사용하자.

static getDerivedStateFromProps(nextProps, prevState)

이 메서드는 velopert님의 자료에 아주 잘 설명 되어있어서 그 부분을 발췌하겠다.

static getDerivedStateFromProps(nextProps, prevState) {
  // 여기서는 setState 를 하는 것이 아니라
  // 특정 props 가 바뀔 때 설정하고 설정하고 싶은 state 값을 리턴하는 형태로
  // 사용됩니다.
  /*
  if (nextProps.value !== prevState.value) {
    return { value: nextProps.value };
  }
  return null; // null 을 리턴하면 따로 업데이트 할 것은 없다라는 의미
  */
}

state 가 props 에 따라 변해야 하는 로직을 작성하는 것. 추가적으로 static 메서드이기 때문에 this를 사용할 수 없다. 따라서 this.setState()가 아닌 object형태로 return 값을 넘겨야 한다. 이 것은 setState와 정확히 같은 방식으로 동작한다.

shouldComponentUpdate(nextProps, nextState): boolean

return으로 true 또는 false를 해줘야 한다. false를 리턴하게 되면, rendering하지 않는다. 랜더링 하지 않는 다는 것은, render()함수가 호출되지 않는 다는 것이고, 그 것은 가상 DOM에 어떤 작업도 하지 않는 다는 것이므로 성능 최적화를 할 수 있다. Props나 State가 변경되어서 컴포넌트가 update될 때, 무조건적인 rendering을 하지 말고 필요없는 redering은 하지 말자.

componentWillUpdate

getSnapshotBeforeUpdate(prevProps, prevState) 로 대체 된다. 컴포넌트가 props가 변해서든 State가 변해서든 update되기 직전에, 원래의 dom상태를 가져온다. 이 것 역시 velopert님의 너무 좋은 자료가 있어 발췌한다.

 getSnapshotBeforeUpdate(prevProps, prevState) {
    // DOM 업데이트가 일어나기 직전의 시점입니다.
    // 새 데이터가 상단에 추가되어도 스크롤바를 유지해보겠습니다.
    // scrollHeight 는 전 후를 비교해서 스크롤 위치를 설정하기 위함이고,
    // scrollTop 은, 이 기능이 크롬에 이미 구현이 되어있는데, 
    // 이미 구현이 되어있다면 처리하지 않도록 하기 위함입니다.
    if (prevState.array !== this.state.array) {
      const {
        scrollTop, scrollHeight
      } = this.list;

      // 여기서 반환 하는 값은 componentDidUpdate 에서 snapshot 값으로 받아올 수 있습니다.
      return {
        scrollTop, scrollHeight
      };
    }
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot) {
      const { scrollTop } = this.list;
      if (scrollTop !== snapshot.scrollTop) return; // 기능이 이미 구현되어있다면 처리하지 않습니다.
      const diff = this.list.scrollHeight - snapshot.scrollHeight;
      this.list.scrollTop += diff;
    }
  }

componentDidUpdate(prevProps, prevState, snapshot)

컴포넌트가 업데이트로 인해 render()메서드가 실행 된 이후에 실행되는 함수. 파라미터로 이전 props와 state, snapshot을 받아온다. 여기서 this.props를 하거나 this.state를 사용하여 현재의 상태를 참조할 수 있기 때문에 파라미터로 받는 것이 prev인것은 당연한 일이다.

componentWillUnmount()

이벤트, setTimeout, 외부 라이브러리 인스턴스들을 제거해주자

더 훌륭한 자료

사이드바 외부 클릭시 사이드바 닫히게 하는 방법

|

토이프로젝트를 구현하면서 사이드바를 구현하게 되었는데, 상단 메뉴바의 햄버거 버튼을 클릭하면 사이드바가 나타나는 형태로 구현했다. 그러나 사이드바를 다시 닫을때 역시 햄버거 버튼을 클릭해야만 닫히는게 상당히 불편했고, 모바일 유저라면 더욱 불편할거라 생각해서 사이드바 영역 외의 부분을 클릭시에도 사이드바를 닫는 기능을 구현하였다.

원리

우선 사이드바의 z-index를 999로 주었다. 그리고 사이드바가 나타나게 되면 그와 동시에 화면 width와 heigt이 100%인, 즉 화면 전체를 div로 감싸며 배경이 투명색인 영역을 나타나게 했다. 여기서 중요한 것은 이 empty space의 z-index가 998이라는 것. 그렇게 되면 z-index가 999인 사이드바만 empty space 영역 앞에 나타나게 된다. 이 상태에서 empty space에 onClick 속성으로 사이드바를 닫는 함수를 넘겼다.

간단하게 코드를 보면

<React.Fragment>
  <SideBar showSideBar={showSideBar}>
    <HambergerIcon
      size="48"
      color="#534847"
      onClick={this.onClickHambergerButton}
    />
  </SideBar>
  // empty space 부분
  <OuterToToggleSideBar
    showSideBar={showSideBar}
    onClickEmptySpace={this.onClickEmptySpace}
  />
</React.Fragment>

SideBar와 OuterToToggleSideBar 두 영역 모두 this.state.showSideBar가 true이면 나타나게 한다.

추가적으로 두 영역의 css이다.

const SideBarLayout = styled.div`
  padding-top: 10px;
  display: flex;
  flex-direction: column;
  position: absolute;
  position: fixed;
  left: 0;
  width: 250px;
  height: 100vh;
  background-color: white;
  z-index: 999;
`;

const EmptySpaceToToggleSideBar = styled.div`
  width: 100%;
  height: 100%;
  position: absolute;
  z-index: 998;
  opacity: 0.5;
  background-color: gray;
  position: fixed;
`;

고찰

이 방법에는 정상 작동 하지만 약간의 문제점이 있다. 이렇게 구현하게되면 사이드 바가 나타난 상태에서, 외부의 어떤 버튼이나 input에 값을 바로 선택할 수 없게된다. 버튼을 클릭하여도 그 영역은 사실 투명색인 empty space가 덮여져 있기 떄문이다. brunch 사이트같은 경우에는 사이드바가 나타난 상태에서도 외부 버튼이 클릭가능하다. 그게더 좋은 방법이지만 어떻게 가능한지 아직은 모르겠다. 시간이 나면 다시 이 부분을 연구해봐야겠다. 일단은 사이드바가 나타났을 때 외부 버튼클릭이 되지 않음을 알리기 위해 empty space의 배경색을 회색으로 바꾸고 opacity를 할당했다.

express.js에서 class 메서드를 routing할때 bind

|

express.js로 로컬 로그인을 구현 하던 도중 나의 멍청함으로 인한 시간낭비가 발생했다. 이를 해결하기 위한 공부를 하며 아주 조금 성장했지만 일단 적어놓고 똑같은 실수를 반복하지 말자.

일반 적으로 class를 만들고 클래스 메서드를 사용할 때

class A {
  say () {
    console.log('hello');
  }

  func () {
    this.say();
  }
}

const a = new A();
a.func(); // 'hello'

클래스 A의 func 안에 있는 this는 a다(a가 호출했으니).

내가 했던 멍청한 짓은 아래와 같았다.

// auth/AuthCtrl.js
class AuthCtrl {
  isPasswordMatch (plainPassword, hashedPasswod) {
    const result: boolean = compareSync(plainPassword, hashedPasswod);
    return result;
  }
  loginLocalAccount (req, res) {
    //bla bla bla
    const result = this.isPasswordMatch(plainPassword, hashedPasswod);
  }
}
// auth/index.js
...
const auth = express.Router();
const authCtrl = new AuthCtrl();
...
auth.post('/login/local', authCtrl.loginLocalAccount); // 여기가 문제

export default auth;

나는 /login/local 로 접속하면 당연히 정상 실행 될 줄 알았는데, loginLocalAccount의 isPasswordMatch가 전혀 실행되지 않았다. 디버깅을 해보니 this가 undefind로 출력됨을 알 수 있었다. 분명히 const authCtrl = new AuthCtrl(); 이렇게 객체를 생성하고 authCtrl.loginLocalAccount 이렇게 했는데 왜 이럴까?

우선 authCtrl.loginLocalAccount는 함수를 호출한 게 아니다. 만약 authCtrl.loginLocalAccount() 이렇게 했으면 loginLocalAccount의 this가 authCtrl이 되었겠지만 그 상황이 아니라 함수만 넘겨주는 상황이었다. 즉 authCtrl안에 있는 loginLocalAccount함수 자체만 express가 실행하게끔 넘겨준 것이다. (그럼 왜 this가 express 객체가 아니라 undefind인지는 모르겠다.) 따라서 이 라우터가 실행할 loginLocalAccount함수 안의 this는 authCtrl이 아닌 것! 그럼 해결방법은?

바로 Function.prototype.bind함수이다.

auth.post('/login/local', authCtrl.loginLocalAccount.bind(authCtrl));

이렇게 하면 이 라우팅에서 실행되는 loginLocalAccount함수 안의 this는 모두 authCtrl을 가르키게 된다!