4.2.1 • Published 9 months ago

@canlooks/reactive v4.2.1

Weekly downloads
-
License
MIT
Repository
-
Last release
9 months ago

@canlooks/reactive

This is a simple and lightweight React tool for responding data, auto re-render and managing state.

Installation

npm i @canlooks/reactive

A quick example

A react-class-component which using @canlooks/reactive will look like this.

import {RC} from '@canlooks/reactive/react'

@RC
export default class Index extends React.Component {
    count = 1
    test = 2

    onClick() {
        this.count++ // This component will update.
    }

    myTest() {
        this.test++
        // This component will not update,
        // because "test" have never referred in "render()" 
    }

    render() {
        return (
            <div>
                <div>{this.count}</div>
                <button onClick={this.onClick}>Increase</button>
                <button onClick={this.myTest}>Test</button>
            </div>
        )
    }
}

and Function component will look like this:

import {RC, useReactive} from '@canlooks/reactive/react'

const Index = RC(() => {
    const data = useReactive({
        count: 1
    })

    const increase = () => {
        data.count++
    }

    return (
        <div>
            <div>{data.count}</div>
            <button onClick={increase}>Increase</button>
        </div>
    )
})

Contents

Basic API

automatic allocation

External data & sharing state

Additional functions

Hooks


reactive() & reactive.deep()

There are 3 ways to create a reactive data.

import {reactive} from '@canlooks/reactive'

// create by object
const data = reactive({
    a: 1,
    b: 2
})

// create by class
const DataClass = reactive(class {
    a = 1
    static b = 2 // Both instance and static properties are reactive.
})

// using decorator
@reactive
class Data {
    a = 1
}

You can also create a reactive React Component like quick example.


reactor()

import {act, reactive, reactor} from '@canlooks/reactive'

const obj = reactive({
    a: 1,
    b: 2
})

const disposer = reactor(() => obj.a, (to, from) => {
    console.log(`"obj.a" was changed from ${from} to ${to}`)
})

act(() => obj.a++) // log: "obj.a" was changed from 1 to 2
act(() => obj.b++) // nothing happen

disposer() // Remember to dispose if you don't use it anymore.
declare type ReactorOptions = {
    immediate?: boolean
    once?: boolean
}

declare function reactor<T>(refer: () => T, effect: (newValue: T, oldValue: T) => void, options?: ReactorOptions): () => void

autorun()

const obj = reactive({
    a: 1
})

const disposer = autorun(() => {
    console.log('Now "obj.a" is: ' + obj.a)
})

action()

Every methods for modifying reactive data are strongly suggest wrapping in "action".

const obj = reactive({
    a: 1,
    b: 2
})

reactor(() => [obj.a, obj.b], () => {
    // ...
})

// Good, effect will trigger only once.
const increase = action(() => {
    obj.a++
    obj.b++
})

// Bad, effect will trigger twice.
const decrease = () => {
    obj.a++
    obj.b++
}

act()

IIFE for action()

act(() => {
    //
})
// is equivalent to
action(() => {
    //
})()

Automatic allocation

Each property in reactive object or class will allocate automatically.

const data = reactive({
    // Become reactive property
    count: 1,
    
    // Become computed property
    get double() {
        // This function will not execute repeatedly until "count" change.
        return this.count * 2
    },
    
    // Become action
    increase() {
        this.count++
    }
})

External data & sharing state

No need to provide/inject, just use it.

import {act, reactive} from '@canlooks/reactive'
import {RC} from '@canlooks/reactive/react'

const data = reactive({
    a: 1,
    b: 2
})

@RC
class A extends React.Component {
    render() {
        return (
            <div>{data.a}</div>
        )
    }
}

const B = RC(() => {
    return (
        <div>
            <div>{data.a}</div>
            <div>{data.b}</div>
        </div>
    )
})

// Modify data everywhere.
act(() => data.a++) // Both component "A" and "B" will update.
act(() => data.b++) // Only component "B" will update.

Additional functions

\

<Chip/> can take component to pleces for updating only a small part.

import {Chip, RC, useReactive} from '@canlooks/reactive/react'

