React使用小結

分類:技術 時間:2017-01-13

園子都荒廢兩個月了,實在是懶呀..

近段時間用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工具安裝 reactreact-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的時候才更新頁面

可以看到,在 rendercomponentDidUpdate 階段,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


ads
ads

相關文章
ads

相關文章

ad