createElement函数
在react里面JSX会被转译成用React.createElement方法包裹的代码,比如
const title = Hello, world!
会经过一个语法分析的库(babel里面)参考:https://www.jianshu.com/p/7e872afeae42
,最终被转化为:
const title = React.createElement(
'h1',
{ className: 'title' },
'Hello, world!'
);
不管JSX,先简单实现一下createElement这个函数,从jsx转译结果来看,createElement方法的参数是这样:
createElement( tag, attrs, child1, child2, child3 );
第一个参数是DOM的标签名,比如div,h1,span等等
第二个参数是一个对象,里面包含了所有的属性,可能包含了className,id等等
第三个参数开始,就是它的子节点
最简单实现,返回一个对象进行包裹:
function createElement( tag, attrs, ...children ) {
return {
tag,
attrs,
children
}
}
试着进行封装和调用:
const React = {
createElement
}
const element = (
helloworld!
);
console.log( element );
这时候会遇到上面的错误,因为目前我们的语法是JSX,所以需要引入babel的支持,参考:https://babeljs.io/docs/en/,这里我们采用一种更简单的方式,单纯对JSX进行转换
1,新建一个.babelrc文件,指定transform-react-jsx用的方法就是上述的createElement:
{
"presets": ["env"],
"plugins": [
["transform-react-jsx", {
"pragma": "React.createElement"
}]
]
}
2, 使用parcel打包工具,对jsx语法进行词法分析,然后用上述的createElement包裹起来,先进行安装:
npm install -g parcel-bundler
3,调整代码结构,新建index.html和index.js,js内容就是上面的
4,使用 ‘parcel index.html’ 命令进行打包操作
5,打包完成后得到dist的输出
运行index.html可以得到下述结果(可能需要更改下js的路径)
ReactDOM.render方法
参考react里面的写法
ReactDOM.render(
Hello, world!
,
document.getElementById('root')
);
实际上是JSX进过转换是这样的
ReactDOM.render(
React.createElement( 'h1', null, 'Hello, world!' ),
document.getElementById('root')
);
render方法的作用就是将对象转换成虚拟DOM,再最终渲染成真实的DOM
function render( vnode, container ) {
// 当vnode为字符串时,渲染结果是一段文本
if ( typeof vnode === 'string' ) {
const textNode = document.createTextNode( vnode );
return container.appendChild( 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 container.appendChild( dom ); // 将渲染结果挂载到真正的DOM上
}
还有setAttribute方法:
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 );
}
}
}
每次render前,清理一下之前的内容:
const ReactDOM = {
render: ( vnode, container ) => {
container.innerHTML = '';
return render( vnode, container );
}
}
在index.html里面增加容器:
重新
parcel index.html
运行,得到输出结果:
源码地址:https://github.com/liuxiaocong/dailyMove/tree/master/simpleReact