ecamp / hal-json-vuex

Use a HAL JSON API with a Vuex store.
MIT License
8 stars 3 forks source link

Vue3 and future of hal-json-vuex #277

Open usu opened 2 years ago

usu commented 2 years ago

Knowing that we don't want to rewrite hal-json-vuex: I still found some spare time to get a bit more familiar with Vue3 and its reactivity system. Instead of just following a random tutorial, I decided to code a simple HAL json consumer. This issues is mainly meant as some inspiration for a 3am brainstorming during the next hackathon and not as a serious code proposal.

Disclaimer: The demo is really simple. No special cases or edge cases are implemented. No proxies to not throw more complexity at it already at the beginning. No updating of resources. No reloading of all already loaded entities.

How to run the demo:

npm install
npm run dev

Variant 1

Available at master branch: https://github.com/usu/hal-json-vue-demo/tree/master

Relevant code in:

Before coding anything reactive, I implemented a async promise-based consumer (main commit at https://github.com/usu/hal-json-vue-demo/commit/c2478f784c67848227b7247653d7ff7a22a62cbb). This was by purpose to find out the bare minimum necessary for a consumer which doesn't need any reactive data (e.g. print-nuxt or print-react). Pretty straight-forward and clean. More magic could be added with proxies, but does the job for simple use-cases.

Secondly, I implemented a separate module which returns reactive resources (mainly the next 4 commits, probably easier to look at the final code in the master branch). The idea was to keep the 2 modules completely separated, with the idea that this could theoretically even be 2 different npm packages.

A problem I immediately run into, was that api responses can have side effects beyond the requested resource (e.g. I request/load a specific resource and receive data for 10 additional embedded resources). Hence, a way is needed to signal the reactive objects when data in a resource changes. I implemented this with tracking a small dependency-map and with emitting events from the async module, whenever new data for a resource is received.

Nothing spectacular and works well. Plus keeps the 2 modules well separated and minimum dependencies needed for the async module. However, this is for an easy base case only (updating data in a resource). More complexity would probably come with updating relations (=changing a link from one entity to another) or changing collections. My gut feeling says that this is a bit of a rabbit hole and we would end up implementing a lot of functionality that others have already solved better (=reactivity system).

Variant 2

So variant 2 was born: Insteading of using an own dependency-tracking & event-system, this is using the Vue reactivity system to signal updates from the async module to the reactive module.

Visible at vue-reactivity branch: https://github.com/usu/hal-json-vue-demo/tree/vue-reactivity Main change in this commit https://github.com/usu/hal-json-vue-demo/commit/b2224412ef55070d6662a76bda2ecbd8d9f84626 (basically removing the manual dependency tracking and making objects already reactive in the async module)

Additional plus: Further features felt natural and straight-forward (would have struggled a bit how to tackle them with variant 1), e.g.:

usu commented 2 years ago

Variant 3: relation loading

https://github.com/usu/hal-json-vue-demo/blob/vue-reactivity-resource-loader/src/api/reactive.js https://github.com/usu/hal-json-vue-demo/blob/vue-reactivity-resource-loader/src/components/ReactiveTest.vue

Allows loading deeply nested relations, e.g. entity1.getRelation("parent").getRelation("nonEmbeddedRelation");

Still so far without proxies and only bare minimum. No collections.

One caveat: changing relation links would make it necessary to update all already requested subpaths. E.g. when entity1.getRelation("parent") points to a new parent, then entity1.getRelation("parent").getRelation("nonEmbeddedRelation") will probably also point to a new relation (or might not even exist anymore as a relation on the new parent).

This is not yet working in this demo.

usu commented 2 years ago

Some good packages to collect some inspiration (e.g. on internals, on the module's API, on the test cases):

carlobeltrame commented 1 year ago

Another place to collect inspiration from is React Query, or the Vue version Vue Query. I hear this blog post series is quite good at explaining the use cases and pitfalls: https://tkdodo.eu/blog/practical-react-query This library can be used to handle the loading / reloading / caching, while being very open about how the data is actually fetched (axios, localstorage, anything we want). It was described to me as a similar thing to swr, but with more features.