[TIL] 220122

Lee Syong·2022년 1월 22일
0

TIL

목록 보기
157/204
post-thumbnail

📝 오늘 한 것

  1. unit converter 완성

  2. underdash 코드 리뷰 정리(1)


📚 배운 것

1. unit converter

어제 공부에서 이어서

1) km-mile 단위 변환기 구현

minutsToHours 컴포넌트와 동일한 구조로 만들었다.
핵심은 input의 value가 변할 때마다 amount가 업데이트됨과 동시에 컴포넌트가 리렌더링 된다는 것이다.

const KmToMiles = () => {
  const [amount, setAmount] = React.useState(0);
  const [flipped, setFlipped] = React.useState(false);

  const onChange = (event) => setAmount(event.target.value);
  const reset = () => setAmount(0);
  const onFlip = () => {
    setFlipped((current) => !current);
    reset();
  };

  return (
    <div>
      <div>
        <label htmlFor="km">Km</label>
        <input
          value={!flipped ? amount : amount * 1.609}
          id="km"
          placeholder="Km"
          type="number"
          onChange={onChange}
          disabled={flipped}
        />
      </div>
      <div>
        <label htmlFor="miles">Miles</label>
        <input
          value={flipped ? amount : amount / 1.609}
          id="miles"
          placeholder="Miles"
          type="number"
          onChange={onChange}
          disabled={!flipped}
        />
      </div>
      <button onClick={reset}>Reset</button>
      <button onClick={onFlip}>Flip</button>
    </div>
  );
};

2) 단위 변환기 선택 메뉴바(select) 구현

(1) select, option

const App = () => {
  return (
    <div>
      <select>
        <option value="select">Select unit converter</option>
        <option value="0">Minutes & Hours</option>
        <option value="1">Km & Miles</option>
      </select>
    </div>
  );
};

(2) index state

선택한 option의 value 값을 저장하는 index state를 생성한다.
이를 select의 value에 할당한다.
기본값은 임의로 "select"로 설정했다.(선택 안내 문구)

const App = () => {
  const [index, setIndex] = React.useState("select");

  return (
    <div>
      <select value={index}>
        <option value="select">Select unit converter</option>
        <option value="0">Minutes & Hours</option>
        <option value="1">Km & Miles</option>
      </select>
    </div>
  );
};

(3) onChange 이벤트

select에서 change 이벤트가 발생하면 index를 선택된 option의 value 값으로 업데이트 하고 바뀐 데이터를 바탕으로 App 컴포넌트를 리렌더링한다.

이때 업데이트된 index의 값에 따라 다른 단위 변환기를 보여주도록 한다.
JSX 문법에서는 { } 안에 자바스크립트 코드를 쓸 수 있다.

const App = () => {
  const [index, setIndex] = React.useState("select");

  const onSelect = () => {
    setIndex(event.target.value); // option의 value
  };

  return (
    <div>
      <h1>Super Converter</h1>
      <select value={index} onChange={onSelect}>
        <option value="select">Select unit converter</option>
        <option value="0">Minutes & Hours</option>
        <option value="1">Km & Miles</option>
      </select>
      {index === "select" ? <h4>Please select unit converter</h4> : null}
      {index === "0" ? <MinutesToHours /> : null}
      {index === "1" ? <KmToMiles /> : null}
    </div>
  );
};

const root = document.getElementById("root");
ReactDOM.render(<App />, root);

2. underdash 코드 리뷰 정리(1)

MDN 사이트 참고

1) early return

if/else 연달아 쓰지 말고 if/return 구조를 사용할 것

2) 읽기 쉬운 코드

무조건 코드의 길이를 줄이는 것보다 가독성이 더 중요하다.
ex. 삼항 조건 연산자 되도록 한 번에 하나만 사용할 것

3) 줄 간격

return 앞에서 혹은 문맥이 바뀔 때 줄 간격을 띄우도록 한다.
ex. 변수를 쭉 선언하고 함수를 선언하기 전에 줄 간격을 띄운다.

4) for...in 돸 / Object.hasOwnProperty()

(1) 문제가 된 코드

