React 基礎實例教程

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

近段時間用React開發了幾個頁面,在使用過程當中着實碰到了一些問題,估計剛開始學習的夥伴們都會遇到各類各樣的坑react

總結記錄一下,只看文檔是碰不上問題的,內容基礎也不基礎,高手還請繞道哈哈哈npm

     文章略長,整個目錄吧,想看哪兒看哪兒bootstrap

 

  1.    基本使用
    1. 同一頁面中使用
    2. 獨立文件中使用
  2.   JSX
    1. return後面只能有一個父級
    2. {}中嵌套JS表達式
    3. 受限的HTML屬性
    4. 智能的...展開操做符
    5. 事件綁定與event對象傳值
    6. 需閉合標籤
  3.   屬性、狀態
    1. 屬性
    2. 狀態
  4.   組件的三種定義方式
    1. 函數式定義
    2. React.createClass方式定義
    3. extends React.Component方式定義
  5.   組件的生命週期
    1. 實例化期(Mounting)
    2. 存在期(Updating)
    3. 銷燬期(Unmounting)
  6.   組件間的通訊
    1. 父子通訊
    2. 子父通訊
    3. 兄弟通訊
  7.   受控組件與非受控組件
    1. 非受控組件
    2. 受控組件
  8.   組件的複製
    1. 彈窗中的組件並非在彈窗以後才加載,實際上是初始就加載

 

1、基本使用

1. 同一頁面中使用

首先,須要核心庫react.js與React的DOM操做組件react-dom.jsreact-native

其次,若是須要在當前HTML頁面中直接寫react的代碼,就要引入browser.js文件,用於解析相關的JSX語法,同時,script標籤指定好type數組

引入browser是爲了在瀏覽器端能直接解析JSX,不過至關耗時,因此建議在本地解析以後再引入ES5的語法文件。固然,JSX語法是沒必要要的,只是推薦使用。瀏覽器

經過ReaactDOM.render方法渲染,參數1指定組件,參數2指定標籤元素babel

2. 獨立文件中使用

使用babel工具對文件進行解析,Sublime Text中怎麼配置babel編譯?dom

查看編譯後的文件異步

能夠看到,JSX語法,核心就是React的createElement方法,我能夠也直接使用這個方法建立。

這一丁點代碼就編譯了那麼久,確實應該在本地先編譯好

 

除了直接在瀏覽器引入react和react-dom以外,既然須要本地先編譯,也可使用構建工具如Webpack,不只支持ES6與JSX的解析,還提供了一系列如代碼壓縮文件合併的功能,也便於管理,沒必要每次都得手動編譯

能夠經過npm工具安裝react和react-dom包後,引入直接使用(須要ES6基礎)

這裏就不展開說明了,有興趣的能夠自行去查查相關用法

 

2、JSX

JSX是React中和重要的部分,直觀的表現是將HTML嵌入到了JS中,經過工具(如Babel)編譯成支持的JS文件

複製代碼
var Info = React.createClass({
    render: function() {
        return <p className="user">{this.props.name}</p>
    }
});

ReactDOM.render(
    <Info name="Jack" />,
     document.getElementById('box')
);
複製代碼

能夠看到,return關鍵字後接上一個<p>標籤,其中使用{}置入了JS語法。

1. 須要注意的是,return後面只能有一個父級標籤

複製代碼
var Info = React.createClass({
 render: function() {
     return <p className="user">
         {
             this.props.name == 'Jack' ?
             <span>is Jack</span>
             : ''
         }
         </p>
 }
});
複製代碼

2. {}中能夠嵌入JS表達式,常見的是三目運算符與map操做

須要注意的是,三目運算符以後也只能接一個父級的標籤,不然會報錯

還能夠置入組件

複製代碼
var Jack = React.createClass({
 render: function() {
     return <p>I'm Jack</p>
 }
});
var Pual = React.createClass({
 render: function() {
     return <p>I'm Pual</p>
 }
});

var Info = React.createClass({
 render: function() {
     return (
         <div className="user">
         {
             this.props.name == 'Jack' ?
             <Jack />
             :
             <Pual />
         }
         </div>
     )
 }
});

