New Phone Models

    immhelper

    1.0.52 • Public • Published

    immhelper

    Fast and lightweight library helps you to update js objects without mutating them

    Install with npm

    npm install immhelper --save
    

    Features

    1. Extreme fast
    2. Lightweight
    3. Provide many powerful mutating actions
    4. Easy to define custom mutating actions
    5. Support batch processing per spec
    6. Support pipe processing per spec
    7. Support deep updating with target path
    8. Support sub spec with filter
    9. Support named mutating actions
    10. Support typescripts auto complete
    11. Support proxy for selecting and updating target
    12. Support low level API to update spec for special cases
    13. Support conditional update actions: if, unless, switch

    Playground

    https://p5243pkx6q.codesandbox.io/

    Change Logs:

    1.0.35: Improve performance. Become fastest package for copying immutable objects.

    Benchmarks (Fastest to Slowest)

    Show details

    Normal

    immhelper: Total elapsed = 72 ms (read) + 2344 ms (write) = 2416 ms
    Object.assign: Total elapsed = 104 ms (read) + 2394 ms (write) = 2498 ms
    immutable-assign: Total elapsed = 82 ms (read) + 3814 ms (write) = 3896 ms
    immer: Total elapsed = 74 ms (read) + 7490 ms (write) = 7564 ms
    seamless-immutable: Total elapsed = 103 ms (read) + 60833 ms (write) = 60936 ms
    immutability-helper: Total elapsed = 84 ms (read) + 65249 ms (write) = 65333 ms
    update-immutable: Total elapsed = 88 ms (read) + 71726 ms (write) = 71814 ms

    With Deep Freeze

    Object.assign: Total elapsed = 107 ms (read) + 30407 ms (write) = 30514 ms
    immhelper: Total elapsed = 96 ms (read) + 33167 ms (write) = 33263 ms
    immer: Total elapsed = 103 ms (read) + 39337 ms (write) = 39440 ms
    immutable-assign: Total elapsed = 102 ms (read) + 46764 ms (write) = 46866 ms
    immutability-helper: Total elapsed = 104 ms (read) + 105779 ms (write) = 105883 ms
    update-immutable: Total elapsed = 108 ms (read) + 107985 ms (write) = 108093 ms

    Summary

    1.03x Faster than Object.assign
    1.61x Faster than immutable-assign
    3.13x Faster than immer
    25.22x Faster than seamless-immutable
    27.04x Faster than immutability-helper
    29.72x Faster than update-immutable

    Samples

    import {
      update,
      $push,
      $unshift,
      $splice,
      $assign,
      $toggle,
      $unset,
      $set,
      $remove
    } from "immhelper";
     
    const original = {
      a: {
        b: {
          c: {
            d: {
              e: {
                f: {}
              }
            }
          }
        }
      },
      arrayPush: [],
      objMerge: {
        name: "Peter"
      },
      toggleMe: false,
      toggleMyProp: {
        done: false,
        completed: true
      },
      removeSecond: [1, 2, 3, 4],
      removeAppleAndBanana: ["Orange", "Apple", "Banana"],
      unsetMyProp: {
        data1: new Date(),
        data2: true
      },
      sqrt: 100,
      doubleItems: [1, 2, 3, 4, 5, 6, 7, 8],
      swapItems: ["left", "right"],
      increaseProps: {
        one: 1,
        two: 2,
        three: 3
      },
      removeByIndexes: [1, 2, 3, 4],
      batchProcessing: {},
      pipeProcessing: "hello",
      doubleOddNumbers: [1, 2, 3, 4],
      parentNode: {
        childNode: {}
      },
      parentNodes: [{ id: 0 }, { id: 1 }],
      updateTree: {
        text: "root",
        children: [
          {
            text: "child 1",
            data: {},
            children: [{ text: "child 1.1" }]
          },
          {
            text: "child 2",
            data: {},
            children: [{ text: "child 2.1" }, { text: "child 2.2" }]
          }
        ]
      },
      usingIfToUpdate: {
        value: 1
      },
      usingUnlessToUpdate: {
        dataLoaded: false
      },
      usingSwitchToUpdate1: 1,
      usingSwitchToUpdate2: {
        value: true
      },
      usingFilter: [1, 2, 3, 4, 5],
      unsetWithFilter: {
        data1: true,
        data2: false,
        data3: true,
        data4: false
      }
    };
    const specs = {
      // you can change separator by using configure({ separator: /pattern/ })
      "a.b.c.d.e.f": [$set, 100],
      "a.b.c.d.e": [$set, "newProp", 100],
      arrayPush: [$push, 1, 2, 3, 4, 5],
      objMerge: [$assign, { age: 20 }, { school: "A" }],
      // using obj method as modifier
      sqrt(x) {
        return Math.sqrt(x);
      },
      // toggle property itself
      toggleMe: [$toggle],
      // toggle child properties
      toggleMyProp: [$toggle, "done", "completed"],
      unsetMyProp: [$unset, "data1", "data2"],
      removeSecond: [$splice, 1, 1],
      // remove array items by its value
      removeAppleAndBanana: [$remove, "Apple", "Banana"],
      // using sub spec to update all array items
      // sub spec syntax [spec]
      // spec can be [action, ...args] or spec tree { a: {  b: ....} }
      doubleItems: [[x => x * 2]],
      // use action name instead of function
      swapItems: ["swap", 0, 1],
      // using sub spec to update all obj values
      increaseProps: [[x => x + 1]],
      removeByIndexes: ["removeAt", 3, 1],
      batchProcessing: ["batch", ["set", "name", "Peter"], ["set", "age", 20]],
      pipeProcessing: ["batch", x => x.toUpperCase(), x => x + " WORLD!!!"],
      //  apply sub spec for only odd numbers
      doubleOddNumbers: [[x => x * 2], x => x % 2],
      parentNode: {
        // remove childNode its self from parentNode
        childNode: ["unset"]
      },
      // remove item at index 1 from parentNodes array
      parentNodes: {
        1: ["unset"]
      },
      updateTree: {
        // using conditional spec to update all nodes which has text prop, exclude all data nodes
        "?": [node => node && node.text, ["set", "done", true]],
        // do same thing with pattern matching
        "?/text/i": ["set", "deleted", true],
        children: {
          // using diff spec for each node
          "?"(node, prop) {
            if (node && node.text) {
              return prop % 2 === 0
                ? ["set", "isEven", true]
                : ["set", "isOdd", true];
            }
            return undefined;
          }
        }
      },
      usingIfToUpdate: [
        "if",
        x => x % 2 === 0,
        ["set", "isEven", true],
        ["set", "isOdd", true]
      ],
      usingUnlessToUpdate: [
        "unless",
        x => x.dataLoaded,
        ["set", "text", "loading..."]
      ],
      usingSwitchToUpdate1: [
        "switch",
        {
          1: ["set", "one"],
          2: ["set", "two"],
          default: ["set", "other"]
        }
      ],
      usingSwitchToUpdate2: [
        "switch",
        x => (x.value ? "male" : "female"),
        {
          male: ["set", "sex", "male"],
          default: ["set", "sex", "female"]
        }
      ],
      usingFilter: ["filter", x % 2 === 0],
      unsetWithFilter: ["unset", (value, key) => !!value]
    };
    const result = update(original, specs);
    expect(result).not.toBe(original);
    expect(result).toEqual({
      a: {
        b: {
          c: {
            d: {
              e: {
                f: 100,
                newProp: 100
              }
            }
          }
        }
      },
      arrayPush: [1, 2, 3, 4, 5],
      objMerge: {
        name: "Peter",
        age: 20,
        school: "A"
      },
      toggleMe: true,
      toggleMyProp: {
        done: true,
        completed: false
      },
      unsetMyProp: {},
      sqrt: 10,
      removeSecond: [1, 3, 4],
      removeAppleAndBanana: ["Orange"],
      doubleItems: [2, 4, 6, 8, 10, 12, 14, 16],
      swapItems: ["right", "left"],
      increaseProps: {
        one: 2,
        two: 3,
        three: 4
      },
      removeByIndexes: [1, 3],
      batchProcessing: {
        name: "Peter",
        age: 20
      },
      pipeProcessing: "HELLO WORLD!!!",
      doubleOddNumbers: [2, 2, 6, 4],
      parentNode: {},
      parentNodes: [{ id: 0 }],
      updateTree: {
        text: "root",
        children: [
          {
            text: "child 1",
            done: true,
            deleted: true,
            isEven: true,
            data: {},
            children: [
              { text: "child 1.1", done: true, deleted: true, isEven: true }
            ]
          },
          {
            text: "child 2",
            done: true,
            deleted: true,
            isOdd: true,
            data: {},
            children: [
              { text: "child 2.1", done: true, deleted: true, isEven: true },
              { text: "child 2.2", done: true, deleted: true, isOdd: true }
            ]
          }
        ]
      },
      usingIfToUpdate: {
        value: 1,
        isOdd: true
      },
      usingUnlessToUpdate: {
        dataLoaded: false,
        text: "loading..."
      },
      usingSwitchToUpdate1: "one",
      usingSwitchToUpdate2: {
        value: true,
        sex: "male"
      },
      usingFilter: [2, 4],
      unsetWithFilter: {
        data2: false,
        data4: false
      }
    });

    Typescript support

    immhelper.d.ts

    declare namespace ImmHelper {
        // tuple [selector, action, ...args]
        type UpdateSpec<T> = [(model: T) => any, string | Function, ...any[]];
        interface Update {
            <T>(model: T, ...specs: UpdateSpec<T>[]): T;
     
            default: ImmHelper.Update;
        }
    }
     
    declare var updatePath: ImmHelper.Update;
    export = updatePath;

    Usages

    /// <reference path="./immhelper.d.ts"/>
    import { updatePath, $push, $set } from "immhelper";
    const state = {
      a: {
        b: {
          c: []
        }
      }
    };
    const newState1 = updatePath(
      state,
      [x => x.a.b.c, $push, 1, 2, 3],
      [x => x.a.b, $set, "newProp", 100]
    );
    // this is shorter way but there are some limitations
    // 1. Do not try to get prop value from obj, using originalState to get value instead
    // 2. Only mutating actions are allowed
    // 3. Mutating actions must be defined by using define(actionName, func) or define({ actionName: func })
    const newState2 = updatePath(
      state,
      x => x.a.b.c.push(1, 2, 3),
      x => (x.a.b.newProp = 100)
    );
    console.log(newState1, newState2);

    Mutating actions

    [$push, ...items]
    ['push', ...items]
    actions.push(target, ...items)
    actions.$push(target, ...items)

    push() all the items in array on the target

    [$pop]
    ['pop']
    actions.pop(target)
    actions.$pop(target)

    [$unshift, ...items]
    ['unshift', ...items]
    actions.unshift(target, ...items)
    actions.$unshift(target, ...items)

    unshift() all the items in array on the target.

    [$splice, index, count, ...items]
    ['splice', index, count, ...items]
    actions.splice(target, index, count, ...items)
    actions.$splice(target, index, count, ...items)

    splice() remove item at specified index and push new items at there.

    [$remove, ...items]
    ['remove', ...items]
    actions.remove(target, ...items)
    actions.$remove(target, ...items)

    remove specified items from target array

    [$removeAt, ...indexes]
    ['removeAt', ...indexes]
    actions.removeAt(target, ...indexes)
    actions.$removeAt(target, ...indexes)

    remove items from array by indexes

    [$set, value]
    ['set', value]
    actions.set(target, value)
    actions.$pop(target, value)

    replace the target entirely.

    [$set, prop, value]
    ['set', prop, value]
    actions.set(target, prop, value)
    actions.$set(target, prop, value)

    set value for specified prop of the target

    [$toggle]
    ['toggle']
    actions.toggle(target)
    actions.$toggle(target)

    toggle target's value.

    [$toggle, ...props]
    ['toggle', ...props]
    actions.toggle(target, ...props)
    actions.$toggle(target, ...props)

    toggle all prop values of the target

    [$unset, ...props]
    ['unset', ...props]
    actions.unset(target, ...props)
    actions.$unset(target, ...props)

    remove keys from the target object or set undefined for array indexes

    [$assign, ...objects]
    ['assign' ...objects]
    actions.assign(target ...objects)
    actions.$assign(target, ...objects)

    copy the values of all enumerable own properties from one or more source objects to a target object

    define(actionName, actionFunc, disableAutoClone)

    define new mutating action, if disableAutoClone = true, original value will be passed instead of cloned value. Set disableAutoClone = true if you want to handle value manually, return original value if no change needed.

    define("removeAt", function(originalArray, index) {
      // nothing to remove
      if (index >= originalArray.length) return originalArray;
      const newArray = originalArray.slice();
      newArray.splice(index, 1);
      return newArray;
    }, true);

    Special use cases

    import { without, compact, zip } from "lodash";
    import { define, updatePath } from "immhelper";
     
    define({
      "+"(current, value) {
        return current + value;
      },
      "-"(current, value) {
        return current - value;
      },
      without,
      compact,
      zip
    });
     
    const state = { counter: 0 };
     
    updatePath(state, [x => x.counter, "+", 1]);
    updatePath(state, [x => x.counter, "-", 1]);

    Define custom mutating actions

    import { define, $set } from "immhelper";
    define({
      "=": $set,
      addOne(currentValue, ...args) {
        return currentValue + 1;
      }
    });

    Install

    npm i immhelper

    DownloadsWeekly Downloads

    79

    Version

    1.0.52

    License

    MIT

    Unpacked Size

    445 kB

    Total Files

    28

    Last publish

    Collaborators

    • linq2js