Giter Site home page Giter Site logo

realar-project / realar Goto Github PK

View Code? Open in Web Editor NEW
45.0 2.0 0.0 1.36 MB

5 kB Advanced state manager for React

License: MIT License

JavaScript 0.02% TypeScript 99.98%
model reactive frontend react state-manager reactive-programming observable data-flow react-hooks decorators

realar's People

Contributors

betula avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

realar's Issues

low: filter instead of stoppable in view and wrap

export const buttonLangPress = signal().wrap(() => {
  const stop = stoppable();
  if (buttonLangDisabled.val) {
    stop();
  }
});

// -->

export const buttonLangPress = signal().filter(() => (
  !buttonLangDisabled.val
))

Think about It

low: replace effect method to some unsubscriber registrator

effect(() => () => ( // Ugly (((
  this.items_destroy && this.items_destroy()
));

// Must be pretty

unsub(() => {
  this.items_destroy && this.items_destroy()
});

// ... Think about

// Or perfect one!

un(() => {
  this.items_destroy && this.items_destroy()
});

low: try deprecate stopppable

selector(init_value?, (stop) => {});
selector((stop?) => {});

cycle((stop) => {});
on((stop) => {}, () => {});




// But I think pool no need to use "(stop) =>" syntax, maybe stoppable() is better for pool?
// pool.stop

pool(async () => {
  const stop = pool.stop;
  pool.stop();
  pool.stop.throw();
});

pool.stoppable((stop) => {
  return async () => {

  }
});

high: new `flow` brainstorm (signal.combine consistency)

test('should work signal combine', async () => {
  const spy = jest.fn();
  const v = value(1);
  const s = signal.from(v.select(v => v + v));

// `signal.from` should be a stoppable selector, not a new signal, with a change argument subscription.
// `flow` - is a stoppable selector (maybe simple selector is enough, and make another way to create stopping for flow)
// for example `flow.filter`, and `wrap.filter` for making stoppable flow, maybe its enough. Because breaking flow a change type to `Ensurable`

// but stoppable selector is very very interesting - is the possibility to stop flow, without the creation of new `on` subscription.

// simplest way for `sel` and `on` - is making stop exception.
const s = sel(() => {
  if (v.val > 0) stop(); // This will be next exported function from reactive-box
  // It is a special exception throwing
});

// It's possible to use in another case:
on(() => {
  if (v.val > 0) stop(); // <-- its will work automatically)) because `on` used `sel` inside.
}, fn);


// ===

  const c = signal.combine(v, s);
  c.watch(v => spy(v));

  expect(c.val).toEqual([1, 2]);
  s(10);
  s(10);
  v(2);
  v(2);

  expect(spy).toHaveBeenNthCalledWith(1, [1, 10]);
  expect(spy).toHaveBeenNthCalledWith(2, [1, 10]);
  expect(spy).toHaveBeenNthCalledWith(3, [2, 10]); // Todo: hmm
  expect(spy).toHaveBeenNthCalledWith(4, [2, 4]);
  expect(spy).toBeCalledTimes(4);
});

low: promise property to signal

const { signal, loop } = require('realar');

const a = signal();
const b = signal();
const c = signal();

loop(async () => {
    await a; console.log('a ready');
    await b; console.log('b ready');
    await c; console.log('c ready');
    
    // possible syntax
    // const [pa, pb, pc] = [a.promise, b.promise, c.promise];
    // await pa;
    // await pb;
    // await pc;
    // console.log('ready');
});

a();
b();
c();

https://runkit.com/betula/60572b6307e748001a619912

Or maybe make another type of call

a.safe();
b.safe();
c.safe();

No... first suggest was better...

normal: add interceptor method

const a = value(5);
const b = value(1);

const unsub = a.interceptor((new_value, current_value, stop?) => {
  if (new_value == 1) {
    b.set(10);
    stop();
    return;
  }
  return current_value + new_value;
});
// stoppable supported

a(1); // b.val === 10, a.val === 5
a(2); // a.val === 7

low: flow.to proposal

sharedLocale().lang.flow.to(formattedTextOverStyles, (curr_code, curr_styles?, prev_code?) => {
  const data = getInstructionData() as any;
  return data['styles_' + code] || {};
});

