{"id":15204146,"url":"https://github.com/lume/variable","last_synced_at":"2025-10-02T21:30:40.141Z","repository":{"id":45316455,"uuid":"241844419","full_name":"lume/variable","owner":"lume","description":"DEPRECATED, use https://solidjs.com and https://github.com/lume/classy-solid directly instead. Create reactive variables and observe their changes in a simple and concise way with less code and less coupling.","archived":true,"fork":false,"pushed_at":"2023-11-22T03:15:16.000Z","size":271,"stargazers_count":13,"open_issues_count":0,"forks_count":1,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-09-03T18:42:25.548Z","etag":null,"topics":["3d","3d-graphics","custom-elements","event-handlers","html-elements","lume","reactive-computations","reactive-programming","reactive-variables","reactivity","threejs","web-components","webgl"],"latest_commit_sha":null,"homepage":"https://lume.io","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/lume.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-02-20T09:32:59.000Z","updated_at":"2023-12-09T01:42:21.000Z","dependencies_parsed_at":"2023-02-05T09:00:34.240Z","dependency_job_id":"4b8ba4b8-7d89-4fc0-9dca-2df7955ff9c3","html_url":"https://github.com/lume/variable","commit_stats":{"total_commits":141,"total_committers":3,"mean_commits":47.0,"dds":0.05673758865248224,"last_synced_commit":"103cfcdf70fa02d8099ed9dbf341440dc7eba4f3"},"previous_names":["infamous/variable"],"tags_count":43,"template":false,"template_full_name":null,"purl":"pkg:github/lume/variable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Fvariable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Fvariable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Fvariable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Fvariable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lume","download_url":"https://codeload.github.com/lume/variable/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Fvariable/sbom","scorecard":{"id":605075,"data":{"date":"2025-08-11","repo":{"name":"github.com/lume/variable","commit":"103cfcdf70fa02d8099ed9dbf341440dc7eba4f3"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.8,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"project is archived","details":["Warn: Repository is archived."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/lume/variable/tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/lume/variable/tests.yml/main?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/tests.yml:23","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-21T01:23:52.607Z","repository_id":45316455,"created_at":"2025-08-21T01:23:52.607Z","updated_at":"2025-08-21T01:23:52.607Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":277686071,"owners_count":25859652,"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","status":"online","status_checked_at":"2025-09-30T02:00:09.208Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["3d","3d-graphics","custom-elements","event-handlers","html-elements","lume","reactive-computations","reactive-programming","reactive-variables","reactivity","threejs","web-components","webgl"],"created_at":"2024-09-28T05:22:19.210Z","updated_at":"2025-10-02T21:30:39.868Z","avatar_url":"https://github.com/lume.png","language":"TypeScript","readme":"# DEPRECATED, use [solid-js](https://solidjs.com/) and [classy-solid](https://github.com/lume/classy-solid) directly instead\n\n# @lume/variable\n\nMake reactive variables and react to their changes.\n\n#### `npm install @lume/variable --save`\n\n## React to changes in reactive variables\n\nIn the following we make a reactive variable `count`, increment its value every\nsecond, and re-run a piece of code that logs the value to the console\non each change:\n\n```js\nimport {variable, autorun} from '@lume/variable'\n\nconst count = variable(0)\n\nsetInterval(() =\u003e count(count() + 1), 1000)\n\nautorun(() =\u003e {\n\t// Log the count variable any time it changes.\n\tconsole.log(count())\n})\n```\n\nThe function passed into `autorun` (sometimes referred to as \"an autorun\" or \"a\ncomputation\") automatically re-runs every second due to `count` being\nincremented every second.\n\nCalling `count()` gets the current value, while calling `count(123)` with an\narg sets the value. Thus `count(count() + 1)` increments the value.\n\nAny reactive variables used inside an autorun function are registered (or\n\"tracked\") as \"dependencies\" by `autorun`, then any time those dependencies\nchange, the autorun re-runs.\n\nWe call this \"dependency-tracking reactivity\".\n\nAn autorun with multiple variables accessed inside of it will re-run any time\nany of the accessed variables change:\n\n```js\nautorun(() =\u003e {\n\t// Log these variables every time any of them change.\n\tconsole.log(firstName(), lastName(), age(), hairColor())\n})\n```\n\n`autorun`s can be grouped in any way we like:\n\n```js\nautorun(() =\u003e {\n\t// This re-runs only when firstName or lastName have changed.\n\tconsole.log(firstName(), lastName())\n})\n\nautorun(() =\u003e {\n\t// This re-runs only when age or hairColor have changed.\n\tconsole.log(age(), hairColor())\n})\n```\n\nIf we wish to stop an `autorun` from re-running, we can call its returned stop\nfunction (note, this is not necessary if we or the JS engine no longer have\nreferences to any of the reactive variables that are dependencies of the\n`autorun`, and in that case everything will be garbage collected and will no\nlonger re-run):\n\n```js\nimport {variable, autorun} from '@lume/variable'\n\nconst count = variable(0)\n\nsetInterval(() =\u003e count(count() + 1), 1000)\n\nconst stop = autorun(() =\u003e {\n\t// Log the count variable any time it changes.\n\tconsole.log(count())\n})\n\n// Stop the autorun (and therefore no more logging will happen) after 5 seconds:\nsetTimeout(stop, 5000)\n```\n\n## Power and Simplicity\n\nLearn how dependency-tracking reactivity makes your code cleaner and more\nconcise compared to another more common pattern.\n\n\u003cdetails\u003e\u003csummary\u003eClick to expand.\u003c/summary\u003e\n\n\u003cbr /\u003e\n\nReactive computations (autoruns) are nice because it doesn't matter how we\ngroup our variables (dependencies) within computations. What matters is we\nwrite what we care about (expressions using our variables) without having\nto think about how to wire reactivity up.\n\nWith an event-based pattern, in contrast, our code would be more verbose and less\nconvenient.\n\nLooking back at our simple autorun for logging several variables,\n\n```js\nautorun(() =\u003e {\n\t// Log these variables every time any of them change.\n\tconsole.log(firstName(), lastName(), age(), hairColor())\n})\n```\n\nwe will see that writing the same thing with some sort of event pattern is more verbose:\n\n```js\nfunction log() {\n\t// Log these variables every time any of them change.\n\tconsole.log(firstName.value, lastName.value, age.value, hairColor.value)\n}\n\n// We need to also register an event handler for each value we care to react to:\nfirstName.on('change', log)\nlastName.on('change', log)\nage.on('change', log)\nhairColor.on('change', log)\n```\n\nWith this hypothetical event pattern, we had to share our logging function with\neach event emitter in order to wire up the reactivity, having us write more\ncode. Using `autorun` was simpler and less verbose.\n\nNow let's say we want to add one more item to the `console.log` statement.\n\nHere is what that looks like with an autorun:\n\n```js\nautorun(() =\u003e {\n\t// Log these variables every time any of them change.\n\tconsole.log(firstName(), lastName(), age(), hairColor(), favoriteFood())\n})\n```\n\nWith an event emitter pattern, there is more to do:\n\n```js\nfunction log() {\n\t// Log these variables every time any of them change.\n\tconsole.log(firstName.value, lastName.value, age.value, hairColor.value, favoriteFood.value)\n}\n\nfirstName.on('change', log)\nlastName.on('change', log)\nage.on('change', log)\nhairColor.on('change', log)\nfavoriteFood.on('change', log) // \u003c-------- Don't forget to add this line too!\n```\n\nNot only is the event pattern more verbose, but it is more error prone because\nwe can forget to register the event handler: we had to modify the code in two\nplaces in order to add logging of the `favoriteFood` value.\n\nHere's where it gets interesting!\n\nReactive computations allow us to decouple the reactivity implementation from\nplaces where we need reactivity, and to focus on the code we want to write.\n\nLet's say we want to make a class with properties, and abserve any of them when\nthey change.\n\nFirst, let's use a familiar event pattern to show the less-than-ideal scenario first:\n\n```js\n// Let's say this is in a lib called 'events'.\nclass EventEmitter {\n\taddEventHandler(eventName, fn) {\n\t\t/*...use imagination here...*/\n\t}\n\tremoveEventHandler(eventName, fn) {\n\t\t/*...use imagination here...*/\n\t}\n\temit(eventName, data) {\n\t\t/*...use imagination here...*/\n\t}\n}\n```\n\nNow let's use `EventEmitter` to make a class whose poperties we can observe the\nchanges of. In the following class, we'll make getter/setter pairs so that any\ntime a setter is used to set a value, it will emit a \"change\" event.\n\n```js\nimport {EventEmitter} from 'events'\n\n// We need to extend from EventEmitter (or compose it inside the class, but the amount\n// of code would be similar).\nclass Martian extends EventEmitter {\n\t_firstName = ''\n\tget firstName() {\n\t\treturn this._firstName\n\t}\n\tset firstName(v) {\n\t\tthis._firstName = v\n\t\tthis.emit('change', 'firstName') // Emit any time the property is set.\n\t}\n\n\t_lastName = ''\n\tget lastName() {\n\t\treturn this._lastName\n\t}\n\tset lastName(v) {\n\t\tthis._lastName = v\n\t\tthis.emit('change', 'lastName')\n\t}\n\n\t_age = 0\n\tget age() {\n\t\treturn this._age\n\t}\n\tset age(v) {\n\t\tthis._age = v\n\t\tthis.emit('change', 'age')\n\t}\n\n\t_hairColor = ''\n\tget hairColor() {\n\t\treturn this._hairColor\n\t}\n\tset hairColor(v) {\n\t\tthis._hairColor = v\n\t\tthis.emit('change', 'hairColor')\n\t}\n\n\t_favoriteFood = ''\n\tget favoriteFood() {\n\t\treturn this._favoriteFood\n\t}\n\tset favoriteFood(v) {\n\t\tthis._favoriteFood = v\n\t\tthis.emit('change', 'favoriteFood')\n\t}\n}\n\nconst martian = new Martian()\n```\n\nThe following shows how we would react to changes in three of the five properties of a `Martian`:\n\n```js\nmartian.addEventHandler('change', property =\u003e {\n\tif (['firstName', 'hairColor', 'favoriteFood'].includes(property)) {\n\t\t// Log these three variables every time any of the three change.\n\t\tconsole.log(martian.firstName, martian.hairColor, martian.favoriteFood)\n\t}\n})\n```\n\nIt works, but we can still make this better while still using the same event\npattern.\n\nLet's say we want to make it more efficient: instead of all event handlers\nbeing subscribed to a single `change` event (because Martians probably have\nlots and lots of properties) and filtering for the properties we care to\nobserve, we can choose specific event names for each property and subscribe\nhandlers to specific property events:\n\n```js\nimport {EventEmitter} from 'events'\n\nclass Martian extends EventEmitter {\n\t_firstName = ''\n\tget firstName() {\n\t\treturn this._firstName\n\t}\n\tset firstName(v) {\n\t\tthis._firstName = v\n\t\tthis.emit('change:firstName') // Emit a specific event for the firstName property.\n\t}\n\n\t_lastName = ''\n\tget lastName() {\n\t\treturn this._lastName\n\t}\n\tset lastName(v) {\n\t\tthis._lastName = v\n\t\tthis.emit('change:lastName') // Similar for the lastName property.\n\t}\n\n\t_age = 0\n\tget age() {\n\t\treturn this._age\n\t}\n\tset age(v) {\n\t\tthis._age = v\n\t\tthis.emit('change:age') // And so on.\n\t}\n\n\t_hairColor = ''\n\tget hairColor() {\n\t\treturn this._hairColor\n\t}\n\tset hairColor(v) {\n\t\tthis._hairColor = v\n\t\tthis.emit('change:hairColor')\n\t}\n\n\t_favoriteFood = ''\n\tget favoriteFood() {\n\t\treturn this._favoriteFood\n\t}\n\tset favoriteFood(v) {\n\t\tthis._favoriteFood = v\n\t\tthis.emit('change:favoriteFood')\n\t}\n}\n```\n\nWe can now avoid the overhead of the array filtering we previously had with the `.includes` check:\n\n```js\nconst martian = new Martian()\n\nconst onChange = () =\u003e {\n\t// Log these three variables every time any of the three change.\n\tconsole.log(martian.firstName, martian.hairColor, martian.favoriteFood)\n}\n\nmartian.addEventHandler('change:firstName', onChange)\nmartian.addEventHandler('change:hairColor', onChange)\nmartian.addEventHandler('change:favoriteFood', onChange)\n```\n\nThis is better than before because now if other properties besides the ones\nwe've subscribed to change, the event pattern won't be calling our function\nneedlessly and we won't be doing property name checks every time.\n\nWe can still do better with the event pattern! (Spoiler: it won't get as clean\nas with `autorun` below, which we'll get to next.)\n\nWe can come up with an automatic event-wiring mechanism. It could look\nsomething like the following:\n\n```js\nimport {EventEmitter, WithEventProps} from 'events'\n\n// Imagine `WithEventProps` wires up events for any properties specified in a\n// static `eventProps` field:\nconst Martian = WithEventProps(\n\tclass Martian extends EventEmitter {\n\t\tstatic eventProps = ['firstName', 'lastName', 'age', 'hairColor', 'favoriteFood']\n\n\t\tfirstName = ''\n\t\tlastName = ''\n\t\tage = 0\n\t\thairColor = ''\n\t\tfavoriteFood = ''\n\t},\n)\n\n// Listen to events as before:\n\nconst martian = new Martian()\n\nconst onChange = () =\u003e {\n\t// Log these three variables every time any of the three change.\n\tconsole.log(martian.firstName, martian.hairColor, martian.favoriteFood)\n}\n\nmartian.addEventHandler('change:firstName', onChange)\nmartian.addEventHandler('change:hairColor', onChange)\nmartian.addEventHandler('change:favoriteFood', onChange)\n```\n\nThat is a lot shorter already, but we can still do better! (It still won't be\nas simple as with dependency-tracking reactivity, which is coming up.)\n\nWe can make the event pattern more\n[DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) (\"Don't Repeat\nYourself\") using decorators to allow us to be less repetitive:\n\n```js\nimport {EventEmitter, emits} from 'events'\n\n// Imagine this `@emits` decorator wires up an event for each decorated property.\n@emits\nclass Martian extends EventEmitter {\n\t@emits firstName = ''\n\t@emits lastName = ''\n\t@emits age = 0\n\t@emits hairColor = ''\n\t@emits favoriteFood = ''\n}\n\n// Listen to events as before:\n\nconst martian = new Martian()\n\nconst onChange = () =\u003e {\n\t// Log these three variables every time any of the three change.\n\tconsole.log(martian.firstName, martian.hairColor, martian.favoriteFood)\n}\n\nmartian.addEventHandler('change:firstName', onChange)\nmartian.addEventHandler('change:hairColor', onChange)\nmartian.addEventHandler('change:favoriteFood', onChange)\n```\n\nThis is better than before because now we didn't have to repeat the property\nnames twice, reducing the chance of errors from mismatched names. Instead we\nlabeled them all with a decorator.\n\n---\n\nWe can still do better! 🤯\n\nWith LUME's reactive variables we can further decouple a class's implementation from\nthe reactivity mechanism and make things cleaner.\n\nWe can re-write the previous non-decorator example (and still not using\ndecorators) so that our class does not need to extend from a particular base\nclass to inherit a reactivity implementation:\n\n```js\nimport {variable, autorun} from '@lume/variable'\n\n// This class does not extend from any base class. Instead, reactive variables\n// are defined inside the class.\nclass Martian {\n\tfirstName = variable('')\n\tlastName = variable('')\n\tage = variable(0)\n\thairColor = variable('')\n\tfavoriteFood = variable('')\n}\n\nconst martian = new Martian()\n\nautorun(() =\u003e {\n\t// Log these three variables every time any of the three change.\n\tconsole.log(martian.firstName(), martian.hairColor(), martian.favoriteFood())\n})\n```\n\nThis is better than before because the reactivity is not an inherent part of\nour class hierarchy, instead being a feature of the reactive variables. We can\nuse this form of reactivity in our `Matrian` class or in any other class\nwithout having class inheritance requirements, and other developers do not have\nto make subclasses of our classes just to have reactivity.\n\nPlus, we did not need to subscribe an event listener to specific\nevents like we did earlier with the `addEventHandler` calls. Instead, we\nwrapped our function with `autorun` and it became a \"reactive computation\" with\nthe ability to re-run when its dependencies (the reactive variables used within\nit) change.\n\n...We can still do better! 🤯...\n\nUsing LUME's decorators, the experience is as good as it gets:\n\n```js\nimport {variable, autorun, reactive} from '@lume/variable'\n\n// Now we mark the class and properties as reactive with the `@reactive` decorator.\n@reactive\nclass Martian {\n\t@reactive firstName = ''\n\t@reactive lastName = ''\n\t@reactive age = 0\n\t@reactive hairColor = ''\n\t@reactive favoriteFood = ''\n\n\t// This property is not reactive, as it is not marked with `@reactive`.\n\tcryogenesis = false\n}\n\nconst martian = new Martian()\n\nautorun(() =\u003e {\n\t// Log these four variables every time any of the first three change. Note\n\t// that this will not automatically rerun when cryogenesis changes because cryogenesis\n\t// is not reactive.\n\tconsole.log(martian.firstName, martian.hairColor, martian.favoriteFood, martian.cryogenesis)\n})\n```\n\nThis is better than before because now we can use the properties like regular\nproperties instead of having to call them as functions to read their values\nlike we had to in the prior example. We can write `this.age` instead of\n`this.age()` for reading a value, and `this.age = 10` instead of `this.age(10)`\nfor writing a value.\n\nDependency-tracking reactivity makes things nice and concise.\n\n\u003c/details\u003e\n\n## API\n\n### `const myVar = variable(value)`\n\nCreates a reactive variable with an initial `value`. The return value is a function that\n\n- when called with no argument, returns the reactive variable's value, f.e. `myVar()`.\n- when called with an argument, sets the reactive variable's value, f.e. `myVar(newValue)`.\n\n```js\nconst foo = variable(false)\nconst bar = variable(123)\n```\n\n### `const stop = autorun(fn)`\n\nTakes a function `fn` and immediately runs it, while tracking any reactive\nvariables that were used inside of it as dependencies. Any time those variables\nchange, `fn` is executed again. Each time `fn` re-runs, dependencies are\nre-tracked, which means that conditional branching within `fn` can change which\ndependencies will re-run `fn` next time.\n\n`autorun` returns a `stop` function that when called causes `fn` never to be\nautomatically executed again. This is useful when you no longer care about some\nvariables.\n\n```js\nautorun(() =\u003e {\n\tif (foo()) doSomethingWith(bar())\n})\n```\n\n### `@reactive`\n\nA decorator that makes properties in a class reactive. Besides decorating\nproperties with the decorator, also be sure to decorate the class that shall\nhave reactive variable with the same decorator as well.\n\n```js\n@reactive\nclass Car {\n\t@reactive engineOn = false\n\t@reactive sound = 'vroom'\n}\n\nconst car = new Car()\n\nautorun(() =\u003e {\n\t// Any time the car turns on, it makes a sound.\n\tif (car.engineOn) console.log(car.sound)\n})\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flume%2Fvariable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flume%2Fvariable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flume%2Fvariable/lists"}