上一篇我們已經實現了簡單的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