園子都荒廢兩個月了,實在是懶呀..
近段時間用React開發了幾個頁面,在使用過程中著實碰到了一些問題,估計剛開始學習的伙伴們都會遇到各種各樣的坑
總結記錄一下,只看文檔是碰不上問題的,內容基礎也不基礎,高手還請繞道哈哈哈
文章略長,整個目錄吧,想看哪兒看哪兒
一、基本使用
1. 同一頁面中使用
首先,需要核心庫 react.js 與React的DOM操作組件 react-dom.js
其次,如果需要在當前HTML頁面中直接寫react的代碼,就要引入 browser.js 文件,用于解析相關的JSX語法,同時,script標簽指定好type
引入browser是為了在瀏覽器端能直接解析JSX,不過相當耗時,所以建議在本地解析之后再引入ES5的語法文件。當然,JSX語法是不必要的,只是推薦使用。
通過 ReaactDOM.render 方法渲染,參數1指定組件,參數2指定標簽元素
2. 獨立文件中使用
使用babel工具對文件進行解析, Sublime Text中怎么配置babel編譯?
查看編譯后的文件
可以看到,JSX語法,核心就是React的 createElement 方法,我可以也直接使用這個方法創建。
這一丁點代碼就編譯了那么久,確實應該在本地先編譯好
除了直接在瀏覽器引入react和react-dom之外,既然需要本地先編譯,也可以使用構建工具如 Webpack ,不僅支持ES6與JSX的解析,還提供了一系列如代碼壓縮文件合并的功能,也便于管理,不必每次都得手動編譯
可以通過npm工具安裝 react 和 react-dom 包后,引入直接使用(需要ES6基礎)
這里就不展開說明了,有興趣的可以自行去查查相關用法
JSX是React中和重要的部分,直觀的表現是將HTML嵌入到了JS中,通過工具(如Babel)編譯成支持的JS文件
var Info = React.createClass({ render: function() { return lt;p className=quot;userquot;gt;{this.props.name}lt;/pgt; }});ReactDOM.render( lt;Info name=quot;Jackquot; /gt;, document.getElementById('box'));
可以看到,return關鍵字后接上一個lt;pgt;標簽,其中使用{}置入了JS語法。
1. 需要注意的是,return后面只能有一個父級標簽
var Info = React.createClass({ render: function() { return lt;p className=quot;userquot;gt; { this.props.name == 'Jack' ? lt;spangt;is Jacklt;/spangt; : '' } lt;/pgt; }});
2. {}中可以嵌入JS表達式,常見的是三目運算符與map操作
需要注意的是,三目運算符之后也只能接一個父級的標簽,否則會報錯
還可以置入組件
var Jack = React.createClass({ render: function() { return lt;pgt;I'm Jacklt;/pgt; }});var Pual = React.createClass({ render: function() { return lt;pgt;I'm Puallt;/pgt; }});var Info = React.createClass({ render: function() { return ( lt;div className=quot;userquot;gt; { this.props.name == 'Jack' ? lt;Jack /gt; : lt;Pual /gt; } lt;/divgt; ) }});ReactDOM.render( lt;Info name=quot;Pualquot; /gt;, document.getElementById('box'));
3. 在JSX中,HTML的屬性是受限的
在HTML標簽中使用非原始HTML支持的屬性(可加前綴 data- ),會被React忽略, class 關鍵字需要換成 className 等
事件綁定需要使用 camelCase 形式(如onClick)
var Info = React.createClass({ render: function() { return lt;p className=quot;userquot; me=quot;mequot; name=quot;myNamequot;gt;{this.props.name}lt;/pgt; }});
4. 智能的...展開操作符
JSX支持ES6中很多語法,包括 ... 這個東西。有時不想一個個屬性寫,為了代碼美觀,可以使用
var Info = React.createClass({ render: function() { var myAttr = { 'title': 'myTitle', 'age': 10, 'data-age': 10, 'onClick': function() { console.log('onClick'); }, 'onclick': function() { console.log('onclick'); } } return lt;p className=quot;userquot; me=quot;mequot; {...myAttr}gt;{this.props.name}lt;/pgt; }});ReactDOM.render( lt;Info name=quot;Jackquot; /gt;, document.getElementById('box'));
編譯后將自動展開,其中age被忽略,data-age被保留,onclick被忽略,onClick被保留
5. 事件的綁定與event對象傳值
由于React對事件的綁定處理忽略了原始支持的onclick屬性,在使用其他JS庫時,可能會遇到問題
如WdatePicker日期插件,它的使用方式是直接在HTML中綁定
lt;input type=quot;textquot; name=quot;quot; onclick=quot;WdatePicker()quot; /gt;
lt;input type=quot;textquot; name=quot;quot; onClick=quot;WdatePicker()quot; /gt;
但轉到React中就不適用了,onclick會直接被忽略,onClick因為傳的不是函數也被忽略,所以需要換個法子
render() { // return lt;input type=quot;textquot; name=quot;quot; onclick=quot;WdatePicker()quot; /gt; // return lt;input type=quot;textquot; name=quot;quot; onClick=quot;WdatePicker()quot; /gt; let clickEvent = { onClick: function(e) { console.log(e); WdatePicker(e); } }; return lt;input type=quot;textquot; name=quot;datequot; ref=quot;datequot; {...clickEvent} /gt; }
這樣一來就能綁定上事件,此日期插件需要一個event對象,然而點擊后報錯了,調試輸出該對象似乎有些奇特
再換種方式,在組件渲染之后直接綁定,成功
componentDidMount() { let date = ReactDOM.findDOMNode(this.refs.date); date.onclick = function(e) { console.log(e); WdatePicker(e); } }
雖說這是插件使用方式的不合理,但React傳過來的event對象也已經不是原始的event對象了
6. 支持自閉和的標簽,要顯示地給它關閉
舉個例子,對于lt;inputgt;標簽
lt;input type=quot;textquot; gt;
一般的HTML中這樣是支持的,但在JSX中會報錯
需要加個斜杠,同理用于lt;imggt;等標簽
lt;input type=quot;textquot; /gt;
三、屬性、狀態
React中有屬性與狀態之分,都是為了方便存儲或管理數據
1. 屬性(props)
一旦定義,就不再改變的數據
一般來說,會通過在HTML標簽中添加屬性的方式,讓子組件獲取到該props
ReactDOM.render( lt;Info name=quot;Jackquot; /gt;, document.getElementById('box'));
則Info組件中就可以通過 this.props.name 獲取到該屬性
也可以在組件中自己定義初始的屬性,如果父有傳name屬性,則該初始屬性被覆蓋
getDefaultProps: function() { return { name: 'defaultName' }; }
還可以定義屬性的類型,是否必須
propTypes: { name: React.PropTypes.string.isRequired }
這里定義了name屬性必須有且為字符串,假設傳入的是number類型(注意使用{}包裹,否則始終是字符串),則有警告
ReactDOM.render( lt;Info name={10} /gt;, document.getElementById('box'));
雖然有修改props的方法,但不建議對props進行修改,如果要修改,就使用state吧
2. 狀態(state)
狀態是React中定義之后可改變的數據,只能在組件內部定義
getInitialState: function() { return { age: 10 }; }
在需要修改狀態的時候,調用this.setState()方法即可(注意不能直接設置this.state = newObj)
this.setState({ age: this.state.age 1});
注意必須初始化state對象,即初始化時至少要返回一個空的state對象,age屬性的初始化是不必要的,只是為了便于管理
React的 setState 方法是異步的,在其中取state.age可能取不到預期的值(不過目前還沒遇到過)
這里的異步包含了兩個概念
2.1 調用的時機異步
React的組件有生命周期,在componentWillUpdate與render這兩個時期之間才會調用
2.2 調用之后的異步
setState實際上是一個異步方法,可帶兩個參數
this.setState({ age: this.state.age 1 }, function() { });
更好的做法是直接在第一個參數使用函數,如此便保證了函數內部能取到正確的值,在大型復雜的組件中推薦如此
this.setState(function(prevState, props) { return { age: prevState.age 1 }; });
四、組件的三種定義方式
React推薦將大部件劃分為一個個小部件,解耦。而組件的定義,常見的有三種方式
1. 函數式定義
使用函數的方式定義,它的特點是無狀態,實際上它并沒有被實例化,所以無法訪問 this 對象,不能管理生命周期
多用于純展示的組件
function Info(props) { return lt;pgt;{props.name}lt;/pgt;}ReactDOM.render(lt;Info name=quot;Jackquot; /gt;, document.getElementById('box'));
函數組件接受一個屬性參數,可直接獲取
2. React.createClass方式定義
這種方式看起來像是ES5的形式,較普遍,根據官方說明,將被類形式取代
var Info = React.createClass({ getInitialState: function() { return { name: 'myName' }; }, render: function() { return lt;pgt;{this.state.name}lt;/pgt; }});
在其中也可以使用ES6的語法,為了和類形式的做些區別,代碼多寫了點
let Info = React.createClass({ getInitialState() { return { name: this.props.name || 'myName' }; }, getDefaultProps() { return { year: new Date().getFullYear() }; }, showYear(e) { console.log(this); let elem = ReactDOM.findDOMNode(e.target); console.log('year ' elem.getAttribute('data-year')); }, render() { return lt;p onClick={this.showYear} data-year={this.props.year}gt;{this.state.name}lt;/pgt; }});
綁定了點擊事件,在點擊函數處理中可以直接取到該組件的this對象
3. extends React.Component方式定義
extends一看就是ES6的類形式了,比較推薦使用
class Info extends React.Component { constructor(props) { super(props); this.state = { name: this.props.name || 'myName' }; } showYear(e) { console.log(this); let elem = ReactDOM.findDOMNode(e.target); console.log('year ' elem.getAttribute('data-year')); } render() { return lt;p onClick={this.showYear} data-year={this.props.year}gt;{this.state.name}lt;/pgt; }}Info.defaultProps = { year: new Date().getFullYear()};ReactDOM.render(lt;Info /gt;, document.getElementById('box'));
可以看到一些區別,初始化props與state的方式不一樣
ES5形式中是直接在函數中return的方式,ES6形式的state是在構造函數中直接初始化this.state,而props初始化則需要在外部進行
再看看點擊事件,會發現輸出的this為null,因在ES6的類形式中,React并不會自動綁定函數方法的this對象,需要自行綁定
一般來說,有三種綁定方式
3.1 直接在構造函數中統一綁定
constructor(props) { super(props); this.state = { name: this.props.name || 'myName' }; this.showYear = this.showYear.bind(this); }
3.2 直接在onClick中綁定
相對在構造函數中綁定來說,這種方法會更有針對性,不過多個同一綁定就會顯得代碼冗余
render() { return lt;p onClick={this.showYear.bind(this)} data-year={this.props.year}gt;{this.state.name}lt;/pgt; }
3.3 在onClick綁定中使用回調函數調用
render() { return lt;p onClick={(e) =gt; this.showYear(e)} data-year={this.props.year}gt;{this.state.name}lt;/pgt; }
這種方式需要手動傳入event參數,而上述兩種不需要
五、組件的生命周期
圖片引自: 組件的生命周期
React的組件有從產生到消亡,有個生命周期。宏觀來講有三個時期
1. 實例化期(Mounting)
實例化這個時期主要是組件的初始實例化階段,如圖
主要包括屬性和狀態的初始化階段、組件即將加載( componentWillMount )階段、組件渲染(render)階段、組件加載完成( componentDidMount )階段
除了 render 可在存在期的時候再次進行組件渲染之外,其他階段只會發生一次
class Info extends React.Component { constructor(props) { super(props); this.state = { name: this.props.name, age: 0 }; } // 組件將加載 componentWillMount() { console.log('componentWillMount: ', this.state.age) } // 組件加載完成 componentDidMount() { console.log('componentDidMount: ', this.state.age) } // 渲染 render() { console.log('Info render: ', this.state.age); return lt;pgt;{this.state.name} {this.state.age}lt;/pgt; }}ReactDOM.render(lt;Info name=quot;Jackquot; /gt;, document.getElementById('box'));
2. 存在期間(Updating)
組件實例化之后,在組件存在的時期,隨著與用戶的交互,屬性或狀態的改變,組件可發生一些更新,如圖
componentWillReceiveProps(nextProps)
組件接收到屬性(通常是父級傳來的),帶一個參數,即為該屬性對象
shouldComponentUpdate(nextProps, nextState)
組件是否應該更新,true|false,默認返回true,帶兩個參數,將要更新的屬性對象和狀態對象
需要注意的是,如果自定義了這個方法,就會直接覆蓋默認的方法(若定義之后不返回則表示返回了false)
componentWillUpdate(nextProps, nextState)
組件將更新,帶兩個參數,將要更新的屬性對象和狀態對象
render
再次進入渲染階段
componentDidUpdate(prevProps, prevState)
組件更新完成,帶兩個參數,之前(已經)更新的屬性對象和狀態對象
在這個時期,各個階段按照流程不斷地進行著,舉個栗子
這里定義一個父組件InfoWrap和子組件Info
在實際開發中,為了防止JS阻塞HTML結構的渲染,初始異步獲取數據時一般會放到 componentDidMount 中
class InfoWrap extends React.Component { constructor(props) { super(props); this.state = { name: 'defaultName' }; } componentDidMount() { setTimeout(() =gt; { this.setState({ name: 'Jack' }); }, 1000); setTimeout(() =gt; { this.setState({ name: 'Jack' }); }, 3000); } render() { console.log('InfoWrap render'); return lt;Info name={this.state.name} /gt; }}ReactDOM.render(lt;InfoWrap /gt;, document.getElementById('box'));
通過setTimeout模擬異步,一段時間后改變狀態state中的name值,通過屬性name傳入子Info組件中
這里要注意的是,兩次setState的name值相同,
基于React依照state狀態的 diff 來判斷是否需要重新渲染數據,在InfoWrap中不會更新兩次HTML,但還是會向子Info中傳入兩次屬性props
class Info extends React.Component { constructor(props) { super(props); this.state = { name: this.props.name, age: 0 }; } increaseAge() { this.setState({ age: this.state.age 1 }); } // 組件將加載 componentWillMount() { console.log('componentWillMount: ', this.state.age) } // 組件加載完成 componentDidMount() { console.log('componentDidMount: ', this.state.age) } // 組件接收到新的props componentWillReceiveProps(nextProps) { if (nextProps.name !== this.state.name) { this.setState({ name: nextProps.name }); } console.log('componentWillReceiveProps: ', nextProps) } // 組件是否應該更新 shouldComponentUpdate(nextProps, nextState) { console.log('shouldComponentUpdate: ', nextProps, nextState); // return nextProps.name !== this.state.name; return nextState.age !== 3; } // 組件將更新 componentWillUpdate(nextProps, nextState) { console.log('componentWillUpdate: ', this.state.age) } // 組件更新完成 componentDidUpdate(prevProps, prevState) { console.log('componentDidUpdate: ', this.state.age) } // 組件將移除 componentWillUnmount() { console.log('componentWillUnmount: ', this.state.age) } // 渲染 render() { console.log('Info render: ', this.state.age); // 在這更改狀態將會無限循環 // this.setState({ // age: this.state.age 1 // }); return lt;p onClick={this.increaseAge.bind(this)} gt;{this.state.name} {this.state.age}lt;/pgt; }}
由上圖,子Info被渲染了三次,而實際上第三次name并未改變,其實是不需要渲染的
在實際開發中,為了防止無意義的渲染,通常會在 shouldComponentUpdate 添加判斷,自定義是否需要更新
將其中的 return nextProps.name !== this.state.name; 取消注釋,則不再被更新渲染
細心點可以看到,Info組件中的setState是放在了 componentWillReceiveProps 中
為什么不直接在 shouldComponentUpdate 中判斷是否需要更新后再更新狀態呢?
根據上方的流程圖,如果在這里更新,就會再次觸發state改變,導致又多循環執行了一次
所以一般的做法是在 componentWillReceiveProps 中根據條件判斷是否需要更新狀態,然后在 shouldComponentUpdate 中再根據條件判斷是否需要更新渲染組件
同理,千萬不要在render的時候setState更新狀態,這更危險,會出現死循環,不注意的話可以直接把瀏覽器搞崩了
以上是子組件從父組件獲取數據后更新的情況,下面來看看在子組件中的自我更新(increaseAge方法)
假設現在點擊一次age屬性值自增一次,在age不等于3的時候才更新頁面
可以看到,在 render 和 componentDidUpdate 階段,state的值才被實實在在地更新了,所以在之前的階段取setState之后的新值,仍為舊的值
3. 銷毀期(Unmounting)
銷毀期發生在組件被移除的時候,用于如果卸載組件后需要做一些特殊操作時,一般很少用
六、組件間的通信
組件一多起來,就涉及到不同組件之間的數據交流,主要有三種類型
1. 父子通信
React是單向的數據流動
父組件向子組件傳遞數據,其實就是通過props屬性傳遞的方式,父組件的數據更新,通過props數據的流動,子組件也得到更新
2. 子父通信
子組件與父組件通信,不同于Angular.js的數據雙向綁定,在React中默認支持子同步父的數據
若想實現父同步子的數據,則需要在子數據發生改變的時候,調用執行父props傳來的回調,從而達到父的同步更新
class InputItem extends React.Component { constructor(props) { super(props); this.state = {}; } inputChange(e) { this.props.inputChange(e.target.value); } render() { return lt;p title={this.props.title}gt; [InputItem]-input: lt;input type=quot;textquot; onChange={this.inputChange.bind(this)} /gt; lt;/pgt; }}class Page extends React.Component { constructor(props) { super(props); this.state = { inputValue: '' }; } inputChange(inputValue) { this.setState({ inputValue, }); } render() { return ( lt;divgt; lt;pgt;[Page]-input: lt;input type=quot;inputquot; value=http://www.tuicool.com/articles/{this.state.inputValue} /gt;lt;/pgt; lt;InputItem title=quot;myInputquot; inputChange={this.inputChange.bind(this)} /gt; lt;InputItem title=quot;myInputquot; inputChange={this.inputChange.bind(this)} /gt; lt;/divgt; ) }}ReactDOM.render(lt;Page /gt;, document.getElementById('box'));
這里定義了一個父組件Page,子組件InputItem
在父組件中 lt;InputItem title=quot;myInputquot; ... /gt; 其實就有了父與子的通信(props傳遞)
Page向InputItem傳遞了一個回調屬性,InputItem數據改變后調用此回調,數據得到更新
3. 兄弟通信
上述是父同步子的數據,如果要實現兄弟之間(或者兩個沒什么關系的組件)的數據同步,就得結合父與子、子與父的方式
class InputItem extends React.Component { constructor(props) { super(props); this.state = {}; } inputChange(e) { this.props.inputChange(e.target.value); } render() { return lt;p title={this.props.title}gt; [InputItem]-input: lt;input type=quot;textquot; onChange={this.inputChange.bind(this)} value=http://www.tuicool.com/articles/{this.props.inputValue} /gt; lt;/pgt; }}class Page extends React.Component { constructor(props) { super(props); this.state = { inputValue:'' }; } inputChange(inputValue) { this.setState({ inputValue, }); } render() { return ( lt;divgt; lt;pgt;[Page]-input: lt;input type=quot;inputquot; value=http://www.tuicool.com/articles/{this.state.inputValue} /gt;lt;/pgt; lt;InputItem title=quot;myInputquot; inputChange={this.inputChange.bind(this)} inputValue={this.state.inputValue} /gt; lt;InputItem title=quot;myInputquot; inputChange={this.inputChange.bind(this)} inputValue={this.state.inputValue} /gt; lt;/divgt; ) }}ReactDOM.render(lt;Page /gt;, document.getElementById('box'));
子InputItem更新后,調用父Page的回調,在父Page中將更新后的數據通過props傳至子InputItem
不同組件之間數據得到同步
4. 事件發布/訂閱
這個還沒用過 不清楚..
七、受控組件與非受控組件
在React中的表單Form系統中,有受控組件與非受控組件一說
1. 非受控組件
非受控,即表單項的value不受React的控制,不設初始value值,我們可以隨意更改
但不便于統一使用React進行管理,也不便于設置初始值
class Page extends React.Component { constructor(props) { super(props); this.state = { inputValue: '' }; } inputChange(e) { console.log(e.target.value) } render() { return ( lt;divgt; lt;pgt;lt;input type=quot;inputquot; onChange={this.inputChange.bind(this)} /gt;lt;/pgt; lt;/divgt; ) }}ReactDOM.render(lt;Page /gt;, document.getElementById('box'));
可以看到,此input項目似乎與React沒什么關系,想獲取它的值就必須通過DOM獲取到該元素,不方便管理
2. 受控組件
受控組件,是為了更好地管理表單項的值
但要注意的是,一旦設置了value,將不能通過直接在表單項輸入就能改變value值
因為value已經被React控制,要更新value值,就得更新相應的state狀態值
對于受控組件,又有初始值和值兩種之分
2.1 初始值(defaultValue)
defaultValue這里指的是 input , select , textarea 等,相應的 checkbox radio 是defaultChecked
初始值只是初始的一個值,在第一次設置定義之后就不可改變
在實際開發中,數據的獲取經常是異步的,大部分情況下會先初始設置input表單值為空,獲取到數據后再放到input中(如編輯頁面)
便會有以下代碼
class InputItem extends React.Component { constructor(props) { super(props); this.state = { inputValue: this.props.inputValue || '' }; } componentWillReceiveProps(nextProps) { this.setState({ inputValue: nextProps.inputValue }); } inputChange(e) { let inputValue = http://www.tuicool.com/articles/e.target.value; console.log(inputValue); // this.setState({ // inputValue // }); } render() { return lt;pgt;lt;input type=quot;inputquot; onChange={this.inputChange.bind(this)} defaultValue={this.state.inputValue} /gt;lt;/pgt; }}class Page extends React.Component { constructor(props) { super(props); this.state = { inputValue:'' }; } componentDidMount() { setTimeout(() =gt; { this.setState({ inputValue: 'myValue' }); }, 1000); } render() { return lt;InputItem inputValue=http://www.tuicool.com/articles/{this.state.inputValue} /gt; }}ReactDOM.render(lt;Page /gt;, document.getElementById('box'));
初始在InputItem中設置了defaultValue為空,一段時間后獲取到父Page傳來的新值inputValue,然而InputItem中的defaultValue并不會更新
這種情況,就不適用與defaultValue了,換成用狀態控制的value即可
2.2 值(value)
render() { return lt;pgt;lt;input type=quot;inputquot; onChange={this.inputChange.bind(this)} value=http://www.tuicool.com/articles/{this.state.inputValue} /gt;lt;/pgt; }
獲取到異步的數據后,通過 componentWillReceiveProps 中更新狀態值
加入 onChange 事件,在輸入的時候更新狀態值
而對于onChange事件的調用更新state,也有點點小技巧
假如input項目太多,為每個input定義一個change回調并不實際
這時可以在bind中指定參數,指定是某個input項,或者直接在input項中添加屬性區分,調用的時候再獲取
class InputItem extends React.Component { constructor(props) { super(props); this.state = { userName: this.props.userName || '', age: this.props.age || '' }; } componentWillReceiveProps(nextProps) { this.setState({ userName: nextProps.userName, age: nextProps.age }); } inputChange(name, e) { this.setState({ [name]: e.target.value }); } // inputChange(e) { // this.setState({ // [e.target.getAttribute('name')]: e.target.value // }); // } render() { return ( lt;divgt; lt;pgt;lt;input type=quot;inputquot; name=quot;userNamequot; onChange={this.inputChange.bind(this, 'userName')} value=http://www.tuicool.com/articles/{this.state.userName} /gt;lt;/pgt; lt;pgt;lt;input type=quot;inputquot; name=quot;agequot; onChange={this.inputChange.bind(this,'age')} value=http://www.tuicool.com/articles/{this.state.age} /gt;lt;/pgt; lt;/divgt; ) }}class Page extends React.Component { constructor(props) { super(props); this.state = { userName:'', age: '' }; } componentDidMount() { setTimeout(() =gt; { this.setState({ userName: 'Jack', age: 10 }); }, 1000); } render() { return lt;InputItem userName={this.state.userName} age={this.state.age} /gt; }}ReactDOM.render(lt;Page /gt;, document.getElementById('box'));
默認情況下,如果bind中不填第二個參數,在回調中第一個參數就是觸發的event對象
如果有第二個參數,回調中的第一個參數就是該參數,后續的參數才是觸發的event對象
上述兩個inputChange方法調用之后結果一樣,這里也利用了ES6支持對象屬性名為變量的新特性
另外,由于設置了value值之后的React組件表單項不能直接更改value值,需要修改state相應值。
在使用一些插件的時候可能會遇到問題,如日期插件bootstrap-datepicker
class DatePicker extends React.Component { constructor(props) { super(props); this.state = { timeFrom: '', timeEnd: '' }; } combindDate(date) { let year = date.getFullYear(), month = date.getMonth() 1, day = date.getDate(); month = month lt; 10 ? '0' month : month; day = day lt; 10 ? '0' day : day; return [year, month, day].join('-'); } componentDidMount() { let $timeFrom = $(this.refs.timeFrom); $timeFrom.datepicker({ format: 'yyyy-mm-dd', autoclose: true, language: 'zh-CN' }).on('changeDate', (ev) =gt; { let day = ev.date.getDate(); if (day gt; 15) { $timeFrom.datepicker('update', ''); // this.setState({ // timeFrom: '' // }); } else { // this.setState({ // timeFrom: this.combindDate(ev.date) // }); } }); } render() { return ( lt;divgt; lt;pgt;timeFrom: lt;input type=quot;inputquot; ref=quot;timeFromquot; value=http://www.tuicool.com/articles/{this.state.timeFrom} /gt;lt;/pgt; lt;pgt;timeEnd: lt;input type=quot;inputquot; ref=quot;timeEndquot; value={this.state.timeEnd} /gt;lt;/pgt; lt;/divgt; ) }}ReactDOM.render(lt;DatePicker /gt;, document.getElementById('box'));
且看看這個timeFrom,假設現在的需求是選擇的日期不能大于15號
正常情況下,直接調用 .datepicker('update', ''); 清空即可
但在React受控組件中,這關乎狀態state值,所以要同時進行顯示地setState(包括選成功的賦值與選失敗的清空,即注釋部分)
八、組件的復制
組件的復制也是一塊知識,不過我這里應該不算是復制吧,其實只是一個具體的栗子
1. 彈窗中的組件并不是在彈窗之后才加載,其實是初始就加載
想象一下有這么一個需求:
有很多道題,每道題會有一些附加的文件,需要有個文件的輪播,另外點擊文件還有彈窗預覽,彈窗中下方是文件輪播,上方是文件的預覽輪播
所以一個頁面會出現多個相似的輪播,點擊輪播中的文件可彈窗預覽該文件,在彈窗中下方還有這個相似的輪播
所以要做的其實就是三個組件,頁面組件,文件輪播組件,彈窗預覽組件(該組件中使用一個文件輪播組件)
思路很清晰,不過在實現過程中發現,并不是想象的樣子,彈窗中的文件輪播組件并不是在彈窗之后才加載,其實是頁面加載出來就加載了。
那例子太復雜,用幾個input項模擬一下吧
Page組件是頁面組件,InputItem是共享的,BoxBanner是彈窗組件
class InputItem extends React.Component { constructor(props) { super(props); this.state = { inputIndex: this.props.inputIndex || 0, inputValue: this.props.inputValue || '' }; } componentWillReceiveProps(nextProps) { this.setState({ inputIndex: nextProps.inputIndex, inputValue: nextProps.inputValue }); } componentDidMount() { console.log('componentDidMount ', this.state.inputIndex); } inputChange(e) { this.setState({ inputValue: e.target.value }); } inputClick() { console.log('inputClick'); } render() { return lt;p data-first=quot;1quot; className=quot;check-firstquot;gt;{this.state.inputIndex}、 lt;input type=quot;inputquot; onChange={this.inputChange.bind(this)} onClick={this.inputClick.bind(this)} value=http://www.tuicool.com/articles/{this.state.inputValue} style={{'margin': '10px'}} /gt; lt;/pgt; }}class BoxBanner extends React.Component { constructor(props) { super(props); this.state = { inputIndex: 0, inputValue: '' }; } openBox(e) { let elem = e.target; if (elem.tagName !== 'BUTTON') { return; } this.setState({ inputIndex: elem.getAttribute('data-index'), inputValue: elem.getAttribute('title') }); layer.open({ type: 1, title: false, shadeClose: true, // content: $('.template-box').html(), content: $('.template-box'), // content: $(this.refs.templateBox), success: function(layero) { let $first = $(layero).find('.check-first'); console.log('isFirst: ', $first.attr('data-first')); $first.attr('data-first', '0'); }.bind(this), end: function(layero) { // $('.check-first').attr('data-first', '1'); } }); } render() { return ( lt;divgt; lt;p onClick={this.openBox.bind(this)}gt; lt;button data-index=quot;1quot; title=quot;box1quot;gt;box1lt;/buttongt; lt;button data-index=quot;2quot; title=quot;box1quot;gt;box2lt;/buttongt; lt;button data-index=quot;3quot; title=quot;box1quot;gt;box3lt;/buttongt; lt;/pgt; lt;div className=quot;template-boxquot; ref=quot;templateBoxquot; style={{display: 'none'}}gt; lt;InputItem inputIndex={this.state.inputIndex} inputValue=http://www.tuicool.com/articles/{this.state.title} /gt; lt;/divgt; lt;/divgt; ) }}class Page extends React.Component { constructor(props) { super(props); } render() { return ( lt;divgt; lt;BoxBanner /gt; lt;/divgt; ) }}ReactDOM.render(lt;Page /gt;, document.getElementById('box'));
這里有個要求是,判斷是否是首次彈窗進來,初始設置data-first屬性為1,彈窗后即更新為0
在BoxBanner組件中引入了一個InputItem組件,但InputItem組件被共享,只在頁面開始加載是被加載了
傳遞到layer中的content似乎只是加載后的結果,可以看到isFirst值不是預想的
在layer的content中指定InputItem組件明顯是不可行的,畢竟這是JSX
所以,就得在彈窗關閉之后恢復相關的值,即end回調中的注釋部分
上述的代碼中
// content: $('.template-box').html(), content: $('.template-box'), // content: $(this.refs.templateBox),
最開始用的是第一種方法,但這將只會傳遞html,其中的事件將不被執行
換成第二種,事件的傳遞得到解決,但在React中過多的DOM操作并不推薦,且如果存在多個 .template-box 時,基于彈窗中組件不會重新加載的問題,組件的獲取就不正確
建議是換成第三種,取該組件的ref映射
Page組件中加多一項
render() { return ( lt;divgt; lt;BoxBanner /gt; lt;BoxBanner /gt; lt;/divgt; ) }
Tags: React
文章來源:http://www.cnblogs.com/imwtr/p/6278968.html