react中JSX詳解

什麼是JSX

JSX是JavaScript XML,是React提供的Syntax Sugar, 能讓我們可以在JS中寫html標記語言。

其表現是如何:

  1. 常規的html代碼都可以寫,可以通過{props}往html中插入變量或任意有效的JS表達式,而無須加上$
  2. 此外還可以插入帶參數的函數{func(props)}
<h1>Hello, {getName(props)}</h1>
  1. JSX被編譯後,是一個函數調用,返回值爲JS對象,故JSX也可作爲表達式,例如用於If判斷
  2. 可以在標籤中添加屬性,屬性值若是字符串,則加上引號,若是對象或表達式,則加上{}. ""與{}不能混用。由於JSX更貼近JS,故屬性的key建議使用駝峯法寫法
const element = <div tabIndex="0"></div>;

const element = <img src={user.avatarUrl}></img>;
  1. 若JSX元素沒有子元素/節點,可以單閉合
const element = <img src={user.avatarUrl} />;
  1. 可以給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&gt;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&gt;3 {true && '\u003Ethis is true'}</span></a>
//這樣在表達式裏也可以進行轉義了

注意,若是直接使用var esca = React.createElement('span', null, '5&gt;3 this is true;')創建的,字符串和表達式內的均不會進行轉義

方法二: 使用dangerouslySetInnerHTML

var esca = <a href='https://baidu.com'>5&gt;3{true &&<span dangerouslySetInnerHTML={{__html: ' &gt;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: ' &gt;this is true' } })
);



方法三:在{}通過數組將字符串和表達式包裹在一起

<div>{['First ', <span>&middot;</span>, ' Second']}</div>

此外對於某些字體圖標,可以使用以下的方法
<i data-icon={String.fromCharCode( "f00f" )} />

關於JSX防範XSS攻擊

XSS是跨站腳本注入攻擊, 更多解釋可查看這裏

  1. 由於當你嘗試通過{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

  1. 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"));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章