React.js入門筆記 核心提示 這是本人學習react.js的第一篇入門筆記,估計也會是該系列涵蓋內容最多的筆記,主要內容來自英文官方文檔的快速上手部分和阮一峰博客教程。當然,還有我自己嘗試的實例。日後還將對官方文檔進階和高級部分分專題進行學習並記錄。 儘管前端學習面臨著各種各樣的焦慮,儘管越來 ...
# React.js入門筆記
核心提示
這是本人學習react.js的第一篇入門筆記,估計也會是該系列涵蓋內容最多的筆記,主要內容來自英文官方文檔的快速上手部分和阮一峰博客教程。當然,還有我自己嘗試的實例。日後還將對官方文檔進階和高級部分分專題進行學習並記錄。
儘管前端學習面臨著各種各樣的焦慮,儘管越來越多的框架出現,然而無可否認的是,它們都在從不同的角度提高生產力——從這個角度而言,之所以焦慮,本質原因是因為行業的門檻其實是降低了,而自己變得“不值錢”起來。在目前的環境下,無論如何必須承認,學習一樣技能不可能讓你一輩子靠它吃飯了。如果真有,那就是原生的,基礎的,底層的知識——當然這是旁話了。
假使者互聯網的歷史是一本薄薄的小冊子,那麼我經常看到一個歷史事實:第一頁說今天某個框架很火,有多少人在用,一時間風頭無兩。但翻到歷史的下一頁就是一句話:又出來一個新的框架,原來那個競爭不過,就慫了。
所以,給自己打個氣吧:什麼都可以慫,但是你,別慫了。
- 聲明
React.js可以輕鬆創建互動式ui。 為你的webAPP設計出各種狀態的簡單視圖效果。當數據更改時,react組件可以有效地反映出來。
聲明式的方法使你的代碼更容易可控和調試。 - 基於組件
封裝了各種狀態組件,然後組成複雜的ui。
因為JavaScript編寫的組件邏輯而不是模板,您可以很容易地給你的APP創建豐富的數據,並通過DOM操控它們的狀態。 - 學習一次,用在任何地方
我們不假設你其他的技術棧,所以你可以用react開發新特性時,不必重寫現有代碼。
語言:基於javascript,同時也涉及了ES6的部分語法,比如箭頭函數(Arrow functions)、javascript類(Class>)、模板字元串(Template literals)等。
準備工作:安裝react
筆者操作時基於如下佈局。相關文件可以在官網下載到。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title></title>
<link rel="stylesheet" type="text/css" href="css/css.css"/>
<!-- 核心 -->
<script src="js/react.js"></script>
<!-- 載入dom方法 -->
<script src="js/react-dom.js"></script>
<!-- 將 JSX 語法轉為 JavaScript 語法 -->
<script src="js/browser.min.js"></script>
<!-- 自身的javascript代碼 -->
<script type="text/javascript" src="js/js.js"></script>
</head>
<body>
<div id="example"></div>
<!-- 凡是用到jsx語法的地方type應該為text/babel -->
<script type="text/babel">
</script>
</body>
</html>
上面代碼有兩個地方需要註意。首先,最後一個 <script>
標簽的 type
屬性為 text/babel
。這是因為 React 獨有的 JSX 語法,跟 JavaScript 不相容。凡是使用 JSX 的地方,都要加上 type="text/babel"
。
其次,上面代碼一共用了三個庫: react.js
、react-dom.js
和 Browser.js
,它們必須首先載入。其中,react.js
是 React 的核心庫,react-dom.js
是提供與 DOM 相關的功能,Browser.js
的作用是將 JSX 語法轉為 JavaScript 語法,這一步很消耗時間,實際上線的時候,應該將它放到伺服器完成。
——如果你把react語句寫到外鏈的js裡面,chrome無法支持非http協議的跨域讀取。所以需要在伺服器環境下使用。
一. Hello World!——ReactDOM.render()方法。
ReactDOM.render 是 React 的最基本方法,render直接理解為“渲染”,“表達”、“表述”也沒啥問題。用於將模板轉為 HTML 語言,並插入指定的 DOM 節點。
使用實例
使用方法如下:
ReactDOM.render(要插入的html內容,選擇器)
ReactDOM.render(
<h1>Hello World!</h1>,
document.getElementById('example')
)
上面代碼將一個 h1
標題,插入 example
節點。
渲染模塊:React每次渲染只更新有必要更新的地方,而不會更新整個ui。
第一個參數怎麼寫
ReactDOM.render()方法的第一個參數有點像定義innerHTML,然而你不能這麼惡搞:
<h1>hello world!</h1><h2>hehe</h2>//報錯,兩個頂層標簽
但這樣寫是可以的:
<h1>hello <small>world!</small></h1>//通過:只有一個頂層標簽
說白了,假設第一個參數是集裝箱,那麼就是你一次只能放一個箱子!
你可以給標簽加上data-xxx或是class(註意,寫作className
),id等屬性,但是你加style屬性又會報錯:
<h1>hello <small style="color:red;">world!</small></h1>//有行間樣式會報錯
style具體怎麼寫,會在後面提及。
二. JSX 語法
上面第一個參數的代碼叫做JSX語法。
所謂JSX語法,既不是javascript里的字元串,也不是html。像是在Javascript代碼里直接寫XML的語法,每一個XML標簽都會被JSX轉換工具轉換成純Javascript代碼,並不加任何引號。
JSX允許你使用它生成React里的元素(Element),JSX可以編譯為javascript
React 官方推薦使用JSX, 當然你想直接使用純Javascript代碼寫也是可以的,只是使用JSX,組件的結構和組件之間的關係看上去更加清晰。
JSX語法規則的簡單速成
基本語法規則:允許 HTML 與 JavaScript 的混寫。遇到 HTML 標簽(以
<
開頭),就用 HTML 規則解析;遇到代碼塊(以{
開頭),就用 JavaScript 規則解析。
這意味著javascript里的語法,方法到了jsx一樣適用。另一方面,你可以按照xml的結構為JSX里的元素指定子類和屬性。
然而需要註意的是,JSX的血緣相對於html,還是更親近於javascript,屬性還是得採用駝峰式寫法,之前提到元素的class必須寫成calssName,就是一個例子。JSX代表一個對象。比如說我創建一個如下的jsx元素有一個基本結構是
<h1 class="greeting">Hello world!</h1>
,通常是這樣寫:var element = ( <h1 className="greeting"> Hello, world! </h1> );
或者這樣:
var element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' );
實際上完整版是這樣一個React對象:
var element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world' } };
數組——塞箱子問題
var arr=['hello','React','Vue'];
arr.push('Angular');
ReactDOM.render(
<div>
{
arr.map(function(ele){
return <h2>{ele}!</h2>
})
}
</div>,
document.getElementById('example')
)
效果將打出4個h2。
這似乎馬上顛覆了剛剛建立起來關於集裝箱每次只能放一個箱子的認知。其實更為正確的理解是:ReactDOM.render()作為一個模板被執行了4次。通過它,可以用較少的語句實現塞4個箱子。
其它往集裝箱塞多個箱子的方法——還是數組
你還可以試試別的javascript語句能不能成。比如:
var arr=['hello','React','Vue','Angular'];
var str=arr.join(' ');
ReactDOM.render(
<div>
{
<h1>{arr}!</h1>
}
</div>,
document.getElementById('example')
)
說明代碼塊內可以放變數。
這種功能看起來稍顯老舊,不如這麼做:
var arr=[
<h1 key={0}>hello</h1>,
<h2 key={1}>React,</h2>,
<h2 key={2}>Vue,</h2>,
<h2 key={3}>Angular!!!</h2>
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);
上面代碼的arr
變數是一個數組,結果 JSX 會把它的所有成員,添加到模板。
筆者按:沒加key值,會提示錯誤。
- key值本質是一個字元串。React用它來識別數組中內容的變化。雖然React容忍了把數字作為key的行為。但是更標準的做法是
key={1.toString()}
,當然你有ID名的話,把id作為key也是推薦的。 - key只在數組遍歷時用到。
- 同輩元素之間的key必須是唯一的。
- 加key只是框架內部的需要,不會幫你實現識別元素的功能。
三. 組件:React.createClass及調用
基本理解
區別元素(Elemnt)和組件(Component)
元素是組件的組成部分組件和屬性(props)
組件讓你UI分割為若幹個獨立的、可復用的部分。
從概念上講,組件就像JavaScript函數。 他們接受任意的參數(“props”)並返回React的元素。組件可以套組件。
理解組件最簡單的方法就是寫一個函數
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
用ES6語法寫成的組件函數是:
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
上面的代碼創建一個函數允許你把props值作為參數傳進去。而實際上,在react裡面有自己封裝組件的方法。
封裝組件
React 允許將代碼封裝成組件(component),然後像插入普通 HTML 標簽一樣,在網頁中插入這個組件。React.createClass 方法,顧名思義,就用於生成一個組件類。
var Message=React.createClass({
render:function(){
return <h1>你是我的 {this.props.name}</h1>;
}
});
ReactDOM.render(
<Message name="小甜甜" />,
document.getElementById('example')
);
顯示為h1標題。
讓我們回顧這個例子:
- 我們稱ReactDOM.render()方法渲染的
<Message name="小甜甜" />
為元素。 {name="小甜甜}
作為Message組件的props。<h1>你是我的小甜甜</h1>
是這個組件渲染的結果。
你可以為這個組件類對象創建多個屬性(this.props.xxx
),然後通過給屬性賦值來實例化它們.
所謂組件類,由React.createClass 方法生成。他的參數可以理解為一個對象。對象的constructor指向該組件。組件怎麼表示呢?首先必須存在一個大寫字母開頭的變數比如Message里。然後在ReactDOM.render()方法中引用——<Message name="..."/>
。實際上是個xml。註意是可以個自閉合標簽。
跟之前插入html代碼一樣,實例內只能有一個頂層標簽。
react是非常靈活的,但有一個嚴格的規則:
所有react組件的行為必須像純函數,忠於它們的屬性。
初步小結
於是我們對ReactDOM.render()方法又有了新的認識。到目前為止,ReactDOM.render()方法中,第一個參數必須只有一個頂層,它本質是元素:
- xml語法的元素,
- 允許數組遍歷方法(簡單語句塞多個箱子,本質還是多次渲染)
- 還可以放組件類
對象
的實例
。
四. this.props.children
理解children
前面說到,組件類方法React.createClass()
的參數是個對象。你可以用this.props
定義它的各種屬性,而這個參數與組件的屬性一一對應,但是,有一個例外,就是 this.props.children
屬性。說白了就是你的屬性用什麼英文名都行,除了children
。
所謂children表示組件的所有子節點。
既然是所有屬性的集合,不如試試這樣惡搞:
var Message=React.createClass({
render:function(){
return <h1>你是我的 {this.props.children}</h1>;
}
});
ReactDOM.render(
<Message name="小甜甜" and="和" xxx="牛夫人" />,
document.getElementById('example')
);
雖然不報錯,但你會發現什麼屬性都沒顯示出來。
嘗試給這個this.prop.children加.name
或是["name"]
再或是[0]
尾碼,都不顯示東西。
對children究竟是什麼類型,比較靠譜的解釋是:
這裡需要註意,
this.props.children
的值有三種可能:如果當前組件沒有子節點,它就是undefined
;如果有一個子節點,數據類型是object
;如果有多個子節點,數據類型就是array
。所以,處理this.props.children
的時候要小心。
this.prop.children怎麼用——React.Children.map()
方法
數組確實是強大的功能,有了它可以很快地往箱子里塞東西。同理,React 提供一個工具方法 React.Children
來處理 this.props.children
。我們可以用 React.Children.map
來遍歷子節點,而不用擔心 this.props.children
的數據類型是 undefined
還是 object
。
React.Children.map()
方法結合this.props.children可以幫助我們快速地把組件類的對象打出來。
React.Children.map(this.prop.children,function(child){
return {child}
})
因為children沒有指定對象是誰,所以要把第一個參數改寫為xml的形式
var Message=React.createClass({
render:function(){
return <h2>你是我的
{
React.Children.map(this.props.children,function(child){
return child//如果你想自動生成span那就應該是<span>{child}<span>
})
}</h2>;
}
});
ReactDOM.render(
<Message name="haha" named="hehe">//在此定義name等屬性,不會顯示。
<span>小甜甜</span>
<span>、牛夫人</span>
<span>和小親親~~</span>
</Message>,
document.getElementById('example')
);
列印出來的結構是:
span也不是必須加的,你完全可以把render表達式寫成:
ReactDOM.render(
<Message>小甜甜、牛夫人和小親親~~</Message>,
document.getElementById('example')
);
這是只有一個子節點的情況。而多個子節點可以應用在ul/ol-li這樣的體系中。
React.Children基本方法
我們可以用 React.Children.map 來遍歷子節點.
1.React.Children.map
object React.Children.map(object children, function fn [, object context])
在每一個直接子級(包含在 children 參數中的)上調用 fn 函數,此函數中的 this 指向 上下文。如果 children 是一個內嵌的對象或者數組,它將被遍歷:不會傳入容器對象到 fn 中。如果 children 參數是 null 或者 undefined,那麼返回 null 或者 undefined 而不是一個空對象。
2.React.Children.forEach
React.Children.forEach(object children, function fn [, object context])
類似於 React.Children.map(),但是不返回對象。
3.React.Children.count
number React.Children.count(object children)
返回 children 當中的組件總數,和傳遞給 map 或者 forEach 的回調函數的調用次數一致。
4.React.Children.only
object React.Children.only(object children)
返回 children 中僅有的子級。否則拋出異常。
組合和繼承
React有一個強大的組合模型,官方建議組件之間多使用組合,而不是繼承。
本節將研究如何使用組合做到繼承的事。
容器
一些組件在應用之前,可能不知道他們的children。 比如常見的sidebar(組件欄)或對話框。
建議這樣的組件通過props傳遞到子組件,以期望影響它們的輸出效果:
var Content=React.createClass({//子組件
render:function(){
return (
<div style={{color:this.props.color}}>
{this.props.children}
</div>
);
}
});
var App=React.createClass({//父組件
render:function(){
return (
<Content color="blue">
<h1>歡迎歡迎</h1>
<h2>熱烈歡迎</h2>
</Content>
);//return的內容(props)是到用時再定義的
}
});
ReactDOM.render(
<App/>,
document.getElementById('example')
);
有的時候或許還需要預留介面,你可以定義:
var Xxx=React.createClass({
render:function(){
return (
<h1>歡迎歡迎</h1>
)
}
});
var Yyy=React.createClass({
render:function(){
return (
<h2>熱烈歡迎</h2>
)
}
})
var Content=React.createClass({//子組件
render:function(){
return (
<div>
{this.props.xxx}
{this.props.yyy}
</div>
);
}
});
var App=React.createClass({//父組件
render:function(){
return (
<Content xxx={<Xxx/>} yyy={<Yyy/>}/>
);//把子組件和孫組件一次性封裝,通過props
}
});
ReactDOM.render(
<App/>,
document.getElementById('example')
);
在上面這段代碼中,Content的屬性xxx,和yyy都是可以自定義組件。允許你靈活地放孫級組件。
上面代碼中,xxx,yyy都是靈活的,如果你想讓它變得不可定義,把它寫死就行了。
進一步組合
var Xxx=React.createClass({
render:function(){
return (
<h1>歡迎歡迎</h1>
)
}
});
var Yyy=React.createClass({
render:function(){
return (
<h2>熱烈歡迎</h2>
)
}
});
var Zzz=React.createClass({
render:function(){
return (
<h3>我是this.props.children</h3>
);
}
});
var Content=React.createClass({//子組件
render:function(){
return (
<div>
{this.props.xxx}
{this.props.yyy}
{this.props.children}
</div>
);
}
});
var App=React.createClass({//父組件
render:function(){
return (
<Content xxx={<Xxx/>} yyy={<Yyy/>}>
<Zzz/>
</Content>
);//在此代碼中<Zzz/>屬於props.children
}
});
ReactDOM.render(
<App/>,
document.getElementById('example')
);
繼承呢?
暫時沒有發現非使用繼承不可的地方。
props和組合給你所需要的靈活性,藉此可以明確而安全地定製組件的外觀和行為。 記住,組件可以接受任意的props,包括原始值、react元素,或函數。
如果你在組件間復用非ui相關的函數,建議把它提取到一個單獨的JavaScript模塊。 組件可以調用和使用這個函數,對象,或是類,而不必去擴展它。
五. 組件的protoTypes和React的PropTypes
驗證數據類型——React.PropTypes.number.isRequired
組件的屬性可以接受任意值,字元串、對象、函數等等都可以。有時,我們需要一種機制,驗證別人使用組件時,提供的參數是否符合要求,主要用於調試。
筆者按:
- 如果你嘗試對組件屬性存放一個Object對象或是其實例,或是Date對象,會彈出錯誤!直接提示你對象不適合作為React的一個子屬性,因此愚以為阮氏至少有點片面。
- 再者,屬性不一定都是要拿來顯示到網頁上的。所以你存個函數,存個布爾值,存個對象的方法,都沒問題。只是當你嘗試顯示返回非字元串,數字等內容的時候,顯示為空白。
- 然後,註意大小寫區別。同時也要註意他和prototype原型的區別。
之前提到,React.createClass()方法的參數是一個對象,設計者還給它放置了組件類的protoTypes子屬性(註意是小寫開頭!)。我的理解就是存放限制子屬性設置的地方。
而這個React.PropTypes
屬性(註意是大寫開頭!),就是用來驗證組件實例的屬性是否符合要求。
console.log(React.ProtoType),可以看到它的屬性都是一個對象,擁有各種方法。
其中最為廣泛最多的就是這個isRequire。理解為是“必須的”就可以了。
比如這個例子:
var result=[1,2,3,4]
var Message=React.createClass({
propTypes: {
sum: React.PropTypes.number.isRequired//調用驗證
},
render:function(){
return <h2>1+1=
{
this.props.sum
}
</h2>
}
});
console.log(React.PropTypes)
ReactDOM.render(
<Message sum={result}/>
,document.getElementById('example')
);
輸出為1+1=1234
雖然怎麼看1234都像數字,但它確實是個數組。即使能顯示,但是會報錯。
getDefaultProps
此外,getDefaultProps
方法可以用來設置組件屬性的預設值。
這個功能與其說是get不如說是set。當你定義了各種屬性之後,可以在該組件prototype下的constructor.defaultProps
找到它們。原來它們在本質上還是一個object對象。
藉助這個框架,你可以快速地封裝自己想要的子屬性和方法。
var Xxx=React.create({
protoTypes:{
...
},
getDefaultProp:function(){
return {
prop1:...
prop2:...
prop3:...
}
},//放函數,不要直接放對象!
render:function(){
...
}
}
})
有了它,似乎可以不必在行間定義各種屬性值了,看起來相當美觀。
然而,面臨這樣一個問題。
var Message=React.createClass({
getDefaultProps:function(){
return {
name1:'小親親',
name2:'小甜甜',
name3:'牛夫人'
}
},
render:function(){
return <h2>你是我的
{
this.props.name1
}
</h2>
}
});
//console.log(Message)
ReactDOM.render(
<Message name1="xxx"/>
,document.getElementById('example')
);
列印出的效果是你是我的xxx
。
在constructor.defaultProps
中是找不到的行間定義的name的。
六. 獲取真實的DOM節點——ref的定義與調用
關於ref
在React的典型數據流中,props是組件和它的子屬性交互的唯一方式。每次修改一個child,你就得給再渲染它一次。然而,在個別情況下你需要強行繞過典型數據流修改child,這個child可能是組件的實例。也可能是DOM的節點,因此React提供了一個修改途徑——ref。
React支持特殊的屬性ref,你可以附加到任何組件上。 ref屬性需要一個回調函數,此函數將組件安裝或卸載後立即執行。ref屬性用於HTML元素時,ref回調接收底層的DOM元素作為它的參數。
再通俗一點點
組件並不是真實的 DOM 節點,而是存在於記憶體之中的一種數據結構,叫做虛擬 DOM (virtual DOM)。只有當它插入文檔以後,才會變成真實的 DOM 。根據 React 的設計,所有的 DOM 變動,都先在虛擬 DOM 上發生,然後再將實際發生變動的部分,反映在真實 DOM上,這種演算法叫做 DOM diff ,它可以極大提高網頁的性能表現。
但是,有時需要從組件獲取真實 DOM 的節點,這時就要在你需要的節點中插入 ref
屬性,然後通過相關事件去定義它,註意,調用時的節點為this.refs.ref名。
案例:
一個組件,返回一個文本框和一個按鈕。要求點擊按鈕後,獲取文本框節點
var MyComponment=React.createClass({
btnClick:function(){//btnClick是自定義的函數名,用於定義觸發事件後實行的方法,調用為this.btnClick
this.refs.myTextInput.focus();//獲取焦點。
},
turnGreen:function(){//載定義一個改變value值的方法
this.refs.myTextInput.value="我怎麼綠了";
this.refs.myTextInput.style.color="green";
},
render:function(){
return (
<div>
<input type="text" ref="myTextInput" onFocus="this.turnGreen" />//要求自閉合標簽全部寫上“/”!否則報錯
<input type="button" value="Focus this text input" onClick={this.btnClick} />
</div>
);
}
});
ReactDOM.render(
<MyComponment/>
,document.getElementById('example')
);
上面代碼中,組件 MyComponent
的子節點有一個文本輸入框,用於獲取用戶的輸入。這時就必須獲取真實的 DOM 節點,虛擬 DOM 是拿不到用戶輸入的。為了做到這一點,文本輸入框必須有一個 ref
屬性,然後 this.refs.[refName]
就會返回這個真實的 DOM 節點。
在獲取焦點之後,馬上對其執行turnGreen方法。使得裡面的樣式變綠。
如果直接在輸入框節點中指定value值,結果是只讀的。用戶無法修改。報錯信息如下
react.js:19287 Warning: Failed form propType: You provided a
value
prop to a form field without anonChange
handler. This will render a read-only field. If the field should be mutable usedefaultValue
. Otherwise, set eitheronChange
orreadOnly
. Check the render method ofMyComponment
.
解決思路參見第八章 表單
需要註意的是,由於** this.refs.[refName]
屬性獲取的是真實 DOM ,所以必須等到虛擬 DOM 插入文檔以後,才能使用這個屬性,否則會報錯。**上面代碼中,通過為組件指定 Click
事件的回調函數btnClick
,確保了只有等到真實 DOM 發生 Click
事件之後,才會讀取 this.refs.[refName]
屬性。
React 組件支持很多事件,除了 Click
事件以外,還有 KeyDown
、Copy
、Scroll
等,完整的事件清單請查看官方文檔。
不要濫用ref
學習了refs之後,你的第一反應就是“用事件觸發它發生”。如果真是這樣的話,不如花點事件想想你的組件結構應該有哪些狀態(state),顯然,每個組件應該有自己的合適狀態。參見組件的生命周期。
七. 狀態——state
組件免不了要與用戶互動,React 的一大創新,就是將組件看成是一個狀態機,一開始有一個初始狀態,然後用戶互動,導致狀態變化,從而觸發重新渲染 UI。
根據狀態響應不同的內容
接下來結合ref來做個demo
var ToggleState=React.createClass({
getInitialState:function(){//getInitialState是固有方法
return {check:false};
},//設置一個布爾值狀態屬性check
toggleClick:function(event){
this.setState({
check:!this.state.check
});//每次觸發就改變布爾值
if(this.state.check){
this.refs.para.innerText='我不喜歡';
this.refs.para.style.color="green";
}else{
this.refs.para.innerText='我喜歡';
this.refs.para.style.color="purple";
}
},
render:function(){
return (
<p ref="para" onClick={this.toggleClick}>
你喜歡男人嗎?點擊切換。
</p>
)
}
});
ReactDOM.render(
<ToggleState/>,
document.getElementById('example')
);
上面代碼是一個 toggleState
組件,它的 getInitialState
方法用於自定義一個check屬性並設置其初始狀態,可以通過 this.state
屬性讀取。當用戶點擊組件,導致狀態變化,this.setState
方法修改狀態值,每次修改以後,都會自動調用 this.render
方法,再次渲染組件。
由於 this.props
和 this.state
都用於描述組件的特性,可能會產生混淆。一個簡單的區分方法是,this.props
表示那些一旦定義後只讀的特性,是靜態的。而 this.state
是會隨著用戶互動而產生變化的特性。是動態的。
正確使用狀態
- 用
setState({xx:yyy})
,不要用this.state.xx=yyy
一個看上去很悲劇的事實:狀態更新可能是非同步的。
因為this.props
和this.state
可能都是非同步刷新,因此,不要根據state的值去計算並定義下一個狀態。比如:this.setState({ counter: this.state.counter + this.props.increment, });
實際上,setState方法還可以接收兩個參數
// Correct this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));//相當於return
以上兩種寫法是一樣效果的。第一個參數是前面前一個狀態,第二個參數是當下時刻的props值。// Correct this.setState(function(prevState, props) { return { counter: prevState.counter + props.increment }; });
狀態更新合併
當你調用設置setState,React對象可以根據你所提供的新狀態值重新計算合併為一個新的state。
例如,你的狀態可能包含幾個獨立變數:
狀態的數據流
無論父或子組件都無法知道某個組件有沒有state,它們也不關心這個組件被定義為一個函數或是一個類。
這就是為什麼state通常只在組件內部封裝和調用。除了組件自身,外部組件是無法訪問到該組件的state對象的。
組件可以選擇通過其state來作為props:比如本章案例中的toggleClick函數。在組件嵌套的時候,你也可以把父組件的state傳給子組件——通過設置字組件props。
重點來了:這通常被稱做“自上而下”或“單向”數據流的行為方式了。 任何收到其它組件(比如組件A)state影響的組件(比如組件B),A必定是B的父級組件。子組件不可能通過自身的state影響上層的父級組件。
假想組件嵌套是一條河流,如果說props
是河床,那麼每個子組件的state就是各個小支流的源頭。水不可能往高處流。
比如說本章例子,我創建一個新組件,包括了三個:
ReactDOM.render(
<div>
<ToggleState/>
<ToggleState/>
<ToggleState/>
</div>,
document.getElementById('example')
);
在這個例子中,三個子組件彼此都是孤立的,自己擁有各自的state,互不影響。
共用狀態
通常,幾個組件需要反映相同的數據變化。 最好的辦法是幾個子組件共用它們共同父組件的state。
比如說
在本節中,我們將創建一個計算器來計算溫度的水是否會煮在一個給定的溫度。
我們將從一個組件稱為BoilingVerdict開始。 它接受攝氏溫度作為支撐,並列印是否足以煮水:
事件處理方法
react的元素處理事件方法非常類似於DOM元素的行間處理事件方法。
它不是行間javascript
現在我們知道在行間加javascript處理函數是非常不規範的。但是react的加事件處理函數並不是真正的行間js。在語法和函數使用上可以看出本質差異:
- react使用駝峰命名命名事件,而不是小寫。
JSX語法下,你的事件處理程式傳遞的是一個函數一個函數,而不是一個字元串。
<p onclick="toggleClick()"><!--行間javascript-->
在react是這樣:
<p ref="para" onClick={this.toggleClick}>//這是react的方式
阻止瀏覽器預設行為:react不允許用return false!而應該用
toggleClick:function(event){ event.preventDefault(); }
這裡用到了參數event
事件處理方法的參數event
在這裡,event是一個合成的事件。它由React根據W3C規範定義,所以你不必擔心跨瀏覽器相容性。 看到SyntheticEvent參考指南瞭解更多信息。
使用React時你通常不需要調用addEventListener偵聽器。 相反,只需要提供一個偵聽器時最初渲染的元素。
比如我有一個按鈕組。當點擊一個按鈕想獲得該按鈕的響應,獲取方法就是event.target
必須留意JSX回調的內涵。 在JavaScript中,對象方法不受限制。在本章案例中調用toggleClick方法時,如果你不給toggleClick綁定this
,就將其傳遞給onClick,得到的將是undefined
。
這是一個JavaScript函數的基本原理之一。
八. 表單
表單是用戶和網頁實現動態交互的最直接實例。用戶在表單填入的內容,屬於用戶跟組件的互動,由於虛擬DOM的特性,所以不能用 this.props
。
在HTML表單元素,如<input>
、<textarea>
,和<select>
通常根據用戶輸入情況而更新。 在react中,可變狀態通常保存在組件的state裡面,想要實時更新,只有用setState()
方法。
應用於輸入框
var Input=React.createClass({
getInitialState:function(){
return {
value:'文本框內容隨輸入變化而變化噢'
};
},//定義設置value的初始狀態為hello
change:function(event){//定義輸入框value改變的回調函數
this.setState({