// No use `on` inside, use only `flow`
// No possible because flow not used subscription, its necessary for making flow body, not a flow intersections
const to = (src, target, fn) => {
  const [get] = flow((stop, prev_code) => {
    fn(src.val, target.val, prev_code)
  });
}

// Hmmmmmm, but `on` impl: ->
const to = (src, target, fn) => {
  on(src, (v, prev_v) => {
    target.set(fn(v, target.get(), prev_v))
  });

  const [get] = flow((stop, prev_code) => {
    fn(src.val, target.val, prev_code)
  });
}



// And update reactive-box API:

sell((prev_value?) => {

});
flow(init_, (stop?, prev_value?) => {

});

low: autodestroyable uncontrolled scopes

export class Locale {
  constructor() {
    link(on(() => this.code, (code) => console.log(code)));
  }

  createText = (source: TextSource) => {
    const text = value(source[this.code]);
    on(() => this.code, (code) => {
      text(source[code]);
    });
  }
}

Signature:
link(destructor);

Examples:

link(on(() => this.code, (code) => console.log(code)));
destruct(on(() => this.code, (code) => console.log(code)));
sub(on(() => this.code, (code) => console.log(code)));

But is it not comfortable.

isolate(on(() => this.code, (code) => console.log(code)));

For removing on destructor from context scope.

low: loop generators support

[] Improve cycle to async operations and yield notation.

cycle(async ([isCancelled]) => {
  const data = await action;
  if (isCancelled()) return;
  // ...
});

cycle(function* (cancel) {
  const data = yield action;
  // ...
});

About abstraction of access visibility levels.

The basic level of scopes for React developers is a component level scope (for example useState, and other React hooks has that level).

Every React component instance has its own local state, which is saved every render for the component as long as the component is mounted.

In the Realar ecosystem useLocal hook used to make components local state.

const CounterLogic = () => {

  const [get, set] = box(0);
  const inc = () => set(get() + 1);

  return sel(() => ({
    value: get(),
    inc
  }));
}

const Counter = () => {
  const { value, inc } = useLocal(CounterLogic);

  return (
    <p>{value} <button onClick={inc}>+</button></p>
  );
}

export const App = () => (
  <>
    <Counter />
    <Counter />
  </>
);

Play on CodeSandbox

Or If you coding in classes style:

class CounterLogic {
  @prop value = 0;
  inc = () => this.value += 1
}

const Counter = () => {
  const { value, inc } = useLocal(CounterLogic);

  return (
    <p>{value} <button onClick={inc}>+</button></p>
  );
}

Play wrapped on CodeSandbox

This feature can be useful for removing logic from the body of a component to keep that free of unnecessary code, and therefore cleaner.

Implementation

const TickerService = unit({
  current: 0,
  get next() {
    return this.current + 1;
  },
  tick() {
    this.current += 1;
  },
  constructor() {},
  destructor() {}
});

const ticker = new TickerService();
ticker.tick();
console.log(ticker.next);

function action(fn) {
  return fn;
}
function computed(fn) {
  return fn;
}
function setting() {}
function getting() {}

function unit(schema) {
  const source = Object.getOwnPropertyDescriptors(schema);
  const DataKey = "data";
  const descriptors = {};

  Object.keys(source).forEach(key => {
    const { value, get, set } = source[key];
    const descriptor = {};
    if (typeof value === "function") {
      descriptor.value = action(value);
    } else if (get) {
      descriptor.get = computed(get);
      if (set) {
        descriptor.set = action(set);
      }
    } else {
      descriptor.set = function(val) {
        (this[DataKey] = this[DataKey] || {})[key] = val;
        setting();
      };
      descriptor.get = function() {
        const val = (this[DataKey] = this[DataKey] || {})[key] || value;
        getting();
        return val;
      };
    }
    descriptors[key] = descriptor;
  });

  let constructor = source.constructor && source.constructor.value;
  if (constructor) {
    constructor = action(constructor);
  }
  const Class = function() {
    if (constructor) {
      constructor.apply(this, arguments);
    }
  };
  Class.prototype = Object.defineProperties({}, descriptors);
  return Class;
}

https://codesandbox.io/s/elastic-fermi-or16g?file=/src/index.js:0-1406

