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

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" />

 

三、属性、状态

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
            };
        });

 

四、组件的三种定义方式

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参数,而上述两种不需要

 

五、组件的生命周期

图片引自:组件的生命周期

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的时候才更新页面

可以看到,在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 <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. 事件发布/订阅

这个还没用过 不清楚..

七、受控组件与非受控组件

在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(包括选成功的赋值与选失败的清空,即注释部分)

 

八、组件的复制

组件的复制也是一块知识,不过我这里应该不算是复制吧,其实只是一个具体的栗子

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>         )
    }

复制代码


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章