• Stars
    star
    436
  • Rank 99,877 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 7 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A super tiny reactive library. โšก

Hyperactiv logo
Hyperactiv
npm-badge ci-badge Coverage Status license-badge

A super tiny reactive library. โšก๏ธ

Description

Hyperactiv is a super small (~ 1kb minzipped) library which observes object mutations and computes functions depending on those changes.

In other terms whenever a property from an observed object is mutated, every function that depend on this property are called right away.

Of course, Hyperactiv automatically handles these dependencies so you never have to explicitly declare anything. โœจ


Minimal working example

import hyperactiv from 'hyperactiv'
const { observe, computed } = hyperactiv

// This object is observed.
const observed = observe({
    a: 1,
    b: 2,
    c: 0
})

// Calling computed(...) runs the function and memorize its dependencies.
// Here, the function depends on properties 'a' and 'b'.
computed(() => {
    const { a, b } = observed
    console.log(`a + b = ${a + b}`)
})
// Prints: a + b = 3

// Whenever properties 'a' or 'b' are mutatedโ€ฆ
observed.a = 2
// The function will automagically be called.
// Prints: a + b = 4

observed.b = 3
// Prints: a + b = 5

observed.c = 1
// Nothing depends on 'c', so nothing will happen.

Demo

Paint demo

React store demo

React hooks demo

Setup

npm i hyperactiv
<script src="https://unpkg.com/hyperactiv"></script>

Import

Hyperactiv is bundled as an UMD package.

// ESModules
import hyperactiv from 'hyperactiv'
// Commonjs
const hyperactiv = require('hyperactiv')
// Global variable
const { computed, observe, dispose } = hyperactiv

Usage

1. Observe object and arrays

const object = observe({ one: 1, two: 2 })
const array = observe([ 3, 4, 5 ])

2. Define computed functions

let sum = 0

// This function calculates the sum of all elements,
// which is 1 + 2 + 3 + 4 + 5 = 15 at this point.
const calculateSum = computed(() => {
    sum = [
        ...Object.values(object),
        ...array
    ].reduce((acc, curr) => acc + curr)
})

// A computed function is called when declared.
console.log(sum) // -> 15

3. Mutate observed properties

// calculateSum will be called each time one of its dependencies has changed.

object.one = 2
console.log(sum) // -> 16
array[0]++
console.log(sum) // -> 17

array.unshift(1)
console.log(sum) // -> 18
array.shift()
console.log(sum) // -> 17

4. Release computed functions

// Observed objects store computed function references in a Set,
// which prevents garbage collection as long as the object lives.
// Calling dispose allows the function to be garbage collected.
dispose(calculateSum)

Add-ons

Additional features that you can import from a sub path.

A simple but clever react store.

A reactive http cache.

Utility callbacks triggered when a property is mutated.

An Observable class.

Hyperactiv websocket implementation.

Performance

This repository includes a benchmark folder which pits hyperactiv against other libraries.

Important: the benchmarked libraries are not equivalent in terms of features, flexibility and developer friendliness.

While not the best in terms of raw performance hyperactiv is still reasonably fast and I encourage you to have a look at the different implementations to compare the library APIs. For instance there is no .get() and .set() wrappers when using hyperactiv.

Here are the raw results: (100 runs per tiers, average time ignoring the 10 best & 10 worst runs)

bench

Each tier nests observable objects X (10/100/500/1000โ€ฆ) times and performs some computations on the deepest one. This causes reactions to propagate to the whole observable tree.

Disclaimer: I adapted the code from maverickjs which was itself a rewrite of the benchmark from cellx. I also wrote some MobX code which might not be the best in terms of optimization since I am not very familiar with the API.

Code samples

A simple sum and a counter

// Observe an object and its properties.
const obj = observe({
    a: 1,
    b: 2,
    sum: 0,
    counter: 0
})

// The computed function auto-runs by default.
computed(() => {
    // This function depends on a, b and counter.
    obj.sum = obj.a + obj.b
    // It also sets the value of counter, which is circular (get & set).
    obj.counter++
})

// The function gets executed when computed() is calledโ€ฆ
console.log(obj.sum)     // -> 3
console.log(obj.counter) // -> 1
obj.a = 2
// โ€ฆand when a or b are mutated.
console.log(obj.sum)     // -> 4
console.log(obj.counter) // -> 2
obj.b = 3
console.log(obj.sum)     // -> 5
console.log(obj.counter) // -> 3

