React從零實現-節點建立和渲染

logo-og

這一篇主要來實現React的節點渲染部分。該篇須要用到介紹和準備中的內容。node

Element概念

首先引出咱們的第一個概念element,它是以下形式:react

{
  type: 'div',
  props: {
    className: 'container',
    children: ['hello world']
  }
}
複製代碼

暫時能夠先把這個當作React中虛擬節點的概念,後面會有一些變化,這裏先看作是虛擬節點。上節也提到過babel-plugin-transform-react-jsx 會幫助我幫將JSX轉換成如下形式:git

// 轉換前的jsx
<div className="container">hello world</div>

// 轉換後
React.createElement("div",  { className: "container" }, "hello world");
複製代碼

函數createElement的第一個參數字符串標籤名(此處先忽略組件的狀況),第二個參數是它的所接受到的屬性,從第三個參數開始,後面都是它的子節點。github

createElement

理解了這些下面咱們來實現咱們本身的createElement,函數參數與上面一致,返回值是一個「虛擬節點」。npm

function createElement(type, initProps, ...args) {
  const props = Object.assign({}, initProps);
  const hasChildren = args.length > 0;
	
	// 這裏不直接使用args,而使用`[].concat(...args)`是爲了扁平化數組
	// 將[1, [2], [3]]轉爲[1, 2, 3]
  const rawChildren = hasChildren ? [].concat(...args) : [];
	
  // 將children屬性賦值給props
  props.children = children;
  return { type, props };
}
複製代碼

在這個函數中,還有一種子節點是文本節點的狀況,像咱們上面寫的那樣。它跟其餘節點不一樣,其餘節點均可以經過React.createElement函數執行後,返回一個element。文本節點是一個字符串,爲了後面可以減小if判斷,這裏咱們須要把字符串的形式統一爲虛擬節點的形式,下面來寫一個createTextElement函數:數組

function createTextElement(text) {
  return {
    type: 'TEXT ELEMENT',
    props: { nodeValue: text }
  }
}
複製代碼

將其類型規定爲TEXT ELEMENT的形式,同時將值放在nodeValue裏面,方便後面直接給文本節點賦值。有了createTextElement函數,咱們須要改造下咱們的createElement函數:babel

function createElement(type, initProps, ...args) {
  const props = Object.assign({}, initProps);
  const hasChildren = args.length > 0;
  const rawChildren = hasChildren ? args : [];
	
  // 過濾null、undefined和false,不作任何渲染,同時將文本節點進行轉換
  const children = rawChildren
    .filter(child => child != null && child !== false)
    .map(child => child instanceof Object ? child : createTextElement(child));
  props.children = children;
  return { type, props };
}
複製代碼

createElement函數到這裏完成了。app

render

下一步咱們要開始渲染咱們的節點,就像咱們使用ReactDOM.render那樣,咱們的渲染函數render接收兩個參數,第一個是咱們要渲染的element,第二個是咱們要掛載的節點。注意後面全部的命名element都會使用xxxElement的形式,dom節點都會使用xxxDom的形式。dom

function render(element, parentDom) {
  const { type, props } = element;
  // 是文本節點則建立文本節點,這裏建立一個空的文本節點,後面利用nodeValue直接給該節點賦值
  const isTextNode = type === 'TEXT ELEMENT';
  const childElements = props.children || [];
  const childDom = isTextNode
    ? document.createTextNode('')
    : document.createElement(type);

  const isEvent = name => name.startsWith('on');
  const isAttribute = name => !isEvent(name) && name !== 'children';

  // 綁定事件
  Object.keys(props).filter(isEvent).forEach(name => {
    const eventName = name.toLowerCase().substring(2);
    childDom.addEventListener(eventName, props[name]);
  });

  // 添加屬性
  Object.keys(props).filter(isAttribute).forEach(name => {
    childDom[name] = props[name];
  });

  // 遞歸渲染
  childElements.forEach(childElement => {
    render(childElement, childDom);
  });

  // 掛載到父節點
  parentDom.appendChild(childDom);
}
複製代碼

咱們首先建立了要渲染的節點。當爲文本節點時,咱們使用建立了一個空節點,隨後在添加屬性的步驟,經過childDom['nodeValue'] = name的形式給文本節點賦值。全部onXxx的屬性都綁定爲事件,其餘有效屬性都直接賦值給childDom節點。而後對子節點進行遞歸渲染,最後掛載到父節點。整個渲染過程到這裏就完成了。函數

咱們可使用咱們實現的函數來渲染一個界面了,這是Codepen上的演示地址。咱們成功渲染了界面,可是咱們忽略了組件的狀況,下一篇咱們來實現組件和setState。

這是github原文地址。接下來我會持續更新,歡迎star,歡迎watch。

實現React系列列表:

相關文章
相關標籤/搜索