3.6.5 • Published 13 days ago

@canlooks/reactive v3.6.5

Weekly downloads
-
License
MIT
Repository
-
Last release
13 days ago

@canlooks/reactive

This is a very simple and lightweight React tool for responding data and managing state.

Install

npm i @canlooks/reactive

A quick example

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

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

@reactive
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 {reactive, useReactive} from '@canlooks/reactive'

const Index = reactive(() => {
    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

Advance


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.


reactiveClass() & reactiveFC()

In some case, you need to distinguish between ComponentClass and FunctionComponent.


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

You do not have to provide/inject, just use it.

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

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

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

const B = reactive(() => {
    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, reactive, useReactive} from '@canlooks/reactive'

// In this case, component "Index" will never re-render.
const Index = reactive(() => {
    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 alias for <Chip/>

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

\

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

const Index = reactive(() => {
    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 = reactive(() => {
    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 React from 'react'
import {reactive, watch} from '@canlooks/reactive'

@reactive
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 React from 'react'
import {loading, reactive} from '@canlooks/reactive'

@reactive
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 = reactive(() => {
    const method = useLoading(async () => {
        // ...
    })
    
    return method.loading
        ? <div>Loading...</div>
        : <button onClick={method.load}>button</button>
})

storage()

type Options = {
    mode?: 'localStorage' | 'sessionStorage'
    sync?: boolean
}

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

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


Hooks

useReactive()

It's encapsulated like:

function useReactive<T>(initialValue: T): T {
    return useMemo(() => {
        return 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), [])
}

Advance

Set these option in tsconfig.json

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@canlooks/reactive/jsx"
  }
}

If you use @emotion, set options like this

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@canlooks/reactive/jsx/emotion"
  }
}

then, you can use two-way binding like this

const Index = reactive(() => {
    const data = useReactive({
        // This value always sync with value of <input/>
        value: 'hello'
    })
    
    return (
        <input data-model={() => data.value}/>
    )
})

If your <Input/> has particular onChange callback, you can do this

<input
    data-model={() => data.value}
    // return the actual value
    data-model-update={e => e.target.value}
/>
3.6.5

13 days ago

3.6.4

13 days ago

3.6.3

14 days ago

3.6.2

4 months ago

3.6.3-alpha1

4 months ago

3.2.2

10 months ago

3.2.1

10 months ago

3.2.0

10 months ago

3.6.1

6 months ago

3.6.0

6 months ago

3.2.4

8 months ago

3.2.3

9 months ago

3.1.7

10 months ago

3.5.2

6 months ago

3.1.6

10 months ago

3.5.1

6 months ago

3.5.0

6 months ago

3.1.9

10 months ago

3.1.8

10 months ago

3.4.0

8 months ago

3.4.4

8 months ago

3.4.3

8 months ago

3.4.2

8 months ago

3.4.1

8 months ago

3.4.8

7 months ago

3.4.7

8 months ago

3.4.6

8 months ago

3.4.5

8 months ago

3.3.0

8 months ago

3.0.1

1 year ago

3.0.0

1 year ago

3.1.3

1 year ago

3.1.2

1 year ago

3.1.1

1 year ago

3.1.0

1 year ago

3.1.5

12 months ago

3.1.4

12 months ago

2.7.1

1 year ago

2.7.0

1 year ago

2.6.7

1 year ago

2.6.8

1 year ago

2.6.1

1 year ago

2.6.3

1 year ago

2.6.2

1 year ago

2.6.5

1 year ago

2.6.4

1 year ago

2.6.6

1 year ago

2.6.0

1 year ago

2.5.0

1 year ago

2.5.1

1 year ago

2.4.7

1 year ago

2.4.8

1 year ago

2.2.1

2 years ago

2.2.0

2 years ago

2.4.1

1 year ago

2.2.3

2 years ago

2.4.0

1 year ago

2.2.2

2 years ago

2.4.3

1 year ago

2.4.2

1 year ago

2.4.5

1 year ago

2.4.4

1 year ago

2.3.8

1 year ago

2.3.7

1 year ago

2.3.9

1 year ago

2.3.0

2 years ago

2.3.2

2 years ago

2.3.1

2 years ago

2.1.3

2 years ago

2.3.4

2 years ago

2.3.3

2 years ago

2.3.6

1 year ago

2.3.5

2 years ago

2.4.6

1 year ago

2.1.2

2 years ago

2.1.1

2 years ago

2.0.7

2 years ago

2.0.6

2 years ago

2.0.8

2 years ago

2.1.0

2 years ago

2.0.3

2 years ago

2.0.2

2 years ago

2.0.5

2 years ago

2.0.4

2 years ago

2.0.1

2 years ago

1.3.7

2 years ago

1.3.6

2 years ago

1.3.5

2 years ago

1.3.4

2 years ago

2.0.0

2 years ago

1.3.9

2 years ago

1.3.8

2 years ago

1.3.3

2 years ago

1.3.2

2 years ago

1.3.1

2 years ago

1.2.2

2 years ago

1.3.0

2 years ago

1.2.0

2 years ago

1.1.5

2 years ago

1.2.1

2 years ago

1.1.1

2 years ago

1.0.2

2 years ago

1.1.0

2 years ago

1.0.7

2 years ago

1.0.6

2 years ago

1.1.4

2 years ago

1.0.5

2 years ago

1.1.3

2 years ago

1.0.4

2 years ago

1.1.2

2 years ago

1.0.3

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago