In this way you could get huge performance benefits, because you're not doing any work, you don't have to diff anything for changes, because you already know which places in the application need to be updated. Of course this has a cost. Frameworks like Solid or Angular and Vue with this upcoming VaporMode already try to achieve this, but of course it has downsides.
First of all in React if you would adopt signals in React we have to migrate to the single execution components. You no longer have this guarantee that a component will read run, the function will no longer rerun whenever something changes, but only those specific parts in the application changes. This has of course massive implications for what you can and what you can not do in the body of a component. It will be very interesting to see how this transition pans out in something like Angular or Vue, but we can already take a look at the end result in Solid.js that started with signals from the get-go.
Additionally I added this single execution components in quotes, because it's not really single execution. Yes, the outer component, the outer function, only runs once to create all the effects, create all the signals, wire everything together, but then you have within your component all those different places that update at various points in time. For example, here we have three places, the two effects and the place in the JSX, and all of those places have to rerun at certain points in time. Even if you have this model of only creating your components once initially, certain parts of it will rerun again and again depending on the signals that are read in those specific scopes. This can get very complicated very quickly if you have complex components that read from different signals.
In the React world, on the other hand, you can just slap a console lock somewhere, because you know for a fact when the first line of the component runs because something rerendered, then the last line of the component also has to rerun. This gets this problem that certain places run out of order and at various points in time get more complicated the more signals you're using in the application. Additionally, let's imagine we have an even more complex component and maybe another developer came along and now we start mixing the different properties. So A, B, and C are accessors as it's called in Solid to retrieve the value or could also be directly signals. We have the value D, which is just a number, a plain number that will never change. All of a sudden, we have to juggle those two worlds of containers that contain changing values and values themselves that now no longer can update, because in the end we migrated to the single execution component, so it only runs once from the top to bottom.
Everything that isn't a signal will not update and cannot update. Now we are in this world where when you write a component some properties are allowed to change while others, the property D, are not allowed to change or changes would just be ignored. Of course, in something like Solid the compiler works around this a bit so that all properties that are passed to a component are automatically wrapped so that all properties can change. However, you still have the same problem when writing something like custom hooks or the equivalent of custom hooks, because there you still need to manually explicitly specify what arguments can change and what arguments cannot change. The React world on the other hand assumes that everything can change. Every argument and every property of your function can change, and it forces us with linters, compilers etc. It forces us to always hold up this assumption so someone that is using a component they don't have to look inside of the component, because they can safely assume whatever property they change the component will update accordingly and it will still work.
This leads in the React land to more robust code that you have to update less, because in Solid when you write a hook you start out with only one argument being a signal and then sooner or later you realize that a second argument also needs to change in some other use case. You start refactoring the code, adjusting that all arguments are signals, then you have to refactor all the old use cases and the old call places. This then all leads to this mess of having to think about and actually having to explicitly decide which arguments can be signals and which arguments should just be plain values. In React world everything is a plain value and everything can change. Yes the downside is that within our custom hooks, within our components right now we manually have to specify dependencies, the linter screams at us, we have to always memorize functions for example so that it doesn't lose identity and they don't change. However with something like the upcoming compiler this may get better in the future.
To summarize React forces us to assume that everything can change in our application and in turn we get to work with plain values, can just take any arbitrary value, a number, a data type, an object, a plain object, whatever and we can pass those around and everything will still work as long as we have these plain values and follow the assumptions of React. If you now still want to have signals in React or if you have performance issues in your application and you hope that signals can solve those or if you're just interested in real world experiences from a real world signal-based project then let's chat! Contact me via Twitter, via email or on Discord. I'm available there as well and I hope you learned something today and have some interesting questions for me. Have fun in the conference and talk to you later!
Comments