low: reset for signals, values, etc..

Two kind of syntax:

// 1.

const a = ready();
const b = signal();
const c = value();

a.reset();
b.reset();
c.reset();

// or

reset(a)
reset(b)
reset(c)

low: stoppable in view

const v = value(0);
const positive_v = v.view((n) => n >= 0 ? n : stoppable().stop()); // Add stop method to stop signal. (Make external stop siglal interface)

on(positive_v, console.log) // only positive reactions
// but inside value can be any values, and negative too.

// Task: make a positive stream, ignore negative values.

const v = value(0);
const b = value(0);
sync(v, (val) => val >= 0 && b(val));

// what is the method (shortcut) for making it

v.view((dest, val) => val >= 0 && dest(val));

// This is the perfect view.

// or

v.view((val) => val < 0 && stoppable.stop());

// No have reasons for making stoppable in view. Because it will stop only subscribers reaction, but what to be received for direct reading? (undefined)

// That method provide a new entity with the same set of methods.

// But Its only view. Its parts of one entity. Its not a one task.

v.flow((dest, val, prev?) => val >= 0 && dest(val)); // That case is new one.

// That necessary for creating a new entity (signal, value, ready, etc...), from another entity

low: from method

Add synthetic value creator function

const v = synthetic(set, get); // make new synthetic value

v.val
v.update
v.reset !! //no defined here)

Or provide possibility for creation both synthetic entities value and signal.

const s = signal.synthetic(set, get);
// or
const s = signal.from(set, get); // without reset method

// and of course

const v = value.synthetic(set, get);
// or
const v = value.from(set, get);

const r = ready.synthetic(set, get);
// or
const r = ready.from(set, get);

low: add signal combine

const a = value(0);

const s = signal.combine(a, () => a.val + 1); // readonly

s.watch(
  ([a, a_plus_one]) => console.log(a, a_plus_one)
);

// And I think it's possible to make `value.combine`
// Should be detected changes in each value and if some of them changed, fire changes outside

const v = value.combine(a, () => a.val + 1); // readonly

very low: state machine impl

This abstraction is necessary for the implementation of serial operations. Reactions on each section of the serial operations.

const m = machine()
  .step(a, () => {})
  .step(b, () => 0)
  .step(machine.oneOf()
    .select(a, () => {})
    .select(b)
    .select(c), () => {})
  .loop();

But I think the yield generator function is an interesting way of decision.

loop(function *() {
  yield a;
  yield b;
  yield loop.oneOf([a, b, c]) // or loop.oneOf().select(a, () => {}).select(b, () => {})... chain function
  yield loop.race([a, b, c])
  yield loop.all([a, b, c])

  // Promise namespace operations for suggest
});

low: async pool

This task started from the idea and hypothesis possible syntax:

const info = pool(async () => {
  return await search(...)
});

// info.proc - parallel process count
// info.pending - proc > 0
// info.stop() - function for stop all async process in pool
// info.run() - function for execute in async pool
// [ run ] // length > 0 for array destruction detect

// // info.data?
// // info.error?
// And possible to call `info` equivalents as info.run (I think function inheritance in throttle and debounce possible)

info.run();
assert(info.proc === 1);
assert(info.pending === true);

// It's possible that pure `pool` not so interesting for use.

pool.single(async ({ cancelled }) => {
  const zip = await search();
  if (cancelled()) return;
  return await unpack(zip);
});

// If exists active promise each query return It

// pool.throttle(150, async ({ cancelled, abortController?.., cancel }) => {
pool.throttle(150, async ({ stopped, abortController?.., stop }) => {
  const zip = await search();
  if (zip === 0) stop();
  if (stopped()) return;
  return await unpack(zip);
});

pool.debounce(150, async function * ({ cancel }) {
  const zip = yield search();
  if (zip === 0) cancel();
  return yield unpack(zip);
});

normal: rename filter to wrap.filter

const a = value(0);
const new_a_with_wrap_filter = a.wrap.filter(v => v);

// or maybe
const new_a_as_filtered_source = a.flow.filter(v => v);

very low: add jsx method

const a = value(0);

return (
  <p>{a.jsx}</p>
)

