JSX 知識準備
JSX 並不是一門全新的語言,僅僅是一個語法糖,允許開發者在javascript中編寫XML語言。這樣使用JavaScript來構建元件以及元件之間關係的應用,在程式碼層面顯得更加清晰,而不再是使用JavaScript操作DOM來建立元件以及元件之間的巢狀關係。
作為是React的核心部分,JSX使用XML標記的方式直接宣告頁面。在JavaScript程式碼裡直接寫XML的語法,每一個XML標籤都會被JSX轉換工具轉換成純JavaScript程式碼。HTML直接寫到JavaScript中不加任何分號,這就是JSX的語法奧義
JSX環境準備
JSX必須藉助ReactJS環境才能執行。在編寫JSX程式碼之前,需要先載入ReactJS檔案,比react.js.光有ReactJs還不行,還需要載入JSX解析器。下面大家一起準備好一個最基礎的ReactJS環境
-
建立一個test.html檔案
-
在
<head>
標籤中引入如下檔案<script src="http://cdn.bootcss.com/react/15.2.0/react.js"></script> <script src="http://cdn.bootcss.com/react/15.2.0/react-dom.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script> 複製程式碼
這樣環境就搭建好了,然後按照慣例,雙手奉上一個Hello World 程式。
<body>
<div id="example"></div>
<script type="text/babel">
var HelloComponent = React.createClass({
render: function(){
return <span>Hello World !</span>
}
});
ReactDOM.render(
<HelloComponent />,
document.getElementById('example')
);
</script>
</body>
複製程式碼
執行結果
如上面程式碼,我們在JavaScript中書寫HTML標籤時,不再像以前那樣作為字串用引號引起來,而是想在XML檔案中書寫一樣,直接寫即可。
除了<span>Hello World !</span>
這種直接使用的HTML標籤外,還有一個<HelloComponent />
標籤。這個是ReactJS建立的元件類標籤,通過這種方式,把建立的HelloComponent元件引用進來。ReactJS約定,自定義的元件標籤首字母一定要大些,用來區分是元件標籤還是HTML標籤。
注意幾點
- 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語法,讓瀏覽器直接使用。
JSX執行JavaScript表示式
JSX使用{}來執行JavaScript表示式。
var arr = [
<h1>Hello world!</h1>,
<h2>React is awesome</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);
複製程式碼
JSX定義屬性&&樣式使用
HTML中可以通過標籤上的屬性來改變當前元素的樣式,當然,這在JSX中也是可以的,只不過JSX在使用行內樣式的時候是有缺陷的。。使用{{}}而不是引號的方式,如下
React.render(
<div style={{color:'red',marginTop:'20px'}}>
xxxxx
</div>,
document.body
);
複製程式碼
注:直接在標籤上使用style屬性時,要寫成style={{}}是兩個大括號,外層大括號是告知jsx這裡是js語法,和真實DOM不同的是,屬性值不能是字串而必須為物件,需要注意的是屬性名同樣需要駝峰命名法。即margin-top要寫成marginTop,屬性之間以逗號間隔。還有class 屬性需要寫成 className ,for 屬性需要寫成 htmlFor ,這是因為 class 和 for 是 JavaScript 的保留字。
JSX中的延展屬性
在JSX中我們可以使用ES6中的最新語法...
來遍歷物件
var HelloMessage = React.createClass({
render: function() {
return <h1>Hello {this.props.name} get {this.props.votes} votes</h1>;
}
});
var Lucy = {
name: "feng",
votes: "23"
}
ReactDOM.render(
<HelloMessage {...Lucy} />,
document.getElementById('example')
);
複製程式碼
JSX中的事件繫結
JSX支援事件的繫結,語法類似於HTML中事件的繫結,不同的是這裡事件的名稱必須按照駝峰式,下面給個例子大家參考一下。
var HelloComponent = React.createClass({
testClick: function (str) {
alert('hello ' + str)
},
render: function() {
return (
<p
onClick={this.testClick.bind(this, 'feng')} style={{olor:'#ff6600',width:'200px', height:'100p'}}
>
Click me
</p>
)
}
})
ReactDOM.render(
<HelloComponent />,
document.getElementById('example')
);
複製程式碼
知曉了JSX的基本使用之後,開始介紹ReactJS的元件的概念和使用方法
元件初步
ReactJS的基礎就是例項化,即按功能封裝成一個又一個的元件,各個元件維護自己的狀態和UI,當狀態改變時,會自動重新渲染整個元件,多個元件一起協作構成了整個ReactJS應用。
以一個簡單的Hello World開始
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://cdn.bootcss.com/react/15.2.0/react.js"></script>
<script src="http://cdn.bootcss.com/react/15.2.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
<head>
<body>
<div id="example"></div>
<script type="text/babel">
var HelloMessage = React.createClass({
render: function() {
return <div>Hello,{this.props.name}</div>
}
})
ReactDOM.render(<HelloMessage name="World" />, document.getElementById('example'))
</script>
</body>
複製程式碼
執行結果:
審查元素髮現DOM結構如下:
上面的程式碼中,我們呼叫React提供的工廠方法來建立元件,其中:
- React是全域性物件,ReactJS的所有頂級API都掛在這個物件下。
- React.createClass是ReactJS用來建立元件類的方法,這個物件必須包含一個render方法和若干可選的生命週期(詳情請見下文)方法,其中
- render是一個元件級的API,是React.createClass內部最常用的API,該方法主要用來返回元件結構
- React.render(這裡我用的是ReactDOM.render)是最常用的方法,用於將模板轉換成HTML語言,並將轉換後的DOM結構插入到制定的DOM節點上
- 簡而言之就是:先通過React.createClass來建立一個元件類,呼叫元件的render方法輸出元件的DOM結構,最後呼叫React.render將組建插入到id為example的節點上
這裡我們需要保證render函式是純函式:即同樣的輸入始終返回相同的輸出,並且執行過程中沒有副作用(和DOM互動或者發Ajax請求)。但一個元件要和DOM互動或者發Ajax請求需求是很正常的,那麼就要用到其他生命週期方法了。
##元件的狀態與屬性
元件本質上是狀態機,輸入確定,輸出一定確定。元件把狀態與結果一一對應起來,元件中有state與prop(狀態與屬性)。
- 屬性(props)是由父元件傳遞給子元件的;
- 狀態(state)是子元件內部維護的資料,當狀態發生變化的同時,元件也會進行更新。當狀態發生轉換時會觸發不同的鉤子函式(詳見下文中生命週期),從而讓開發者有機會做出相應。
props屬性的用法
props是一個物件,是組建用來接收外面傳來的引數,元件內部是不允許修改自己的props屬性的,只能夠通過父元件來修改。具體的使用方法如下
-
接收鍵值對資料
<HelloWorld name= ? />
-
字串:"XiaoFeng"
-
求值表示式 {123}、{"XiaoFeng"}
-
陣列{[1,2,3]}
-
變數{variable}
-
函式求值表示式{function}(不推薦,如果需要函式可以單獨把函式提取出來然後單獨呼叫函式)
var HelloWorld =React.createClass({ render:function () { console.log(this.props) return <p>Hello,{this.props.name ? this.props.name : " World"}</p>; }, }); var HelloUniverse = React.createClass({ getInitialState:function () { return { name:'' }; }, handleChange: function (event) { this.setState({name: event.target.value}); }, render: function () { return (<div> <HelloWorld name={this.state.name}></HelloWorld> <br/> <input type="text" onChange={this.handleChange} /> </div> ); } }); ReactDOM.render(<HelloUniverse />,example); 複製程式碼
-
-
setProps
已過時,即將廢棄
-
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 屬性的值是一個數值。
var data = 123; ReactDOM.render( <MyTitle title={data} />, document.body ); 複製程式碼
這樣一來,title屬性就通不過驗證了。控制檯會顯示一行錯誤資訊
-
getDefaultProps
getDefaultProps 方法可以用來設定元件屬性的預設值
var MyTitle = React.createClass({ getDefaultProps : function () { return { title : 'Hello World' }; }, render: function() { return <h1> {this.props.title} </h1>; } }); ReactDOM.render(<MyTitle />,document.body); 複製程式碼
-
this.props.children
this.props 物件的屬性與元件的屬性一一對應,但是有一個例外,就是 this.props.children 屬性。它表示元件的所有子節點
var NotesList = React.createClass({ render: function() { return ( <ol> { React.Children.map(this.props.children, function (child) { return <li>{child}</li>; }) } </ol> ); } }); ReactDOM.render( <NotesList> <span>hello</span> <span>world</span> </NotesList>,example ); 複製程式碼
上面程式碼的 NoteList 元件有兩個 span 子節點,它們都可以通過 this.props.children 讀取。這裡需要注意, this.props.children 的值有三種可能:
- 如果當前元件沒有子節點,它就是 undefined;
- 如果有一個子節點,資料型別是 object;
- 如果有多個子節點,資料型別就是 array
React 提供一個工具方法 React.Children 來處理 this.props.children 。我們可以用 React.Children.map 來遍歷子節點,而不用擔心 this.props.children 的資料型別是 undefined 還是 object。
state屬性的用法
React把元件當成狀態機,一旦使用者互動導致狀態發生變化,就會觸發重新渲染UI。this.state會隨著使用者的互動而發生變化。
-
工作原理
常用的通知 React 資料變化的方法是呼叫 setState(data, callback)。這個方法會合並(merge) data 到 this.state,並重新渲染元件。渲染完成後,呼叫可選的 callback 回撥。大部分情況下不需要提供 callback,因為 React 會負責把介面更新到最新狀態。
-
getInitialState
object getInitialState()
getInitialState 方法用於定義初始狀態,也就是一個物件,這個物件可以通過 this.state 屬性讀取。在元件掛載之前呼叫一次。返回值將會作為 this.state 的初始值。
-
setState
setState(object nextState[, function callback])
this.setState 方法用於修改狀態值,每次修改以後,自動呼叫 this.render 方法,再次渲染元件。
注意:
- 絕對不要直接改變 this.state,因為在之後呼叫 setState() 可能會替換掉你做的更改。把 this.state 當做不可變的。
- setState()不會立刻改變 this.state,而是建立一個即將處理的state轉變。在呼叫該方法之後獲取 this.state 的值可能會得到現有的值,而不是最新設定的值。
- 不保證 setState()呼叫的同步性,為了提升效能,可能會批量執行state轉變和DOM 渲染。
- setState ( ) 將總是觸發一次重繪,除非在 shouldComponentUpdate()中實現了條件渲染邏輯。如果使用可變的物件,但是又不能在shouldComponentUpdate()中實現這種邏輯,僅在新 state 和之前的 state 存在差異的時候呼叫 setState()可以避免不必要的重新渲染。
常用的模式是建立多個只負責渲染資料的無狀態(stateless)元件,在它們的上層建立一個有狀態(stateful)元件並把它的狀態通過 props 傳給子級。這個有狀態的元件封裝了所有使用者的互動邏輯,而這些無狀態元件則負責宣告式地渲染資料。
元件的生命週期
生命週期各階段
在整個ReactJS的宣告週期中,主要會經歷這四個階段:建立階段、例項化階段、更新階段、銷燬階段。下面通過一段程式碼,來深入瞭解元件各個環節的運作流程
var OneComponent = React.createClass({
//1.建立階段
getDefaultProps: function() {
//在建立類的時候被呼叫
console.log('getDefaultProps');
return {}
},
//2.例項化階段
getInitialState: function() {
//獲取this.state的預設值
console.log('getInitialState');
return {};
},
componentWillMount: function() {
//在render之前呼叫此方法
//業務邏輯的處理都應該放在這裡,比如對state的操作等
console.log('componentWillMount')
},
render: function() {
//渲染並返回一個虛擬DOM
console.log('render');
return (
<div> hello <b> {this.props.name} </b></div>
)
},
componentDidMount: function() {
//該方法發生在render方法之後
//在該方法中,ReactJS會使用render方法返回的虛擬DOM物件來建立真實DOM結構
console.log('componentDidMount');
},
//3.個更新階段
componentWillReceieveProps: function() {
//該方法發生在this.props被修改或富元件呼叫setProps()方法之後
console.log('componentWillRecieveProps');
},
shouldComponentUpdate: function() {
//是否需要更新
console.log('shouldComponentUpdate');
return true;
},
componentWillUpadate: function() {
//將要更新
console.log('componentWillUpadate');
},
componentDidUpdate: function() {
//更新完畢
console.log('componentDidUpdate');
},
//4.銷燬階段
componentWillUnmout: function() {
//銷燬時被呼叫
console.log('componentWillUnmout');
}
})
ReactDOM.render(
<OneComponent name="World "/>,
document.getElementById('example')
);
複製程式碼
建立階段:
該階段主要發生在建立元件類的時候,即在呼叫React.createClass的時候。這個階段只會觸發一個getDefaultProps方法,該方法會返回一個物件,並快取下來。然後與富元件制定的props物件合併,最後賦值給this.props作為該元件的預設屬性。
例項化階段
該階段主要發生在例項化元件類的時候,也就是元件類被呼叫的時候。
ReactDOM.render(
<OneComponent name="World "/>,
document.getElementById('example')
);
複製程式碼
該元件被呼叫的時候,這個階段會觸發一系列的流程,具體的執行順序如下所示。
-
getInitialState。初始化元件的state的值,其返回值會賦值給元件的this.state屬性。
-
componentWillMount 在渲染(掛載)之前呼叫一次。
此時this.refs物件為空。如果在此時呼叫this.setState()則會更新this.state物件,而render只會呼叫一次。
-
render 根據state的值,生成頁面需要的虛擬DOM結構,並返回該結構
-
componentDidMount 在渲染之後呼叫一次。根據虛擬DOM結構而生成的真實DOM進行相應的處理,元件內部可以通過this.getDOMNode()來獲取當前元件的節點,然後就可以像在web開發中那樣操作裡面的DOM元素了。
componentDidMount () { const textbox = React.findDOMNode(this.refs.text) if (this.props.autoFocus) textbox.focus() } 複製程式碼
更新階段
主要發生在使用者操作或者元件有更新的時候,此時會根據使用者的操作行為進行相應的頁面結構的調整。該階段也會觸發一系列的流程,具體的執行順序如下所示。
-
componentWillReceiveProps 在將要接收props時呼叫。在該函式中,通常可以呼叫this.setState方法來完成對state的修改。
props是父元件傳遞給資組建的。父元件發生render的時候子元件就會呼叫componentWillReceiveProps(不管props有沒有更新,也不管父子元件之間有沒有資料交換)。
componentWillReceiveProps (nextProps) { if (this.props.disabled !== nextProps.disabled) { // disabled這個屬性改變了 } } 複製程式碼
-
shouldComponentUpdate:該方法用來攔截新的props或state,然後根據事先設定好的判斷邏輯,返回值決定元件是否需要update。
元件掛載之後,每次呼叫setState後都會呼叫shouldComponentUpdate判斷是否需要重新渲染元件。預設返回true,需要重新render。在比較複雜的應用裡,有一些資料的改變並不影響介面展示,可以在這裡做判斷,優化渲染效率。
shouldComponentUpdate (nextProps, nextState) { // 比較props或者states,返回true則更新照常,返回false則取消更新,且不會呼叫下面的兩個生命週期函式 } 複製程式碼
-
componentWillUpdate :元件更新之前呼叫一次。當上一部中shouldComponentUpdate方法返回true時候,就可以在該方法中做一些更新之前的操作。
-
render 根據一系列的diff演算法,聲稱需要更心動額虛擬DOM資料。實際表明,在render中,最好只做資料和模板的結合,而不進行state等邏輯的修改,這樣元件結構更清晰。
-
componentDidUpdate 元件的更新已經同步到DOM中後出發。
除了首次render之後呼叫componentDidMount,其它render結束之後都是呼叫componentDidUpdate。
銷燬階段
- componentWillUnmount 會在元件即將從掛載點移去時呼叫,用來取出去除即將被銷燬的DOM節點的引用,或者是清除計時器,取消監聽的時間等等。
注意:
絕對不要在componentWillUpdate和componentDidUpdate中呼叫this.setState方法,否則將導致無限迴圈呼叫。
componentWillMount、componentDidMount和componentWillUpdate、componentDidUpdate可以對應起來。區別在於,前者只有在掛載的時候會被呼叫;而後者在以後的每次更新渲染之後都會被呼叫。
元件生命週期流程圖
元件之間的通訊
先建立一個父類元件Parent,它內部呼叫一個叫做Child的子元件,並將接收到的外部引數name傳遞給子元件Child。
var Parent = React.createClass({
handleClick: function() {
this.refs.myTextInput.focus();
},
render: function() {
return (
<div onClick={this.handleClick}>
<input type="text" ref="myTextInput" />
Parent is:
<Child name={this.props.name}></Child>
</div>
)
}
});
複製程式碼
在建立一個子類元件Child
var Child = React.createClass({
render: function() {
return <span>{this.props.name}</span>
}
});
複製程式碼
自後通過React.render方法將元件渲染到頁面上
ReactDOM.render(
<Parent name="React" />,document.getElementById('example')
);
複製程式碼
執行結果
整個應用的功能是父元件接收傳入的使用者名稱稱,並將用於名稱傳給子元件,最後再由子元件渲染顯示到頁面上。這些元件直接按是怎麼通訊的呢?下面通過兩方面介紹一下
- 子元件呼叫父元件的方法。從上面的例子可以看出,子元件要拿到父元件的屬性,需要通過this.props方法。所以,如果子元件想要呼叫父元件的方法,只需要父元件把要被呼叫的方法以屬性的方式放在子元件上,子元件內部便可通過"this.props.被呼叫的方法"這樣的方式拿到name屬性的。然後,每次父元件修改了傳入的name屬性,子元件便會得到通知,然後自動獲取新的name屬性
- 父元件呼叫子元件的方法。子元件呼叫父元件是通過prop屬性,而反過來,父元件呼叫子元件通過的就是 ref 屬性。子元件被設定了ref屬性之後,父元件便可以通過this.ref.xxx來獲取到子元件了,其中xx為子元件的ref值。
元件實踐
1. 靜態元件
var MyComponent=React.createClass({
render: function() {
return <h1 className="my-h1" style={{color:'red'}}>Hello world!</h1>;
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
複製程式碼
執行結果
2. 動態元件
var Counter = React.createClass({
incrementCount: function(){
this.setState({
count: this.state.count + 1
});
},
getInitialState: function(){
return {
count: 0
}
},
render: function(){
return (
<div className="my-component">
<h1>Count: {this.state.count}</h1>
<button type="button" onClick={this.incrementCount}>{this.props.name}</button>
</div>
);
}
});
ReactDOM.render(
<Counter name="遞增按鈕" />,
document.getElementById('mount-point')
);
複製程式碼
執行結果:
參考內容
- React入門例項教程
- React Native入門與實踐
- React 生命週期小結