// In this case, component "Index" will never re-render.
const Index = RC(() => {
    const data = useReactive({
        a: 1,
        b: 2
    })

    return (
        <div>
            <Chip>
                {/*Re-render when only "a" is modified*/}
                {() => <ChildA count={data.a}/>}
            </Chip>
            <Chip>
                {/*Re-render when only "b" is modified*/}
                {() => <ChildB data={data.b}/>}
            </Chip>
        </div>
    )
})

chip() is an function way for <Chip/>

chip(() => <AnyComponent/>)
// is equivalent to
<Chip>{() => <AnyComponent/>}</Chip>

\

<Model/> has advanced usage like advanced, and common usage like this.

const Index = RC(() => {
    const data = useReactive({
        // This value always sync with value of <input/>
        value: 'Hello'
    })

    return (
        <div>
            <Model refer={() => data.value}>
                <input/>
            </Model>
        </div>
    )
})

useModel()

const Index = RC(() => {
    const data = useModel('Hello Reactive')
    // "data" has "value" and "onChange" props.

    return (
        <div>
            <p>Input value is: {data.value}</p>
            <input {...data}/>
            {/*or use like this*/}
            <input value={data.value} onChange={data.onChange}/>
        </div>
    )
})

@watch()

@watch() is a syntactic sugar of reactor()

import {watch} from '@canlooks/reactive'
import {RC} from '@canlooks/reactive/react'

@RC
class Index extends React.Component {
    @watch(() => someExternal.data)
    effect1() {
        // This effect will trigger when "someExternal.data" modified.
    }

    a = 1

    @watch(t => t.a) // t === this
    effect2() {
        // This effect will trigger when "this.a" modified.
    }
}

@loading()

import {loading} from '@canlooks/reactive'
import {RC} from '@canlooks/reactive/react'

@RC
class Index extends React.Component {
    busy = false

    @loading(t => t.busy) // t === this
    async myAsyncMethod() {
        // It changes "busy" to true,
        // and changes false back until this function return.
    }

    stack = 0

    @loading(t => t.stack)
    async concurrent() {
        // It make "stack" +1,
        // and -1 until this function return.
    }

    render() {
        return (
            <div>
                {(this.busy || this.stack !== 0) &&
                    <div>I'm busy</div>
                }
            </div>
        )
    }
}

useLoading()

const Index = RC(() => {
    const method = useLoading(async () => {
        // ...
    })
    
    return method.loading
        ? <div>Loading...</div>
        : <button onClick={method.load}>button</button>
})

autoload()

To define a common business data automatically.

abstract class Autoload<D, A> {
    loading: boolean
    data: D
    setData(v: D | undefined): void
    abstract loadData(...args: A[]): D | Promise<D>
    update(...args: A[]): Promise<D>
}

function autoload<D, A>(loadData: (...args: A[]) => D | Promise<D>, options?: ReactiveOptions): Autoload<D, A>

example:

@reactive
class MyData extends Autoload {
    async loadData() {
        const res = await fetch('fetch/my/data')
        return await res.json()
    }
}

const myData = new MyData()

// Automatic load data on first use.
console.log(myData.data)

// Load data again.
myData.update()

defineStorage()

This method create a reactive data which always sync with 'localStorage' or 'sessionStorage'.

type Options = {
    mode?: 'localStorage' | 'sessionStorage'
    async?: boolean
    debounce?: number
}

declare function defineStorage<T>(
    name: string,
    initialvalues?: T,
    options?: Options
): T

example:

const userStorage = defineStorage('user', {
    name: 'canlooks',
    age: 18
})
// then modifying "userStorage" will sync with 'localStorage'
userStorage.age++

defineForage()

It's similar to defineStorage(), but it use localforage to store data.

type Options = {
    instance?: typeof localforage
    deep?: boolean
}

declare function defineForage<T>(
    name: string,
    initialvalues?: T,
    options?: Options
): T

or using Forage class which extends Autoload

class Forage extends Autoload {
    dispose(): void
}

Hooks

useReactive()

It's encapsulated like:

function useReactive<T>(initialValue: T): T {
    return useMemo(() => reactive(initialValue), [])
}

useReactor()

function useReactor(refer: () => T, effect: (newValue: T, oldValue: T) => void, options?: ReactorOptions): void {
    useEffect(() => {
        return index(refer, effect, options)
    }, [])
}

useAutorun()

function useAutorun(fn: () => void): void {
    useEffect(() => {
        return autorun(fn)
    }, [])
}

useAction()