Nested functions

const obj = observe({
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    totalSum: 0
})

const aPlusB = () => {
    return obj.a + obj.b
}
const cPlusD = () => {
    return obj.c + obj.d
}

// Depends on a, b, c and d.
computed(() => {
    obj.totalSum = aPlusB() + cPlusD()
})

console.log(obj.totalSum) // -> 10
obj.a = 2
console.log(obj.totalSum) // -> 11
obj.d = 5
console.log(obj.totalSum) // -> 12

Chaining computed properties

const obj = observe({
    a: 0,
    b: 0,
    c: 0,
    d: 0
})

computed(() => { obj.b = obj.a * 2 })
computed(() => { obj.c = obj.b * 2 })
computed(() => { obj.d = obj.c * 2 })

obj.a = 10
console.log(obj.d) // -> 80

Asynchronous computations

// Promisified setTimeout.
const delay = time => new Promise(resolve => setTimeout(resolve, time))

const obj = observe({ a: 0, b: 0, c: 0 })
const multiply = () => {
    obj.c = obj.a * obj.b
}
const delayedMultiply = computed(

    // When dealing with asynchronous functions
    // wrapping with computeAsync is essential to monitor dependencies.

    ({ computeAsync }) =>
        delay(100).then(() =>
            computeAsync(multiply)),
    { autoRun: false }
)

delayedMultiply().then(() => {
    console.log(obj.b) // -> 0
    obj.a = 2
    obj.b = 2
    console.log(obj.c) // -> 0
    return delay(200)
}).then(() => {
    console.log(obj.c) // -> 4
})

Batch computations

// Promisified setTimeout.
const delay = time => new Promise(resolve => setTimeout(resolve, time))

// Enable batch mode.
const array = observe([0, 0, 0], { batch: true })

let sum = 0
let triggerCount = 0

const doSum = computed(() => {
    ++triggerCount
    sum = array.reduce((acc, curr) => acc + curr)
})

console.log(sum) // -> 0

// Even if we are mutating 3 properties, doSum will only be called once asynchronously.

array[0] = 1
array[1] = 2
array[2] = 3

console.log(sum) // -> 0

delay(10).then(() => {
    console.log(`doSum triggered ${triggerCount} time(s).`) // -> doSum triggered 2 time(s).
    console.log(sum) // -> 6
})

Observe only some properties

const object = {
    a: 0,
    b: 0,
    sum: 0
}

// Use props to observe only some properties
// observeA reacts only when mutating 'a'.

const observeA = observe(object, { props:  ['a'] })

// Use ignore to ignore some properties
// observeB reacts only when mutating 'b'.

const observeB = observe(object, { ignore: ['a', 'sum'] })

const doSum = computed(function() {
    observeA.sum = observeA.a + observeB.b
})

// Triggers doSum.

observeA.a = 2
console.log(object.sum) // -> 2

// Does not trigger doSum.

observeA.b = 1
observeB.a = 1
console.log(object.sum) // -> 2

// Triggers doSum.

observeB.b = 2
console.log(object.sum) // -> 3

Automatically bind methods

let obj = new SomeClass()
obj = observe(obj, { bind: true })
obj.someMethodThatMutatesObjUsingThis()
// observe sees all!

This and class syntaxes

class MyClass {
    constructor() {
        this.a = 1
        this.b = 2

        const _this = observe(this)

        // Bind computed functions to the observed instance.
        this.doSum = computed(this.doSum.bind(_this))

        // Return an observed instance.
        return _this
    }

    doSum() {
        this.sum = this.a + this.b
    }
}

const obj = new MyClass()
console.log(obj.sum) // -> 3
obj.a = 2
console.log(obj.sum) // -> 4
const obj = observe({
    a: 1,
    b: 2,
    doSum: function() {
        this.sum = this.a + this.b
    }
}, {
    // Use the bind flag to bind doSum to the observed object.
    bind: true
})

obj.doSum = computed(obj.doSum)
console.log(obj.sum) // -> 3
obj.a = 2
console.log(obj.sum) // -> 4

API

observe

Observes an object or an array and returns a proxified version which reacts on mutations.

