什麼是JSX
JSX是JavaScript XML,是React提供的Syntax Sugar, 能讓我們可以在JS中寫html標記語言。
其表現是如何:
- 常規的html代碼都可以寫,可以通過{props}往html中插入變量或任意有效的JS表達式,而無須加上$
- 此外還可以插入帶參數的函數{func(props)}
<h1>Hello, {getName(props)}</h1>
- JSX被編譯後,是一個函數調用,返回值爲JS對象,故JSX也可作爲表達式,例如用於If判斷
- 可以在標籤中添加屬性,屬性值若是字符串,則加上引號,若是對象或表達式,則加上{}. ""與{}不能混用。由於JSX更貼近JS,故屬性的key建議使用駝峯法寫法
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;
- 若JSX元素沒有子元素/節點,可以單閉合
const element = <img src={user.avatarUrl} />;
- 可以給html添加類但class需改寫成className,另外若添加自定義的要渲染的屬性,最好以data-開頭
JSX將XML語法加入到JavaScript中,在JS中寫了JSX將會被預處理成React Element
爲什麼React要創建JSX:
渲染的邏輯處理與UI邏輯其實是耦合的, event, state, data互相關聯,既然如此,那麼就把html標記語言與邏輯處理相關的js內容放在一起,組成一個鬆耦合的模塊,這個模塊就是JSX元素
寫JSX實際做了什麼
首先怎麼才能寫JSX呢,在普通的JS文件中需引入react,reactDOM(若要對DOM進行操作)以及babel或者通過Babel在線編譯
JSX其實是一個對象,而且這個對象內的值都被進行了轉義(escape),見以下的例子:
var esca = <a href='https://baidu.com'><span>5>3{true && '--this is true'}</span></a>
console.log(esca)
ReactDOM.render(
esca,
document.getElementById('root')
);
輸出結果如下
JSX轉義.png
什麼? 我的true呢?
讓我們把代碼放到Babel看下JSX生成了什麼東西
//Babel輸出
var esca = React.createElement(
'a',
{ href: 'https://baidu.com' },
React.createElement(
'span',
null,
'5>3 ',
true && '$gt;this is true'
)
);
可以看到react將JSX代碼片段分爲幾塊,類型(元素名), 屬性值props(包含children和屬性參數等), key和ref, owner和store
我們發現沒有被{}包住的默認是字符串,會進行轉義,而{}包住的則會被當作表達式,不被轉義,由於true是真,故顯示後面的值,但問題來了,若我們想在表達式裏要進行轉義成HTML呢?
方法一: 使用Unicode編碼
var esca = <a href='https://baidu.com'><span>5>3 {true && '\u003Ethis is true'}</span></a>
//這樣在表達式裏也可以進行轉義了
注意,若是直接使用
var esca = React.createElement('span', null, '5>3 this is true;')
創建的,字符串和表達式內的均不會進行轉義
方法二: 使用dangerouslySetInnerHTML
var esca = <a href='https://baidu.com'>5>3{true &&<span dangerouslySetInnerHTML={{__html: ' >this is true'}}></span>}</a>
//或者定義一個新的JS元素,將需要轉義的內容放入
<AllowedHtml html={data}/>
等價於
var esca = React.createElement(
'a',
{ href: 'https://baidu.com' },
'5>3',
true && React.createElement('span', { dangerouslySetInnerHTML: { __html: ' >this is true' } })
);
方法三:在{}通過數組將字符串和表達式包裹在一起
<div>{['First ', <span>·</span>, ' Second']}</div>
此外對於某些字體圖標,可以使用以下的方法<i data-icon={String.fromCharCode( "f00f" )} />
關於JSX防範XSS攻擊
XSS是跨站腳本注入攻擊, 更多解釋可查看這裏
- 由於當你嘗試通過{html}進行插入html代碼時, React會自動將html轉爲字符串,故React可部分防止XSS攻擊
例如以下代碼
const username = "<img onerror='alert(\"Hacked!\")' src='invalid-image' />";
class UserProfilePage extends React.Component {
render() {
return (
<h1> Hello {username}!</h1>
);
}
}
ReactDOM.render(<UserProfilePage />, document.querySelector("#app"));
顯示爲
xss_{}.png
- JSX中是通過傳入函數作爲事件處理方式,而不是傳入字符串,字符串可能包含惡意代碼
儘管如此,還是可以通過一些手段進行XSS攻擊,如:<a href="{...}" />, <img src={...} />, <iframe src="{...} />,css注入style={...} prop,還可利用上述所說的一些方法
const aboutUserText = "<img onerror='alert(\"Hacked!\");' src='invalid-image' />";
class AboutUserComponent extends React.Component {
render() {
return (
<div dangerouslySetInnerHTML={{"__html": aboutUserText}} />
);
}
}
ReactDOM.render(<AboutUserComponent />, document.querySelector("#app"))
或者通過設置a的href爲javascript:xxx
const userWebsite = "javascript:alert('Hacked!');";
class UserProfilePage extends React.Component {
render() {
return (
<a href={userWebsite}>My Website</a>
)
}
}
ReactDOM.render(<UserProfilePage />, document.querySelector("#app"));
以及使用base64 編碼的數據進行替換
const userWebsite = "data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGFja2VkISIpOzwvc2NyaXB0Pg==";
class UserProfilePage extends React.Component {
render() {
const url = userWebsite.replace(/^(javascript\:)/, "");
return (
<a href={url}>My Website</a>
)
}
}
ReactDOM.render(<UserProfilePage />, document.querySelector("#app"));
又或從用戶處接受了被惡意控制的props
const customPropsControledByAttacker = {
dangerouslySetInnerHTML: {
"__html": "<img onerror='alert(\"Hacked!\");' src='invalid-image' />"
}
};
class Divider extends React.Component {
render() {
return (
<div {...customPropsControledByAttacker} />
);
}
}
ReactDOM.render(<Divider />, document.querySelector("#app"));