The code after chapter Updating children

function createVComponent(tag, props) {
  return {
    tag: tag,
    props: props,
    dom: null,
  }
}

function createVElement(tag, config, children = null) {
  const { className, style } = config;

  return {
    tag: tag,
    style: style,
    props: {
      children: children,
    },
    className: className,
    dom: null,
  }
}

function createElement(tag, config, children) {
  // If the tag is a function. We have a component!
  // we will see later why. 
  if (typeof tag === 'function') {
    //of course we could do some checks here if the props are 
    //valid or not.
    const vNode = createVComponent(tag, config);
    return vNode;
  }

  //Add children on our props object, just as in React. Where
  //we can acces it using this.props.children;

  const vNode = createVElement(tag, config, children);
  return vNode;
}


function mount(input, parentDOMNode) {
  if (typeof input === 'string' || typeof input === 'number') {
    //we have a vText
    return mountVText(input, parentDOMNode);
  } 
  else if (typeof input.tag === 'function') {
    //we have a component
    return mountVComponent(input, parentDOMNode);
  }
  // for brevity make an else if statement. An
  // else would suffice. 
  else if (typeof input.tag === 'string') {

    //we have a vElement
    return mountVElement(input, parentDOMNode)
  }
}

function mountVComponent(vComponent, parentDOMNode) {
  const { tag, props } = vComponent;

  const Component = tag;
  const instance = new Component(props);

  const nextRenderedElement = instance.render();
  //create a reference of our currenElement
  //on our component instance.
  instance._currentElement = nextRenderedElement;
  //create a reference to the passed
  //DOMNode. We might need it.
  instance._parentNode = parentDOMNode; 


  const dom = mount(nextRenderedElement, parentDOMNode);

  //save the instance for later!
  vComponent._instance = instance;
  vComponent.dom = dom;

  parentDOMNode.appendChild(dom);
}

function mountVText(vText, parentDOMNode) {
  // Oeeh we received a vText with it's associated parentDOMNode.
  // we can set it's textContent to the vText value. 
  // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
  parentDOMNode.textContent = vText;
}

function mountVElement(vElement, parentDOMNode) {
  const { className, tag, props, style } = vElement;

  const domNode = document.createElement(tag);
  vElement.dom = domNode;

  if (props.children) {
    if (!Array.isArray(props.children)) {
      mount(props.children, domNode)
    } else {
      props.children.forEach(child => mount(child, domNode));
    }
  }

  if (className !== undefined) {
    domNode.className = className;
  }

  if (style !== undefined) {
    Object.keys(style).forEach(sKey => domNode.style[sKey] = style[sKey]);
  }

  parentDOMNode.appendChild(domNode);

  return domNode;
}

function updateVElement(prevElement, nextElement) {
  const dom = prevElement.dom;
  nextElement.dom = dom;

  if (nextElement.props.children) {
    updateChildren(prevElement.props.children, nextElement.props.children, dom);
  }

  if (prevElement.style !== nextElement.style) {
    Object.keys(nextElement.style).forEach((s) => dom.style[s] = nextElement.style[s])
  }
}

function updateVText(prevText, nextText, parentDOM) {
  if (prevText !== nextText) {
    parentDOM.firstChild.nodeValue = nextText;
  }
}

function updateChildren(prevChildren, nextChildren, parentDOMNode) {
  if (!Array.isArray(nextChildren)) {
    nextChildren = [nextChildren];
  }
  if (!Array.isArray(prevChildren)) {
    prevChildren = [prevChildren];
  }

  for (let i = 0; i < nextChildren.length; i++) {
    //We're skipping a lot of cases here. Like what if
    //the children array have different lenghts? Then we 
    //should replace smartly etc. :)
    const nextChild = nextChildren[i];
    const prevChild = prevChildren[i];


    //Check if the vNode is a vText
    if (typeof nextChild === 'string' && typeof prevChild === 'string') {
      //We're taking a shortcut here. It would cleaner to
      //let the `update` function handle it, but we would to add some extra
      //logic because we don't have a `tag` property. 
      updateVText(prevChild, nextChild, parentDOMNode);
      continue;
    } else {
      update(prevChild, nextChild);
    }
  }
}