observe(Object | Array, {
    props: String[],
    ignore: String[],
    batch: boolean,
    deep: boolean = true,
    bind: boolean
}) => Proxy

Options

  • props: String[]

Observe only the properties listed.

  • ignore: String[]

Ignore the properties listed.

  • batch: boolean | int

Batch computed properties calls, wrapping them in a setTimeout and executing them in a new context and preventing excessive calls. If batch is an integer greater than zero, the calls will be debounced by the value in milliseconds.

  • deep: boolean

Recursively observe nested objects and when setting new properties.

  • bind: boolean

Automatically bind methods to the observed object.

computed

Wraps a function and captures observed properties which are accessed during the function execution. When those properties are mutated, the function is called to reflect the changes.

computed(fun: Function, {
    autoRun: boolean,
    callback: Function
}) => Proxy

Options

  • autoRun: boolean

If false, will not run the function argument when calling computed(function).

The computed function must be called at least once to calculate its dependencies.

  • callback: Function

Specify a callback that will be re-runned each time a dependency changes instead of the computed function.

dispose

Will remove the computed function from the reactive Maps (the next time an bound observer property is called) allowing garbage collection.

dispose(Function) => void

batch

Only when observables are created with the {batch: โ€ฆ} flag

Will perform accumulated b.ed computations instantly.

const obj = observe({ a: 0, b: 0 }, { batch: true })
computed(() => obj.a = obj.b)
obj.b++
obj.b++
console.log(obj.a) // => 0
batch()
console.log(obj.a) // => 2

More Repositories

1

wretch

A tiny wrapper built around fetch with an intuitive syntax. ๐Ÿฌ
TypeScript
4,692
star
2

yett

๐Ÿ”A small webpage library to control the execution of (third party) scripts
JavaScript
769
star
3

bosket

Collection of tree view components for front-end frameworks. ๐ŸŒณ
JavaScript
450
star
4

crystalline

A Language Server Protocol implementation for Crystal. ๐Ÿ”ฎ
Crystal
432
star
5

cryomongo

A MongoDB driver written in pure Crystal. โ„๏ธ
Crystal
72
star
6

zap

Another [insert blazing fast synonyms] JavaScript package manager
Crystal
54
star
7

wretch-middlewares

Collection of middlewares for the Wretch library. ๐ŸŽ
TypeScript
47
star
8

quadtree-lib

Efficient quadtrees library written in CoffeeScript.
CoffeeScript
46
star
9

moongoon

An object-document mapper for MongoDB. ๐ŸŒ™
Crystal
45
star
10

openapi-generator

An OpenAPI document generator. โš™๏ธ
Crystal
22
star
11

bson.cr

A pure Crystal Implementation of the BSON Specification. ๐Ÿƒ
Crystal
19
star
12

spotify-zeroconf

Spotify Web API authentication, the easy way.
JavaScript
17
star
13

normaliz

A tiny library that normalizes data. ๐Ÿญ
JavaScript
14
star
14

crystal-lsp

An implementation of the Language Server Protocol written in Crystal
Crystal
13
star
15

hyperactiv-hooks-demo

Hyperactiv hooks isomorphic demo with SSR.
JavaScript
5
star
16

htmltree

A quick and dirty html tree experiment. ๐Ÿ’ญ
JavaScript
5
star
17

hermes-crystal

Crystal bindings to the Hermes protocol used by the Snips platform. ๐Ÿ’Ž
Crystal
4
star
18

quickey

Shell commands at your fingertips โœŒ๏ธ
JavaScript
2
star
19

rescript-hyperactiv

Hyperactiv bindings for ReScript
ReScript
2
star
20

hermes-reason

ReasonML / Ocaml bindings to the Hermes protocol used by the Snips platform. ๐Ÿช
OCaml
2
star
21

ppx-ctypes-helper

Ppx that helps serializing structures and enums to / from c. ๐Ÿญ
OCaml
2
star
22

Pendu

Ocaml hangman game (client/server)
OCaml
1
star
23

struct-mappings

Generate Crystal classes mapping C structures. ๐Ÿ—บ๏ธ
Crystal
1
star
24

shannon-bindings

Shannon cipher node.js bindings
C
1
star
25

CTigre-to-MIPS-compiler

Compiler from CTigre to MIPS written in OCaml
OCaml
1
star