ReactDOM.render(
 <Info name="Pual" />,
  document.getElementById('box')
);
複製代碼

3. 在JSX中,HTML的屬性是受限的

在HTML標籤中使用非原始HTML支持的屬性(可加前綴data-),會被React忽略,class關鍵字須要換成className等

事件綁定須要使用camelCase形式(如onClick)

var Info = React.createClass({
 render: function() {
     return <p className="user" me="me" name="myName">{this.props.name}</p>
 }
});

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 <p className="user" me="me" {...myAttr}>{this.props.name}</p>
 }
});

ReactDOM.render(
 <Info name="Jack" />,
  document.getElementById('box')
);
複製代碼

編譯後將自動展開,其中age被忽略,data-age被保留,onclick被忽略,onClick被保留

 

5. 事件的綁定與event對象傳值

因爲React對事件的綁定處理忽略了原始支持的onclick屬性,在使用其餘JS庫時,可能會遇到問題

如WdatePicker日期插件,它的使用方式是直接在HTML中綁定

<input type="text" name="" onclick="WdatePicker()" />
<input type="text" name="" onClick="WdatePicker()" />

但轉到React中就不適用了,onclick會直接被忽略,onClick由於傳的不是函數也被忽略,因此須要換個法子

複製代碼
render() {
        // return <input type="text" name="" onclick="WdatePicker()" />
        // return <input type="text" name="" onClick="WdatePicker()" />
        let clickEvent = {
            onClick: function(e) {
                console.log(e);
                WdatePicker(e);
            }
        };
        return <input type="text" name="date" ref="date" {...clickEvent} />
    }
複製代碼

這樣一來就能綁定上事件,此日期插件須要一個event對象,然而點擊後報錯了,調試輸出該對象彷佛有些奇特

再換種方式,在組件渲染以後直接綁定,成功

複製代碼
componentDidMount() {
        let date = ReactDOM.findDOMNode(this.refs.date);
        date.onclick = function(e) {
            console.log(e);
            WdatePicker(e);
        }
    }
複製代碼

雖然說這是插件使用方式的不合理,但React傳過來的event對象也已經不是原始的event對象了

6. 支持自閉合的標籤,要顯示地給它關閉

舉個例子,對於<input>標籤

<input type="text" >

通常的HTML中這樣是支持的,但在JSX中會報錯

須要加個斜槓,同理用於<img>等標籤

<input type="text" />

 

3、屬性、狀態

React中有屬性與狀態之分,都是爲了方便存儲或管理數據

1. 屬性(props)

一旦定義,就再也不改變的數據

通常來講,會經過在HTML標籤中添加屬性的方式,讓子組件獲取到該props

ReactDOM.render(
    <Info name="Jack" />,
    document.getElementById('box')
);

則Info組件中就能夠經過this.props.name獲取到該屬性

也能夠在組件中本身定義初始的屬性,若是父有傳name屬性,則該初始屬性被覆蓋

getDefaultProps: function() {
        return {
            name: 'defaultName'
        };
    }

還能夠定義屬性的類型,是否必須

propTypes: {
        name: React.PropTypes.string.isRequired
    }

這裏定義了name屬性必須有且爲字符串,假設傳入的是number類型(注意使用{}包裹,不然始終是字符串),則有警告

ReactDOM.render(
    <Info name={10} />,
    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
            };
        });

 

4、組件的三種定義方式

React推薦將大部件劃分爲一個個小部件,解耦。而組件的定義,常見的有三種方式

1. 函數式定義

使用函數的方式定義,它的特色是無狀態,實際上它並無被實例化,因此沒法訪問this對象,不能管理生命週期

多用於純展現的組件

function Info(props) {
    return <p>{props.name}</p>
}

ReactDOM.render(<Info name="Jack" />, document.getElementById('box'));

函數組件接受一個屬性參數,可直接獲取

 

2. React.createClass方式定義

這種方式看起來像是ES5的形式,較廣泛,根據官方說明,將被類形式取代

