GithubHelp home page GithubHelp logo

Comments (12)

trusktr avatar trusktr commented on May 5, 2024

Oh wait, I missed the Signals doc. EDIT: nvm, it doesn't show using inline signals. Continuing reading...

from solid.

trusktr avatar trusktr commented on May 5, 2024

Interesting, in the Mutability doc, you basically said it yourself:

It really comes down to the fact that in most systems once you exit a certain boundary/domain you are better off/only able to pass data by messages rather than by reference. And at that point all the performance gained by mutability is essential lost.

from solid.

trusktr avatar trusktr commented on May 5, 2024

When to using Immutable State:

  • Places where frequent partial updates aren't expected

This one, I will have animations on various props of various elements at any given time, which means lots of frequent partial updates.

from solid.

trusktr avatar trusktr commented on May 5, 2024

Just finished reading the docs. It is awesome, good job on those! It'd be sweet to add the plain S.js signal usage to the Signals page. :)

Let me see if I can gather how to do it from reading ko.jsx and mobx-jsx.

from solid.

trusktr avatar trusktr commented on May 5, 2024

If I understood correctly, I need to make a file similar to https://github.com/ryansolid/mobx-jsx/blob/master/src/index.js, using S.js, and should be good to go.

Or does Solid already have it built in?

EDIT: looks like it is built in already, the line

export const r = createRuntime({wrap: S.makeComputationNode});

from solid.

trusktr avatar trusktr commented on May 5, 2024

The last question is, what does it look like?

This is a Surplus example:

const className = S.data("foo"),
      div = <div className={className()}/>;

Judging from the babel-plugin-jsx-dom-expressions doc for createRuntime, I would pass the data without calling it, like follows?

const className = S.data("foo"),
      div = <div className={className}/>;

and then the runtime made with createRuntime will call it instead? Or do I still need to call it?

from solid.

trusktr avatar trusktr commented on May 5, 2024

Yep, that's it. Just forked the solid-todomvc example and tested it, the parens are not required.

So to modify the TodoMVC header to show seconds since opening the app, it looks like this:

const seconds = S.value(0)
setInterval(() => seconds(seconds() + 1), 1000)

const TodoHeader = ({ addTodo }) =>
  <header class='header'>
    <h1>todos {seconds}</h1>
    ...
  </header>

Awesome! It's so niiiiiiiice!

from solid.

ryansolid avatar ryansolid commented on May 5, 2024

It sounds like you answered most of your own questions. The State objects just wrap S.data structures so at it's core everything is S() and S.data(). The renderer isn't specific for using the State object. So as I suggested in the other issue coming up with a new State representation isn't terribly hard. I've been working on 2, (State and ImmutableState). The primary one is Mutable under the hood so it works like a nested tree of S signals ala MobX. It's the one in the benchmark and has great performance. It should work well for your scenario.

The Immutable one similar to ImmerJS in that while it constantly clones and updates it still updates under the same clock mutably (ie it clones the first time then mutates along the same change propagation and then when when settles notifies out). So as far as immutable goes it's still reasonably performant but I haven't focused on it less recently.

The State object is mostly for control flow and my thinking a simpler way for people used to React like interfaces to enter this world of fine grained changes. I sort of purposefully skimmed it a bit in the docs to reduce confusion but I think I could put an example in the Signals section.

As for the parens required, for attributes they should be required as it's possible to pass functions to props so I wouldn't expect the className example above to work. For inserted content between the tags (seconds examples) that should work because returned functions are considered computations. S.js has no isObservable or the like so it's necessary.

from solid.

ryansolid avatar ryansolid commented on May 5, 2024

I don't feel like I've properly explained this yet still. I think it comes down to the fact that Signals are just functions. So that means it's an S.data etc or any function that contains the execution of an S.data. In so functions need to be handled specially as we can't make any assumptions about them. Nothing makes them a Signal. This holds true for the render tree, operators, pretty much anything around this. It's only the fact there is a computation higher up in the execution stack that means they behave in a certain way.

Someone new to this paradigm might go overkill on making computations. If you only care about d2 these are almost equivalent except example 1 does slightly more work:

const s = S.data();
// 1
const d1 = S(() => s()),
  d2 = S(d1);

// 2 
const d1 = () => s(),
  d2 = S(d1)

What this does mean is you generally have to be pretty conscious of what you are doing under certain scenarios. Like while in your seconds example it works you actually created an extra unnecessary computation by doing it that way. My experience with Knockout over the years which allows the same sort of direct referencing in binding is when to include and when not to include the parens ends up being an endless source small bugs. New devs miss it or someone refactors something adding more conditions to the binding now they have to add the parens but miss it and it always evals as true in a conditional etc.. not important things. I actually like the consistency of always including the parens.

The interesting challenge here in general is that at compiler time we don't know if it is a function being passed in and it isn't until runtime we can make the resolutions. Surplus actually optimizes around this which is why you can't not include the parens, since it looks for them as a decision to wrap the expressions in a function. My Babel plugin instead uses outer double parens (( )) to indicate no wrap. I want a better way but I haven't found one yet.

Another place where things get interesting is dealing with stores and nested data. Signals are setup for tracking Immutable Data. There is a single getter and setter and once set you can not mutate the value and have it track. However it is inefficient data structures and trees to always track through everything so you nest Signals in Signals etc. It means when moving data from the Store or Service layer you have to map over it. Sometimes this happens in rendering, but other times you need specific context references. This problem is always awkward since automating it usually has a cost and serializing it is always a consideration when trying to pass around the data to places not aware of Signals. The second you try to interface with model data you will hit this problem. I disliked how heavy this was and since Proxies are dynamic I can actually only wrap properties in Signals that are being listened to. It's not an every time I set overhead but rather a small overhead just on initial read. And best of all you don't have those concerns.

