React的简单实现(二)组件

上一篇我们已经实现了简单的createElement,借助词法分析库将JSX转换成JS对象:https://www.jianshu.com/p/1c6bc9171b0c

如果JSX片段中的某个元素是组件,那么createElement的第一个参数tag将会是一个方法,而不是字符串。

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

例如在处理时,createElement方法的第一个参数tag,实际上就是我们定义Count的方法:

class Count extends React.Component {
    render() {
        return 

Count: {this.props.initCount}

;

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 

Hello, { this.props.name }

; } } const element = ; 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 
Count: {this.state.count}
; } } const ReactDOM = { render: (vnode, container) => { container.innerHTML = ''; return React.render(vnode, container); }, }; const element = ; console.log(element); ReactDOM.render( element, document.getElementById('root'), );

运行结果为


image.png

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

你可能感兴趣的:(React的简单实现(二)组件)