複製代碼
var Info = React.createClass({
 getInitialState: function() {
     return {
         name: 'myName'
     };
 },
 render: function() {
     return <p>{this.state.name}</p>
 }
});
複製代碼

在其中也可使用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 <p onClick={this.showYear} data-year={this.props.year}>{this.state.name}</p>
    }
});
複製代碼

綁定了點擊事件,在點擊函數處理中能夠直接取到該組件的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 <p onClick={this.showYear} data-year={this.props.year}>{this.state.name}</p>
    }
}

Info.defaultProps = {
    year: new Date().getFullYear()
};

ReactDOM.render(<Info />, 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 <p onClick={this.showYear.bind(this)} data-year={this.props.year}>{this.state.name}</p>
    }

3.3 在onClick綁定中使用回調函數調用

render() {
        return <p onClick={(e) => this.showYear(e)} data-year={this.props.year}>{this.state.name}</p>
    }

這種方式須要手動傳入event參數,而上述兩種不須要

 

5、組件的生命週期

圖片引自:組件的生命週期

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 <p>{this.state.name} {this.state.age}</p>
    }
}

ReactDOM.render(<Info name="Jack" />, 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(() => {
            this.setState({
                name: 'Jack'
            });
        }, 1000);

        setTimeout(() => {
            this.setState({
                name: 'Jack'
            });
        }, 3000);
    }

    render() {
        console.log('InfoWrap render');
        return <Info name={this.state.name} />
    }
}

ReactDOM.render(<InfoWrap />, 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 <p onClick={this.increaseAge.bind(this)} >{this.state.name} {this.state.age}</p>
    }
}
複製代碼

由上圖,子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)

銷燬期發生在組件被移除的時候,用於若是卸載組件後須要作一些特殊操做時,通常不多用

 

6、組件間的通訊

組件一多起來,就涉及到不一樣組件之間的數據交流,主要有三種類型

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 <p title={this.props.title}>
            [InputItem]-input: <input type="text" onChange={this.inputChange.bind(this)} />
        </p>
    }
}

class Page extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            inputValue: ''
        };
    }

    inputChange(inputValue) {
        this.setState({
            inputValue,
        });
    }

    render() {
        return (
            <div>
                <p>[Page]-input: <input type="input" value={this.state.inputValue} /></p>
                <InputItem title="myInput" inputChange={this.inputChange.bind(this)} />
                <InputItem title="myInput" inputChange={this.inputChange.bind(this)} />
            </div>
        )
    }
}

ReactDOM.render(<Page />, document.getElementById('box'));
複製代碼

這裏定義了一個父組件Page,子組件InputItem

在父組件中<InputItem title="myInput" ... /> 其實就有了父與子的通訊(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 <p title={this.props.title}>
        [InputItem]-input: <input type="text" onChange={this.inputChange.bind(this)} value={this.props.inputValue} />
        </p>
    }
}

class Page extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            inputValue: ''
        };
    }

    inputChange(inputValue) {
        this.setState({
            inputValue,
        });
    }

    render() {
        return (
            <div>
                <p>[Page]-input: <input type="input" value={this.state.inputValue} /></p>
                <InputItem title="myInput" inputChange={this.inputChange.bind(this)} inputValue={this.state.inputValue} />
                <InputItem title="myInput" inputChange={this.inputChange.bind(this)} inputValue={this.state.inputValue} />

            </div>
        )
    }
}

ReactDOM.render(<Page />, document.getElementById('box'));
複製代碼

子InputItem更新後,調用父Page的回調,在父Page中將更新後的數據經過props傳至子InputItem

不一樣組件之間數據獲得同步

4. 事件發佈/訂閱

這個還沒用過 不清楚..

7、受控組件與非受控組件

在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 (
            <div>
                <p><input type="input" onChange={this.inputChange.bind(this)} /></p>
            </div>
        )
    }
}

ReactDOM.render(<Page />, document.getElementById('box'));
複製代碼

能夠看到,此input項目彷佛與React沒什麼關係,想獲取它的值就必須經過DOM獲取到該元素,不方便管理

 

2. 受控組件

受控組件,是爲了更好地管理表單項的值

