One specific challenge that multiple frameworks have attempted to solve over the years is the binding of the DOM to data. We briefly discussed data-binding in the last chapter's section on MVVM. Any GUI needs to have a way of having its displayed pixels reflect its underlying data.
Via the DOM, we can dynamically create specific elements and place them as we wish. The user can then impose their intent on the application by interfacing with these elements, usually via input fields and buttons. These user actions, which we bind to via DOM events, may then affect a change in underlying data. This change needs to be reflected in the DOM. This back-and-forth is usually termed two-way-binding. Historically, to achieve this, we would manually create a DOM tree, set up event listeners on elements, and then manually mutate those DOM elements when any underlying data (or state) changed.
The challenge in manually fiddling with the DOM ourselves is that it doesn't scale very well without some kind of abstraction. It is easy enough to take a piece of data and derive a DOM tree from that data, but having the DOM tree tied to changes within the data and having the data tied to user-derived changes in the DOM (for example, clicking on buttons) are quite burdensome things to implement.