上一篇我们已经实现了简单的createElement,借助词法分析库将JSX转换成JS对象:https://www.jianshu.com/p/1c6bc9171b0c
如果JSX片段中的某个元素是组件,那么createElement的第一个参数tag将会是一个方法,而不是字符串。
function createElement( tag, attrs, ...children ) {
return {
tag,
attrs,
children
}
}
例如在处理
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' )
);
运行结果为:
加一段更新代码试试,绑定事件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'),
);
运行结果为
源码地址:https://github.com/liuxiaocong/dailyMove/commit/c7bb2c8ae4d1f29af04503291e929402207122c3