return (
  <p>{a.jsx(v => v.map((i, k) => <i key={k}>{i}</i>))}</p>
)

๐Ÿ˜‰

a.jsx
a.jsx.if
a.jsx.else
a.jsx.ifelse
a.jsx.map

// or

jsx(a)
jsx.if(a)
jsx.map(a)
// etc..

For making super performant interfaces ๐Ÿ‘

very low: stop as argument for loop and cycle

loop(async (stop) => {
  const data = await action;
  if (stop.val) return;
  // ...
});

loop(function* (stop) {
  const data = yield action;
  // ...
});

cycle((stop) => {
  this.a = a.val;
});

You need to take this as a shortcut to the stoppable mechanism.

low: proposal for link (or bind) decorator for binding value to prop

No have possibility to use "bind", because in classes with decorators style exists a usually "bind" decorator function that means bind context of the class method to this.

const v = value(0);
class A {
  @link v = v;
}
console.log(shared(A).v); // 0

For example, the cache alias will be

class A {
  @link c = selector(() => 0);
}
shared(A).c // 0

But no possible to reassign.

low: add queue implementation

const q = queue<Type?>(init?: Type[]);

// q(10); // Think about queue - is a reactive value with set of elements or is the signal for add next one... Hmm.

// q.queue // []

q.front // top
q.back // last element
// q.all // array of elements in queue
q.size
q.clear()

//

q.val
q.first: Value
q.last: Value
q.size: Value
q.active: Value
q.clear()

// Support queue for q.wrap and q.view // think about api
// val: <array_of_elements> readonly
// q(<new_element>)

Or q() - no possible, but 

q.add(value: Type)
q.val: Type
q.first: Value
q.last: Value
q.size: Value
q.active: Value
q.clear()

// How I can make queue with limited size same as in my swipe impl?

q = queue();

q(values: Type[]);
q.set(values: Type[]);
q.update(...);
q.val: Type;

q.add(value: Type);

q.first: Value
q.last: Value
q.size: Value
q.active: Value
q.empty: Value
q.clear()


//

const t = q.first

t.val: Type
t.started: Value
t.start();
t.release(); // remove item from queue (or dequeue)

// or

t.val: Type
t.start: ready(false).to(true)
t.remove(); // remove item from queue // or ready(false).to(true) too

low: jsx property for value jsx node creation

Hi performant binding signal, value or another store, to JSX.
That property provide jsx node with binded reactive value.

return (
  <p>{v.jsx}</p>
)

v.jsx - return JSX node with value binded

return (
  <p>{v.jsx.map(m => <i>{m}</i>)}</p>
)

return (
  <p>{v.jsx.if(k, m => <i>{m}</i>)}</p>
  <p>{v.jsx.else(m => <i>{m}</i>)}</p>
  <p>{v.jsx.ifelse(m => <i>{m}</i>)}</p>
)

Dream))

My dream is an extension for javascript/typescript syntax. It's will be amazing))

let ^a = 0; // creation of reactive value
let ^b = a + a;

on(a, console.log); // for reaction
a = 10
(a += 'hello').splice(0, 1); // will be reacted

// composite reaction support
on(() => a + 77, console.log);

// of course
return (
  <p>{a}</p> // for jsx bindings
);

class {
  ^a = 10; // As usually property using
}

low: sub for signal incorrect typings

export const cardListChangeEffectStart = signal<Direction>();
export const cardListChangeEffectFinished = signal();

export const cardListChangeEffectInProgress = signal(false);

cardListChangeEffectInProgress.sub(cardListChangeEffectStart, () => true); // ^~~~ error
cardListChangeEffectInProgress.sub(cardListChangeEffectFinished, () => false); // ^~~~ error

Migrate from reactive-box syntax to more oop

Remove basic API functions:

  • expr
  • sel
  • box

New API functions:

const a = value(0);
a.val += 10;
console.log(a.val);
a(11) // change value to 11
const s = selector(() => a.val + 10);
console.log(s.val);

Remove action

New API signal

const i = signal(10);
console.log(i.val);
i(10) // call signal

low proposal: add automatic unsubscribe for previous time creations in all reaction places

class A {
  @prop m;
  constructor() {
    k_signal.watch(k => this.m = k);
  }
}

