Skip to content

Latest commit

 

History

History
178 lines (140 loc) · 4.27 KB

File metadata and controls

178 lines (140 loc) · 4.27 KB

140. Virtual DOM III - Functional Component

Problem

https://bigfrontend.dev/problem/virtual-DOM-III-Functional-Component

Problem Description

This is a follow-up on 118. Virtual DOM II - createElement.

In problem 118, you are asked to implement createElement() and render() function which supports intrinsic HTML elements, like <p/>, <div/> etc.

In this problem, you are ask to support custom Functional Component.

Functional Component are functions that:

  1. accept single object argument -props, which contains children, className and other properties.
  2. returns an MyElement by calling createElement().

Say we have a Functional Component - Title

const h = createElement;
const Title = ({ children, ...res }) => h('h1', res, ...children);

Then we should be able to use it in createElement and render(), just the same way as an intrinsic element.

h(Title, {}, 'This is a title');

h(Title, { className: 'class1' }, 'This is a title');

Please modify your createElement() and render() from 118. Virtual DOM II - createElement if necessary, so that the example in problem 118 could be rewritten as below:

const Link = ({ children, ...res }) => h('a', res, ...children);
const Name = ({ children, ...res }) => h('b', res, ...children);
const Button = ({ children, ...res }) => h('button', res, ...children);
const Paragraph = ({ children, ...res }) => h('p', res, ...children);
const Container = ({ children, ...res }) => h('div', res, ...children);

h(
  Container,
  {},
  h(Title, {}, ' this is '),
  h(
    Paragraph,
    { className: 'paragraph' },
    ' a ',
    h(Button, {}, ' button '),
    ' from ',
    h(Link, { href: 'https://bfe.dev' }, h(Name, {}, 'BFE'), '.dev')
  )
);

Solution 1

/**
 * MyElement is the type your implementation supports
 *
 * type MyNode = MyElement | string
 * type FunctionComponent = (props: object) => MyElement
 */

/**
 * @param { string | FunctionComponent } type - valid HTML tag name or Function Component
 * @param { object } [props] - properties.
 * @param { ...MyNode} [children] - elements as rest arguments
 * @return { MyElement }
 */
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children,
    },
  };
}

/**
 * @param { MyElement }
 * @returns { HTMLElement }
 */
function render(myElement) {
  if (typeof myElement === 'string') {
    return document.createTextNode(myElement);
  }

  const { type, props } = myElement;

  if (typeof type === 'function') {
    return render(type(props));
  }

  const { children, ...attrs } = props;
  const element = document.createElement(type);

  for (const attrName in attrs) {
    const _attrName = attrName === 'className' ? 'class' : attrName;
    element.setAttribute(_attrName, attrs[attrName]);
  }

  for (const child of children) {
    element.appendChild(render(child));
  }

  return element;
}

Solution 2

/**
 * MyElement is the type your implementation supports
 *
 * type MyNode = MyElement | string
 * type FunctionComponent = (props: object) => MyElement
 */

/**
 * @param { string | FunctionComponent } type - valid HTML tag name or Function Component
 * @param { object } [props] - properties.
 * @param { ...MyNode} [children] - elements as rest arguments
 * @return { MyElement }
 */
function createElement(type, props, ...children) {
  if (typeof type === 'function') {
    return type({ children, ...props });
  }

  return {
    type,
    props: {
      ...props,
      children,
    },
  };
}

/**
 * @param { MyElement }
 * @returns { HTMLElement }
 */
function render(myElement) {
  if (typeof myElement === 'string') {
    return document.createTextNode(myElement);
  }

  const {
    type,
    props: { children, ...attrs },
  } = myElement;
  const element = document.createElement(type);

  for (const attrName in attrs) {
    const _attrName = attrName === 'className' ? 'class' : attrName;
    element.setAttribute(_attrName, attrs[attrName]);
  }

  for (const child of children) {
    element.appendChild(render(child));
  }

  return element;
}