但要注意的是,一旦設置了value,將不能經過直接在表單項輸入就能改變value值

由於value已經被React控制,要更新value值,就得更新相應的state狀態值

對於受控組件,又有初始值和值兩種之分

2.1 初始值(defaultValue) -- 注:其實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 = e.target.value;

        console.log(inputValue);

        // this.setState({
        //     inputValue
        // });
    }

    render() {
        return <p><input type="input" onChange={this.inputChange.bind(this)} defaultValue={this.state.inputValue} /></p>
    }
}

class Page extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            inputValue: ''
        };
    }

    componentDidMount() {
        setTimeout(() => {
            this.setState({
                inputValue: 'myValue'
            });
        }, 1000);
    }

    render() {
        return <InputItem inputValue={this.state.inputValue} />
    }
}

ReactDOM.render(<Page />, document.getElementById('box'));
複製代碼

初始在InputItem中設置了defaultValue爲空,一段時間後獲取到父Page傳來的新值inputValue,然而InputItem中的defaultValue並不會更新

這種狀況,就不適用與defaultValue了,換成用狀態控制的value便可

2.2 值(value)

render() {
        return <p><input type="input" onChange={this.inputChange.bind(this)} value={this.state.inputValue} /></p>
    }

獲取到異步的數據後,經過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 (
            <div>
                <p><input type="input" name="userName" onChange={this.inputChange.bind(this, 'userName')} value={this.state.userName} /></p>
                <p><input type="input" name="age" onChange={this.inputChange.bind(this, 'age')} value={this.state.age} /></p>
            </div>
        )
    }
}

class Page extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            userName: '',
            age: ''
        };
    }

    componentDidMount() {
        setTimeout(() => {
            this.setState({
                userName: 'Jack',
                age: 10
            });
        }, 1000);
    }

    render() {
        return <InputItem userName={this.state.userName} age={this.state.age} />
    }
}

ReactDOM.render(<Page />, 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 < 10 ? '0' + month : month;
        day = day < 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) => {
            let day = ev.date.getDate();
            if (day > 15) {
                $timeFrom.datepicker('update', '');

                // this.setState({
                //     timeFrom: ''
                // });
            } else {
                // this.setState({
                //     timeFrom: this.combindDate(ev.date)
                // });
            }
        });
    }

    render() {
        return (
            <div>
                <p>timeFrom: <input type="input" ref="timeFrom" value={this.state.timeFrom} /></p>
                <p>timeEnd: <input type="input" ref="timeEnd"  value={this.state.timeEnd} /></p>
            </div>
        )
    }
}

ReactDOM.render(<DatePicker />, document.getElementById('box'));
複製代碼

且看看這個timeFrom,假設如今的需求是選擇的日期不能大於15號

正常狀況下,直接調用.datepicker('update', '');清空便可

但在React受控組件中,這關乎狀態state值,因此要同時進行顯示地setState(包括選成功的賦值與選失敗的清空,即註釋部分)

 

8、組件的複製

組件的複製也是一塊知識,不過我這裏應該不算是複製吧,其實只是一個具體的栗子

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 <p data-first="1" className="check-first">{this.state.inputIndex}、
            <input
                type="input"
                onChange={this.inputChange.bind(this)}
                onClick={this.inputClick.bind(this)}
                value={this.state.inputValue}
                style={{'margin': '10px'}}
            />
        </p>
    }
}

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 (
            <div>
                <p onClick={this.openBox.bind(this)}>
                    <button data-index="1" title="box1">box1</button>
                    <button data-index="2" title="box1">box2</button>
                    <button data-index="3" title="box1">box3</button>
                </p>
                <div className="template-box" ref="templateBox" style={{display: 'none'}}>
                    <InputItem inputIndex={this.state.inputIndex} inputValue={this.state.title} />
                </div>
            </div>
        )
    }
}

class Page extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <div>
                <BoxBanner />
            </div>
         )
    }
}
ReactDOM.render(<Page />, 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 (
            <div>
                <BoxBanner />
                <BoxBanner />
            </div>
         )
    }
複製代碼

相關文章
相關標籤/搜索