| I would say the traditional distinction between a library and a framework, to the extent that the industry has any settled definition for these terms at all, is about who dictates the application architecture and the control flow. A framework typically establishes a runtime environment and then calls into your code to respond to specific events. Common examples are GUI applications that run an event loop responding to operating system events and web servers where the entry points to your own code are handlers for different requests. In the early days, JavaScript was mostly used similarly, as the entry point to your own JS code tended to be a specific event on a specific DOM node. There might be some code to set things up when your program starts, but once you hand over control to the framework, it decides what the run and when, and your code doesn’t do anything unless the framework asks it to. A library, in the not-a-framework sense, works the other way around. Your own code ultimately has control and you call into the library when you require specific functionality. The library doesn’t do anything until you ask it to. So to answer your question, yes, I did know those things, but I don’t think the equivalence between those calls is necessarily appropriate in this context. It’s not just about who calls the function the first time. It’s also about what happens next — whether your own code continues to call the library in a similar way later, or whether you hand over control to a framework once and that deals with everything from then on. Calling ReactDOM.render doesn’t have to be a fire-and-forget operation; it can simply perform a single render and then return having completed its task. You might then call it many further times to rerender that part of your page in response to changes in the data it depends on. Control will often flow back and forth between your own code and library or framework code. Sometimes this happens on a small scale, like the sorting example I mentioned where you are calling a higher order function and it takes a simple callback function as a parameter. Sometimes it happens on a much larger scale, like calling React to render a component you supply, and then React calling that component’s own render logic, which in turn calls back into React to create child components, and then React calling their own render logic, and so on. You can certainly argue that the latter starts to resemble a framework, but if you’re only talking about rendering a component tree that is triggered by a call from your own code and returns control to your code again once the rendering has finished, the difference is merely a matter of scale. Surely everyone would agree that the sorting example is a library call, so where do you draw the line? The overall architecture of your application and its control flow is still determined by your own code. You certainly can use React more like a framework, in the sense that you call into it once to set things up and then hand over control. You can write components that have additional responsibilities like maintaining your application state and fetching data from remote APIs, and you can use hooks (or class-based component state and lifecycle methods) so that interesting events from those other sources will automatically trigger a rerender. My point is that you don’t necessarily have to design your application in that style just because you’re using React. If you choose not to, then React doesn’t “call you”, it doesn’t permeate your codebase in the way you described before, and it certainly doesn’t exclude the use of architectural patterns like MVC or MVVM. |
IMHO, it's more fruitful to ponder the possibility that certain projects are not frameworks because they fundamentally lack something that frameworks provide. Suppose we tried to use nothing but lodash to implement a web app. It's certainly doable (given that people write vanilla apps), lodash would "permeate" the codebase and it would probably call a sizable number of your callbacks. But I think we'd both agree lodash is not a framework. But why? I'd argue it's because organizing units (in the "unit test" sense) coherently in a way that is maintainable is outside of its expressed scope. In other words, it's not suitable to that task, even if we wanted it to. React is not only suitable for it, it's specifically designed for that purpose.
Speaking of units, another way we can approach this is by looking at where unit boundaries are in comparison to user code boundaries. If you look at any usage of node's `child_process` API, the event handler functions are generally not considered units; the entire child_process instance setup might be one unit, and the event handlers may call other units that are ideally decoupled from whether callback arguments are partial Buffers or error codes and so on. Similarly, a sorting callback is not typically a unit (i.e. you never test that the callback returns 1 or -1, you test that the code sorts correctly instead). React components on the other hand are units in and of themselves. I believe it is accurate to say that frameworks provide abstractions whose purpose is to delineate where a unit boundary is. Components fit that bill perfectly, just as controllers and views and services do in other architectures.