function updateVComponent(prevComponent, nextComponent) {

  //get the instance. This is Component. It also 
  //holds the props and _currentElement; 
  const { _instance } = prevComponent;
  const { _currentElement } = _instance;

  //get the new and old props!
  const prevProps = prevComponent.props;
  const nextProps = nextComponent.props;

  //Time for the big swap!
  nextComponent.dom = prevComponent.dom;
  nextComponent._instance = _instance;
  nextComponent._instance.props = nextProps;

  if (_instance.shouldComponentUpdate()) {
    const prevRenderedElement = _currentElement;
    const nextRenderedElement = _instance.render();
    //finaly save the nextRenderedElement for the next iteration!
    nextComponent._instance._currentElement = nextRenderedElement;
    //call update 
    update(prevRenderedElement, nextRenderedElement, _instance._parentNode);
  }
}

class Component {
  constructor(props) {
    this.props = props || {};
    this.state = {};

    this._currentElement = null;
    this._pendingState = null;
    this._parentNode = null;
  }

  //add this in existing code
  shouldComponentUpdate() {
    return true;
  }

  updateComponent() {
    const prevState = this.state;
    const prevElement = this._currentElement;

    if (this._pendingState !== prevState) {
      this.state = this._pendingState;
    }

    this._pendingState = null;
    const nextElement = this.render();
    this._currentElement = nextElement;


    update(prevElement, nextElement, this._parentNode);
  }

  setState(partialNewState) {
    // I know this looks weired. Why don't pass state to updateComponent()
    // function, I agree. 
    // We're just getting a little familiair with putting data on instances. 
    // seomthing that React uses heavily :)
    this._pendingState = Object.assign({}, this.state, partialNewState);
    this.updateComponent();
  }
  //will be overridden
  render() {}
}

function update(prevElement, nextElement) {
  //Implement the first assumption!
  if (prevElement.tag === nextElement.tag) {
    //Inspect the type. If the `tag` is a string
    //we have a `vElement`. (we should actually
    //made some helper functions for this ;))
    if (typeof prevElement.tag === 'string') {
      updateVElement(prevElement, nextElement);
    } else if (typeof prevElement.tag === 'function') {
      updateVComponent(prevElement, nextElement);
    }
  } else {
    //Oh oh two elements of different types. We don't want to 
    //look further in the tree! We need to replace it!
  }
}


//get native DOM, For now let's use the body;
const root = document.body;
//create vElement
class NestedApp extends Component {
  constructor(props) {
    super(props);
  }

  shouldComponentUpdate() {
    return this.props.counter % 2;
  }

  render() {
    return createElement('h1', { style: { color: '#'+Math.floor(Math.random()*16777215).toString(16) } }, `The count from parent is: ${this.props.counter}`)
  }
}

class App extends Component {
  constructor() {
    super();
    this.state = {
      counter: 1
    }
    setInterval(() => {
      this.setState({ counter: this.state.counter + 1 })
    }, 500);
  }

  render() {
    const { counter } = this.state;
    //background color stolen from: https://www.paulirish.com/2009/random-hex-color-code-snippets/
    return createElement('div', { style: { height: `${10 * counter}px`, background: '#'+Math.floor(Math.random()*16777215).toString(16) } }, [
      `the counter is ${counter}`,
      createElement('h1', { style: { color: '#'+Math.floor(Math.random()*16777215).toString(16) } }, `${'BOOM! '.repeat(counter)}`),
      createElement(NestedApp, { counter: counter})
    ]);
  }
}

mount(createElement(App, { message: 'Hello there!' }), root);

results matching ""

    No results matching ""