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…