JSX的动态数据绑定属于作者的React和前端工程实践。本文设计的参考资料参考React学习与实践资料索引。如果还有疑问,可以参考《现代JavaScript开发:语法基础与实战技巧》。基于JSX的动态数据绑定作者在2016-MyFront-EndRoad:ToolingandEngineering一文中提到,前端社区花了15年时间分离HTML、JavaScript和CSS,但随着JSX的出现,事情似乎又回到了原点到解放前一夜之间。Angular、Vue.js等MVVM前端框架使用指令来描述业务逻辑,而JSX本质上是JavaScript,即用JavaScript来描述业务逻辑。尽管JSX被一些开发者评论为语法丑陋,但作者仍然坚持JavaScriptFirst原则,尽可能使用JavaScript编写业务代码。在上一篇React初看:JSX详解中,我们探讨了JSX的前世今生和基本用法,这部分我们着手编写一个简单的面向DOM的JSX解析和动态数据绑定库;这部分涉及的代码总结在Ueact库中。JSX解析和DOM元素构造元素构造作者在JavaScript语法树和代码转换实践一文中介绍了Babel的原理和使用。这里我们仍然使用Babel作为JSX语法分析工具;为了将JSX语句转换为createElement调用,这里我们需要在项目的.babelrc文件中做如下配置:"plugins":["transform-decorators-legacy","async-to-promises",["transform-react-jsx",{"pragma":"createElement"}]],这里的createElement函数声明如下:/***DescriptionbuildsvirtualDOMfromJSX*@paramtagNametagname*@parampropsattribute*@paramchildrenArgs子元素列表*/exportfunctioncreateElement(tagName:string,props:propsType,...childrenArgs:[any]){}该函数包含三个参数,分别指定标签名称、属性对象和子元素列表;实际上,经过Babel的改造后,JSX文本会变成如下函数调用(这也包括ES2015其他的语法转换):...(0,_createElement.createElement)('section',null,(0,_createElement.createElement)('section',null,(0,_createElement.createElement)('button',{className:'link',onClick:handleClick},'CustomDOMJSX'),(0,_createElement.createElement)('input',{type:'text',onChange:functiononChange(e){console.log(e);}}))),...拿到元素标签后,我们首先要做的就是创建元素;创建元素createElementByTag过程中需要注意区分普通元素和SVG元素:exportconstcreateElementByTag=(tagName:string)=>{if(isSVG(tagName)){returndocument.createElementNS('http://www.w3.org/2000/svg',tagName);}returndocument.createElement(tagName);};属性处理新建一个element对象后,我们需要对createElement函数传入的后续参数进行处理,即设置相应的属性对于元素;基本属性包括样式类、内联样式、标签属性、事件、子元素和简单的HTML代码等。首先,我们需要处理子元素://处理所有子元素,如果子元素元素是简单的字符串,直接创建文本节点constchildren=flatten(childrenArgs).m??ap(child=>{//如果子元素也是Element,则创建子元素的副本if(childinstanceofHTMLElement){returnchild;}if(typeofchild==='boolean'||child===null){child='';}returndocument.createTextNode(child);});这里可以看出createElement函数的执行是自下而上执行的,所以传入的子元素参数实际上已经传递了渲染的HTML元素。接下来,我们需要处理其他属性:...//支持class和className都设置constclassName=props.class||props.className;//如果有样式类,则设置if(className){setAttribute(tagName,el,'class',classNames(className));}//内联样式解析getStyleProps(props).forEach(prop=>{el.style.setProperty(prop.name,prop.value);});//解析其他HTML属性getHTMLProps(props).forEach(prop=>{setAttribute(tagName,el,prop.name,prop.value);});//设置事件监听,这里为了解决一些异步问题浏览器所以使用同步写法letevents=getEventListeners(props);for(leteventofevents){el[event.name]=event.listener;}...React也允许直接设置元素内部的HTML代码,这里我们还需要判断是否存在dangerouslySetInnerHTML属性://如果手动设置HTML,则添加HTML,否则设置显示子元素if(setHTML&&setHTML.__html){el.innerHTML=setHTML.__html;}else{children.forEach(child=>{el.appendChild(child);});}到这里我们就完成了JSX格式的简单DOM标签转换的createElement函数,完整源码在这里。简单使用这里我们仍然使用create-webpack-app脚手架来搭建示例工程,这里我们以一个简单的计数器为例来说明它的使用方法。需要注意的是,这部分还没有介绍双向数据绑定,或者自动状态变化更新,或者简单的DOM选择器查询更新方法://App.jsimport{createElement}from'../../../src/dom/jsx/createElement';//页面中的Stateconststate={count:0};/***描述点击事件处理*@parame*/constandleClick=e=>{state.count++;document.querySelector('#count').innerText=state.count;};exportdefault(
