React中JSX的理解
JSX
是快速生成react
元素的一種語法,實際是React.createElement(component, props, ...children)
的語法糖,同時JSX
也是Js
的語法擴充套件,包含所有Js
功能。
描述
JSX發展過程
在之前,Facebook
是PHP
大戶,所以React
最開始的靈感就來自於PHP
。
在2004
年這個時候,大家都還在用PHP
的字串拼接來開發網站。
$str = "<ul>";
foreach ($talks as $talk) {
$str += "<li>" . $talk->name . "</li>";
}
$str += "</ul>";
這種方式程式碼寫出來不好看不說,還容易造成XSS
等安全問題。應對方法是對使用者的任何輸入都進行轉義Escape
,但是如果對字串進行多次轉義,那麼反轉義的次數也必須是相同的,否則會無法得到原內容,如果又不小心把HTML
標籤給轉義了,那麼HTML
標籤會直接顯示給使用者,從而導致很差的使用者體驗。
到了2010
年,為了更加高效的編碼,同時也避免轉義HTML
標籤的錯誤,Facebook
開發了XHP
。XHP
是對PHP
的語法擴充,它允許開發者直接在PHP
中使用HTML
標籤,而不再使用字串。
$content = <ul />;
foreach ($talks as $talk) {
$content->appendChild(<li>{$talk->name}</li>);
}
這樣的話,所有HTML
標籤都使用不同於PHP
的語法,我們可以輕易的分辨哪些需要轉義哪些不需要轉義。不久的後來,Facebook
的工程師又發現他們還可以建立自定義標籤,而且通過組合自定義標籤有助於構建大型應用。
到了2013
年,前端工程師Jordan Walke
向他的經理提出了一個大膽的想法:把XHP
的擴充功能遷移到Js
中,首要任務是需要一個擴充來讓Js
支援XML
語法,該擴充稱為JSX
。因為當時由於Node.js
在Facebook
已經有很多實踐,所以很快就實現了JSX
。
const content = (
<TalkList>
{talks.map(talk => <Talk talk={talk} />)}
</TalkList>
);
為何使用JSX
React
認為渲染邏輯本質上與其他UI
邏輯內在耦合,比如在UI
中需要繫結處理事件、在某些時刻狀態發生變化時需要通知到UI
,以及需要在UI
中展示準備好的資料。
React
並沒有採用將標記與邏輯進行分離到不同檔案這種人為地分離方式,而是通過將二者共同存放在稱之為元件的鬆散耦合單元之中,來實現關注點分離。
React
不強制要求使用JSX
,但是大多數人發現,在JavaScript
程式碼中將JSX
和UI
放在一起時,會在視覺上有輔助作用,它還可以使React
顯示更多有用的錯誤和警告訊息。
簡單來說,JSX
可以很好的描述頁面html
結構,很方便的在Js
中寫html
程式碼,並具有Js
的全部功能。
優點
JSX
的優點主要體現在以下三點:
- 快速,
JSX
執行更快,因為它在編譯為JavaScript
程式碼後進行了優化。 - 安全,與
JavaScript
相比,JSX
是靜態型別的,大多是型別安全的。使用JSX
進行開發時,應用程式的質量會變得更高,因為在編譯過程中會發現許多錯誤,它也提供編譯器級別的除錯功能。 - 簡單,語法簡潔,上手容易。
JSX例項
規則定義
JSX
中定義了一些規則以及用法:
JSX
只能有一個根元素,JSX
標籤必須是閉合的,如果沒有內容可以寫成自閉和的形式<div />
。- 可以在
JSX
通過{}
嵌入Js
表示式。 JSX
會被babel
轉換成React.createElement
的函式呼叫,呼叫後會建立一個描述HTML
資訊的Js
物件。JSX
中的子元素可以為字串字面量。JSX
中的子元素可以為JSX
元素。JSX
中的子元素可以為儲存在陣列中的一組元素。JSX
中的子元素可以為Js
表示式,可與其他型別子元素混用;可用於展示任意長度的列表。JSX
中的子元素可以為函式及函式呼叫。JSX
中的子元素如果為boolean/null/undefined
將會被忽略,如果使用&&
運算子,需要確保前面的是布林值,如果是0/1
則會被渲染出來。- 在物件屬性中定義
React
元件,可以使用object
的點語法使用該元件。 React
元素會被轉換為呼叫React.createElement
函式,引數是元件,因此React
和該元件必須在作用域內。React
元素需要大寫字母開頭,或者將元素賦值給大小字母開頭的變數,小寫字母將被認為是HTML
標籤。- 不能使用表示式作為
React
元素型別,需要先將其賦值給大寫字母開頭的變數,再把該變數作為元件。
JSX的使用
在示例中我們宣告瞭一個名為name
的變數,然後在JSX
中使用它,並將它包裹在大括號中。在JSX
語法中,可以在大括號內放置任何有效的JavaScript
表示式。例如2 + 2
、user.firstName
或formatName(user)
都是有效的JavaScript
表示式。
const name = "Josh Perez";
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById("root")
);
同樣JSX
也是一個表示式,JSX
天生就是需要被編譯之後才可以使用的,在編譯之後JSX
表示式會被轉為普通JavaScript
函式呼叫,並且對其取值後得到JavaScript
物件。也就是說,你可以在if
語句和for
迴圈的程式碼塊中使用JSX
,將JSX
賦值給變數,把JSX
當作引數傳入,以及從函式中返回JSX
。
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
通常可以通過使用引號來將屬性值指定為字串字面量,也可以使用大括號來在屬性值中插入一個JavaScript
表示式,在屬性中嵌入JavaScript
表示式時,不要在大括號外面加上引號。因為JSX
語法上更接近JavaScript
而不是HTML
,所以React DOM
使用camelCase
小駝峰命名來定義屬性的名稱,而不使用HTML
屬性名稱的命名約定。例如JSX
裡的class
變成了className
,而tabindex
則變為tabIndex
。
const element1 = <div tabIndex="0"></div>;
const element2 = <img src={user.avatarUrl}></img>;
JSX
中也可以使用</>
來閉合標籤,另外JSX
同樣也可以直接定義很多子元素。
const element1 = <img src={user.avatarUrl} />;
const element2 = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
你可以安全地在JSX
當中插入使用者輸入內容,React DOM
在渲染所有輸入內容之前,預設會進行轉義,這樣可以確保在你的應用中,永遠不會注入那些並非自己明確編寫的內容,所有的內容在渲染之前都被轉換成了字串,可以有效地防止 XSS
跨站指令碼攻擊。
const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = <h1>{title}</h1>;
實際上Babel
會把JSX
轉譯成一個名為React.createElement()
函式呼叫,通過React.createElement()
定義的元素與使用JSX
生成的元素相同,同樣這就使得JSX
天生就是需要編譯的。
const element1 = (
<h1 className="greeting">
Hello, world!
</h1>
);
// 等價
const element2 = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement()
會預先執行一些檢查,以幫助你編寫無錯程式碼,但實際上它建立了一個這樣的物件。這些物件被稱為React 元素
,它們描述了你希望在螢幕上看到的內容,React
通過讀取這些物件,然後使用它們來構建DOM
以及保持隨時更新。
// 注意:這是簡化過的結構
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
實際上,這就是虛擬DOM
的一個節點,Virtual DOM
是一種程式設計概念,在這個概念裡,UI
以一種理想化的,或者說虛擬的表現形式被儲存於記憶體中,並通過如ReactDOM
等類庫使之與真實的DOM
同步,這一過程叫做協調。這種方式賦予了React
宣告式的API
,您告訴React
希望讓UI
是什麼狀態,React
就確保DOM
匹配該狀態,這樣可以從屬性操作、事件處理和手動DOM
更新這些在構建應用程式時必要的操作中解放出來。
與其將Virtual DOM
視為一種技術,不如說它是一種模式,人們提到它時經常是要表達不同的東西。在React
的世界裡,術語Virtual DOM
通常與React
元素關聯在一起,因為它們都是代表了使用者介面的物件,而React
也使用一個名為fibers
的內部物件來存放元件樹的附加資訊,上述二者也被認為是React
中Virtual DOM
實現的一部分,Virtual DOM
也為使用diff
演算法奠定了基礎。
<div class="root" name="root">
<p>1</p>
<div>11</div>
</div>
// 使用Js物件去描述上述節點以及文件
{
type: "tag",
tagName: "div",
attr: {
className: "root"
name: "root"
},
parent: null,
children: [{
type: "tag",
tagName: "p",
attr: {},
parent: {} /* 父節點的引用 */,
children: [{
type: "text",
tagName: "text",
parent: {} /* 父節點的引用 */,
content: "1"
}]
},{
type: "tag",
tagName: "div",
attr: {},
parent: {} /* 父節點的引用 */,
children: [{
type: "text",
tagName: "text",
parent: {} /* 父節點的引用 */,
content: "11"
}]
}]
}
示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>JSX示例</title>
</head>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
this.timer = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
tick() {
this.setState({ date: new Date() });
}
render() {
return (
<div>
<h1>{this.props.tips}</h1>
<h2>Now: {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
showClock: true,
tips: "Hello World!"
}
}
updateTips() {
this.setState((state, props) => ({
tips: "React update"
}));
}
changeDisplayClock() {
this.setState((state, props) => ({
showClock: !this.state.showClock
}));
}
render() {
return (
<div>
{this.state.showClock && <Clock tips={this.state.tips} />}
<button onClick={() => this.updateTips()}>更新tips</button>
<button onClick={() => this.changeDisplayClock()}>改變顯隱</button>
</div>
);
}
}
var vm = ReactDOM.render(
<App />,
document.getElementById("root")
);
</script>
</html>
每日一題
https://github.com/WindrunnerMax/EveryDay
參考
https://www.zhihu.com/question/265784392
https://juejin.cn/post/6844904127013584904
https://zh-hans.reactjs.org/docs/introducing-jsx.html