{"id":19125195,"url":"https://github.com/crimx/value-enhancer","last_synced_at":"2025-05-08T21:21:51.670Z","repository":{"id":41112338,"uuid":"430471616","full_name":"crimx/value-enhancer","owner":"crimx","description":"Enhance value with plain and explicit reactive wrapper. Think of it as hook-style signals.","archived":false,"fork":false,"pushed_at":"2024-12-31T16:20:49.000Z","size":1988,"stargazers_count":26,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-02T03:54:53.316Z","etag":null,"topics":["data-flow","frp","functional-reactive-programming","model","observable","react","reactive"],"latest_commit_sha":null,"homepage":"https://value-enhancer.js.org/v5/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/crimx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-21T20:23:07.000Z","updated_at":"2025-01-22T15:15:48.000Z","dependencies_parsed_at":"2023-12-21T06:23:36.052Z","dependency_job_id":"fa016ab5-e33f-4c8c-ad4b-0bfc68bbd5fd","html_url":"https://github.com/crimx/value-enhancer","commit_stats":{"total_commits":347,"total_committers":2,"mean_commits":173.5,"dds":0.008645533141210415,"last_synced_commit":"6c035efeb78d6c9d5dae3494b2c75f3e3d87f691"},"previous_names":[],"tags_count":78,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crimx%2Fvalue-enhancer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crimx%2Fvalue-enhancer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crimx%2Fvalue-enhancer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crimx%2Fvalue-enhancer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/crimx","download_url":"https://codeload.github.com/crimx/value-enhancer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238044093,"owners_count":19407128,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-flow","frp","functional-reactive-programming","model","observable","react","reactive"],"created_at":"2024-11-09T05:34:58.118Z","updated_at":"2025-05-08T21:21:51.655Z","avatar_url":"https://github.com/crimx.png","language":"TypeScript","readme":"# [value-enhancer](https://github.com/crimx/value-enhancer)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"200\" src=\"https://raw.githubusercontent.com/crimx/value-enhancer/main/assets/value-enhancer.svg\"\u003e\n\u003c/p\u003e\n\n[![Build Status](https://img.shields.io/github/actions/workflow/status/crimx/value-enhancer/build.yml)](https://github.com/crimx/value-enhancer/actions/workflows/build.yml)\n[![npm-version](https://img.shields.io/npm/v/value-enhancer.svg)](https://www.npmjs.com/package/value-enhancer)\n[![Coverage Status](https://img.shields.io/coveralls/github/crimx/value-enhancer/main)](https://coveralls.io/github/crimx/value-enhancer?branch=main)\n[![tree-shakable](https://img.shields.io/badge/%20tree-shakable-success)](https://bundlejs.com/?q=value-enhancer)\n[![no-dependencies](https://img.shields.io/badge/dependencies-none-success)](https://bundlejs.com/?q=value-enhancer)\n[![side-effect-free](https://img.shields.io/badge/%20side--effect-free-success)](https://bundlejs.com/?q=value-enhancer)\n\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?maxAge=2592000)](http://commitizen.github.io/cz-cli/)\n[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-brightgreen.svg?maxAge=2592000)](https://conventionalcommits.org)\n[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)\n\nEnhance value with plain and explicit reactive wrapper. Think of it as hook-style signals.\n\nDocs: \u003chttps://value-enhancer.js.org\u003e\n\n## Install\n\n```bash\nnpm add value-enhancer\n```\n\n## Features\n\n- Plain reactivity.  \n  Without the implicit-cast Proxy magic like Vue Reactivity and MobX.\n- Single-layer shallow reactivity.  \n  It does not convert the value with `Object.defineProperty` nor `Proxy`. Keeping everything as plain JavaScript value makes it easier to work with other libraries and easier for the JavaScript engine to optimize.\n- Safe and fast lazy computation.  \n  It solves multi-level derivation issue (like in [Svelte Stores](\u003c(https://svelte.dev/repl/6218ae0ecf5c455195b4a76d7f0cff9f?version=3.49.0)\u003e)) with smart lazy value evaluation.\n- Weak side effects.  \n  `Val`s are managed with `FinalizationRegistry` and `WeakRef` which means you can create and access derived `Val.value` without worrying about memory leaks. Disposers returned by subscriptions can also be easily managed with libraries like [`@wopjs/disposable`](https://github.com/wopjs/disposable).\n- Explicit.  \n  Reactive objects are easy to tell since their types are different from normal objects. Subscription also requires explicit dependency declaration which reduce the work of repetitive dynamic dependency collection in Proxy/Signal implementations.\n- Simple DX.  \n  Designed with ergonomics in mind. No hidden rules for getting or setting values. What you see is what you get.\n\n## Sizes\n\n\u003c!-- size-section-start --\u003e\n\n| import                        | size(brotli) |\n| ----------------------------- | ------------ |\n| `*`                           | 1.85 kB      |\n| `{ readonlyVal, val }` (core) | 1.05 kB      |\n| `{ from }`                    | 26 B         |\n| `{ derive }`                  | 93 B         |\n| `{ combine }`                 | 204 B        |\n| `{ compute }`                 | 270 B        |\n| `{ flattenFrom }`             | 227 B        |\n| `{ flatten }`                 | 36 B         |\n| `{ reactiveMap }`             | 489 B        |\n| `{ reactiveSet }`             | 359 B        |\n| `{ reactiveList }`            | 528 B        |\n\n\u003c!-- size-section-end --\u003e\n\n## Quick Q\u0026A\n\n\u003cdetails\u003e\n\u003csummary\u003eWhy not MobX?\u003c/summary\u003e\n\nMobX is cleverly designed to make properties magically reactive. But after using it in many of our large projects people started to complain about this implicit behavior. It is hard to tell if a property is reactive unless enforcing some kind of code style rules. Rules of MobX are easy to be broken especially for new team members.\n\nMobX does not work well with other libraries. It could break other libraries if you forget to exclude third-party instances from making observable. `toJS` is also needed if data is passed to other libraries.\n\nMobX also prints error when it sees another version of MobX in the global. It is not a good choice for making SDK or library that will be delivered into customer's environment.\n\nIn my opinion, the vision of MobX has to be implemented as language-level features, otherwise it will create all kinds of compatibility issues. Svelte, SolidJS and even Vue are heading towards the compiler direction now, looking forward to the next generation of MobX.\n\nIn `value-enhancer` reactive Vals and plain JavaScript values are easy to distinguish since they have different types. The values of reactive Vals are still plain JavaScript values so it works fine with other libraries. It is small and does not have global variable issues.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eWhy not Vue Reactivity?\u003c/summary\u003e\n\nVue3 brings Reactivity as standalone APIs. It is beautifully designed and I had learned a lot from its source code.\n\nBut even though it is made standalone, it is still very Vue centered. Many extra works related to Vue Components are added under the hood.\n\nVue supports lazy deep reactive conversion. It converts plain JavaScript values into reactive values which means it also suffers from the same issues of MobX.\n\nIt is a good choice if you are choosing the Vue ecosystem. The implementation of `value-enhancer` absorbs many optimization strategies from Vue Reactivity while staying framework agnostic.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eWhy not RxJS?\u003c/summary\u003e\n\nI love RxJS and the reactive paradigm behind it. But the goal of RxJS is to compose asynchronous or callback-based code. It is not optimized for state management.\n\nIt also requires you to write code in a pipe-able way which may not be acceptable for everyone.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eWhat about React Hooks?\u003c/summary\u003e\n\nThe signature of `combine` and `derive` in `value-enhancer` may look familiar to those who have used React hooks.\n\n```ts\nimport { useMemo } from \"react\";\n\nconst derived = useMemo(() =\u003e source + 1, [source]);\n```\n\nI really like the explicit dependency declaration, but in React it is error-prone since people keep forgetting adding or removing dependencies. The React team even made a `exhaustive-deps` linter rule for this.\n\n`value-enhancer` solves this by absorbing the RxJS-style callbacks.\n\n```ts\nimport { val, derive, combine } from \"value-enhancer\";\n\nconst source$ = val(1);\nconsole.log(source$.value); // 1\n\nconst derived$ = derive(source$, source =\u003e source + 1);\nconsole.log(derived$.value); // 2\n\nconst combined$ = combine(\n  [source$, derived$],\n  ([source, derived]) =\u003e source + derived\n);\nconsole.log(combined$.value); // 3\n```\n\nSince the type of reactive objects are different from its values, it is hard to have mismatched dependencies inside the `transform` function.\n\n`value-enhancer` can be used in React with a super-simple hook [`use-value-enhancer`](https://www.npmjs.com/package/use-value-enhancer).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eSvelte Stores?\u003c/summary\u003e\n\nSvelte offers excellent support for Observables. Svelte store is one of the simplest implementations. The code is really neat and clean.\n\nSvelte store works well for simple cases but it also leaves some edge cases unresolved. For example, when `derived` a list of stores, the transform function could be [invoked with intermediate states](https://svelte.dev/repl/6218ae0ecf5c455195b4a76d7f0cff9f?version=3.49.0).\n\nSvelte also adds a `$xxx` syntax for subscribing Observables as values. The compiled code is really simple and straightforward.\n\n`value-enhancer` is compatible with Svelte Store contract. It can be used in Svelte just like Svelte stores.\n\n`value-enhancer` also fixes the edge cases of Svelte stores by leveraging Vue's layered subscriber design.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eSolidJS?\u003c/summary\u003e\n\nSolidJS \"create\"s are like React hooks but with saner signatures. It is also thoughtfully optimized for edge cases.\n\nA thing that one may feel odd in SolidJS is accessing reactive value by calling it as function. `value-enhancer` keeps the `xxx.value` way to access reactive value which I think should be more intuitive.\n\nIt also suffers from implicit magic issues like MobX and Vue where you ended up using something like [`mergeProps`](https://www.solidjs.com/docs/latest/api#mergeprops) and [`splitProps`](https://www.solidjs.com/docs/latest/api#splitprops).\n\n`value-enhancer` is compatible with SolidJS using [`from`](https://www.solidjs.com/docs/latest/api#from).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ePreact Signals?\u003c/summary\u003e\n\nPreact recently released [Signals](https://preactjs.com/blog/introducing-signals/) which shares similar ideas with `value-enhancer`. It is like signals of SolidJS but without the odd function-like value accessing. It flushes reactions top-down then bottom-up like Vue and `value-enhancer`.\n\nThe Preact team also took a step further to support writing Signals directly within TSX. This offers Svelte-like neat coding experience.\n\n```tsx\nconst count = signal(0);\n\n// Instead of this:\n\u003cp\u003eValue: {count.value}\u003c/p\u003e\n\n// … we can pass the signal directly into JSX:\n\u003cp\u003eValue: {count}\u003c/p\u003e\n\n// … or even passing them as DOM properties:\n\u003cinput value={count} /\u003e\n```\n\nBut it also uses Vue-like magic to collect effects.\n\n```tsx\nconst counter = signal(0);\n\neffect(() =\u003e {\n  console.log(counter.value);\n});\n```\n\nIt might seem clean at first but it's not a self-consistent solution either. You'll probably meet weird issues and find workarounds like [`signal.peek()`](https://github.com/preactjs/signals#signalpeek) which is error-prone.\n\n```tsx\nconst counter = signal(0);\nconst effectCount = signal(0);\n\neffect(() =\u003e {\n  console.log(counter.value);\n\n  // Whenever this effect is triggered, increase `effectCount`.\n  // But we don't want this signal to react to `effectCount`\n  effectCount.value = effectCount.peek() + 1;\n});\n```\n\nThis issue does not exist in `value-enhancer` because we do not collect dependencies implicitly by default.\n\nIn case of subscribing to flexible dynamic dependencies are needed, `value-enhancer` does offer a simple `compute` API which is similar to Jotai.\n\n```ts\nimport { val, compute } from \"value-enhancer\";\n\nconst count$ = val(0);\nconst a$ = val(\"a\");\nconst b$ = val(\"b\");\n\nconst s$ = compute(get =\u003e {\n  return get(count$) % 2 === 0 ? get(a$) : get(b$);\n});\n```\n\n\u003c/details\u003e\n\n## Devtools\n\n\u003cdetails\u003e\n\u003csummary\u003eCustom Formatter\u003c/summary\u003e\n\n`value-enhancer` in development mode supports DevTools [custom formatters](https://www.mattzeunert.com/2016/02/19/custom-chrome-devtools-object-formatters.html). You may enable it by checking the \"Enable custom formatters\" option in the \"Console\" section of DevTools general settings.\n\n\u003c/details\u003e\n\n## Usage\n\n## Create Writable Val\n\n```js\nimport { val } from \"value-enhancer\";\n\nconst count$ = val(2);\n\nconsole.log(count$.value); // 2\n\ncount$.set(3);\nconsole.log(count$.value); // 3\n\ncount$.value = 4;\nconsole.log(count$.value); // 4\n```\n\n## Create Readonly Val\n\n```js\nimport { readonlyVal } from \"value-enhancer\";\n\nconst [count$, setCount] = readonlyVal(2);\n\nconsole.log(count$.value); // 2\n\nsetCount(3);\nconsole.log(count$.value); // 3\n\ncount$.value = 4;\nconsole.log(count$.value); // 4\n```\n\n## Subscribe to value changes\n\n```js\nimport { val, combine, derive, nextTick } from \"value-enhancer\";\n\nconst count$ = val(3);\n\n// Emit the current value synchronously, then emit the new value when it changes.\nconst disposeSubscribe = count$.subscribe(count =\u003e {\n  console.log(`subscribe: ${count}`);\n}); // printed \"subscribe: 3\"\n\n// Only emit the new value when it changes.\nconst disposeReaction = count$.reaction(count =\u003e {\n  console.log(`reaction: ${count}`);\n}); // (nothing printed)\n\n// `Object.is` equality check by default\ncount$.set(3); // nothing happened\n\n// subscription triggered asynchronously by default\ncount$.set(4); // nothing happened\n\nawait nextTick(); // subscription triggered asynchronously by default\n\n// printed \"subscribe: 4\"\n// printed \"reaction: 4\"\n\ndisposeSubscribe();\ndisposeReaction();\n```\n\n## Derive Val\n\n`derive` a new Val from another Val.\n\n```js\nimport { val, derive } from \"value-enhancer\";\n\nconst count$ = val(2);\n\nconst derived$ = derive(count$, count =\u003e count * 3);\n\nconsole.log(derived$.value); // 6\n```\n\nPipe-able style with functional lib like `rubico`: [(CodeSandbox)](https://codesandbox.io/p/sandbox/value-enhancer-derive-functional-with-rubico-t7d4gm?file=%2Fsrc%2Findex.ts%3A15%2C1)\n\n```ts\nimport { derive, val } from \"value-enhancer\";\nimport { pipe, map, filter } from \"rubico\";\n\nconst isOdd = (number: number) =\u003e number % 2 == 1;\n\nconst square = (number: number) =\u003e number ** 2;\n\nconst numbers$ = val([1, 2, 3, 4, 5]);\n\nconst derived$ = derive(numbers$, pipe([filter(isOdd), map(square)]));\n\nconsole.log(derived$.value); // [1, 9, 25]\n\nderived$.reaction(numbers =\u003e {\n  console.log(\"reaction\", numbers);\n});\n\nnumbers$.set([6, 7, 8, 9, 10]);\n\n// `reaction [49, 81]`\n```\n\n## Combine Val\n\n`combine` multiple Vals into a new Val.\n\n```js\nimport { val, derive, combine } from \"value-enhancer\";\n\nconst count$ = val(2);\n\nconst derived$ = derive(count$, count =\u003e count * 3);\n\nconst combined$ = combine(\n  [count$, derived$],\n  ([count, derived]) =\u003e count + derived\n);\n\nconsole.log(combined$.value); // 8\n```\n\n## Flatten Val\n\n`flatten` the inner Val from a Val of Val. This is useful for subscribing to a dynamic Val that is inside another Val.\n\n```js\nimport { val, flatten } from \"value-enhancer\";\n\nconst itemList$ = val([val(1), val(2), val(3)]);\n\nconst firstItem$ = flatten(itemList$, itemList =\u003e itemList[0]);\n\nconsole.log(firstItem$.value); // 1\n\nitemList$.set([val(4), val(5), val(6)]);\n\nconsole.log(firstItem$.value); // 4\n```\n\n## Compute\n\n`compute` is useful for subscribing to flexible dynamic dependencies.\n\nThe `get` function passed to the effect callback can be used to get the current value of a Val and subscribe to it.\nThe effect callback will be re-evaluated whenever the dependencies change.\nStale dependencies are unsubscribed automatically.\n\n```js\nimport { val, compute } from \"value-enhancer\";\n\nconst count$ = val(0);\nconst a$ = val(\"a\");\nconst b$ = val(\"b\");\n\nconst s$ = compute(get =\u003e {\n  return get(count$) % 2 === 0 ? get(a$) : get(b$);\n});\n```\n\n## From\n\n`from` creates a Val from any value source. Both `derive` and `combine` are implemented using `from` under the hood.\n\n```ts\nimport { from } from \"value-enhancer\";\n\nconst prefersDark = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\nconst isDarkMode$ = from(\n  () =\u003e prefersDark.matches,\n  notify =\u003e {\n    prefersDark.addEventListener(\"change\", notify);\n    return () =\u003e prefersDark.removeEventListener(\"change\", notify);\n  }\n);\n```\n\n## FlattenFrom\n\n`flattenFrom` creates a Val from any value source like `from` but also flatten the value if the value is a Val. `flatten` is implemented using `flattenFrom`.\n\n## Custom Equal\n\nBy default, `Object.is` equality check is used to determine whether a value has changed. You can customize the equality check by passing a `equal` function.\n\n```js\nimport { val } from \"value-enhancer\";\n\nconst isSameXYPosition = (p1, p2) =\u003e p1.x === p2.x \u0026\u0026 p1.y === p2.y;\nconst isSameXYZPosition = (p1, p2) =\u003e\n  p1.x === p2.x \u0026\u0026 p1.y === p2.y \u0026\u0026 p1.z === p2.z;\n\nconst xyzPosition$ = val({ x: 0, y: 0, z: 0 }, { equal: isSameXYZPosition });\nconst xyPosition$ = derive(xyzPosition$, ({ x, y }) =\u003e ({ x, y }), {\n  equal: isSameXYPosition,\n});\n\nxyPosition$.set({ x: 0, y: 0, z: 0 }); // nothing happened\n```\n\n## Synchronous subscription\n\nSubscription is triggered asynchronously on next tick by default. To trigger synchronously, set `eager` parameter to `true`.\n\n```js\ncount$.subscribe(count =\u003e console.log(`subscribe: ${count}`), true);\ncount$.reaction(count =\u003e console.log(`reaction: ${count}`), true);\n```\n\nOr set `eager` to `true` when creating the Val.\n\n```js\n// subscription of count$ is trigger synchronously by default\nconst count$ = val(3, { eager: true });\n\nconst derived$ = derive(count$, count =\u003e count * 3, { eager: true });\n```\n\n## Reactive Collections\n\nThe Reactive Collections are a group of classes that expand on the built-in JavaScript collections, allowing changes to the collections to be observed. See [docs](https://value-enhancer.js.org/modules/collections.html) for API details.\n\n```ts\nimport { derive } from \"value-enhancer\";\nimport { reactiveList } from \"value-enhancer/collections\";\n\nconst list = reactiveList([\"a\", \"b\", \"c\"]);\n\nconst item$ = derive(list.$, list =\u003e list[2]); // watch the item at index 2\n\nconsole.log(item$.value); // \"c\"\n\nlist.set(2, \"d\");\n\nconsole.log(item$.value); // \"d\"\n```\n\n```ts\nimport { val, flatten } from \"value-enhancer\";\nimport { reactiveMap } from \"value-enhancer/collections\";\n\nconst map = reactiveMap();\nconst v = val(\"someValue\");\n\nconst item$ = flatten(map.$, map =\u003e map.get(\"someKey\")); // watch the item at \"someKey\"\n\nconsole.log(item$.value); // undefined\n\nmap.set(\"someKey\", v); // set a val, the value inside the val is subscribed and flatten to `item$`\n\nconsole.log(item$.value); // \"someValue\"\n\nv.set(\"someValue2\"); // you can also set a non-val value, which is passed to `item$`` directly\n\nconsole.log(item$.value); // \"someValue2\"\n```\n\n## Patterns\n\n### Use in Class with different types.\n\nWith this pattern, Writable `Val` properties are exposed as `$$` and `ReadonlyVal` properties are exposed as `$`.\n\nNote that they are all Writable `Val` under the hood. The difference is just the type.\n\n```ts\nimport { val, type ReadonlyVal, type Val } from \"value-enhancer\";\n\ninterface MyClassVals {\n  a: number;\n  b: string;\n}\n\nexport type MyClass$ = {\n  [K in keyof MyClassVals]: ReadonlyVal\u003cMyClassVals[K]\u003e;\n};\n\nexport type MyClass$$ = {\n  [K in keyof MyClassVals]: Val\u003cMyClassVals[K]\u003e;\n};\n\nexport class MyClass {\n  public readonly $: MyClass$;\n  public readonly $$: MyClass$$;\n\n  public constructor() {\n    this.$ = this.$$ = {\n      a: val(1),\n      b: val(\"2\"),\n    };\n  }\n}\n\nconst myClass = new MyClass();\nconsole.log(myClass.$.a.value);\nmyClass.$$.a.set(3);\n```\n\n### Use in Class with ReadonlyVal and setter\n\nIf you want to ensure maximum safety and prevent others from modifying the value accidentally, you can use a real `ReadonlyVal`.\n\n```ts\nimport {\n  readonlyVal,\n  type ReadonlyVal,\n  type ValSetValue,\n} from \"value-enhancer\";\n\ninterface MyClassVals {\n  a: number;\n  b: string;\n}\n\nexport type MyClass$ = {\n  [K in keyof MyClassVals]: ReadonlyVal\u003cMyClassVals[K]\u003e;\n};\n\nexport type MyClassSet$ = {\n  [K in keyof MyClassVals]: ValSetValue\u003cMyClassVals[K]\u003e;\n};\n\nexport class MyClass {\n  public readonly $: MyClass$;\n  public readonly set$: MyClassSet$;\n\n  public constructor() {\n    const [a$, setA] = readonlyVal(1);\n    const [b$, setB] = readonlyVal(\"2\");\n    this.$ = { a: a$, b: b$ };\n    this.set$ = { a: setA, b: setB };\n  }\n}\n\nconst myClass = new MyClass();\nconsole.log(myClass.$.a.value);\nmyClass.set$.a(3);\n```\n\n### Use in Class with GroupVals\n\nWriting all these ReadonlyVals and setters could be cumbersome. With `groupVals` you can easily create a group of ReadonlyVals and setters.\n\n```ts\nimport {\n  type ReadonlyVal,\n  type ValSetValue,\n  type FlattenVal,\n  readonlyVal,\n  groupVals,\n} from \"value-enhancer\";\n\nexport interface Foo$ {\n  a: ReadonlyVal\u003cnumber\u003e;\n  b: ReadonlyVal\u003cnumber\u003e;\n  c: ReadonlyVal\u003cstring\u003e;\n}\n\nexport class Foo {\n  public readonly $: Foo$;\n  private readonly set$: { [K in keyof Foo$]: ValSetValue\u003cUnwrapVal\u003cFoo$[K]\u003e\u003e };\n\n  public constructor() {\n    const [vals, set$] = groupVals({\n      a: readonlyVal(1),\n      b: readonlyVal(2),\n      c: readonlyVal(\"3\"),\n    });\n    this.$ = vals;\n    this.set$ = set$;\n  }\n\n  public myMethod() {\n    this.set$.a(2);\n    this.set$.c(\"4\");\n  }\n}\n\nconst foo = new Foo();\nconsole.log(foo.$.a.value); // 1\n\nfoo.myMethod();\nconsole.log(foo.$.a.value); // 2\n```\n\n### Sharing vals to other Classes\n\nSharing vals to other classes directly should be careful. Other classes may `dispose` the vals and cause unexpected behavior.\n\nTo share ReadonlyVals to other classes, use `.ref()` to create a ref ReadonlyVal. It is just like `derive` a val without `transform`. It is simpler hence more efficient.\n\n```ts\nimport { val, type ReadonlyVal } from \"value-enhancer\";\n\ninterface AProps {\n  a$: ReadonlyVal\u003cnumber\u003e;\n}\n\nclass A {\n  a$: ReadonlyVal\u003cnumber\u003e;\n\n  constructor(props: AProps) {\n    this.a$ = props.a$;\n  }\n\n  dispose() {\n    this.a$.dispose();\n  }\n}\n\nconst a$ = val(1);\nconst a = new A({ a$: a$.ref() });\na.dispose(); // will not affect a$\n```\n\nTo share writable vals to other classes, use a `.ref(true)`.\n\nIt creates a new Val referencing the value of the current Val as source.\nAll ref Vals share the same value from the source Val.\nThe act of setting a value on the ref Val is essentially setting the value to the source Val.\n\nThe ref Vals can be safely disposed without affecting the source Val and other ref Vals.\n\n```ts\nimport { val, type Val } from \"value-enhancer\";\n\ninterface AProps {\n  a$: Val\u003cnumber\u003e;\n}\n\nclass A {\n  a$: Val\u003cnumber\u003e;\n\n  constructor(props: AProps) {\n    this.a$ = props.a$;\n  }\n\n  dispose() {\n    this.a$.dispose();\n  }\n}\n\nconst a$ = val(1);\nconst a1 = new A({ a$: a$.ref(true) });\nconst a2 = new A({ a$: a$.ref(true) });\n\na2.a$.set(2);\n\nconsole.log(a$.value); // 2\nconsole.log(a1.a$.value); // 2\nconsole.log(a2.a$.value); // 2\n\na1.dispose(); // will not affect a$ and a2.a$\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrimx%2Fvalue-enhancer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrimx%2Fvalue-enhancer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrimx%2Fvalue-enhancer/lists"}