k_signal.view(k => new A());

let t;
(cycle and loop)(() => {
  (await )k_signal;
  t = new A();
});

Automatic unsubscribe k_signal.watch... When? At the next iteration, but

loop(() => {
  if (await k_signal) {
    t = new A();
  }
});

In that case, automatic destroy should be not on the next iteration.

very low: external package realar-rehook

realar-rehook
or
realar-rehooks

This module provides the possibility for reusing standard and custom React hooks. One function for creating a container with use-between hooks implementation inside.

import { useState, useEffect } from "react";
import { value, useLocal} from "realar";
import { rehook } from "realar-rehook';

class Logic {
  a = value("");

  b = rehook(() => {
    const [name, setName] = useState("");

    // And possible to realar logic here
    useEffect(() => (
      this.a.watch(setName)
    ), []);
    useEffect(() => this.a.set(name), [name]);
    // The useEffect should be run synchronously because we don't have mount/unmount component phase

    return {
      name,
      setName
    }
  });

  constructor() {
    this.b.val.setName("Joe");
  }
}

const Comp = () => {
  const logic = useLocal(Logic);
  return (
    <p>{logic.b.val.name} - {logic.a.val}</p>
  )
}

And I can implement React compatibility mode

const hooks = rehook.compat(() => {
  console.log(1);
  useEffect(() => console.log(3), []);
  console.log(2);

  // And support the useLayoutEffect as alias for useEffect
}); 

Add loop function

loop(async () => {
  await Promise.all([signal1, signal2]);
  signal3();
});

// [] And yield support in future

low: remove loop

The loop will be removed because serial of async operation not compatible with serial of sync operations.

API

API

  • useService
  • useUnit
  • service
  • unit
  • collection
  • instance
  • Zone
  • sync
  • watch
  • on
  • action

low: make an article about the powerful data flow

import React from "react";
import { value, useLocal, useValue } from "realar";

const Logic = () => {
  const name = value("Joe");
  const inputHandler = name.pre((ev: React.ChangeEvent<HTMLInputElement>) => (
    ev.currentTarget.value
  ));

  return {
    name,
    inputHandler
  }
};


const Form = () => {
  const logic = useLocal(Logic);
  const name = useValue(logic.name);
  
  return (
    <>
      <h1>Hello {name}!</h1>
      <p>
        Name: 
        <input 
          type="text" 
          value={name}
          onChange={logic.inputHandler} />
      </p>
    </>
  );
};

const App = () => (
  <Form />
);

export default App;

Try on CodeSandbox

low: stop method to stoppable result

Add stop method to stop signal. (and export stop signal from library optional)

stoppable().stop();
// Or may be
stoppable.stop(); // Think about that syntax as a shortcut for the previous one

No reading zone in logic factories and constructors

class CounterLogic {
  @prop value = 0;
  inc = () => (this.value += 1);

  constructor() {
    console.log(this.value);
  }
}

let n = 0;

const Counter = observe(() => {
  const { inc } = useLocal(CounterLogic);

  console.log("Render", n++);

  return <button onClick={inc}>+</button>;
});

export const App = () => <Counter />;

Edit on CodeSandbox

The component was subscribed to value and reacted the first time on It change, but not used inside.

low: flow method

// v: signal|value|ready|etc...

v.flow((dest, val, prev?) => val >= 0 && dest(val)); // That case is new one.

// That necessary for creating a new entity (signal, value, ready, etc...), from another entity

normal: Ensurable watch typings bug

const swipeOffset = value(0);
const local_offset = value(0);
const stop = signal.stop();

const k = swipeOffset.flow.filter(() => !stop.val);

k.watch(local_offset);

Argument of type 'Value<number, number>' is not assignable to parameter of type '(value: Ensurable<number>, prev: Ensurable<number>) => void'.
  Types of parameters 'data' and 'value' are incompatible.
    Type 'Ensurable<number>' is not assignable to type 'number'.
      Type 'void' is not assignable to type 'number'

low: think about "in one transaction" feature

const s = signal();
const v = value();

on(s, (s_v) => v.val += s_v);
on(() => [s,v], console.log);


on.transaction(s, (s_v) => v.val += s_v); // used one transaction with changed s

// Think about

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.