React的簡單實現(二)組件

上一篇我們已經實現了簡單的createElement,借助詞法分析庫將JSX轉換成JS對象:http://www.lxweimin.com/p/1c6bc9171b0c

如果JSX片段中的某個元素是組件,那么createElement的第一個參數tag將會是一個方法,而不是字符串。

function createElement( tag, attrs, ...children ) {
    return {
        tag,
        attrs,
        children
    }
}

例如在處理<Count initCount="1" />時,createElement方法的第一個參數tag,實際上就是我們定義Count的方法:

class Count extends React.Component {
    render() {
        return <h1>Count: {this.props.initCount}</h1>;
   

Component實現

React.Component包含了一些預先定義好的變量和方法

class Component {}
state & props

React.Component定義的組件有自己的state和props,可以通過this訪問
所以在構造函數中,需要初始化state和props,掛接到this上面

class Component {
    constructor( props = {} ) {
        this.state = {};
        this.props = props;
    }
}
setState
# 后面再實現
const renderComponent = (component)=>{}
class Component {
    constructor( props = {} ) {
        // ...
    }

    setState( stateChange ) {
        // 將修改合并到state
        Object.assign( this.state, stateChange );
        renderComponent( this );
    }
}
render

需要修改之前的ReactDOM.render方法,讓其支持渲染組件。
這是上一篇提到的render:

function render( vnode, container ) {
    return container.appendChild( _render( vnode ) );
}

function _render( vnode ) {

    if ( vnode === undefined || vnode === null || typeof vnode === 'boolean' ) vnode = '';

    if ( typeof vnode === 'number' ) vnode = String( vnode );

    if ( typeof vnode === 'string' ) {
        let textNode = document.createTextNode( vnode );
        return textNode;
    }

    const dom = document.createElement( vnode.tag );

    if ( vnode.attrs ) {
        Object.keys( vnode.attrs ).forEach( key => {
            const value = vnode.attrs[ key ];
            setAttribute( dom, key, value );
        } );
    }

    vnode.children.forEach( child => render( child, dom ) );    // 遞歸渲染子節點

    return dom; 
}

增加一個對tag的識別

function createComponent( component, props ) {

    let inst;
    // 如果是類定義組件,則直接返回實例
    if ( component.prototype && component.prototype.render ) {
        inst = new component( props );
    // 如果是函數定義組件,則將其擴展為類定義組件
    } else {
        inst = new Component( props );
        inst.constructor = component;
        inst.render = function() {
            return this.constructor( props );
        }
    }

    return inst;
}
function _render( vnode ) {

    // ...

    if ( typeof vnode.tag === 'function' ) {

        const component = createComponent( vnode.tag, vnode.attrs );

        setComponentProps( component, vnode.attrs );

        return component.base;
    }
    
    // ...
}
// set props
function setComponentProps( component, props ) {

    if ( !component.base ) {
        if ( component.componentWillMount ) component.componentWillMount();
    } else if ( component.componentWillReceiveProps ) {
        component.componentWillReceiveProps( props );
    }

    component.props = props;

    renderComponent( component );

}

定義renderComponent函數,在里面掛接更新生命周期函數,setstate時進行調用

export function renderComponent( component ) {

    let base;

    const renderer = component.render();

    if ( component.base && component.componentWillUpdate ) {
        component.componentWillUpdate();
    }

    base = _render( renderer );

    if ( component.base ) {
        if ( component.componentDidUpdate ) component.componentDidUpdate();
    } else if ( component.componentDidMount ) {
        component.componentDidMount();
    }

    if ( component.base && component.base.parentNode ) {
        component.base.parentNode.replaceChild( base, component.base );
    }

    component.base = base;
    base._component = component;

}

完整JS代碼為:

function createElement(tag, attrs, ...children) {
  return {
    tag,
    attrs,
    children,
  };
}

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

  setState(stateChange) {
    // 將修改合并到state
    Object.assign(this.state, stateChange);
    renderComponent(this);
  }
}

function createComponent(component, props) {

  let inst;
  // 如果是類定義組件,則直接返回實例
  if (component.prototype && component.prototype.render) {
    inst = new component(props);
    // 如果是函數定義組件,則將其擴展為類定義組件
  } else {
    inst = new Component(props);
    inst.constructor = component;
    inst.render = function() {
      return this.constructor(props);
    };
  }

  return inst;
}

function setComponentProps(component, props) {

  if (!component.base) {
    if (component.componentWillMount) component.componentWillMount();
  } else if (component.componentWillReceiveProps) {
    component.componentWillReceiveProps(props);
  }

  component.props = props;

  renderComponent(component);

}

function renderComponent(component) {

  let base;

  const renderer = component.render();

  if (component.base && component.componentWillUpdate) {
    component.componentWillUpdate();
  }

  base = _render(renderer);

  if (component.base) {
    if (component.componentDidUpdate) component.componentDidUpdate();
  } else if (component.componentDidMount) {
    component.componentDidMount();
  }

  if (component.base && component.base.parentNode) {
    component.base.parentNode.replaceChild(base, component.base);
  }

  component.base = base;
  base._component = component;

}

const React = {
  createElement,
  createComponent,
  Component,
};

function render(vnode, container) {
  return container.appendChild(_render(vnode));
}

function _render(vnode) {

  if (vnode === undefined || vnode === null || typeof vnode === 'boolean') vnode = '';

  if (typeof vnode === 'number') vnode = String(vnode);

  if (typeof vnode === 'string') {
    let textNode = document.createTextNode(vnode);
    return textNode;
  }
  if (typeof vnode.tag === 'function') {

    const component = createComponent(vnode.tag, vnode.attrs);

    setComponentProps(component, vnode.attrs);

    return component.base;
  }

  const dom = document.createElement(vnode.tag);

  if (vnode.attrs) {
    Object.keys(vnode.attrs).forEach(key => {
      const value = vnode.attrs[key];
      setAttribute(dom, key, value);
    });
  }

  vnode.children.forEach(child => render(child, dom));    // 遞歸渲染子節點

  return dom;
}

function setAttribute(dom, name, value) {
  // 如果屬性名是className,則改回class
  if (name === 'className') name = 'class';

  // 如果屬性名是onXXX,則是一個事件監聽方法
  if (/on\w+/.test(name)) {
    name = name.toLowerCase();
    dom[name] = value || '';
    // 如果屬性名是style,則更新style對象
  } else if (name === 'style') {
    if (!value || typeof value === 'string') {
      dom.style.cssText = value || '';
    } else if (value && typeof value === 'object') {
      for (let name in value) {
        // 可以通過style={ width: 20 }這種形式來設置樣式,可以省略掉單位px
        dom.style[name] = typeof value[name] === 'number' ? value[name] + 'px' : value[name];
      }
    }
    // 普通屬性則直接更新屬性
  } else {
    if (name in dom) {
      dom[name] = value || '';
    }
    if (value) {
      dom.setAttribute(name, value);
    } else {
      dom.removeAttribute(name);
    }
  }
}

非框架代碼是下面的,可以運行一下:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, { this.props.name }</h1>;
  }
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
    element,
    document.getElementById( 'root' )
);

運行結果為:


image.png

加一段更新代碼試試,綁定事件onClick是通過setAttribute加上去的,本質就是document.createElement('p')['onclick'] = ()=>{}實現

import React from './react';
// end with react frame work
class Count extends React.Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    }
  }
  render() {
    return <div>
      <div>Count: {this.state.count}</div>
      <button
        onClick={()=>{
          this.setState({
            count: this.state.count+1,
          })
        }}
      >
        Add
      </button>
    </div>;
  }
}

const ReactDOM = {
  render: (vnode, container) => {
    container.innerHTML = '';
    return React.render(vnode, container);
  },
};
const element = <Count name="Sara"/>;
console.log(element);
ReactDOM.render(
  element,
  document.getElementById('root'),
);

運行結果為


image.png

源碼地址:https://github.com/liuxiaocong/dailyMove/commit/c7bb2c8ae4d1f29af04503291e929402207122c3

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。