React 基础实例教程

园子都荒废两个月了,实在是懒呀..

近段时间用React开发了几个页面,在使用过程中着实碰到了一些问题,估计刚开始学习的伙伴们都会遇到各种各样的坑

总结记录一下,只看文档是碰不上问题的,内容基础也不基础,高手还请绕道哈哈哈

     文章略长,整个目录吧,想看哪儿看哪儿

 

  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. 同一页面中使用

首先,需要核心库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>
         )
    }

 

posted @ 2017-01-12 16:59  -渔人码头-  阅读(11840)  评论(0编辑  收藏  举报