From my perspective a lot of specifics when dealing with Signals directly. So the State object and Solid in general is just a wrapper so those sort of details are not a consideration starting out. You can use Solid without being aware of Signals so it's very intentional to expose only the baseline stuff from S directly through the library. I was looking to providing a React-ish experience for fine grained. Where you start simple and grow into it. The only piece of Signals that really expose themselves is the fact that inorder to run operators you need to write selectors which is an idea familiar to the React community. Essentially by writing a function that accesses state properties you get a Signal without writing anything with S. ex..

// Psuedo Signal, AKA a Selector
const s = () => state.firstName + ' ' + state.lastName;

So I'm not sure if it's misleading but I wrote the documentation in a way that the end user would not be aware of Signals even though that is the underpinnings. I'm very familiar with fine grained libraries and I truly believe when you get into the harder problems and optimizations it is a superior representation to say React's lifecycle functions but they are a lot at first as anyone starting with like RxJS will attest to. So it's a bit ambitious, all the performance and power of fine grained yet a very gradual learning curve.

from solid.

ryansolid avatar ryansolid commented on May 5, 2024

I realized re-reading the original post their may been confusion. Solid State object is mutable. It just has an immutable interface. While there is slight overhead from proxies it's actually ideal for nested trees. It saves you from having to manually map everything with Signals which can be tedious. Signals themselves are immutable atoms. Solid State takes away the concern of having to construct the structure of Signals your self while letting you leverage stuff like destructuring. I've removed the Immutable State experiment since I wasn't really progressing it and it was flawed.

from solid.

trusktr avatar trusktr commented on May 5, 2024

Thanks for all that explanation @ryansolid! I'm still working on other stuff over at http://github.com/trusktr/infamous, but I'm planning to make some example declarative app examples with it soon, and I'd like to use Solid for some of them.

in your seconds example it works you actually created an extra unnecessary computation by doing it that way.

Mind showing how the example would be not adding the extra computation?

It saves you from having to manually map everything with Signals which can be tedious

Can you make a small example of having to "map everything with Signals", vs using the "mutable" state object?

from solid.

ryansolid avatar ryansolid commented on May 5, 2024

You can scratch that first comment about the extra computation given the way that I've changed the code now. Previously all expressions were wrapped in functions by the compiler which were turned into computations. And if it returned a function in a content insert (not for an attribute where a function is a perfectly valid value to set) like in your example that would create another nested computation. It would work since that's intended, but I've since removed that logic to be explicit. So now nothing is wrapped by default and you need to supply a function to make it get wrapped in a computation. Because it's still a bit verbose for none signals like the state object to write {() => state.value} I've introduced a syntax of using outer wrapping parens so that you can write {( state.value )} instead. That being said attributes still behave different given that functions are valid values so I'd like to standardize it a bit better, but that still needs some consideration given the concerns I express above.

As for the wrapping. It is pretty common case, you can even see it in the JS Frameworks Benchmark. Most of the time you are going to get blobs of JSON data from a server. Whether they are normalized or not at some point you going to want to denormalize them to display them in the view. So say you have a format of for a Blog Post:

{
  id: 1,
  title: 'Some Title',
  content: '...'
  createdAt: '2018-04-05'
  author: {
    id: 234,
    profileUrl: ''
    firstName: 'John',
    lastName: 'Smith'
  }
  comments: [{
    id: 56,
    content: '...',
    author: {
      id: 4007,
      profileUrl: ''
      firstName: 'Janet',
      lastName: 'Jameson'
    }
  }]
}

Let's say the user who happens to be the commentor on a particular blog post clicks in the corner and changes their Profile Pic, but you want to make sure the picture that is beside their comment updates. It won't update unless that nested profileUrl is a Signal.

With a library like React some change to the Store is going to trigger either a full re-render or using some sort of MapStateToProps only the relevant components will re-render. From there essentially mapping happens as you call your render functions as you update the url reference on the img tag in the VDOM as it re-evaluates everything.

With KVO(Kev Value Observables, what Signals are) for this to update you will need to navigate to the specific signal to update. But since Comments are dynamic too they need to be Signals as well or they won't appear. Now it's possible you have these Signals Globally and normalized so it isn't as big of a deal but generally when you end up is with Signals that contain mapped versions of the data with signals for values that contain more mapped objects with Signals. Like you'd put the Post in a Signal, and then make a Signal for the Comments array, and then for each Comment you make a Signal for every field that could update, and the same for each field on the Author. In the end you have a completely different nested parallel data structure the View is working with. To your benefit the changes will be pinpoint and not cause re-evaluation of anything else. But you need to write toJS methods ever to get Raw data back out and the process of pulling out values is a chain of post().comments().author().firstName(). You will find this pattern over and over again with KVO. It's big part of MobX design decisions and the choices made here.

I find people new to these patterns tend to overwrap everything in Signals. But it's usually not as necessary to be so fine grained and you can get away re-evaluating certain things so there is room for optimization here. It's just unfortunate if you need to tweak your data structure as you make those decisions. Like if you decide to make something dynamic later. Part of that is put the owness of mapping on the components which is generally a pattern I like but it definitely is more work. With the State Objects while it can't quite be optimized it basically only creates Signals for values you are reading in a Computed Context. None of this is a consideration and you get to handle it just like a POJO. The touch point is usually just calling my reconcile method on the immutable data source (Global Store/Server data) you wish to do fine grained change on.

from solid.

Related Issues (20)

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.