JSX 和 虛擬DOM

jsx是一種語法糖,通過babel 編譯後生成 React.createElement(component, props, ...children) 函數。css

例如react

const Element = (
  <div className="title">
    Hello 
    <span style={{fontSize:"20px",color:"#f00"}}>World!</span>
  </div>
)

通過babel編譯後:數組

const Element = React.createElement(
  'div',
  {
    className: 'title',
  },
  'Hello',
  React.createElement(
    'span',
    {
      style: {
        fontSize: '20px',
        color: '#f00',
      },
    },
    'World!'
  )
);

jsx語法通過babel編譯後生成一種對象,即虛擬dom,在react中,經過render函數將虛擬dom渲染成真實的dom綁定到對應節點中babel

import React from 'react'
import ReactDOM from 'react-dom'

// 虛擬dom
const Element = (
  <div className="title">
    Hello 
    <span style={{fontSize:"20px",color:"#f00"}}>World!</span>
  </div>
)
/*
等同於
const Element = React.createElement(
  'div',
  {
    className: 'title',
  },
  'Hello',
  React.createElement(
    'span',
    {
      style: {
        fontSize: '20px',
        color: '#f00',
      },
    },
    'World!'
  )
)
*/

//綁定節點
const el = document.getElementById('root')

// render方法渲染到頁面
ReactDOM.render(Element, el)

這裏須要兩個函數完成頁面的渲染:createElementrender方法app

createElement函數

createElement函數將babel轉換事後的參數簡化,返回對象type和propsdom

/**
 * 生成虛擬DOM對象
 * @param {string} type      dom節點類型
 * @param {object} config    屬性對象
 * @param {*} [children]     子數組
 *
 * @return {object} {type,props}
 */
function createElement(type,config,children) {
  let props = {};
  for(let propsName in config){
    props[propsName] = config[propsName]
  };

  let childsLen = arguments.length - 2;
  if(childsLen===1){
    props.children = [children]
  }else if(childsLen>1){
    props.children = Array.prototype.slice.call(arguments, 2)
  }

  return { type, props }

}

render函數

render函數負責將虛擬dom轉化爲真實dom,咱們將createElement生成的虛擬dom對象傳入render函數的第一個參數,結構出type值和props對象函數

/**
 * @param {createElement} element    虛擬DOM對象
 * @param {HTMLElement}} container    綁定的dom節點
 */
function render(element, container) {
  if (typeof element === 'string') {
    return container.appendChild(document.createTextNode(element))
  }

  let type  = element.type;
  let props  = element.props;

  if (type.isReactComponent) { // 若是是類組件
    element = new type(props).render()
    type = element.type
    props = element.props
  } else if (typeof type === 'function') { // 若是是函數組件
    element = type(props)
    type = element.type
    props = element.props
  }

  const el = document.createElement(type)
  for (let propName in props) {
    let value = props[propName]
    if (propName === 'className') {
      el.className = value
    } else if (propName === 'style') {
      let cssText = Object.keys(value).map((attr) => {
          let _attr = attr.replace(/([A-Z])/g, (a) => `-${a.toLocaleLowerCase()}`);
          return `${_attr}:${value[attr]}`
        }).join(';');

      el.style.cssText = cssText
    } else if (propName === 'children') {
      value.forEach((item) => render(item, el))
    } else {
      el.setAttribute(propName, value)
    }
  }
  return container.appendChild(el)
}

這裏咱們還須要判斷type值是類組件仍是函數組件this

類組件

關於如何判斷類組件,咱們能夠在組件繼承的Component類中添加靜態屬性isReactComponent供render函數判斷spa

class Component {
  static isReactComponent = true;
  constructor(props){
    this.props = props;
  }
}

類組件

let Element = React.createElement(fnElemen, { name: 'Hello', fontSize: '28px' })

class clsComp extends React.Component {
  render() {
    return React.createElement(
      'h1',
      { className: 'title' },
      this.props.name,
      React.createElement(
        'span',
        {
          style: { color: '#0f0', fontSize: this.props.fontSize }
        },
        'World'
      )
    )
  }
};

let Element = React.createElement(clsComp, { name: 'Hello', fontSize: '28px' })

ReactDOM.render(Element, document.getElementById('root'))

函數組件

function fnElemen(props){
  return React.createElement(
    'h1',
    { className: 'title' },
    props.name,
    React.createElement(
      'span',
      { style: { color: '#0f0', fontSize: props.fontSize } },
      'World'
    )
  )
}

let Element = React.createElement(fnElemen, { name: 'Hello', fontSize: '28px' })

ReactDOM.render(Element, document.getElementById('root'))

開發中的常見問題

爲什麼必須引用React

做用域內必須引入react,不然會致使編譯失敗,這是由於jsx會編譯爲React.createElement的形式調用,因此react在jsx的做用域內必需要引入。prototype

用戶定義的組件以大寫字母開頭

babel在編譯時會把小寫字母開頭的元素斷定爲原生DOM標籤,createElement 會把它編譯成字符串,例如<div>或者 <span>,因此在編寫組件的時候,須要以大寫字母開頭,這樣createElement纔會把第一個變量被編譯爲對象

import React from 'react';

// 錯誤!組件應該以大寫字母開頭:
function hello(props) {
  // 正確!這種 <div> 的使用是合法的,由於 div 是一個有效的 HTML 標籤
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // 錯誤!React 會認爲 <hello /> 是一個 HTML 標籤,由於它沒有以大寫字母開頭:
  return <hello toWhat="World" />;
}

Props 默認值爲 「True」

若是你沒給 prop 賦值,它的默認值是 true。如下兩個 JSX 表達式是等價的:

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

一般,咱們不建議不傳遞 value 給 prop,由於這可能與 ES6 對象簡寫混淆,{foo}{foo: foo} 的簡寫,而不是 {foo: true}。這樣實現只是爲了保持和 HTML 中標籤屬性的行爲一致。

相關文章
相關標籤/搜索