JSX什么鬼(一起来写一个JSX渲染引擎)

原文地址:https://jasonformat.com/wtf-is-jsx/

JSX 实际上很简单:读完这篇文章,你就会完全了解这个可选择的模版引擎

副标题:“和JSX共处”

注解

你在每个文件和每个函数里定义这个,告诉转译器(如:Babel)每个节点在运行时阶段需要调用的函数名。

在下面的例子里,我们称之为“对每个节点,插入h()函数的调用”

/*@jsx h/

转译

如果你还没有使用过转译器,你应该尝试使用。因为用es6/ES2015写,调试,测试或运行js都更加有效率。其中Babel是最流行的,也是最被推荐使用的。我们假设你使用了babel

如今babel不仅提供转换你的ES6/ES7+语法支持,并且提供直接开箱即用,转换JSX的支持。你可以直接使用这种特性。
我们先来看个简单的例子:
有jsx之前(你怎么写代码)

/** @jsx h */
let foo = <div id="foo">Hello!</div>; 

有jsx后(你运行的代码)

var foo = h('div', {id:"foo"}, 'Hello!');

你可能看到第二段代码,觉得用函数来创建UI也不错

这就是我为什么从JSX讲起:如果没有这个,你手动写出来仍然很简单

JSX只是一种已经很优雅语法的语法糖

有人甚至整个项目用它hypescript

我们来写个jsx渲染器

首先,我们要定义下我们转换的代码调用的h()函数。

你可以把这个函数命名为任何名字,我之所以用h(),是因为在hypescript里这种类型的‘build’函数就是这么称呼的

  function h(nodeName, attributes, ...args) {  
      let children = args.length ? [].concat(...args) : null;
      return { nodeName, attributes, children };
}

好了,就是这么简单
你不熟悉ES6/ES2005?

1.参数中的'...'是剩余参数,该操作符会收集剩余的参数到一个数组里
2.concat(...args)是spread操作符:这个操作符会把刚刚的参数数组展开到arguments里,再传给concat()方法。这里用concat()是为了合并所有子节点的嵌套数组。

现在我们通过h()方法输出一个嵌套的JSON对象,这个‘树状’对象就像下面这样:

{
  nodeName: "div",
  attributes: {
    "id": "foo"
  },
  children: ["Hello!"]
}

所以我们只需要一个函数,接受这样的参数格式,并输出真实的dom节点

function render(vnode) {  
    // Strings just convert to #text Nodes:
    if (vnode.split) return document.createTextNode(vnode);

    // create a DOM element with the nodeName of our VDOM element:
    let n = document.createElement(vnode.nodeName);

    // copy attributes onto the new node:
    let a = vnode.attributes || {};
    Object.keys(a).forEach( k => n.setAttribute(k, a[k]) );

    // render (build) and then append child nodes:
    (vnode.children || []).forEach( c => n.appendChild(render(c)) );

    return n;
}

理解这个很容易。
你也可以把‘虚拟DOM’认为是如何构建DOM结构的一种简单配置。

虚拟dom的优势是它十分轻量。轻量对象引用其他轻量对象。非常容易优化的应用程序结构。这也表示它没有绑定任何渲染逻辑和很慢的DOM方法。

使用JSX

现在知道JSX被转换成了对h()的调用。这些函数调用创建一个简单的“虚拟”DOM树。
我们可以使用render()函数去创建匹配的“真实”DOM树。

就像这样:

// JSX -> VDOM:
let vdom = <div id="foo">Hello!</div>;

// VDOM -> DOM:
let dom = render(vdom);

// add the tree to <body>:
document.body.appendChild(dom);  

局部模版,迭代和逻辑:没有新语法

区别于模版语言引入的有限概念和局限,我们有javascript所拥有的一切能力

'局部模版'是由无逻辑/少逻辑的模版引擎为了在不同的地方重用视图而引入的概念。

迭代是几乎所有模版语法都会引入的一个东西(我也是这么做)。同样,对于JSX:和其他javascript程序一样。你可以选择一种适合的迭代方式:[].forEach,[].map(),for和while循环等等。

和迭代一样,模版语法都喜欢重定义逻辑。一方面,无逻辑模版里,视图加入逻辑很不方便:不合理的,如{{#if value}}这样的设计,逻辑加入到了controller层,使controller变的十分臃肿。这种规避方式,创造了一种语言来描述更复杂的逻辑,使得bug不好预测而且容易产生安全隐患。

另一方面,用代码生成技术的引擎(一种简陋到不可原谅的技术),通常自诩可以执行任何JavaScript所写的逻辑甚至迭代表达式。这里有原因,我们为什么一定要避免使用这项技术:你的代码已经脱离了原来的’位置‘(模块,闭包或这标签内),在别处执行。这对我而言,不可预测,并且不安全。

JSX拥有javascript的一切能力,不需要在build阶段生成怪异的代码,并且不使用eval()

// Array of strings we want to show in a list:
let items = ['foo', 'bar', 'baz'];

// creates one list item given some text:
function item(text) {  
    return <li>{text}</li>;
}

// a "view" with "iteration" and "a partial":
let list = render(  
  <ul>
    { items.map(item) }
  </ul>
);

render()返回一个DOM节点(上面是<ul>),所以我们只要把它加入DOM中:

document.body.appendChild(list);  

合在一起

这里是我们虚拟DOM渲染的完整版,下面的是CodePen上有样式的版本

const ITEMS = 'hello there people'.split(' ');

// turn an Array into list items: 
let list = items => items.map( p => <li> {p} </li> );

// view with a call out ("partial") to generate a list from an Array:
let vdom = (  
    <div id="foo">
        <p>Look, a simple JSX DOM renderer!</p>
        <ul>{ list(ITEMS) }</ul>
    </div>
);

// render() converts our "virtual DOM" (see below) to a real DOM tree:
let dom = render(vdom);

// append the new nodes somewhere:
document.body.appendChild(dom);

// Remember that "virtual DOM"? It's just JSON - each "VNode" is an object with 3 properties.
let json = JSON.stringify(vdom, null, '  ');

// The whole process (JSX -> VDOM -> DOM) in one step:
document.body.appendChild(  
    render( <pre id="vdom">{ json }</pre> )
);

CodePenDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容

  • HTML模版 之后出现的React代码嵌套入模版中。 1. Hello world 这段代码将一个一级标题插入到指...
    ryanho84阅读 6,211评论 0 9
  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,044评论 0 29
  • 0. 写在前面 当你开始工作时,你不是在给你自己写代码,而是为后来人写代码。 —— Nichloas C. Zak...
    康斌阅读 5,291评论 1 42
  • 对于天秤座来说,选择是我们的死穴。一直认定天秤座是纠结的,所以我也纠结,这是天生的。但幼稚的想法随之自己经历...
    I叫不纠结阅读 151评论 0 1
  • 好好照顾自己的身体,才能给家人更好的爱。 “我跟你说一件事。”彦先生表情突然变得严肃起来,我在短短几秒脑海里过了无...
    纾琪阅读 393评论 14 8