原文地址:https://jasonformat.com/wtf-is-jsx/
JSX 實際上很簡單:讀完這篇文章,你就會完全了解這個可選擇的模版引擎
副標題:“和JSX共處”
注解
你在每個文件和每個函數(shù)里定義這個,告訴轉譯器(如:Babel)每個節(jié)點在運行時階段需要調用的函數(shù)名。
在下面的例子里,我們稱之為“對每個節(jié)點,插入h()函數(shù)的調用”
/*@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!');
你可能看到第二段代碼,覺得用函數(shù)來創(chuàng)建UI也不錯
這就是我為什么從JSX講起:如果沒有這個,你手動寫出來仍然很簡單
JSX只是一種已經很優(yōu)雅語法的語法糖
有人甚至整個項目用它hypescript
我們來寫個jsx渲染器
首先,我們要定義下我們轉換的代碼調用的h()函數(shù)。
你可以把這個函數(shù)命名為任何名字,我之所以用h(),是因為在hypescript里這種類型的‘build’函數(shù)就是這么稱呼的
function h(nodeName, attributes, ...args) {
let children = args.length ? [].concat(...args) : null;
return { nodeName, attributes, children };
}
好了,就是這么簡單
你不熟悉ES6/ES2005?
1.參數(shù)中的'...'是剩余參數(shù),該操作符會收集剩余的參數(shù)到一個數(shù)組里
2.concat(...args)是spread操作符:這個操作符會把剛剛的參數(shù)數(shù)組展開到arguments里,再傳給concat()方法。這里用concat()是為了合并所有子節(jié)點的嵌套數(shù)組。
現(xiàn)在我們通過h()方法輸出一個嵌套的JSON對象,這個‘樹狀’對象就像下面這樣:
{
nodeName: "div",
attributes: {
"id": "foo"
},
children: ["Hello!"]
}
所以我們只需要一個函數(shù),接受這樣的參數(shù)格式,并輸出真實的dom節(jié)點
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的優(yōu)勢是它十分輕量。輕量對象引用其他輕量對象。非常容易優(yōu)化的應用程序結構。這也表示它沒有綁定任何渲染邏輯和很慢的DOM方法。
使用JSX
現(xiàn)在知道JSX被轉換成了對h()的調用。這些函數(shù)調用創(chuàng)建一個簡單的“虛擬”DOM樹。
我們可以使用render()函數(shù)去創(chuàng)建匹配的“真實”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);
局部模版,迭代和邏輯:沒有新語法
區(qū)別于模版語言引入的有限概念和局限,我們有javascript所擁有的一切能力
'局部模版'是由無邏輯/少邏輯的模版引擎為了在不同的地方重用視圖而引入的概念。
迭代是幾乎所有模版語法都會引入的一個東西(我也是這么做)。同樣,對于JSX:和其他javascript程序一樣。你可以選擇一種適合的迭代方式:[].forEach,[].map(),for和while循環(huán)等等。
和迭代一樣,模版語法都喜歡重定義邏輯。一方面,無邏輯模版里,視圖加入邏輯很不方便:不合理的,如{{#if value}}這樣的設計,邏輯加入到了controller層,使controller變的十分臃腫。這種規(guī)避方式,創(chuàng)造了一種語言來描述更復雜的邏輯,使得bug不好預測而且容易產生安全隱患。
另一方面,用代碼生成技術的引擎(一種簡陋到不可原諒的技術),通常自詡可以執(zhí)行任何JavaScript所寫的邏輯甚至迭代表達式。這里有原因,我們?yōu)槭裁匆欢ㄒ苊馐褂眠@項技術:你的代碼已經脫離了原來的’位置‘(模塊,閉包或這標簽內),在別處執(zhí)行。這對我而言,不可預測,并且不安全。
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節(jié)點(上面是<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> )
);