ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript/lib.] Lodash
    Front-End(Web)/Javascript 2021. 8. 17. 21:45
    반응형

    🧐 서론

    lodash 역시 회사 실무를 접하면서, 처음으로 경험해본 Javascript 라이브러리이다.

    사실, Javascript에서 제공하는 왠만한 메서드들을 표방한 것이다보니 필요성이 있을까 싶지만,

    _cloneDeep() 으로 객체를 깊은 복사하거나, _has() 로 객체의 필드 포함여부를 확인하는 등 다양한 기능을 수행할 수 있다.

     

    현재 실무에서는, 더 다양한 Lodash 메서드들을 사용하고 있기 때문에, 이의 기원과 대표적인 메서드들을 정리해보고자 이 글을 포스팅한다.


    📒 Lodash 란?

    Lodash는 Javascript 유틸리티 라이브러리의 일종이다. 즉, 모듈성, 성능 등을 제공하기 위한 라이브러리인 것이다.

    * Lodash는, node.js 에서 많이 쓰이는 underscore 라이브러리에서 영감을 받았다. 

    (참고링크 : https://m.blog.naver.com/sssang97/222014670813 )

     

    Lodash의 가장 큰 의의는 Array, Object, String 등 데이터 내 값들을 Handling 하는 것을 용이하게 해준다는 점이다.

    Lodash는 코드가 간결해진다는 장점 외에도, 아래와 같은 다양한 강점을 보이기에 React를 포함한 JS환경에서 환영받고 있다.

    • 기존 Javascript 메서드로 작성하는 것에 비해 코드가 간결해짐
    • Javascript에서 지원하지 않는 다양한 메서드들을 가지고 잇음
    • 퍼포먼스 측면에서 native 메서드보다 더 나은 성능을 보임

     

    - 설치

    마찬가지로, CDN과 npm 환경 2가지 방법으로 설치할 수 있다.

    // CDN
    <script src="https://raw.githubusercontent.com/lodash/lodash/4.17.15-npm/lodash.min.js"></script>
    // npm
    npm i --save lodash
    yarn add lodash

     

    - 사용

    lodash를 import 하거나, 필요한 메서드만 가져올수도 있다.

    import _ from "lodash";
    import _get from "lodash/get";
    // node.js
    var _ = require('lodash');
    var _ = require('lodash/core');

     

    📒 Lodash 주요 메서드

    Lodash 에서는 Array, Object, Collection(배열, 객체 모두), Util(유틸리티 관련) 등 카테고리들로 분류한다.

     

    1. Array

    _.chunk (array, [size = 1])

    1차원 배열을 2차원 배열로 수정할 때 유용하다. 첫 번째 인자는 타겟 배열, 두 번째 인자는 각 2차원 배열의 원소개수가 된다.

    _.chunk(['a', 'b', 'c', 'd'], 2);
    // => [['a', 'b'], ['c', 'd']]
     
    _.chunk(['a', 'b', 'c', 'd'], 3);
    // => [['a', 'b', 'c'], ['d']]

     

    _.findIndex(array, [predicate=_.identity(function)], [fromIndex = 0(number)]) / _.findLastIndex

    조건을 만족하는 첫 번째 요소의 인덱스를 반환한다.

    첫 번째 인자는 타겟 배열, 두 번째 인자는 조건 함수, 세 번째 인자는 시작 인덱스이다.

    var users = [
      { 'user': 'barney',  'active': false },
      { 'user': 'fred',    'active': false },
      { 'user': 'pebbles', 'active': true }
    ];
     
    _.findIndex(users, function(o) { return o.user == 'barney'; });
    // => 0
     
    // The `_.matches` iteratee shorthand.
    _.findIndex(users, { 'user': 'fred', 'active': false });
    // => 1

     

    _.uniq(array) / _.uniqBy(array, [iteratee=_.identity(function)])

    uniq는 배열 요소들의 중복값을 제거한 배열을, uniqBy는 배열의 객체 요소들의 중복값을 제거할 때 사용한다.

    _.uniq([2, 1, 2]);
    // => [2, 1]
    
    _.uniqBy([2.1, 1.2, 2.3], Math.floor);
    // => [2.1, 1.2]
     
    // The `_.property` iteratee shorthand.
    _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
    // => [{ 'x': 1 }, { 'x': 2 }]

     

    2. Object

    _.get(object, path, [defaultValue]) / _.has(object, path)

    get은 해당하는 경로의 값을 가져오는 것이다. has는 객체에 해당 경로가 존재하는지 여부를 반환한다.

    var object = { 'a': [{ 'b': { 'c': 3 } }] };
     
    _.get(object, 'a[0].b.c');
    // => 3
     
    _.get(object, ['a', '0', 'b', 'c']);
    // => 3
    
    
    var object = { 'a': { 'b': 2 } };
    var other = _.create({ 'a': _.create({ 'b': 2 }) });
     
    _.has(object, 'a');
    // => true
     
    _.has(object, 'a.b');
    // => true
     
    _.has(object, ['a', 'b']);
    // => true
     
    _.has(other, 'a');
    // => false

     

    _.omit(object, [paths]) / _pick(object, [paths])

    객체의 filter() 메서드라고 생각하면 되겠다. 객체에 해당하는 키를 제외하는 omit()과 키만 선택하는 pick()이 있다.

    var object = { 'a': 1, 'b': '2', 'c': 3 };
     
    _.omit(object, ['a', 'c']);
    // => { 'b': '2' }
    
    _.pick(object, ['a', 'c']);
    // => { 'a': 1, 'c': 3 }

    이 역시도, [paths] 대신 value를 판정하는 함수를 인자로 받는 _.omitBy(), _.pickBy() 등이 있다.

     

    _.unset(object, path)

    해당 경로의 value를 삭제하는 메서드이다. 독특하게 반환값은 boolean 이며, 메서드 적용 후 원본객체는 수정되어있다.

    var object = { 'a': [{ 'b': { 'c': 7 } }] };
    _.unset(object, 'a[0].b.c');
    // => true
     
    console.log(object);
    // => { 'a': [{ 'b': {} }] };
     
    _.unset(object, ['a', '0', 'b', 'c']);
    // => true
     
    console.log(object);
    // => { 'a': [{ 'b': {} }] };

     

    3. Collection

    _.forEach, _.filter, _.includes, _.map, _.reduce 등 우리가 흔히 사용해온 Javascript 메서드들을 포함하고 있다.

     

    4. Date

    _.now() : 현재 시간을 milliseconds 단위로 반환한다.

     

    5. Function

    _.bind(function, thisArg, [partials])

    함수에 매개변수를 this 바인딩하는 메서드이다. thisArg를 통해 객체의 각 키값들을 this로 넘길 수 있으며, partials는 함수의 추가적인 매개변수들에 해당한다.

    function greet(greeting, punctuation) {
      return greeting + ' ' + this.user + punctuation;
    }
     
    var object = { 'user': 'fred' };
     
    var bound = _.bind(greet, object, 'hi');
    bound('!');
    // => 'hi fred!'
     
    // Bound with placeholders.
    var bound = _.bind(greet, object, _, '!');
    bound('hi');
    // => 'hi fred!'

     

    _.debounce(function, [wait=0], [options={ }])

    디바운스 메서드를 통해, 일정시간(wait) 동안 연이어 호출되는 함수들 중 마지막 함수만 호출되도록 하는 것이다.

    * 디바운싱(Debouncing) : 연이어 호출되는 함수들 중 마지막 함수만 호출되도록 하는 것 (최종결과가 중요할 때 사용)

    • function(Function) : 디바운스가 걸리는 동작. 콜백함수이다.
    • [wait](Number) : 디바운스를 제한하기 위한 시간. 숫자 타입으로, millisecond 단위로 입력해주면 된다.
    • [options](Object) : 디바운스 옵션 객체. (leading, maxWait, trailing)
    const sendQuery = async (query: string) => {
      if (query.length === 0) return;
      try {
        const res = await apiCall.get(`url/search?query=${query}`);
        setData(res?.data);
      }
      catch(err) console.log(err);
    };
    
    const dbcQuery = useRef(_.debounce((q) => sendQuery(q), 500)).current;
    
    const handleChangeSearch = (event: React.ChangeEvent<any>) => {
      setSearchValue(event.target.value);
      dbcQuery(event.target.value);
      if (searchValue.trim() === '') {
        setData({
          stores: [],
          nearbys: [],
        });
      }
    };

    단순 API 예시보다는 블로그의 좋은 예시가 있어 차용해와봤다. _.debounce() 를 통해 0.5초마다 데이터를 최신화하는 로직이었다.

    [searchValue, setSearchValue]는 input 값의 최신화가 목적인듯하며, [data, setData] 를 다루는 부분이 중점이다.

    sendQuery() 메서드가 비동기 요청을 전담하며, dbcQuery() 의 경우에는 쿼리요청을 0.5초 간격으로만 보내도록 디바운싱해준다.

     

    _.throttle(function, [wait=0], [options={ }])

    쓰로틀링 메서드를 통해, 마지막 함수가 호출되고나서 일정시간(wait)이 지나기 전까지 재호출하지 않는다.

    * 쓰로틀링(Throttling) : 마지막 함수가 호출된 후 일정시간이 지나기 전까지 다시 호출되지 않도록 하는것(스크롤, 드래그 등 즉각적임)

    • function(Function) : 디바운스가 걸리는 동작. 콜백함수이다.
    • [wait](Number) : 디바운스를 제한하기 위한 시간. 숫자 타입으로, millisecond 단위로 입력해주면 된다.
    • [options](Object) : 디바운스 옵션 객체. (leading, maxWait, trailing)
    kako.maps.event.addListener(
      _map,
      'bounds_changed',
      _.throttle(() => {
        const bounds = _map.getBounds();
        const swLatLng = bounds.getSouthWest();
        const neLatLng = bounds.getNorthEast();
        setMapBounds({ swLatLng, neLatLng });
      }, 1000)
    );

    역시, _.throttle() 을 통해 1초마다 카카오맵 API를 통해 좌표값을 최신화하는 로직을 가져와봤다.

     

    * 디바운싱은 아무리 많은 이벤트가 발생해도 특정시간 내에서는 딱 한번만 실행을 시킨다.

    반면, 쓰로틀링은 한 번 실행된 뒤 일정한 시간간격으로 호출이 되기 때문에 중간과정이 갱신되어야 할 경우 주로 사용된다.

     

     

    이외에도, 결과를 저장하는 _.memoize(), 콜스택이 빈 뒤 함수를 실행하는 _.defer(), 한번만 실행하는 _.once() 등이 있다.

     

    6. Lang

    _.clone(value) / _.cloneDeep(value)

    객체를 복제하는데 통용되는 메서드다. _.clone()은 얕은 복사, _.cloneDeep()은 깊은 복사를 하게 된다.

    var objects = [{ 'a': 1 }, { 'b': 2 }];
     
    var shallow = _.clone(objects);
    console.log(shallow[0] === objects[0]);
    // => true
    
    var deep = _.cloneDeep(objects);
    console.log(deep[0] === objects[0]);
    // => false

     

    이외에도, 타입을 검증하는 isNumber, isNull 등과, 타입을 변환하는 toNumber, toSring 등과, 숫자를 비교하는 gt, lt 등이 있다.

     

    7. ETC

    • Math : 사칙연산, 각종 연산, 소수점 다루는 메서드들
    • Number / String  : 숫자와 문자열을 다루는 각종 메서드들 (Number: _.random(), String: _.trim())
    • Seq : 이터레이터를 다루는 메서드들 (_.chain, _.prototype.toJSON ...)
    • Util : 범위를 구하는 _.range, 함수명을 키값으로 재활용하는 _.mixin, 거듭 실행하는 _.times 등 다양한 기능의 메서드들

    금방 끝날 줄 알았던 내용이지만 생각보다 포스팅에 할애된 시간이 길어졌다.

    다양한 메서드를 효율적으로 가져다쓰는건 숙련도를 요하겠지만, _.findIndex, _.cloneDeep 같은 경우엔 상당히 유용할 것 같다.

    또한, 고성능의 기능을 구현하기 위해 디바운스와 쓰로틀링의 개념과 메서드(_.debounce, _.throttle)에 대해서도 새로이 알게되었다.

     

    [출처]

    - lodash 공식문서 : https://lodash.com/docs/  

    - kysung05 님의 블로그 : https://velog.io/@kysung95/%EC%A7%A4%EB%A7%89%EA%B8%80-lodash-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90  

    - harry the great 님의 블로그 : https://harrythegreat.tistory.com/entry/%EC%96%B8%EB%8D%94%EC%8A%A4%EC%BD%94%EC%96%B4-%EC%A0%95%EB%A6%AC#delay

    - edie_ko 님의 블로그(debounce, throttle) : https://velog.io/@edie_ko/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%84%B1%EB%8A%A5-%ED%96%A5%EC%83%81-%EC%8B%9C%ED%82%A4%EA%B8%B0-feat.-Lodash-throttle-debounce

    반응형
Designed by Tistory.