1. Responsive front-end frameworks
@[toc]
1.1. What is responsive development
Wiki explanation
reactive programming is a declarative programming paradigm concerned with data streams and the propagation of Change (Responsive development is a declarative programming paradigm that focuses on data flow and change propagation)
Reactive programming means that the target operation is not performed directly, but is achieved in a more concise way by proxy.
Imagine that in various front-end frameworks, we now want to change the view, not using jquery to command the DOM, but by setState(), modifying this.data or modifying $scope.data…
1.1.1. The concept
For example
let a =3;
let b= a*10;
console.log(b) / / 30
a=4
//b = a * 10
console.log(b)/ / 30
Copy the code
In this case, b does not automatically change according to the value of A. It needs to be set again every time when B = a * 10. So this is not reactive.
Between B and A is like A spreadsheet formula in Excel. The value of B1 changes “responsively” according to the value edited by A1
A | B | |
---|---|---|
1 | 4 | 40(fx=A1*10) |
onAChanged((a)= > {
b = a * 10
})
Copy the code
Suppose we implement this function: onAChanged. You can think of it as an observer, an event callback, or a subscriber. It doesn’t matter. The point is, as long as we do this perfectly, B will always be 10 times as much as A.
If we write it imperative (imperative and declarative), we usually write it like this:
<span class="cell b1"></span>
document.QuerySelector ('.cell.b1 ').textContent = state.a *10
Copy the code
To make it declarative, let’s add a method to it:
<span class="cell b1"></span>
onStateChanged((a)= > {
document.QuerySelector ('.cell.b1 ').textContent = state.a *10
})
Copy the code
Further, our tags are converted into templates, which are compiled into the render function, so we can make the js above simpler.
Templates (or JSX rendering functions) are designed so that we can easily describe the relationship between state and view, just like the Excel formula above.
<span class="cell b1">
{{ state.a * 10 }}
</span>
onStateChanged(() => {
view = render(state)
})
Copy the code
Now we’ve got that nice formula that we’re all familiar with: View = render(state) What we assign to view depends on how we look at it. In the virtual DOM, it’s a new virtual DOM tree. Let’s forget about the virtual DOM and think of it as a direct manipulation of the actual DOM.
But how does our application know when to re-execute the update function onStateChanged?
let update
const onStateChanged = _update= > {
update = _update
}
const setState = newState= > {
state = newState
update()
}
Copy the code
When setting a new state, call the update() method. Update when the status changes. Again, this is just a code sketch.
1.2. In different frameworks
In the react:
onStateChanged((a)= > {
view = render(state)
})
setState({ a: 5 })
Copy the code
Story:
store.subscribe((a)= > {
view = render(state)
})
store.dispatch({
type: UPDATE_A,
payload: 5
})
Copy the code
angularjs
$scope.$watch((a)= > {
view = render($scope)
})
$scope.a = 5
// auto-called in event handlers
$scope.$apply()
Copy the code
angular2+:
ngOnChanges() {
view = render(state)
})
state.a = 5
// auto-called if in a zone
Lifecycle.tick()
Copy the code
A real framework would not be this simple, but would require updating a complex component tree.
1.3. Update process
How is it done? Is it synchronous or asynchronous?
1.3.1. Angularjs Dirty Check
Dirty check core code
(See test_cast, line 30)
Scope.prototype.? digestOnce =function () { // Run the digestOnce command at least two times and a maximum of 10 iterations. You can see the use case that gives up on the watches after 10 iterations in test_Case
var self = this;
var newValue, oldValue, dirty;
_.forEachRight(this.? watchers,function (watcher) {
try {
if (watcher) {
newValue = watcher.watchFn(self);
oldValue = watcher.last;
if(! self.? areEqual(newValue, oldValue, watcher.valueEq)) { self.? lastDirtyWatch = watcher; watcher.last = (watcher.valueEq ? _.cloneDeep(newValue) : newValue); watcher.listenerFn(newValue, (oldValue === initWatchVal ? newValue : oldValue), self); dirty =true;
} else if(self.? lastDirtyWatch === watcher) {return false; }}}catch (e) {
// console.error(e);}});return dirty;
};
Copy the code
The Digest loop is synchronized. When angularJS custom events are triggered, such as ng-click,$HTTP,$timeout, etc., dirty checks are triggered synchronously. (angularjs-demos/twowayBinding)
The only optimization is to reduce subsequent traversal of the Watcher array through the lastDirtyWatch variable (see test_case:’ends the digest when the last watch is clean’). Demo of SRC
There’s actually an asynchronous update API called $applyAsync. It needs to be called actively. For example, $HTTP sets useApplyAsync(true) to merge HTTP responses that are received at approximately the same time.
Why AngularJS is dying out (not Angular, mind you), even though there are plenty of historical projects still in use.
- Data flow is unclear, loops, and bidirectional (child scope can manipulate a parent scope’s property in test_Case).
- The API is too complex, dark technology
- Componentization is the general trend
1.3.2. React (Reconciliation Process)
Harmonic code
function reconcile(parentDom, instance, element) { // Instance represents an element object already rendered into the DOM. Element is the new virtual DOM
if (instance == null) { //1. If instance is null, the element is added directly to the DOM
// Create instance
const newInstance = instantiate(element);
parentDom.appendChild(newInstance.dom);
return newInstance;
} else if (element == null) { //2. Element is null, which removes a node from the page
// Remove instance
parentDom.removeChild(instance.dom);
return null;
} else if (instance.element.type === element.type) { //3. We update the attribute and reuse the DOM node
// Update instance
updateDomProperties(instance.dom, instance.element.props, element.props);
instance.childInstances = reconcileChildren(instance, element); // Harmonic child
instance.element = element;
return instance;
} else { //4
// Replace instance
const newInstance = instantiate(element);
parentDom.replaceChild(newInstance.dom, instance.dom);
returnnewInstance; }}// A simple version of child reconciliation, without matching key reconciliation of child elements
// This algorithm will only match the child elements in the same position of the child element array. The downside is that we can't reuse DOM nodes when we change the order of child elements between renders
function reconcileChildren(instance, element) {
const dom = instance.dom;
const childInstances = instance.childInstances;
const nextChildElements = element.props.children || [];
const newChildInstances = [];
const count = Math.max(childInstances.length, nextChildElements.length);
for (let i = 0; i < count; i++) {
const childInstance = childInstances[I];
const childElement = nextChildElements[I];
const newChildInstance = reconcile(dom, childInstance, childElement); // Call the harmonic algorithm recursively
newChildInstances.push(newChildInstance);
}
return newChildInstances.filter(instance= >instance ! =null);
}
Copy the code
SetState does not immediately synchronize to call page render (otherwise the page would have been refreshed ðŸ˜),setState causes redrawing (within a transaction) by causing a component update. Source setState in SRC/isomorphic/modern/class/ReactComponent js (15.0.0) under
For example:
this.state = {
count:0
}
function incrementMultiple() {
const currentCount = this.state.count;
this.setState({count: currentCount + 1});
this.setState({count: currentCount + 1});
this.setState({count: currentCount + 1});
}
Copy the code
How much is setState going to be added up here?
React setState determines whether to update this.state directly or put it in a queue later based on isBatchingUpdates. IsBatchingUpdates are false by default. SetState will synchronize this.state. However, there is a function called batchedUpdates that will set isBatchingUpdates to true. When React calls batchedUpdates before the event handler is called, setState controlled by React does not update this.state synchronously.
But if you write a setTimeout or add a native event using addEventListener, the state will be updated synchronously after setState and the render function will be executed immediately after the update.
(Example under demo/setState-demo)
When will React be updated? This refers to another concept in the source code called transactions. I’m not going to go into the details of transactions here, but just remember for now, no matter how many times you set state in a click event, you’re still in the same transaction.
1.3.3. Vue (Dependency Tracing)
Core code:
export function defineReactive(obj, key, val) {
var dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter() {
// console.log('geter be called once! ')
var value = val
if (Dep.target) {
dep.depend()
}
return value
},
set: function reactiveSetter(newVal) {
// console.log('seter be called once! ')
var value = val
if(newVal === value || (newVal ! == newVal && value ! == value)) {return
}
val = newVal
dep.notify()
}
})
}
Copy the code
1.3.4. Component tree updates
React setState vue this.obj. X = XXX Angular state.x = x
An optimization method
In VUE, component dependencies are automatically tracked during rendering, so the system knows exactly which components really need to be rerendered. You can understand that every component has shouldComponentUpdate automatically, but there is also a performance overhead when the dependency collection is too fine-grained.
1.4. MV* and component development
1.4.1. MV * design
MVP is a variant of MVC. The View and Model are not connected. The complete decoupling of Model and View a View is very thin and doesn’t deploy any business logic, which is called a “passive View,” meaning it doesn’t have any initiative, whereas a Presenter is very thick and all the logic is there.
Presenter calls the View method to set up the interface and still requires a lot of annoying code, which is really uncomfortable.
Can you tell a View a data structure, and then the View can automatically change with that data structure?
So the ViewModel comes in, saves a lot of writing cases in the View layer by bidirectional binding, just changing the data. (AngularJS and Vuejs are both typical MVVM architectures)
In addition, MVC is so classic that it is still widely used on both the client side (IOS,Android) and the back end.
1.4.1.1. So what’s wrong with front-end MVC or MV*?
-
Controller and View are highly coupled
The following figure shows how the View layer and controller layer interact with each other on the front end and server side. As you can see from the server side, the View layer and controller layer only interact in two ways. Between the front end and back end.
But putting MVC in the front end is problematic, because controllers are highly dependent on the View layer. In some frameworks, it’s even created by views (such as AngularJS ng-Controller). Controllers handle both event responses and business logic at the same time, breaking the single responsibility principle, which can result in the controller layer becoming increasingly bloated.
-
Overly bloated Model layer
On the other hand, the front end has two data states to deal with, one is the application state from the server, and the other is the UI state of the front end itself (buttons are not gray, ICONS are not displayed). It also violates the single responsibility of the Model layer.
1.4.1.2. How to solve the componentized development mode?
The component is: View + event handling + UI state.
The figure below shows what Flux does, which is to handle application state and business logic
Good separation of concerns
1.5. Virtual DOM, templates, and JSX
1.5.1. Vue and react
The virtual DOM is essentially a lightweight JS object. Like this:
const element = {
type: "div".props: {
id: "container".children: [{type: "input".props: { value: "foo".type: "text"}}, {type: "a".props: { href: "/bar"}}, {type: "span".props: {}}]}};Copy the code
Corresponds to the following DOM:
<div id="container">
<input value="foo" type="text">
<a href="/bar"></a>
<span></span>
</div>
Copy the code
Render to the interface using the Render method (equivalent to reactdom.render)
function render(element, parentDom) {
const { type, props } = element;
const dom = document.createElement(type);
const childElements = props.children || [];
childElements.forEach(childElement= > render(childElement, dom)); / / recursion
parentDom.appendChild(dom);
// add attributes and event listeners to it
}
Copy the code
jsx
<div id="container">
<input value="foo" type="text" />
<a href="/bar">bar</a>
<span onClick={e= > alert("Hi")}>click me</span>
</div>
Copy the code
A syntactic sugar, if not, we will directly use the following function call writing.
Babel (a precompilation tool) converts the above JSX to the following:
const element = createElement(
"div",
{ id: "container" },
createElement("input", { value: "foo".type: "text" }),
createElement(
"a",
{ href: "/bar" },
"bar"
),
createElement(
"span",
{ onClick: e= > alert("Hi")},"click me"));Copy the code
CreateElement returns the virtual DOM object above, which was the original Element
function createElement(type, config, ... args) {
const props = Object.assign({}, config);
const hasChildren = args.length > 0; props.children = hasChildren ? [].concat(... args) : [];return { type, props };
/ /... Omit some other processing
}
Copy the code
Similarly, we write vUE instances like this:
// create a template.
new Vue({
data: {
text: "before",},template: `
text: {{text}}
`
})
// Render function react JSX
new Vue({
data: {
text: "before",
},
render (h) {
return (
<div>
<span>text:</span> {{text}}
</div>)}})Copy the code
Since vue2. X also introduces virtual DOM, they are first translated into the same representation by parsing functions
new Vue({
data: {
text: "before",
},
render(){
return this.__h__('div', {},this.__h__('span', {},this.__toString__(this.text)])
])
}
})
Copy the code
This. his the same as the creatElement method in React.
1.5.2. js parser: parser
Finally, how do expressions in templates become page results?
A simple example is to write {{a+b}} in an Angular or Vue template.
Lexer becomes Tokens
[{text: 'a'.identifier: true},
{text: '+'},
{text: 'b'.identifier: true}]Copy the code
And then it goes through the AST Builder and converts it into abstract syntax numbers.
{
type: AST.BinaryExpression,
operator: '+'.left: {
type: AST.Identifier,
name: 'a' },
right: {
type: AST.Identifier,
name: 'b'}}Copy the code
Finally, the AST Compiler becomes an expression function
function(scope) {
return scope.a + scope.b;
}
Copy the code
- Lexical analysis will read characters one by one and process them differently. For example, there will be peek method. For example, when encountering expressions such as x += y, it will scan one more character when processing +.
(can see angularjs source test_case under 516 lines’ parses an addition ‘, finally running the return to the function of ASTCompiler.prototype.com
1.6. RXJS
The most popular library for responsive development: RXJS
Netflix, Google and Microsoft are contributing heavily to the ReactiveX project
RxJS is the JavaScript version of the ReactiveX programming concept. ReactiveX, from Microsoft, is programming for asynchronous data streams. Simply put, it streams all data, whether HTTP requests, DOM events, or normal data, and then streams it with powerful and rich operators that allow you to programmatically process asynchronous data asynchronously and combine different operators to easily and elegantly implement what you need.
Examples are under demos/rxjs-demo
1.7 summary
Responsive development is the trend, and each front-end framework has its own responsive system implementation. In addition, Observables should be added to the ES standard, probably in ES7+.
Reference link: medium.com/j_lim_j/su…
Medium.freecodecamp.org/is-mvc-dead…