React技術
注意:本章節運行環境在上一章節React項目構建之上。
簡介
- React是Facebook開發並開源的前端框架。
- 當時他們的團隊在市面上沒有找到合適的MVC框架,就自己寫了一個Js框架,用來架設Instagram(圖片分享社交 網絡)。2013年React開源。
- React解決的是前端MVC框架中的View視圖層的問題。
Virtual DOM
-
DOM(文檔對象模型Document Object Model)
-
將網頁內所有內容映射到一棵樹型結構的層級對象模型上,瀏覽器提供對DOM的支持,用戶可以是用腳本調用DOM API來動態的修改DOM結點,從而達到修改網頁的目的,這種修改是瀏覽器中完成,瀏覽器會根據DOM的改變重繪改變的DOM結點部分。
-
修改DOM重新渲染代價太高,前端框架爲了提高效率,儘量減少DOm的重繪,提出了Virtual DOM。Virtual DOM是一個JavaScript對象,性能更高。所有的修改都是先生成一個新的Virtual DOM,通過比較算法比較新舊Virtual DOM,得到差異化Virtual DOM,將這部分差異更新到瀏覽器DOM,瀏覽器只需要渲染這部分變化就行了。
React實現了DOM Diff算法可以高效比對Virtual DOM。
支持JSX語法
JSX是一種JavaScript和XML混寫的語法,是JavaScript的擴展。
React.render(
<div>
<div>
<div>content</div>
<div/>
</div>,
document.getElementById('example')
)
測試程序
- 替換
/src/index.js
爲先的代碼
import React from 'react';
import ReactDom from 'react-dom';
class Root extends React.Component{
render(){
return <div>hello xdd</div>
}
}
ReactDom.render(<Root/>,document.getElementById("root"))
- 保存文件後,會自動編譯,並重新裝載刷新瀏覽器端頁面。
程序解釋
import React from 'react';
導入react模塊import ReactDOM from 'react-dom';
導入react的DOM模塊class Root extends React.Component
組件類定義,從React.Component類上繼承。這個類生成JSXElement對象即React元素。render()
渲染函數。返回組件中渲染的內容。注意,只能返回唯一一個頂級元素回去。ReactDom.render(<Root/>,document.getElementById('root'));
第一個參數是JSXElement對象,第二個是DOM的Element元素。將React元素添加到DOM的Element元素中並渲染。
- 還可以使用React.createElement創建react元素,第一個參數是React組件或者一個HTML的標籤名稱(例如div、span)。
return React.createElement('div',null,'hello hello xdd');
ReactDom.reader(React.createElement(Root),document.getElementById("root"));
-
很明顯JSX更簡潔易懂,推薦使用JSX語法。
-
增加一個子元素
import React from 'react';
import ReactDom from 'react-dom';
class SubEle extends React.Component{
render(){
return <div>Sub content</div>;
}
}
class Root extends React.Component{
render(){
return(<div><h2>Welcome xdd.com</h2><br/> <SubEle/></div>);
}
}
ReactDom.render(<Root />,document.getElementById('root'));
- 注意:
- React組件的render函數return返回,只能是一個頂級元素
- JSX語法是XML,要求所有元素必須閉合,注意
<br />
不能寫成<br>
JSX規範
- 約定標籤中首字母小寫就是html標記,首字母大寫就是組件
- 要求嚴格的HTML標記,要求所有標籤都必須閉合。
br
也應該寫成<br />
,/
前留一個空格。 - 單商省略小括號,多行請使用小括號
- 元素有嵌套,建議多行,注意縮進
- JSX表達式:表達式使用
{}
括起來,如果大括號內使用了引號,會當做字符串處理,例如<div>{'2>1?true:false'}</div>
裏面的表達式成了字符串了。
組件狀態state
-
每一個React組件都有一個狀態屬性state,它是一個JavaScript對象,可以爲它定義屬性來保存值。如果狀態變化了,會觸發UI的重新渲染。使用setState()方法可以修改state值。
-
注意:state是每一個組件自己內部使用的,是組件自己的屬性。
-
1.示例:依然修改
/src/index.js
import React from 'react';
import ReactDom from 'react-dom';
class Root extends React.Component{
//定義一個對象
state = {p1:'www.hao123',p2:'.com'};//構造函數中定義state
render(){
this.state.p1 = 'python.xdd';// 可以修改屬性值
// this.setState(p1:'python.xddabc');//不可用對還在更新中的state使用setState。會觸發遞歸調用render函數
// Warning: setState(...): Cannot update during an existing state transition (such as within render).
setTimeout(()=> this.setState({p1:'python.xdd'}),1000);
return(
<div>
<div>Welcome to {this.state.p1}{this.state.p2}</div>
</div>
);
}
}
ReactDom.render(<Root />,document.getElementById('root'));
- 2.複雜例子
先看一個網頁
<html>
<head>
<script type="text/javascript">
function getEventTrigger(event) {
x = event.target; // 從事件中獲取元素
alert("觸發的元素的id是:" + x.id);
}
</script>
</head>
<body>
<div id="t1" onmousedown="getEventTrigger(event)"> 點擊這句話,會觸發一個事件,並彈出一個警示框 </div>
</body>
</html>
-
div的id是t1,鼠標按下事件捆綁了一個函數,只要鼠標在對象上按下就會觸發調用getEventTrigger函數,瀏覽器 會送給它一個參數event。event是事件對象,當事件觸發時,event包含觸發這個事件的對象。
-
HTML DOM的JavaScript事件
屬性 | 此事件發生在何時 |
---|---|
onabort | 圖像加載被中斷 |
onblur | 元素失去焦點 |
onchange | 域的內容被改變 |
onclick | 當用戶點擊某個對象時調用的事件句柄 |
ondblclick | 當用戶雙擊某個對象時調用的事件句柄 |
onerror | 在加載文檔或圖像時發生錯誤 |
onfocus | 元素獲得焦點 |
onkeydown | 某個鍵盤按鍵被按下 |
onkeypress | 某個鍵盤按鍵被按下並鬆開 |
onkeyup | 某個鍵盤按鍵被鬆開 |
onload | 一張頁面或一幅圖像完成加載 |
onmousedown | 鼠標按鈕被按下 |
onmousemove | 鼠標被移動 |
onmouseout | 鼠標從某元素移開 |
onmouseover | 鼠標移到某元素之上 |
onmouseup | 鼠標按鍵被鬆開 |
onreset | 重置按鈕被點擊 |
onresize | 窗口或框架被重新調整大小 |
onselect | 文本被選中 |
onsubmit | 確認按鈕被點擊 |
onunload | 用戶退出頁面 |
- 使用React實現上面的傳統的HTML
import React from 'react';
import ReactDom from 'react-dom';
class Toggle extends React.Component{
state = {flag:true}; //類中定義state
handleClick(event){
console.log(event.target.id);
console.log(event.target === this);
console.log(this);
console.log(this.state);
this.setState({flag:!this.state.flag});
}
render(){
/*注意一定要綁定this onClick寫成小駝峯 */
return <div id='xdd' onClick={this.handleClick.bind(this)}>
點擊這句話會觸發一個事件。{this.state.flag.toString()}
</div>;
}
}
class Root extends React.Component {
//定義一個對象
state = {p1:'www.xdd',p2:'.com'};//構造函數中定義state
render(){
return(
<div>
<div>Welcome to {this.state.p1}{this.state.p2}</div>
<br />
<Toggle />
</div>
)
}
}
ReactDom.render(<Root />,document.getElementById('root'))
- Toggle類分析
- 它有自己的state屬性
- 當render完成後,網頁上有一個div標籤,div標籤對象捆綁了一個click事件的處理函數,div標籤內有文本內容。如果通過點擊左鍵,就觸發了click方法關聯的handleClick函數,在這個函數裏將狀態值改變。
- 使用
this.state.flag = !this.state.flag
方式改變,不會觸發render函數執行 - 使用
this.setSatte({flag:!this.state.flag})
方式,會觸發該組件和其子組件render函數的執行
- 使用
- 狀態值state的改變將引發render重繪。
- 如果組件自己的state變了,只會觸發自己的render方法重繪。
-
注意:
{this.handleClick.bind(this)}
,不能外加引號this.handleClick.bind(this)
一定要綁定this,否則當觸發捆綁的函數時,this是函數執行的上下文決定的,this已經不是觸發事件的對象了。console.log(event.target.id)
,取回的參數事件的對象的id,但是這不是我們封裝的組件對象。所以,console.log(event.target===this)
是false。所以這裏一定要用this,而這個this是通過綁定來的。- this寫在類中,始終指的是React組件示例本身。
-
React中的事件
- 使用小駝峯命名
- 使用JSX表達式,表達式中指定事件處理函數
- 不能使用return false,如果要阻止事件默認行爲,使用event.preventDefault()
屬性props
- props就是組件的屬性properties。
- 把React組件當做標籤使用,可以爲其增加屬性。如:
<Toggle name="school" parent={this} />
- 爲上面的Toggle元素增加屬性:
name = "school"
,這個屬性會作爲一個單一的對象傳遞給組件,加入到組件的props屬性中parent = {this}
,注意這個this是在Root元素中,指的是Root組件本身- 在Root中爲使用JSX語法爲Toggle增加子元素,這些子元素也會被加入Toggle組件的props.children中
- 爲上面的Toggle元素增加屬性:
import React from 'react';
import ReactDom from 'react-dom';
class Toggle extends React.Component{
state = {flag:true}; //類中定義state
handleClick(event){
console.log(event.target.id);
console.log(event.target === this);
console.log(this);
console.log(this.state);
this.setState({flag:!this.state.flag});
}
render(){
/*注意一定要綁定this onClick寫成小駝峯 */
return <div id='xdd' onClick={this.handleClick.bind(this)}>
點擊這句話會觸發一個事件。{this.state.flag.toString()}<br />
顯示props<br />
{this.props.name}:{this.props.parent.state.p1}{this.props.parent.state.p2}
{this.props.children}
</div>;
}
}
class Root extends React.Component {
//定義一個對象
state = {p1:'www.xdd',p2:'.com'};//構造函數中定義state
render(){
return(
<div>
<div>Welcome to {this.state.p1}{this.state.p2}</div>
<br />
<Toggle name= "school" parent={this} >{/*自定義2個屬性通過props傳給Togle組件對象 */}
<hr />{/*子元素通過props.children訪問 */}
<span>我是Toggle元素的子元素</span>{/*子元素通過props.children訪問 */}
</Toggle>
</div>
)
}
}
ReactDom.render(<Root />,document.getElementById('root'))
注意:props中的屬性值不能被修改,否則會拋出 TypeError: Cannot assign to read only property 'name' of object '# <Object>
異常。也就是說Props在組件內部不能修改,只讀。但可以通過重繪組件重新定義props內的屬性。
應該說state是私有private的,屬於組件自己的屬性,組件外無法直接訪問。可以修改state,但是建議使用setState方法。
props是共有public屬性,組件外也可以訪問,但組件內只讀。
props是一種組件外部傳入向組件內部傳入數據的一種方式,只不過採用標籤屬性的方式。
構造器constructor
- 使用ES6的構造器,要提供一個參數props,並把這個參數使用super傳遞給父類
import React from 'react';
import ReactDom from 'react-dom';
class Toggle extends React.Component{
// state = {flag:true}; //類中定義state
constructor(props){
super(props);//一定要調用super父類構造器,否則會報錯
this.state = {flag:true}; //類中定義state
}
handleClick(event){
console.log(event.target.id);
console.log(event.target === this);
console.log(this);
console.log(this.state);
this.setState({flag:!this.state.flag});
}
render(){
/*注意一定要綁定this onClick寫成小駝峯 */
return <div id='xdd' onClick={this.handleClick.bind(this)}>
點擊這句話會觸發一個事件。{this.state.flag.toString()}<br />
顯示props<br />
{this.props.name}:{this.props.parent.state.p1}{this.props.parent.state.p2}
{this.props.children}
</div>;
}
}
class Root extends React.Component {
//定義一個對象
state = {p1:'www.xdd',p2:'.com'};//構造函數中定義state
render(){
return(
<div>
<div>Welcome to {this.state.p1}{this.state.p2}</div>
<br />
<Toggle name= "school" parent={this} >{/*自定義2個屬性通過props傳給Togle組件對象 */}
<hr />{/*子元素通過props.children訪問 */}
<span>我是Toggle元素的子元素</span>{/*子元素通過props.children訪問 */}
</Toggle>
</div>
)
}
}
ReactDom.render(<Root />,document.getElementById('root'))
組件的聲明週期
- 組件的生命週期可分成三個狀態:
- Mounting:已插入真實DOM
- Updating: 正在被重新渲染
- Unmounting: 已移出真實DOM
- 組件的生命週期狀態,說明在不同時機訪問組件,組件正處在生命週期的不同狀態上。
- 在不同的生命週期狀態訪問,就產生不同的方法。
- 生命週期方法如下:
- 裝載組件時觸發
- componentWillMount在渲染前調用,在客戶端也在服務端。只會在裝載之前調用一次。
- componentDidMount:在第一次渲染後調用,只在客戶端。之後組件已經生成了對應的DOM結構,可以通過this.getDOMNode()來進行訪問。如果你想和其他JavaScript框架一起使用,可以在這個方法中調用setTimeout,setInterval或者發送AJAX請求等操作(防止異步操作阻塞UI)。只在裝載完成後調用一次,在render之後。
- 更新組件觸發。這些方法不好在首次render組件的週期調用。
- componentWillReceiveProps(nextProps)在組件接收到一個新的prop時被調用。這個方法在初始化render時補回被調用。
- shouldComponentUpdate(nextProps,nextState)返回一個布爾值。在組件接收到新的props或者state時被調用。初始化時或者使用forceUpdate時不被調用。
- 可以在你確認不需要更新組件時使用。
- 如果設置爲false,就是不允許更新組件,那麼componentWillUpdate、componentDidUpdate不會執行。
- componentWillUpdate(nextProps,nextState)在組件接收到新的props或者state但還沒有render時被調用。在初始化時補回被調用。
- componentDidUpdate(prevProps,prevState)在組件完成更新後立即調用。在初始化時不會被調用。
- 卸載組件觸發
- componentWillUnmount在組件從DOM中移除的時候立刻被調用。
- 裝載組件時觸發
由圖可知:
- constructor構造器是最早執行的函數。
- 組件構建好之後,如果更新組件的state或props(注意在組件內props是隻讀的),就會在render渲染前觸發一系列的更新聲明周期函數。
- 重新編寫
/src/index.js
構造兩個組件,在子組件Sub中,加入所有生命週期函數。下面例子添加是裝載、卸載的生命週期函數
import React from 'react';
import ReactDom from 'react-dom';
class Sub extends React.Component{
constructor(props){
console.log('Sub constructor');
super(props);
this.state = {count:0};
}
handleClick(){
this.setState({count:this.state.count+1});
}
/* 組件實例化後被調用。。 */
componentWillMount(){
// constructor之後,第一次render之前
console.log('---------渲染前被調用了componentWillMount------')
}
/**第一次render調用後 */
componentDidMount(){
// 第一次render後
console.log('------第一次render調用後 componentDidMount----')
}
/* Props被改變,將要更新才調用 */
componentWillReceiveProps(nextProps){
// props變更時,接到新props了,交給shouldComponentUpdate
// props組件內只讀,只能從外部改變
console.log('---------------componentWillReceiveProps start-------------------')
console.log(this.props);
console.log(nextProps);
console.log("--------Props變更了,componentWillReceiveProps end----------",this.state.count)
}
/**將要更新,同意更新後,真正更新前,之後調用render */
componentWillUpdate(nextProps,nextState){
// 是否組件更新,props或state方式改變時,返回布爾值,true纔會更新
console.log('------state或props更新前-----------',this.state.count,nextState)
}
/**是否組件更新,props或者state方式改變時,返回布爾值,true纔會更新Dom界面。阻止不了值的變化 */
shouldComponentUpdate(nextProps,nextState){
// 是否組件更新,props或state方式改變時,返回布爾值,true纔會更新
console.log('---有組件發生了更新----shouldComponentUpdate----------',this.state.count,nextState);
return true; //return false將攔截Dom的更新
}
componentDidUpdate(prevProps,prevState){
//同意更新後,真正更新後,在render之後調用
console.log('------state或props更新後-----------',this.state.count,prevState)
}
/**移除才調用 */
componentWillUnmount(){
//清理工作
console.log('-------移除前被調用componentWillUnmount----------')
}
render(){
console.log('Sub render被調用---');
return (
<div id="sub" style={{ height: 200 + 'px', color: 'red', backgroundColor: '#f0f0f0', padding:'20px' }}
onClick={this.handleClick.bind(this)}>
<a style={{backgroundColor: 'white'}} onClick = {this.handleClick.bind(this)}>Sub's count = {this.state.count}</a>
</div>
)
}
}
class Root extends React.Component{
constructor(props){
console.log('Root Construnctor');
super(props);
this.state = {remot:false,xdd:1,name:'xdd'};
}
remot(){
this.setState({remot:!this.state.remot})
console.log('-------remot--'+this.state.remot.toString()+'----')
}
handleClick(){
this.setState({xdd:this.state.xdd +1});
}
handleClick2(){
this.setState({name:this.state.name+1})
}
render(){
if (this.state.remot){
return <div>
<h1>Root</h1><a onClick={this.remot.bind(this)}>點我重新加載</a>
</div>
}
else{
return (<div onDoubleClick = {this.remot.bind(this)}>
<h1>Root</h1>
<div onClick={this.handleClick.bind(this)}>點我改變重繪Sub並改變props xdd={this.state.xdd}</div>
<a onClick={this.handleClick2.bind(this)}>點我只改變父組件中的屬性值 name={this.state.name}</a>
<Sub xdd={this.state.xdd} prent={this} />{/**父組件的render,會引起下以及組件的更新流程,導致props重新發送,即使子組件props沒有改變過。 */}
</div>)
}
}
}
ReactDom.render(<Root />,document.getElementById('root'))
- componentWillMount 第一次裝載,在首次render之前。例如控制state、props
- componentDidMount 第一次裝載結束,在首次render之後。例如控制state、props
- componentWillReceiveProps 在組件內部,props是隻讀不可變的,但是這個函數可以接收到新的props,可以對 props做一些處理,this.props = {name:‘roooooot’};這就是偷樑換柱。componentWillReceiveProps觸發,也會 走shouldComponentUpdate。
- shouldComponentUpdate 判斷是否需要組件更新,就是是否render,精確的控制渲染,提高性能。
- componentWillUpdate 在除了首次render外,每次render前執行,componentDidUpdate在render之後調用。
- 不過,大多數時候,用不上這些函數,這些鉤子函數是爲了精確的控制。
- 如果子組件和父組件使用了相同的事件,可以認爲點擊子組件也是點擊了父組件,父組件重繪,就會把子組件 props更新,引起子組件組件更新流程,就會從componentWillReceiveProps開始執行。如果子組件自己修改自己 的state,不會執行componentWillReceiveProps。
函數試組件
- React從15.0開始支持函數式組件,定義如下:
import React from 'react';
import ReactDom from 'react-dom';
function Root(props){
return <div>{props.scholName}</div>
}
ReactDom.render(<Root scholName='xdd' />,document.getElementById("root"))
- 開發中,很多情況下,組件其實很簡單,不需要state狀態,也不需要使用生命週期函數。
- 函數式組件的函數應該提供一個參數props,返回一個React元素。
- 函數式組件的函數本身就是render函數。
- 改寫上面示例代碼
import React from 'react';
import ReactDom from 'react-dom';
let Root = props => <div>{props.scholName}</div>
ReactDom.render(<Root scholName='xdd' />,document.getElementById("root"))
以前函數式組件還有名字叫stateless components無狀態組件。當前React發佈了16.8,已經可以在函數式組件中使用state了,所有官方建議叫函數式組件。