function useAction<F extends (...args: any[]) => any>(fn: F): F {
    return useCallback(action(fn), [])
}

useAutoload()

function useAutoload<D, A>(loadData: (...args: A[]) => D | Promise<D>, options?: ReactiveOptions): Autoload<D, A> {
    return useMemo(() => autoload(), [])
}
4.2.1

9 months ago

4.2.0

10 months ago

4.1.8

10 months ago

4.1.7

10 months ago

4.1.9

10 months ago

4.1.6

10 months ago

4.1.5

10 months ago

4.0.4

1 year ago

4.0.1

1 year ago

4.0.0

1 year ago

4.0.3

1 year ago

4.0.2

1 year ago

3.6.9

1 year ago

3.6.8

1 year ago

3.6.7

1 year ago

4.1.4

11 months ago

4.1.3

11 months ago

4.1.0

1 year ago

4.1.2

11 months ago

4.1.1

12 months ago

3.6.6

1 year ago

3.6.5

1 year ago

3.6.4

1 year ago

3.6.3

1 year ago

3.6.2

1 year ago

3.6.3-alpha1

1 year ago

3.2.2

2 years ago

3.2.1

2 years ago

3.2.0

2 years ago

3.6.1

2 years ago

3.6.0

2 years ago

3.2.4

2 years ago

3.2.3

2 years ago

3.1.7

2 years ago

3.5.2

2 years ago

3.1.6

2 years ago

3.5.1

2 years ago

3.5.0

2 years ago

3.1.9

2 years ago

3.1.8

2 years ago

3.4.0

2 years ago

3.4.4

2 years ago

3.4.3

2 years ago

3.4.2

2 years ago

3.4.1

2 years ago

3.4.8

2 years ago

3.4.7

2 years ago

3.4.6

2 years ago

3.4.5

2 years ago

3.3.0

2 years ago

3.0.1

2 years ago

3.0.0

2 years ago

3.1.3

2 years ago

3.1.2

2 years ago

3.1.1

2 years ago

3.1.0

2 years ago

3.1.5

2 years ago

3.1.4

2 years ago

2.7.1

2 years ago

2.7.0

2 years ago

2.6.7

2 years ago

2.6.8

2 years ago

2.6.1

2 years ago

2.6.3

2 years ago

2.6.2

2 years ago

2.6.5

2 years ago

2.6.4

2 years ago

2.6.6

2 years ago

2.6.0

2 years ago

2.5.0

2 years ago

2.5.1

2 years ago

2.4.7

2 years ago

2.4.8

2 years ago

2.2.1

3 years ago

2.2.0

3 years ago

2.4.1

3 years ago

2.2.3

3 years ago

2.4.0

3 years ago

2.2.2

3 years ago

2.4.3

3 years ago

2.4.2

3 years ago

2.4.5

3 years ago

2.4.4

3 years ago

2.3.8

3 years ago

2.3.7

3 years ago

2.3.9

3 years ago

2.3.0

3 years ago

2.3.2

3 years ago

2.3.1

3 years ago

2.1.3

3 years ago

2.3.4

3 years ago

2.3.3

3 years ago

2.3.6

3 years ago

2.3.5

3 years ago

2.4.6

3 years ago

2.1.2

3 years ago

2.1.1

3 years ago

2.0.7

3 years ago

2.0.6

3 years ago

2.0.8

3 years ago

2.1.0

3 years ago

2.0.3

3 years ago

2.0.2

3 years ago

2.0.5

3 years ago

2.0.4

3 years ago

2.0.1

3 years ago

1.3.7

3 years ago

1.3.6

3 years ago

1.3.5

3 years ago

1.3.4

3 years ago

2.0.0

3 years ago

1.3.9

3 years ago

1.3.8

3 years ago

1.3.3

3 years ago

1.3.2

3 years ago

1.3.1

3 years ago

1.2.2

3 years ago

1.3.0

3 years ago

1.2.0

3 years ago

1.1.5

3 years ago

1.2.1

3 years ago

1.1.1

4 years ago

1.0.2

4 years ago

1.1.0

4 years ago

1.0.7

4 years ago

1.0.6

4 years ago

1.1.4

3 years ago

1.0.5

4 years ago

1.1.3

4 years ago

1.0.4

4 years ago

1.1.2

4 years ago

1.0.3

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago