componentization

This article focuses on componentization. Where does component parsing start? Yes, it should start from the vNode generation phase. When we componentize the programming, the export is actually a Xue options, so the label we get is actually the options, take a look at the following example:

const HelloWorld = {
  // It is not the same
  // ...
}

function Fn(){}

render() {
  return (
    <div>{/* The following tag is parsed by our parsing function, and its tag is actually the HelloWorld object above */}<HelloWorld></HelloWorld>{/* The same goes for functional components. The tag is the function Fn */}.<Fn></Fn>
    </div>
  );
}
Copy the code

After the JSX code is parsed, we will generate VNode. Let’s change the logic of this section:

class VNode {
  constructor(tagMsg, xm) {
    this.xm = xm;
    this.children = [];
    this.attrs = {};
    this.events = {};
    this.tagType = ' ';
    // If it is a JSXObj object, parse it
    if(tagMsg instanceof JSXObj) {
      this.tag = tagMsg.tag;
      Attrs is processed to separate out attributes and events
      tagMsg.attrs && Object.entries(tagMsg.attrs).forEach(([key, value]) = > {
        if(key.match(/on[A-Z][a-zA-Z]*/)) {
          const eventName = key.substring(2.3).toLowerCase() + key.substring(3);
          this.events[eventName] = value;
        }
        else this.attrs[key] = value;
      });
      // Check whether it is a native tag
      if(NativeTags.includes(this.tag)) this.tagType = 'native';
      // This has all been covered before, so skip ahead and go straight to this one
      // If an object is passed in, it is considered a Xue component
      else if(typeof this.tag === 'object') {
        // Componentize logic
        this.tagType = 'component';
      }
      // If it is a function, it is considered a functional component
      // Functional components are simpler to handle, just reparse the function return value and pass attrs as props
      // Return the result directly, so the current this object is actually the return value of parseJsxObj
      else if(typeof this.tag === 'function') {
        this.tagType = 'function';
        return parseJsxObj(xm, tagMsg.tag(this.attrs)); }}else if(tagMsg === null) {
      this.tag = null;
    }
    // If it is not, it is treated as a text node by default. The tag attribute of the text node is an empty string
    else {
      this.tag = ' ';
      this.text = tagMsg; }}// Omit the following content...
}
Copy the code

After completing the VNode class, the next step is to complete the Element class:

class Element {
  constructor(vnode, xm) {
    this.xm = xm;
    this.tagType = 'native';
    // If it is null, no processing is done
    if(vnode.tag === null) return;
    // Text node
    if(vnode.tag === ' ') {
      // Return this sentence is not followed by return
      this.el = document.createTextNode(vnode.text);
      return;
    }

    // Process non-text nodes
    if(vnode.tagType === 'native') {
      this.el = document.createElement(vnode.tag);
      // Bind attributes
      Object.entries(vnode.attrs).forEach(([key, value]) = > {
        this.setAttribute(key, value);
      });
      // Bind events
      Object.keys(vnode.events).forEach(key= > {
        // Cache the function after bind for subsequent function removal
        vnode.events[key] = vnode.events[key].bind(xm);
        this.addEventListener(key, vnode.events[key]);
      });
    }
    // Look directly at the handling of components here
    // When tagType is component
    else if(vnode.tagType === 'component') {
      this.tagType = 'component';
      // Make its parent vNode the root node of the component instance
      vnode.tag.root = vnode.parent && vnode.parent.element.el;
      // Cache the parent component
      vnode.tag.$parent = xm;
      // Pass attrs as props
      vnode.tag.$props = vnode.attrs;
      // vnode.tag is Xue options
      const childXM = new Xue(vnode.tag);
      // Reset the current XM and EL to the instance of new child Xue
      this.xm = childXM;
      this.el = childXM.$el;
      // Update the xM corresponding to vNode
      vnode.updateXM(childXM);
      
      // After component init is complete, remove component Watcher from stackDep.popTarget(); }}// omit the following
  // ...
}
Copy the code

New Xue(options), pass vNode. tag as options, we cannot pass options, we must do some extension:

  1. Set root to the parent node of the vnode
  2. Pass attrs as props

Now we have a new instance of the child Xue. When we have the new instance, we need to update the xM and EL of the current element. We also need to update the xM of the vNode. So you must pop the child watcher back to the parent watcher via dep.popTarget (). Here is an implementation of the two methods in the Watcher class:

// During init, there is a process to push the current watcher onto the stack
// Push the current Wacther to the stack
Dep.pushTarget(xm.$watcher);
xm._callHook.call(xm, 'beforeMount');

// in Dep, the code related to loading and unloading the stack
let targetList = [];
class Dep {
  static target = null;
  static pushTarget(watcher) {
    targetList.push(watcher);
    Dep.target = watcher;
  }
  static popTarget() {
    targetList.pop();
    const length = targetList.length;
    if(length > 0)
      Dep.target = targetList[length - 1];
  }
  // The following content is omitted
  // ...
}
Copy the code

So far, our subcomponent has rendered, but so far its props is not reactive, so we need to set reactive for props:

export const initState = function() {
  this.$data = this.$options.data() || {};
  this.$methods = this.$options.methods;
  // Save the props value so that the props can be accessed directly through this.props
  this.props = this.$options.$props || {};

  const dataNames = Object.keys(this.$data);
  const methodNames = Object.keys(this.$methods);

  // Check if there are any identical data, methods, or props
  const checkedSet = new Set([...dataNames, ...methodNames]);
  if(checkedSet.size < dataNames.length + methodNames.length) return warn('you have same name in data, method');

  // proxies the properties of data, props, and methods to this
  dataNames.forEach(name= > proxy(this.'$data', name));
  // propNames.forEach(name => proxy(this, '$props', name));
  methodNames.forEach(name= > proxy(this.'$methods', name));

  // Set data to reactive
  observe(this.$data);
  // Set props to responsive
  observe(this.props);
  
}
Copy the code

Observe’s logic has been mentioned previously in Chapter 1 and will not be repeated here. In fact, at this point, the componentized content is done. So let’s write a demo and see

demo

let Child = {
  data() {
    return {
      msg: 'i am test1 in Child:'
    }
  },
  beforeCreate() {
    setTimeout((a)= > {
      this.msg = 'hello world:'
    }, 4000)
  },
  render() {
    return (<div>
      { this.msg }
      { this.props.test }
    </div>)}};function Child2(props) {
  return (<div>i am test1 in Child2:{ props.test }</div>)}let father = new Xue({
  root: '#app',
  data() {
    return {
      test1: 'i am text1',
    }
  },
  render() {
    return (<div>
      <div>
        i am test1 in father:{ this.test1 }
      </div>
      <Child test={ this.test1} ></Child>
      <Child2 test={ this.test1} ></Child2>
    </div>);
  },
  mounted() {
    setTimeout((a)= > {
      this.test1 = 'i am text1 change';
    }, 3000)}});Copy the code

The initial render looks like this:

After the 3 s:

After another 1s:

Write a simple routing component

After the component is complete, let’s try to write a routing component using the componentization we wrote. We need a Router component, followed by a Router class to configure options:

export const XueRouterCom = {
  render() {
    // Get the component under the current route
    const Current = this.props.options.getCurrentCom();
    return (
      <div>
        <Current></Current>
      </div>); }};// The hash mode is used as an example
export class XueRouterCls {
  current = null;
  // Refresh the components under the current route
  // Use the arrow function to bind this, otherwise this will point to window after addEventListener
  refresh = (a)= > {
    const currentPath = this.getRoute();
    const currentRoute = this.routes.find(item= > item.path === currentPath);
    // Throw an error if no match is found
    if(! currentRoute)return warn(`no such route ${ currentPath }, this page's route is The ${this.current.path }`);
    this.current = currentRoute;
  }
  constructor({ routes, type = 'hash'{})this.routes = routes;
    this.type = type;
    // It is initialized by default. By default, the 0th route is taken first, because the following refresh methods may not match due to incorrect input
    this.current = routes[0];
    // Refresh the components under the current route
    this.refresh();
    / / monitoring hashchange
    window.addEventListener('hashchange'.this.refresh, false);
  }
  // Get the components under the current route object
  getCurrentCom() {
    return this.current && this.current.component;
  }
  // Get the current route
  getRoute() {
    if(this.type === 'hash')
      return location.hash.slice(1); }};Copy the code

This is a simple implementation of hash routing, um…… It is easy, hahaha.

demo

After completing the routing component, let’s write another demo to test it:

function Child1(props) {
  return (<div>hello world1</div>)}function Child2(props) {
  return (<div>hello world2</div>)}const router = new XueRouterCls({
  routes: [{path: '/hello1'.component: Child1
    },
    {
      path: '/hello2'.component: Child2
    }
  ]
});
let c = new Xue({
  root: '#app',
  render() {
    return (<div>
      <XueRouterCom options={ router} ></XueRouterCom>
    </div>); }});Copy the code

Different components are displayed under different routes:

That’s the end of this series of plans for now, because I have a higher priority to do recently, so this part of the content for now, thank you for watching.

Github project address: Click here to jump to

Chapter 1: start from scratch, using the idea of Vue, develop a JS framework of their own (I) : the basic structure of the construction

Chapter two: starting from scratch, using the idea of Vue, the development of a own JS framework (two) : the first rendering

Chapter 3: start from scratch, using the idea of Vue, develop a own JS framework (2) : Update and diff