JSX是React的核心組成部分,它使用XML標記的方式去直接聲明界面,界面組件之間可以互相嵌套。可以理解為在JS中編寫與XML類似的語言,一種定義帶屬性樹結構(DOM結構)的語法,它的目的不是要在瀏覽器或者引擎中實現,它的目的是通過各種編譯器將這些標記編譯成標準的JS語言。
雖然你可以完全不使用JSX語法,只使用JS語法,但還是推薦使用JSX,可以定義包含屬性的樹狀結構的語法,類似HTML標簽那樣的使用,而且更便于代碼的閱讀。
使用JSX語法后,你必須要引入babel的JSX解析器,把JSX轉化成JS語法,這個工作會由babel自動完成。同時引入babel后,你就可以使用新的es6語法,babel會幫你把es6語法轉化成es5語法,兼容更多的瀏覽器。
我們從最簡單的一個官網例子helloworld開始:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="vendor-js/react.js"></script>
<script src="vendor-js/react-dom.js"></script>
<script src="vendor-js/babel-core/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
</script>
</body>
</html>
在這個簡單的例子中,看不出來有任何jsx語法的地方,當其中<h1>Hello,world</h1>
就是使用到了jsx語法。HTML 語言直接寫在 JavaScript 語言之中,不加任何引號,這就是 JSX 的語法,它允許 HTML 與 JavaScript 的混寫。如果轉化成純JavaScript 的話,就是:
<script type="text/javascript">
ReactDOM.render(
React.DOM.h1(null,'hello,world!'),
document.getElementById('example')
);
</script>
在上述JSX語法中有兩個要注意的地方:
- <script> 標簽的 type 屬性為 text/babel,這是React 獨有的 JSX 語法,跟 JavaScript 不兼容。凡是在頁面中直接使用 JSX 的地方,都要加上 type="text/babel"。
- 一共用了三個庫: react.js 、react-dom.js 和 browser.min.js ,它們必須首先加載。其中,react.js 是 React 的核心庫,react-dom.js 是提供與 DOM 相關的功能, browser.min.js的作用是將 JSX 語法轉為 JavaScript 語法。
將 JSX 語法轉為 JavaScript 語法,這一步很消耗時間。現在前端項目,都會使用前端工程化,不會直接在html頁面中直接寫js代碼,寫好的js代碼都會使用工具進行編譯壓縮等。這樣的話,我們的jsx也會通過編譯直接轉化成js語法,讓瀏覽器直接使用。
ReactDOM.render 是 React 的最基本方法,將模板轉為HTML語言,并插入指定的 DOM 節點。
JSX的特點:
- 類XML語法容易接受,結構清晰
- 增強JS語義
- 抽象程度高,屏蔽DOM操作,跨平臺
- 代碼模塊化
JSX基本語法規則:
JSX本身就和XML語法類似,可以定義屬性以及子元素。唯一特殊的是可以用大括號來加入JavaScript表達式。遇到 HTML 標簽(以 < 開頭),就用 HTML 規則解析;遇到代碼塊(以 { 開頭),就用 JavaScript 規則解析。
var arr = [
<h1>Hello world!</h1>,
<h2>React is awesome</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);
這個就是一個簡單的html與js混用的例子。arr變量中存在html元素,div中又使用了arr這個js變量。轉化成純javascript的話:
var h1=React.DOM.h1(null,'Hello world!');
var h2=React.DOM.h1(null,'React is awesome');
var div=React.DOM.div(null,h1,h2);
ReactDOM.render(
div,
document.getElementById('example')
);
React組件
我們使用jsx來將代碼封裝成React組件,然后像插入普通 HTML 標簽一樣,在其他地方插入這個組件。使用React.createClass用于生成一個組件。
var MyComponent=React.createClass({
render: function() {
return <h1>Hello world!</h1>;
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
上面代碼中,變量 MyComponent就是一個組件類。模板插入 <MyComponent /> 時,會自動生成 MyComponent 的一個實例(下文的"組件"都指組件類的實例)。所有組件類都必須有自己的 render 方法,用于輸出組件。
React 可以渲染 HTML 標簽 (strings) 或 React 組件 (classes)。
- 在react中通常約定組件類的第一個字母必須大寫,html標簽都是小寫。
//要渲染 HTML 標簽,只需在 JSX 里使用小寫字母開頭的標簽名。
var myDivElement = <div className="foo" />;
React.render(myDivElement, document.getElementById('example'));
//要渲染 React 組件,只需創建一個大寫字母開頭的本地變量。
var MyComponent = React.createClass({/.../});
var myElement = <MyComponent />;
React.render(myElement, document.getElementById('example'));
* 還有一個注意點:組件類只能包含一個頂層標簽,否則會報錯。 ```javascript
//var myDivElement =<h1>你好</h1><h1>hello</h1>;
//上述寫法是會報錯的,要寫成只有一個頂層標簽:
var myDivElement =<div><h1>你好</h1><h1>hello</h1></div>;
上述代碼一個靜態的組件,下面看一個動態組件:
var MyComponent=React.createClass({
getInitialState: function() {
return {clickNum: 0};
},
handleClick:function(){
var num=this.state.clickNum;
num++;
this.setState({clickNum:num});
},
render: function() {
return (
<div>
<h1 onClick={this.handleClick}>Hello {this.props.name}!</h1>
<h2 style={{color:'red'}}>點擊{this.props.name}次數:{this.state.clickNum}</h2>
</div>
);
}
});
ReactDOM.render(
<div>
<MyComponent name="張三" />
<hr/>
<MyComponent name="李四" />
</div>,
document.getElementById('example')
);
上面代碼中定義的MyComponent組件包含屬性,狀態和事件,是一個簡單的比較完整的組件。使用props通過父組件進行傳遞值,使用state定義組件自己的狀態,組件支持的大部分的DOM操作事件。
關于屬性props:
- class 屬性需要寫成 className ,for 屬性需要寫成 htmlFor ,這是因為 class 和 for 是 JavaScript 的保留字。
- 直接在標簽上使用style屬性時,要寫成style={{}}是兩個大括號,外層大括號是告知jsx這里是js語法,和真實DOM不同的是,屬性值不能是字符串而必須為對象,需要注意的是屬性名同樣需要駝峰命名法。即margin-top要寫成marginTop。
- this.props.children 不要children作為把對象的屬性名。因為this.props.children獲取的該標簽下的所有子標簽。this.props.children 的值有三種可能:如果當前組件沒有子節點,它就是 undefined ;如果有一個子節點,數據類型是 object ;如果有多個子節點,數據類型就是 array 。所以,處理 this.props.children 的時候要小心。官方建議使用React.Children.map來遍歷子節點,而不用擔心數據類型。
關于狀態state:
- 組件免不了要與用戶互動,React 將組件看成是一個狀態機,一開始有一個初始狀態,然后用戶互動,導致狀態變化,從而觸發重新渲染 UI。
- getInitialState 方法用于定義初始狀態,也就是一個對象,這個對象可以通過 this.state 屬性讀取。當用戶點擊組件,導致狀態變化,this.setState 方法就修改狀態值,每次修改以后,自動調用 this.render 方法,再次渲染組件。
由于 this.props 和 this.state 都用于描述組件的特性,可能會產生混淆。一個簡單的區分方法是,this.props 表示那些一旦定義,就不再改變的特性,而 this.state 是會隨著用戶互動而產生變化的特性。
注意點:
如果往原生 HTML 元素里傳入 HTML 規范里不存在的屬性,React 不會顯示它們。如果需要使用自定義屬性,要加 data- 前綴。
<div data-custom-attribute="foo" />
PropTypes
組件的屬性props可以接受任意值,字符串、對象、函數等等都可以。有時,我們需要一種機制,驗證別人使用組件時,提供的參數是否符合要求。React中使用PropTypes進行參數的校驗。
var MyTitle = React.createClass({
propTypes: {
title: React.PropTypes.string.isRequired,
},
render: function() {
return <h1> {this.props.title} </h1>;
}
});
上面的Mytitle組件有一個title屬性。PropTypes 告訴 React,這個 title 屬性是必須的,而且它的值必須是字符串。當我們給title傳遞一個數字時,控制臺就會報錯:
Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.
此外,getDefaultProps 方法可以用來設置組件屬性的默認值。
獲取真實的DOM節點
組件并不是真實的 DOM 節點,而是存在于內存之中的一種數據結構,叫做虛擬 DOM (virtual DOM)。只有當它插入文檔以后,才會變成真實的 DOM 。
有時需要從組件獲取真實 DOM 的節點,這時就要用到 ref 屬性。
var MyComponent = React.createClass({
handleClick: function() {
this.refs.myTextInput.focus();
},
render: function() {
return (
<div>
<input type="text" ref="myTextInput" />
<input type="button" value="Focus the text input" onClick={this.handleClick} />
</div>
);
}
});
為了獲取真是DOM節點,html元素必須有一個 ref 屬性,然后 this.refs.[refName] 就會返回這個真實的 DOM 節點。需要注意的是,由于 this.refs.[refName] 屬性獲取的是真實 DOM ,所以必須等到虛擬 DOM 插入文檔以后,才能使用這個屬性,否則會報錯。
求值表達式
要使用 JavaScript 表達式作為屬性值,只需把這個表達式用一對大括號 ( { } ) 包起來,不要用引號 ( " " )。
在編寫JSX時,在 { } 中不能使用語句(if語句、for語句等等),但可以使用求值表達式,這本身與JSX沒有多大關系,是JS中的特性,它是會返回值的表達式。我們不能直接使用語句,但可以把語句包裹在函數求值表達式中運用。
條件判斷的寫法
你沒法在JSX中使用 if-else 語句,因為 JSX 只是函數調用和對象創建的語法糖。在 { } 中使用,是不合法的JS代碼,不過可以采用三元操作表達式
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name ? this.props.name : "World"}</div>;
}
});
ReactDOM.render(<HelloMessage name="xiaowang" />, document.body);
可以使用比較運算符“ || ”來書寫,如果左邊的值為真,則直接返回左邊的值,否則返回右邊的值,與if的效果相同。
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name || "World"}</div>;
}
});
函數表達式
( )有強制運算的作用
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {
(function(obj){
if(obj.props.name)
return obj.props.name
else
return "World"
}(this))
}</div>;
}
});
ReactDOM.render(<HelloMessage name="xiaowang" />, document.body);
外括號“ )”放在外面和里面都可以執行。唯一的區別是括號放里面執行完畢拿到的是函數的引用,然后再調用“function(){}(this)()”;括號放在外面的時候拿到的事返回值。
組件的生命周期
組件的生命周期分成三個狀態:
* Mounting:已插入真實 DOM
* Updating:正在被重新渲染
* Unmounting:已移出真實 DOM
React 為每個狀態都提供了兩種處理函數,will 函數在進入狀態之前調用,did 函數在進入狀態之后調用,三種狀態共計五種處理函數。
* componentWillMount()
* componentDidMount()
* componentWillUpdate(object nextProps, object nextState)
* componentDidUpdate(object prevProps, object prevState)
* componentWillUnmount()
此外,React 還提供兩種特殊狀態的處理函數。
* componentWillReceiveProps(object nextProps):已加載組件收到新的參數時調用
* shouldComponentUpdate(object nextProps, object nextState):組件判斷是否重新渲染時調用
注釋
JSX 里添加注釋很容易;它們只是 JS 表達式而已。你只需要在一個標簽的子節點內(非最外層)小心地用 {} 包圍要注釋的部分。
{/* 一般注釋, 用 {} 包圍 */}