核心提示
這是本人學習react.js的第一篇入門筆記,估計也會是該系列涵蓋內容最多的筆記,主要內容來自英文官方文檔的快速上手部分和阮一峯博客教程。固然,還有我本身嘗試的實例。往後還將對官方文檔進階和高級部分分專題進行學習並記錄。
儘管前端學習面臨着各類各樣的焦慮,儘管愈來愈多的框架出現,然而無能否認的是,它們都在從不一樣的角度提升生產力——從這個角度而言,之因此焦慮,本質緣由是由於行業的門檻實際上是下降了,而本身變得「不值錢」起來。在目前的環境下,不管如何須須認可,學習同樣技能不可能讓你一生靠它吃飯了。若是真有,那就是原生的,基礎的,底層的知識——固然這是旁話了。
假使者互聯網的歷史是一本薄薄的小冊子,那麼我常常看到一個歷史事實:第一頁說今天某個框架很火,有多少人在用,一時間風頭無兩。但翻到歷史的下一頁就是一句話:又出來一個新的框架,原來那個競爭不過,就慫了。
因此,給本身打個氣吧:什麼均可以慫,可是你,別慫了。javascript
語言:基於javascript,同時也涉及了ES6的部分語法,好比箭頭函數(Arrow functions)、javascript類(Class>)、模板字符串(Template literals)等。css
筆者操做時基於以下佈局。相關文件能夠在官網下載到。html
<!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協議的跨域讀取。因此須要在服務器環境下使用。java
ReactDOM.render 是 React 的最基本方法,render直接理解爲「渲染」,「表達」、「表述」也沒啥問題。用於將模板轉爲 HTML 語言,並插入指定的 DOM 節點。react
使用方法以下:
ReactDOM.render(要插入的html內容,選擇器)
jquery
ReactDOM.render( <h1>Hello World!</h1>, document.getElementById('example') )
上面代碼將一個 h1
標題,插入 example
節點。
渲染模塊:React每次渲染只更新有必要更新的地方,而不會更新整個ui。git
ReactDOM.render()方法的第一個參數有點像定義innerHTML,然而你不能這麼惡搞:github
<h1>hello world!</h1><h2>hehe</h2>//報錯,兩個頂層標籤
但這樣寫是能夠的:web
<h1>hello <small>world!</small></h1>//經過:只有一個頂層標籤
說白了,假設第一個參數是集裝箱,那麼就是你一次只能放一個箱子!
你能夠給標籤加上data-xxx或是class(注意,寫做className
),id等屬性,可是你加style屬性又會報錯:
<h1>hello <small style="color:red;">world!</small></h1>//有行間樣式會報錯
style具體怎麼寫,會在後面說起。
上面第一個參數的代碼叫作JSX語法。
所謂JSX語法,既不是javascript裏的字符串,也不是html。像是在Javascript代碼裏直接寫XML的語法,每個XML標籤都會被JSX轉換工具轉換成純Javascript代碼,並不加任何引號。
JSX容許你使用它生成React裏的元素(Element),JSX能夠編譯爲javascript
React 官方推薦使用JSX, 固然你想直接使用純Javascript代碼寫也是能夠的,只是使用JSX,組件的結構和組件之間的關係看上去更加清晰。
基本語法規則:容許 HTML 與 JavaScript 的混寫。遇到 HTML 標籤(以 <
開頭),就用 HTML 規則解析;遇到代碼塊(以 {
開頭),就用 JavaScript 規則解析。
這意味着javascript裏的語法,方法到了jsx同樣適用。
另外一方面,你能夠按照xml的結構爲JSX裏的元素指定子類和屬性。
然而須要注意的是,JSX的血緣相對於html,仍是更親近於javascript,屬性仍是得采用駝峯式寫法,以前提到元素的class必須寫成calssName,就是一個例子。
<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={1.toString()}
,固然你有ID名的話,把id做爲key也是推薦的。區別元素(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標題。
讓咱們回顧這個例子:
<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()方法中,第一個參數必須只有一個頂層,它本質是元素:
對象
的實例
。前面說到,組件類方法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
的時候要當心。
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.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模塊。 組件能夠調用和使用這個函數,對象,或是類,而沒必要去擴展它。
組件的屬性能夠接受任意值,字符串、對象、函數等等均可以。有時,咱們須要一種機制,驗證別人使用組件時,提供的參數是否符合要求,主要用於調試。
筆者按:
以前提到,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
方法能夠用來設置組件屬性的默認值。
這個功能與其說是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的。
在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
等,完整的事件清單請查看官方文檔。
學習了refs以後,你的第一反應就是「用事件觸發它發生」。若是真是這樣的話,不如花點事件想一想你的組件結構應該有哪些狀態(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
// Correct this.setState(function(prevState, props) { return { counter: prevState.counter + props.increment }; });
以上兩種寫法是同樣效果的。第一個參數是前面前一個狀態,第二個參數是當下時刻的props值。
不管父或子組件都沒法知道某個組件有沒有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處理函數是很是不規範的。可是react的加事件處理函數並非真正的行間js。在語法和函數使用上能夠看出本質差別:
<p onclick="toggleClick()"><!--行間javascript-->
在react是這樣:
<p ref="para" onClick={this.toggleClick}>//這是react的方式
toggleClick:function(event){ event.preventDefault(); }
這裏用到了參數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({ value:event.target.value//注意事件對象 }); },//觸發事件後,vlue隨着用戶輸入的value而變化。 render:function(){ var value=this.state.value; return ( <div> <input type="text" value={value} onChange={this.change}/> <p>{value}</p> </div> ); } }); ReactDOM.render( <Input/>, document.getElementById('example') )
上面代碼中,文本輸入框的值,不能用 this.props.value
讀取,而要定義一個 onChange
事件的回調函數,經過 event.target.value
讀取用戶輸入的值。textarea
元素、select
元素、radio
元素都屬於這種狀況,更多介紹請參考官方文檔。
先看一個案例:實現一個下拉菜單(select-dropdown)。要求html渲染出如下信息:
<form> <label> 你喜歡: <select> <option value="男人">男人</option> <option value="女人">女人</option> <option selected value="都喜歡">都喜歡</option><!--被選中狀態--> <option value="都不喜歡">都不喜歡</option> </select> </label> <input type="submit" value="提交!"/> </form>
點擊提交時,彈出對應的信息。
分析:有一點須要注意:初始狀態是「都喜歡」被選中。你不能直接在option裏面加selected
屬性。在JSX語法中。定義下拉菜單的選中狀態是<selecte>
元素的value值(對應option的內容)。
var App=React.createClass({ getInitialState:function(){ return ({ list:{ "v1":"男人", "v2":"女人", "v3":"都喜歡", "v4":"都不喜歡" }, value:"都喜歡" }) }, submit:function(e){ var info='Yooooo,原來你喜歡'+this.state.value+'呀!' alert(info); e.preventDefault();//jQuery阻止冒泡 }, change:function(e){ console.log(e) this.setState({ value:e.target.value }) }, render:function(){ var list=[]; for(var i=1;i<=Object.getOwnPropertyNames(this.state.list).length;i++){ var listInfo=this.state.list["v"+i]; list.push( <option key={i.toString()} value={listInfo}>{listInfo}</option> ); } console.log(list) return ( <form> <label> 你喜歡: <select value={this.state.value} onChange={this.change}>{list}</select> </label> <input type="submit" value="提交" onClick={this.submit}/> </form> ) } }); ReactDOM.render( <App/>, document.getElementById('example') );
組件的生命週期分紅三個狀態:
- 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):組件判斷是否從新渲染時調用
這些方法的詳細說明,能夠參考官方文檔。
接下來這個demo
var Hello=React.createClass({ getInitialState:function(){ return { opacity:1.0 }; }, componentDidMount:function(){//組件插入後執行! this.timer=setInterval(function(){ var opacity=this.state.opacity; opacity-=0.05; if(opacity<0.1){ opacity=1.0; } this.setState({ opacity:opacity }); }.bind(this),100);//定時器必須綁定this,不然出錯 }, render:function(){ return ( <div style={{opacity:this.state.opacity}}> Hello {this.props.name} </div> ) } }); ReactDOM.render( <Hello name="World" />, document.getElementById('example') )
上面代碼在hello
組件加載之後,經過 componentDidMount
方法設置一個定時器,每隔100毫秒,就從新設置組件的透明度,從而引起從新渲染。
另外,組件的style
屬性的設置方式也值得注意,不能寫成
style="opacity:{this.state.opacity};"
而要寫成
style={{opacity: this.state.opacity}}
這是由於 React 組件樣式是一個對象,因此第一重大括號表示這是 JavaScript 語法,第二重大括號表示樣式對象。
組件的數據來源,一般是經過 Ajax 請求從服務器獲取,可使用 componentDidMount
方法設置 Ajax 請求,等到請求成功,再用 this.setState
方法從新渲染 UI)。
爲了獲取數據並把它打到頁面上其實思路和上面例子差很少。componentDidMount()
方法執行插入後渲染。
爲了簡化操做,咱們引入jquery文件到頁面中(React自己沒有任何依賴)。同時,本身作一個json文件,放到demo根目錄下,在本地服務器環境下運行網頁。
[ { "owner":{ "login":"Dangjingtao" }, "url":"http://www.baidu.com" } ]
接下來使用getJSON方法獲取這個json數據。徹底沒有用新的其它react方法:
var Info=React.createClass({ getInitialState:function(){ return { userName:'', lastGistUrl:'' }; }, componentDidMount:function(){ $.getJSON(this.props.source,function(data){ console.log(data) var lastVisit=data[0]; if(this.isMounted()){ this.setState({ userName:lastVisit.owner.login, lastVisitUrl:lastVisit.url }); } }.bind(this));//注意,涉及到的方法都綁定! }, render:function(){ return ( <div> {this.state.userName} is Last visit is <a href={this.state.lastVisitUrl}>here</a> </div> ); } }); ReactDOM.render( <Info source="json.json" />, document.getElementById('example') );
here連接向百度。
咱們甚至能夠把一個Promise對象傳入組件。
var RepoList = React.createClass({ getInitialState: function() { return { loading: true, error: null, data: null}; }, componentDidMount() { this.props.promise.then( value => this.setState({loading: false, data: value}), error => this.setState({loading: false, error: error})); }, render: function() { if (this.state.loading) { return <span>Loading...</span>; } else if (this.state.error !== null) { return <span>Error: {this.state.error.message}</span>; } else { var repos = this.state.data.items; var repoList = repos.map(function (repo) { return ( <li> <a href={repo.html_url}>{repo.name}</a> ({repo.stargazers_count} stars) <br/> {repo.description} </li> ); }); return ( <main> <h1>Most Popular JavaScript Projects in Github</h1> <ol>{repoList}</ol> </main> ); } } });
上面代碼從Github的API抓取數據,而後將Promise對象做爲屬性,傳給RepoList
組件。
若是Promise對象正在抓取數據(pending狀態),組件顯示"正在加載";若是Promise對象報錯(rejected狀態),組件顯示報錯信息;若是Promise對象抓取數據成功(fulfilled狀態),組件顯示獲取的數據。
思路:
var Time=React.createClass({ getInitialState:function(){ return { now:new Date().toLocaleTimeString() } }, componentDidMount:function(){//組件插入後執行! console.log(this) this.timer=setInterval(function(){ this.setState({ now:new Date().toLocaleTimeString() }); }.bind(this),1000);//定時器必須綁定this,不然出錯 }, componentDidUnmount:function() { console.log('組件已被移除!') //clearInterval(this.timer); }, render:function(){ return ( <div> <h1>Hello, world!</h1> <h2>如今是: {this.state.now}.</h2> </div> ) } }); ReactDOM.render( <Time/>, document.getElementById('wrap') );
讓咱們快速回顧一下發生了什麼:
1)當<Time/>
傳遞給ReactDOM.render(),組件的調用構造函數。 因爲時鐘須要顯示當前時間,它須要初始化。狀態對象包括當前時間。 在插入文檔以後,咱們會更新這個狀態。
2)接着React調用render()方法。 把初始狀態反映在屏幕上。
3)插入到真實DOM後,調用componentDidMount()。 它在插入後每秒執行一個函數,而每次執行都會從新渲染,從新插入到頁面中。
4)若是組件被移除,將激活componentDidUnmount()
實際上這是數據雙向綁定的例子
實現基本功能:兩個輸入框,在第一個攝氏溫標輸入框輸入數字。第二個輸入框自動綁定計算後的華氏溫標數據。反之亦然。
另外,根據100度沸騰的原則。斷定當前溫度是否沸騰。
思路:輸入框要注意這麼一個事實:若是不進行雙向綁定,會致使輸出失敗
var Judge=React.createClass({ getInitialState:function(){ return ({ info:'', }) }, render:function(){ console.log(this.state); //var info=''; var info=''; if(this.props.judge>=100){ info="水開了" }else{ info="水沒開" }; return ( <span>{info}</span> ) } }); var App=React.createClass({ getInitialState:function(){ return ( { valueC:'', valueF:'' } ) }, change:function(e){ this.setState({ valueC:e.target.value, valueF:this.toF(e.target.value) }) }, toC:function(fahrenheit) { return (fahrenheit - 32) * 5 / 9; }, toF:function(celsius) { return (celsius * 9 / 5) + 32; }, render:function(){ return ( <form> 攝氏溫度:<input type="text" onChange={this.change} value={this.state.valueC} /><br/> 華氏溫度:<input type="text" onChange={this.change} value={this.state.valueF} /><br/> <Judge judge={this.state.valueC}/> </form> ) } }); ReactDOM.render( <App/>, document.getElementById('example') )
曾經考慮過給子組件的判斷函數設置狀態,若是你在render函數內部(非「行間」)設置狀態,會致使死循環,報錯信息建議把狀態設置到生命週期相關的函數中,可是這樣作很是很是之卡。所以我摒棄了這個作法。
模擬一個表單,完成實時輸入校驗。
實際上就是設置一個狀態。組件會監聽你輸入的文字內容,觸發規則以後,改變狀態,再根據這個狀態修改其它監聽的內容。
規則顯示模塊用一個子組件Judge來實現。
var Judge=React.createClass({//子組件 judge:function(type,propsValue){ var content=''; if(type=="email"){//若是type屬性爲email var reEmail=/^\w+@[a-z0-9]+\.[a-z]{2,4}$/; if(this.props.value==''){ content=""; }else if(reEmail.test(propsValue)){ content="輸入正確!"; }else{ content="請輸入正確的郵箱名!"; } } return content; }, setColor:function(ref,contentType){ //根據內容判斷顏色 if(ref){//在首次加載渲染時,this.refs.main爲undefined if(contentType=="輸入正確!"){ ref.style.color="green"; }else{ ref.style.color="red"; } } }, render:function(){ var content=this.judge(this.props.type); this.setColor(this.refs[this.props.type],content); return ( <span ref={this.props.type}>{content}</span> ) } }); var App=React.createClass({ getInitialState:function(){ return ({ emailValue:'' }); }, change:function(e){ this.setState({ emailValue:e.target.value }); }, render:function(){ return ( <form> 郵箱:<input type="text" onChange={this.change} value={this.state.emailValue} /><br/> <Judge type="email" value={this.state.emailValue}/><br/> </form> ) } });
基本效果:
到目前爲止已經實現了對Judge組件的初步封裝
到目前爲止,組件實現看起來都很簡單。
根據一樣的原理,再實現密碼校驗
var Judge=React.createClass({//子組件 judge:function(type,propsValue){ var content=''; if(type=="email"){//若是type屬性爲email var reEmail=/^\w+@[a-z0-9]+\.[a-z]{2,4}$/; if(propsValue==''){ content=""; }else if(reEmail.test(propsValue)){ content="輸入正確!"; }else{ content="請輸入正確的郵箱名!"; } }else if(type=="password"){//若是是password var rePassword=/^[a-zA-Z0-9]{6,10}$/; if(propsValue==''){ content=''; }else if(rePassword.test(propsValue)){ content="輸入正確!"; }else{ content="密碼必須包括6-10位英文字母和數字!"; } } return content; }, setColor:function(ref,contentType){ //根據內容判斷顏色 if(ref){//在首次加載渲染時,this.refs.main爲undefined if(contentType=="輸入正確!"){ ref.style.color="green"; }else{ ref.style.color="red"; } } }, render:function(){ var content=this.judge(this.props.type,this.props.value); this.setColor(this.refs[this.props.type],content); return ( <span ref={this.props.type}>{content}</span> ) } }); var App=React.createClass({ getInitialState:function(){ return ({ emailValue:'', passwordValue:'' }); }, changeEmail:function(e){ this.setState({ emailValue:e.target.value, }); }, changePassword:function(e){ this.setState({ passwordValue:e.target.value, }); },//此函數不太能傳參,不得很少設一個 render:function(){ return ( <form> 郵箱:<input type="text" onChange={this.changeEmail} value={this.state.emailValue} /><br/> <Judge type="email" value={this.state.emailValue}/><br/> 密碼:<input type="text" onChange={this.changePassword} value={this.state.passwordValue} /><br/> <Judge type="password" value={this.state.passwordValue}/><br/> </form> ) } }); ReactDOM.render( <App/>, document.getElementById('example') );
效果能夠本身試試。
沒什麼說的。
實現提交按鈕,把行爲指向百度。若是有一個不符合規範,就不能經過。
由於Judge組件不能很方便把狀態返回到上層,我以爲這是該表單驗證架構的最大短板。
可是父級組件能夠讀取下層的信息,能夠給Judge組件填上本身的ref:,那麼父組件就能夠經過兩個this.refs.xxx.refs.xxx訪問到子組件返回的html結構內容。有了內容,就能夠作判斷了。
var Judge=React.createClass({//子組件 getInitialState:function(){ return { check:false } }, judge:function(type){ var content=''; if(type=="email"){//若是type屬性爲email var reEmail=/^\w+@[a-z0-9]+\.[a-z]{2,4}$/; if(this.props.value==''){ content=""; }else if(reEmail.test(this.props.value)){ content="輸入正確!"; }else{ content="請輸入正確的郵箱名!"; } }else if(type=="password"){//若是是password var rePassword=/^[a-zA-Z0-9]{6,10}$/; if(this.props.value==''){ content=''; }else if(rePassword.test(this.props.value)){ content="輸入正確!"; }else{ content="密碼必須包括6-10位英文字母和數字!"; } } return content; }, setColor:function(ref,contentType){ //根據內容判斷顏色 if(ref){//在首次加載渲染時,this.refs.main爲undefined if(contentType=="輸入正確!"){ ref.style.color="green"; }else{ ref.style.color="red"; } } }, render:function(){ var content=this.judge(this.props.type); this.setColor(this.refs[this.props.type],content); return ( <span ref={this.props.type}>{content}</span> ) } }); var App=React.createClass({ getInitialState:function(){ return ({ emailValue:'', passwordValue:'' }); }, changeEmail:function(e){ this.setState({ emailValue:e.target.value, }); }, changePassword:function(e){ this.setState({ passwordValue:e.target.value }); },//此函數不太能傳參,不得很少設一個 reset:function(){//重置實現 this.setState({ emailValue:'', passwordValue:'' }) }, submit:function(e){ // console.log(this.refs.email.refs.email.innerText); // console.log(this.refs.password.refs.password.innerText); var checkEmail=this.refs.email.refs.email.innerText; var checkPassword=this.refs.password.refs.password.innerText; if(checkEmail=="輸入正確!"&&checkPassword=="輸入正確!"){ alert('註冊成功!'); }else{ alert('請檢查你填寫的信息!'); e.preventDefault(); } }, render:function(){ return ( <form method="post" action="http://www.baidu.com"> 郵箱:<input type="text" onChange={this.changeEmail} value={this.state.emailValue} /><br/> <Judge ref="email" type="email" value={this.state.emailValue}/><br/> 密碼:<input type="text" onChange={this.changePassword} value={this.state.passwordValue} /><br/> <Judge ref="password" type="password" value={this.state.passwordValue}/><br/> <input type="button" value="重置" onClick={this.reset}/> <input type="submit" value="註冊" onClick={this.submit}/> </form> ) } }); ReactDOM.render( <App/>, document.getElementById('example') );
選項卡怕是每一個網頁設計者作的第一個組件。而第一次老是看遍原理卻無從下手。在此就用前面的知識作一個選項卡吧。
一個選項卡,基本結構就是兩個ul-li點擊來回切換。因此主要的結構應該是:
<ul class="btns"> <li><a class="active" href="javascript:;">1</a></li> <li><a href="javascript:;">2</a></li> <li><a href="javascript:;">3</a></li> <li><a href="javascript:;">4</a></li> </ul> <ul class="imgs"> <li class="active"><img src="images/1.jpg"/></li> <li><img src="images/2.jpg"/></li> <li><img src="images/3.jpg"/></li> <li><img src="images/4.jpg"/></li> </ul> </div>
css
/*css-reset*/ *{ margin:0; padding: 0; } ul li{ list-style: none; } a{ text-decoration: none; } /******************/ .tabs{ width: 400px; margin: 200px auto; } .btns a{ display: block; width: 30px; float: left; line-height: 30px; text-align: center; } .btns li{ float: left; } .btns .active{ background: #ccc; } .imgs li{ display: none; } .imgs .active{ display: block; }
基本效果
接下來就經過react的渲染來實現功能。
事實上我以爲這個最難的問題。
首先封裝按鈕ul組件和圖片庫ul組件,而後把它加到div#tabs裏面去。考慮用作兩個數組。
那麼基本初始化樣式就有了。在組件類的render函數下用循環自動生成兩個數組——而後再把此數組插入到對應結構的html中。
render函數以下:
render:function(){ var numArr=[]; var imgArr=[]; for(var i=0;i<this.state.num;i++){ if(i===0){ numArr.push( <li key={i+1}><a className="active" href="javascript:;" onClick={this.change}>{i+1}</a></li> ); imgArr.push( <li className="active" key={i+1}><img src="images/1.jpg" /></li> ); }else{ var str="images/"+(i+1).toString()+".jpg" numArr.push( <li key={i+1}><a href="javascript:;" onClick={this.change}>{i+1}</a></li> ); imgArr.push( <li key={i+1}><img src={str} /></li> ); } } return ( <div className="tabs"> <ul className="btns"> {numArr} </ul> <ul className="imgs"> {imgArr} </ul> </div> ) },
主要思想是:設置一個名字爲num的state,接收來自服務器的數據(傳進來圖片的張數)。而後根據這個num來設置數組須要哪些元素。
在上面的結構生成中,有個點擊觸發的change方法。裏面的dom操做所有依賴於點擊事件的this。實際上點擊發生的對象來自該函數的第一個參數event.target。
change:function(event){ var $all=$(event.target).parent().siblings().children(); $all.removeClass('active'); $(event.target).addClass('active'); var index=$(event.target).parent().index();//獲取索引值 var $allImgList=$(event.target).parent().parent().next().children(); $allImgList.hide(); $allImgList.eq(index).fadeIn(100); },
那麼這個函數就沒問題了,跟jquery選顯卡的代碼差很少。
咱們首先寫一個json.json文件到根目錄,來模擬獲取的服務器數據
[ { "num":"4" } ]
設置初始狀態:
getInitialState:function(){ return { num:0//來自服務器 } },
而後在虛擬dom插入到頁面以前就拿到數據——提示用componentWillMount。這裏是圖片所展示的張數。
在此我定義一個getListUrl屬性,
componentWillMount:function(){ $.getJSON(this.props.getListUrl,function(data){ //console.log(this.state); this.setState({ num:data[0]["num"]//設置狀態值爲獲取到的數據 }) }.bind(this)) },
所有代碼以下
var App=React.createClass({ getInitialState:function(){ return { num:0//來自服務器 } }, componentWillMount:function(){ $.getJSON(this.props.getListUrl,function(data){ //console.log(this.state); this.setState({ num:data[0]["num"] }) }.bind(this)) }, change:function(event){ var $all=$(event.target).parent().siblings().children(); $all.removeClass('active'); $(event.target).addClass('active'); var index=$(event.target).parent().index(); var $allImgList=$(event.target).parent().parent().next().children(); $allImgList.hide(); $allImgList.eq(index).fadeIn(100); }, render:function(){ var numArr=[]; var imgArr=[]; for(var i=0;i<this.state.num;i++){// if(i===0){ numArr.push( <li key={i+1}><a className="active" href="javascript:;" onClick={this.change}>{i+1}</a></li> ); imgArr.push( <li className="active" key={i+1}><img src="images/1.jpg" /></li> ); }else{ var str="images/"+(i+1).toString()+".jpg" numArr.push( <li key={i+1}><a href="javascript:;" onClick={this.change}>{i+1}</a></li> ); imgArr.push( <li key={i+1}><img src={str} /></li> ); } } return ( <div className="tabs"> <ul className="btns"> {numArr} </ul> <ul className="imgs"> {imgArr} </ul> </div> ) }, }); ReactDOM.render(<App getListUrl="json.json"/>,document.getElementById('wrap'));
效果