If your web-app has even a modicum of complexity on the client side, the vanilla DOM based solution is going to get unwieldy real fast.
Consider a simple list of elements to which you can add or delete items. With vanilla DOM, you’ll have to write handlers that will `$.find` the particular element, and then run a `$.remove` to remove it. To insert something, you’ll have to do a manual `$.appendHTML`. And when you have to send the data over to the server, you have to iterate over the elements, collect it into an array and send it across. But if you used something that had an explicit notion of the application state, and allowed you to express your view as a simple function of your state, all you would have to write is an `array.push` and another `_.remove(array)`. The view will update automatically because we’ve already told the 'over-engineered' framework what to render for any given state.
But you could avoid framework and still not fall prey to spaghetti DOM manipulation. All you need is to keep an explicit state variable and have a single `render` function to deal with the DOM. This method will be invoked everytime you change your state, and it'll recreates the necessary DOM from scratch. It is a simple straight-forward implementation, but now you're going to lose cursor position, and with large enough DOM trees, the lag will be noticeable.
To fix this, you can add a virtual-dom implementation that'll maintain the DOM tree in memory and avoid re-rendering things that haven't changed.. and then you can have some sort of immutability checker to skip even the virtual-dom rendering part. And by now you have a home-brewed over-engineered React-like framework, but without any of its documentation or robustness or community.
We definitely need to be conservative in our choice of tools, but React today is a conservative choice to build rich front-end web applications. More than a framework, it is an approach to user interfaces, and it is a damn good one too.