_.each = function (collection, iterator) {
  if (Array.isArray(collection)) {
    for (let i = 0; i < collection.length; i++) {
      iterator(collection[i], i, collection);
    }
  } else {
    for (key in collection) {
      iterator(collection[key], key, collection);

※ 구현 조건: 배열 메서드(ex. forEach, map 등) 사용 X

(2) for...in 돸

for...in 문은 정수가 아닌 이름을 가진 속성과 상속된 모든 열거 가능한 속성들을 반환한다.
따라서, for...in 문은 인덱스의 순서가 중요한 배열에서는 반복을 위해 사용할 수 없으며
대상이 배열이 아닌 객체라 하더라도 for...in 문을 사용하면 의도한 바와 다른 결과가 도출될 수 있다.

// 예제
const collection = [1, 2, 3];

Array.prototype.name = "collection"; // 프로토타입 속성(이자 정수가 아닌 속성)
collection.next = "four"; // 정수가 아닌 속성

for (const key in collection) {
  console.log(key); // 0 1 2 뿐 아니라 next name 도 출력됨
}

(3) Object.hasOwnProperty()

객체의 프로토타입이 아닌 객체 자체에 연결된 속성만 고려하고자 한다면(상속된 속성 고려 X)
for...in 문과 함께 Object.hasOwnProperty()를 사용해야 한다.

Object.hasOwnProperty() 메서드는 객체가 특정 프로퍼티를 자기만의 직접적인 프로퍼티로서 소유하고 있는지를 판단하는데 사용된다.

for (const key in collection) {
  if (collection.hasOwnProperty(key)) {
    console.log(key); // 0 1 2 next (프로토타입 속성 name은 출력되지 않는다)
  }
}

혹은 for...in & Object.hasOwnProperty() 대신 Object.keys()를 이용하여 다음과 같이 작성할 수도 있다.

Object.keys(collection).forEach(key => console.log(key));
// 0 1 2 next (마찬가지로 프로토타입 속성 name은 출력되지 않는다)

(4) for...of

배열의 반복을 위해서는 for...of를 사용하는 것을 추천한다.
변수에 프로퍼티가 담기는 for...in과 달리 for...of 문에서는 프로퍼티의 값이 담긴다.
프로토타입 상속에 의한 프로퍼티와, 배열의 자체 인덱스를 제외하고 임의로 추가한 프로퍼티 모두 무시된다.

const collection = [1, 2, 3];

Array.prototype.name = "collection";
collection.next = "four";

for (const value of collection) {
  console.log(value); // 1 2 3 (collection과 four은 출력되지 않는다)
}

for...of 문과 Array.entries()를 이용하여 문제 코드의 배열 부분을 다음과 같이 고쳐볼 수도 있다.

if (Array.isArray(collection)) {
  for (let i = 0; i < collection.length; i++) {
    iterator(collection[i], i, collection);
  }
}
// 굳이 아래처럼 쓸 필요는 없지만, 위 아래는 같은 코드이다

if (Array.isArray(collection)) {
  for (const [index, value] of collection.entries()) {
    iterator(value, index, collection);
  }
}

한편, for...of는 Object 객체를 대상으로는 사용할 수 없다.
Object 객체에 for...of 문을 사용하기 위해서는 Object.keys()를 이용해 객체의 프로퍼티들로 이루어진 배열을 구한 후 해당 배열에 for...of 문을 사용해야 한다.

const keys = Object.keys(obj); // ["name", "age"]

for (const key of keys) {
  console.log(key, obj[key]); // name syong  age 10
}

(4) 수정한 코드

따라서, 문제 코드는 다음과 같이 수정할 수 있다.

_.each = function (collection, iterator) {
  if (Array.isArray(collection)) {
    for (let i = 0; i < collection.length; i++) {
      iterator(collection[i], i, collection);
    }
  } else {
    for (key in collection) {
      if (collection.hasOwnProperty(key)) { // 추가 ❗
        iterator(collection[key], key, collection);
      }
    }
  }

5) 함수 재활용

함수는 할 수 있다면 최대한 재활용 하는 것이 좋다.

(1) _.filter 함수

처음에 작성한 코드

_.filter = function (collection, test) {
  let newArr = [];
  for (let i = 0; i < collection.length; i++) {
    if (test(collection[i])) {
      newArr.push(collection[i]);
    }
  }
  return newArr;
};

_.each 함수를 재활용하여 수정한 코드

_.filter = function (collection, test) {
  const newArr = [];

  _.each(collection, function (item) {
    if (test(item)) newArr.push(item);
  });

  return newArr;
};

(2) _.reject 함수

처음에 작성한 코드

_.reject = function (collection, test) {
  let newArr = [];
  for (let i = 0; i < collection.length; i++) {
    if (!test(collection[i])) {
      newArr.push(collection[i]);
    }
  }
  return newArr;
};

_.each 함수를 재활용하여 작성한 코드

_.reject = function (collection, test) {
  const newArr = [];

  _.each(collection, function (item) {
    if (!test(item)) newArr.push(item);
  });

  return newArr;
};

6) _.reduce 함수

처음에 작성한 코드

_.reduce = function (collection, iterator, accumulator) {
  if (accumulator !== 0 && accumulator === undefined) {
    for (let i = 0; i < collection.length - 1; i++) {
      if (i === 0) {
        accumulator = iterator(collection[0], collection[1]);
        continue;
      }
      accumulator = iterator(accumulator, collection[i + 1]);
    }
  } else {
    for (key in collection) {
      accumulator = iterator(accumulator, collection[key]);
    }
  }
  return accumulator;
};

기본값 매개변수를 활용하여 처음 작성한 코드

_.reduce = function (collection, iterator, accumulator = collection[0]) {
  const _collection =
        accumulator === collection[0] ? collection.slice(1) : collection;

  for (key in _collection) {
    if (collection.hasOwnProperty(key)) {
      accumulator = iterator(accumulator, _collection[key]);
    }
  }

  return accumulator;
};

기본값 매개변수를 사용하면서 _.contains 함수의 테스트 케이스도 통과하기 위해 collection이 'Object 객체'인 경우도 고려해야 했다.

모든 테스트 케이스를 통과하긴 했지만, accumulator === collection[0] 부분이 다른 테스트 케이스의 경우 에러를 일으키게 된다.
_.reduce 함수 호출 시 accumulator로 전달한 값이 collection[0]과 같은 경우에도 _collection은 collection.slice(1)이 되기 때문이다.

// 테스트 케이스
const result = _.reduce(
  [3, 2, 1],
  function (memo, item) {
    return memo - item;
  },
  3
);
expect(result).to.equal(-3); // 이어야 하는데 실제로는 0이 나온다

기본값 매개변수가 아니라 직접 _.reduce 함수의 초깃값을 설정한 경우에도 기본값 매개변수를 사용한 것처럼 받아들여져 에러가 발생하는 것이다.
결과적으로 이는 잘못 작성된 코드이다.

🙋‍♀️ 그런데 기본값 매개변수를 사용했을 때 accumulator가 기본값 매개변수인지 _.reduce 함수에 전해준 초깃값인지 판별하는 방법을 모르겠다.
기본값 매개변수는 함수 호출 시에 이미 설정이 되는데 코드 안에서 어떻게 구분을 해야 하는 걸까.

최종적으로 수정한 코드

_.reduce = function (collection, iterator, accumulator) {
  if (accumulator !== 0 && accumulator === undefined) {
    accumulator = collection[0];

    for (let i = 1; i < collection.length; i++) {
      accumulator = iterator(accumulator, collection[i]);
    }
  } else {
    for (key in collection) {
      if (collection.hasOwnProperty(key)) {
        accumulator = iterator(accumulator, collection[key]);
      }
    }
  }

  return accumulator;
};

뭐가 크게 바뀐 건 없다.
for 반복문 블록을 살짝 정리해주고 hasOwnProperty()를 추가했다.


🙋‍♀️ 질문

  1. 여기 바로가기

✨ 내일 할 것

  1. React PROP
profile
능동적으로 살자, 행복하게😁

0개의 댓글