• react组件切换

    react组件切换

    展开全文
  • React组件概念想必大家都熟悉了,但是在业务开发过程中,面对通用的业务,怎么编写一通用的React业务组件呢?本文以实际案例说明,如何编写一通用的业务组件。 案例背景 实现一如图所示的组件,可以添加对组件...

    React组件概念想必大家都熟悉了,但是在业务开发过程中,面对通用的业务,怎么编写一个通用的React业务组件呢?本文以实际案例说明,如何编写一个通用的业务组件。

    案例背景

    实现一个如图所示的组件,可以添加对组件进行添加和删除

    在这里插入图片描述

    实现思路

    • 实现组件的界面样式
    • 实现组件的基本功能
    • 考虑通用型和可扩展性

    实现基本的界面样式

    实现上面的界面展示,需要三个方面的东西

    • 一个文本框
    • 一个➖操作
    • 一个➕操作
      jrs可以自己编写样式,也可以引用通用的组件,本文偷个懒,决定引用现成的antd的组件
    import React, { Component } from 'react';
    import {Input, Icon} from 'antd';
    import PropTypes from 'prop-types';
    
    class List extends Component{
        render() {
            let iconStyle = {
                del: {
                   color: 'red', marginLeft: '0.2%'
                },
                add: {
                   color: 'grey', marginLeft: '0.2%'
                }
            }
            return <div style = {{display: 'flex', marginLeft:'10px'}}>
                       <Input/>
                       <Icon type="minus-circle" theme="outlined" style = {iconStyle.del}/>
                       <Icon type="plus-circle" theme="outlined" style = {iconStyle.add}/>
                   </div>
        }
    }
    

    上面就把基本的样式搞定了,这离我们想要的还缺一些基本的功能

    实现基本功能

    组件处于编辑状态的时候

    • 点击➕,增加组件
    • 点击➖,减少相应组件
    • 当只有一个组件的时候,只显示➕
      组件处于非编辑状态的时候
    • 输入框不能编辑
    • ➕和➖都不展示
      实现的思路就是,组件内部维护一个list,然后每次增加和删除的时候,对list进行增加和删除,然后渲染整个组件。
    class List extends Component{
        constructor(props) {
            super(props);
            this.state = {
                list : [1]
            }
        }
        add = (key) => () => {
            let list = this.state.list;
            list.push(1);
            this.setState({
                list: list
            });
        }
        del = (key) => () => {
            let list = this.state.list;
            list.splice(key, 1, 0);
            this.setState({
                list: list
            });
        }
        renderList = () => {
            let list = this.state.list;
            let renderList = [];
            let len = list.length;
            let delShow = list.reduce((total, value) => total + value);
            let iconStyle = {
                del: {
                    color: 'red', marginLeft: '0.2%'
                },
                add: {
                    color: 'grey', marginLeft: '0.2%'
                }
            }
            for (let i =0; i < len; i++) {
                list[i] && renderList.push(<div key = {i} style = {{display: 'flex', marginLeft:'10px'}}>
                    <Input/>
                    {delShow > 1 && <Icon type="minus-circle" theme="outlined" style = {iconStyle.del} onClick = {this.del(i)}/>}
                    <Icon type="plus-circle" theme="outlined" style = {iconStyle.add} onClick = {this.add(i)}/>
                </div>)
            }
            return renderList;
        }
        render() {
            return this.renderList();
        }
    }
    

    实现了上述组件,可以进行添加和删除了,但是这个组件具备一个完整的组件还需要一些功能

    • 可以获取组件的值
    • 可以设置组件的默认值
    • 组件可以设置非编辑状态
    • 可以设置组件的样式
    class List extends Component{
        constructor(props) {
            super(props);
            this.state = {
                list : [1]
            }
        }
        static defaultProps = {
            iconStyle: {
                del: {
                    color: 'red', marginLeft: '0.2%'
                },
                add: {
                    color: 'grey', marginLeft: '0.2%'
                }
            },
            // 提供默认的样式选择
            layoutStyle: {
                display: 'flex',
                width: '40%',
                flexDirection: 'row',
                flexWrap: 'wrap',
                justifyContent: 'flex-start'
            },
            // 通过disabled控制组件的状态
            disabled: false
        }
        add = (key) => () => {
            let list = this.state.list;
            list.push(1);
            this.setState({
                list: list
            });
        }
        del = (key) => () => {
            let list = this.state.list;
            list.splice(key, 1, 0);
            this.setState({
                list: list
            });
            // console.log('exce del action', key);        
        }
        // 获取组件的值,本文中默认所有组件都实现getValue方法,然后通过ref方法获得
        getValue = () => {
            let list = this.state.list;
            let len = list.length;
            let value = [];
            for(let i = 0; i< len; i++) {
                if(list[i]) {
                    value.push(this.refs[i].getValue())
                }
            }
            return value;
        }
        
        renderList = (value) => {
            let list = this.state.list;
            let renderList = [];
            let len = list.length;
            let delShow = list.reduce((total, value) => total + value);
            let { iconStyle, disabled} = this.props;
            for (let i =0; i < len; i++) {
                list[i] && renderList.push(<div key = {i} style = {{display: 'flex', marginLeft:'10px'}}>
                    <Input ref= {i} defaultValue = {value && value[i]} disabled = {disabled}/>
                    {!disabled && delShow > 1 && <Icon type="minus-circle" theme="outlined" style = {iconStyle.del} onClick = {this.del(i)}/>}
                    {!disabled && <Icon type="plus-circle" theme="outlined" style = {iconStyle.add} onClick = {this.add(i)}/>}
                </div>)
            }
            return renderList;
        }
        render() {
            let { value, layoutStyle } = this.props;
            
            return <div style = {layoutStyle}>
                // 通过value设置组件的默认值
                {this.renderList(value)}
            </div>
        }
    }
    

    经过上面的编写,终于完成了组件的基本样式和功能,基本上是够用的了,但是组件还存在一些问题,通用性不够。

    实现更通用的组件

    • 如果不仅仅展示一个input,可能是任意其他任何一个组件怎么办
    • 如果想在组件中间添加,一些特别的字符或者组件,比如且或非逻辑怎么办?
      先来思考第一个
      如果组件不是input而是其他的,怎么办?怎么获取它的值怎么设置它的状态?
      如果换成其他的组件,当然很简单,在多一个参数,把这个参数作为组件就可以。
      ok,这样是可行的,但是怎么获取它的值呢?以及怎么设置他的状态?
      简单来说,就是怎么把 ref = i 注入到组件中去?
      答:实现一个高阶组件就可以了啊。
    class List extends Component{
        constructor(props) {
            super(props);
            this.state = {
                list : [1]
            }
        }
        static defaultProps = {
            iconStyle: {
                del: {
                    color: 'red', marginLeft: '0.2%'
                },
                add: {
                    color: 'grey', marginLeft: '0.2%'
                }
            },
            layoutStyle: {
                display: 'flex',
                width: '40%',
                flexDirection: 'row',
                flexWrap: 'wrap',
                justifyContent: 'flex-start'
            },
            disabled: false
        }
        add = (key) => () => {
            let list = this.state.list;
            list.push(1);
            this.setState({
                list: list
            });
        }
        del = (key) => () => {
            let list = this.state.list;
            list.splice(key, 1, 0);
            this.setState({
                list: list
            });
            // console.log('exce del action', key);        
        }
        getValue = () => {
            let list = this.state.list;
            let len = list.length;
            let value = [];
            for(let i = 0; i< len; i++) {
                if(list[i]) {
                    value.push(this.refs[i].getValue())
                }
            }
            return value;
        }
        renderList = (value) => {
            let list = this.state.list;
            let renderList = [];
            let len = list.length;
            let delShow = list.reduce((total, value) => total + value);
            // InputComponent 就是你的输入组件 ,after则是你需要插入的后缀组件
            let { iconStyle, disabled, InputComponent, after} = this.props;
            for (let i =0; i < len; i++) {
                list[i] && renderList.push(<div key = {i} style = {{display: 'flex', marginLeft:'10px'}}>
                    <InputComponent ref = {i} defaultValue = {value && value[i]} disabled = {disabled}/>
                    {delShow > 1 && after}
                    {!disabled && delShow > 1 && <Icon type="minus-circle" theme="outlined" style = {iconStyle.del} onClick = {this.del(i)}/>}
                    {!disabled && <Icon type="plus-circle" theme="outlined" style = {iconStyle.add} onClick = {this.add(i)}/>}
                </div>)
            }
            return renderList;
        }
        render() {
            let { value, layoutStyle } = this.props;
            
            return <div style = {layoutStyle}>
                {this.renderList(value)}
            </div>
        }
    }
    

    至此,整个功能就编写完成了。
    那么还缺一点东西,那就是参数说明

    List.propTypes = {
        value: PropTypes.array,
        layoutStyle: PropTypes.object,
        iconStyle: PropTypes.object,
        InputComponent: PropTypes.element.isRequired,
        after: PropTypes.node,
        disabled: PropTypes.bool
    };
    

    附录

    附上完整代码

    import React, { Component } from 'react';
    import {Input, Icon} from 'antd';
    import PropTypes from 'prop-types';
    
    class List extends Component{
        constructor(props) {
            super(props);
            this.state = {
                list : [1]
            }
        }
        static defaultProps = {
            iconStyle: {
                del: {
                    color: 'red', marginLeft: '0.2%'
                },
                add: {
                    color: 'grey', marginLeft: '0.2%'
                }
            },
            layoutStyle: {
                display: 'flex',
                width: '40%',
                flexDirection: 'row',
                flexWrap: 'wrap',
                justifyContent: 'flex-start'
            },
            disabled: false
        }
        add = (key) => () => {
            let list = this.state.list;
            list.push(1);
            this.setState({
                list: list
            });
        }
        del = (key) => () => {
            let list = this.state.list;
            list.splice(key, 1, 0);
            this.setState({
                list: list
            });
            // console.log('exce del action', key);        
        }
        getValue = () => {
            let list = this.state.list;
            let len = list.length;
            let value = [];
            for(let i = 0; i< len; i++) {
                if(list[i]) {
                    value.push(this.refs[i].getValue())
                }
            }
            return value;
        }
        renderList = (value) => {
            let list = this.state.list;
            let renderList = [];
            let len = list.length;
            let delShow = list.reduce((total, value) => total + value);
            let { iconStyle, disabled, InputComponent, after} = this.props;
            for (let i =0; i < len; i++) {
                list[i] && renderList.push(<div key = {i} style = {{display: 'flex', marginLeft:'10px'}}>
                    {/* <Input ref= {i} defaultValue = {value && value[i]} disabled = {disabled}/> */}
                    <InputComponent ref = {i} defaultValue = {value && value[i]} disabled = {disabled}/>
                    {delShow > 1 && after}
                    {!disabled && delShow > 1 && <Icon type="minus-circle" theme="outlined" style = {iconStyle.del} onClick = {this.del(i)}/>}
                    {!disabled && <Icon type="plus-circle" theme="outlined" style = {iconStyle.add} onClick = {this.add(i)}/>}
                </div>)
            }
            return renderList;
        }
        render() {
            let { value, layoutStyle } = this.props;
            
            return <div style = {layoutStyle}>
                {this.renderList(value)}
            </div>
        }
    }
    List.propTypes = {
        value: PropTypes.array,
        layoutStyle: PropTypes.object,
        iconStyle: PropTypes.object,
        InputComponent: PropTypes.element.isRequired,
        after: PropTypes.node,
        disabled: PropTypes.bool
    };
    export default List;
    
    展开全文
  • React组件之间传值

    2016-03-21 10:07:37
    今天群里面有很多都在问关于 React 组件之间是如何通信的问题,之前自己写的时候也遇到过这类问题。下面是我看到的一篇不错英文版的翻译,看过我博客的人都知道,我翻译可能不会按部就班,会尽可能用中文的意思,来...

    前言

    今天群里面有很多都在问关于 React 组件之间是如何通信的问题,之前自己写的时候也遇到过这类问题。下面是我看到的一篇不错英文版的翻译,看过我博客的人都知道,我翻译可能不会按部就班,会尽可能用中文的意思,来将作者要讲述的技术描述清楚。英文能力有限,如果有不对的地方请跟我留言,一定修改……^_^

    原著序

    处理 React 组件之间的交流方式,主要取决于组件之间的关系,然而这些关系的约定人就是你。

    我不会讲太多关于 data-stores、data-adapters 或者 data-helpers 之类的话题。我下面只专注于 React 组件本身的交流方式的讲解。

    React 组件之间交流的方式,可以分为以下 3 种:

    • 【父组件】向【子组件】传值;

    • 【子组件】向【父组件】传值;

    • 没有任何嵌套关系的组件之间传值(PS:比如:兄弟组件之间传值)

    一、【父组件】向【子组件】传值

    初步使用

    这个是相当容易的,在使用 React 开发的过程中经常会使用到,主要是利用 props 来进行交流。例子如下:

    // 父组件
    var MyContainer = React.createClass({
      getInitialState: function () {
        return {
          checked: true
        };
      },
      render: function() {
        return (
          <ToggleButton text="Toggle me" checked={this.state.checked} />
        );
      }
    });
    
    // 子组件
    var ToggleButton = React.createClass({
      render: function () {
        // 从【父组件】获取的值
        var checked = this.props.checked,
            text = this.props.text;
    
        return (
            <label>{text}: <input type="checkbox" checked={checked} /></label>
        );
      }
    });
    

    进一步讨论

    如果组件嵌套层次太深,那么从外到内组件的交流成本就变得很高,通过 props 传递值的优势就不那么明显了。(PS:所以我建议尽可能的减少组件的层次,就像写 HTML 一样,简单清晰的结构更惹人爱)

    // 父组件
    var MyContainer = React.createClass({
      render: function() {
        return (
          <Intermediate text="where is my son?" />
        );
      }
    });
    
    // 子组件1:中间嵌套的组件
    var Intermediate = React.createClass({
      render: function () {
        return (
          <Child text={this.props.text} />
        );
      }
    });
    
    // 子组件2:子组件1的子组件
    var Child = React.createClass({
      render: function () {
        return (
          <span>{this.props.text}</span>
        );
      }
    });
    

    二、【子组件】向【父组件】传值

    接下来,我们介绍【子组件】控制自己的 state 然后告诉【父组件】的点击状态,然后在【父组件】中展示出来。因此,我们添加一个 change 事件来做交互。

    // 父组件
    var MyContainer = React.createClass({
      getInitialState: function () {
        return {
          checked: false
        };
      },
      onChildChanged: function (newState) {
        this.setState({
          checked: newState
        });
      },
      render: function() {
        var isChecked = this.state.checked ? 'yes' : 'no';
        return (
          <div>
            <div>Are you checked: {isChecked}</div>
            <ToggleButton text="Toggle me"
              initialChecked={this.state.checked}
              callbackParent={this.onChildChanged}
              />
          </div>
        );
      }
    });
    
    // 子组件
    var ToggleButton = React.createClass({
      getInitialState: function () {
        return {
          checked: this.props.initialChecked
        };
      },
      onTextChange: function () {
        var newState = !this.state.checked;
        this.setState({
          checked: newState
        });
        // 这里要注意:setState 是一个异步方法,所以需要操作缓存的当前值
        this.props.callbackParent(newState);
      },
      render: function () {
        // 从【父组件】获取的值
        var text = this.props.text;
        // 组件自身的状态数据
        var checked = this.state.checked;
    
        return (
            <label>{text}: <input type="checkbox" checked={checked}                 onChange={this.onTextChange} /></label>
        );
      }
    });
    

    图片描述

    我觉得原文作者用代码不是很直观,接下来我话一个流程走向简图来直观描述一下这个过程:

    图片描述

    这样做其实是依赖 props 来传递事件的引用,并通过回调的方式来实现的,这样实现不是特别好,但是在没有任何工具的情况下也是一种简单的实现方式

    这里会出现一个我们在之前讨论的问题,就是组件有多层嵌套的情况下,你必须要一次传入回调函数给 props 来实现子组件向父组件传值或者操作。

    Tiny-Tip: React Event System

    在 onChange 事件或者其他 React 事件中,你能够获取以下东西:

    • 【this】:指向你的组件

    • 【一个参数】:这个参数是一个 React 合成事件,SyntheticEvent。

    React 对所有事件的管理都是自己实现的,与我们之前使用的 onclick、onchange 事件不一样。从根本上来说,他们都是绑定到 body 上。

    document.on('change', 'input[data-reactid=".0.2"]', function () {...});
    

    上面这份代码不是来自于 React,只是打一个比方而已。

    如果我没有猜错的话,React 真正处理一个事件的代码如下:

    var listenTo = ReactBrowserEventEmitter.listenTo;
    ...
    function putListener(id, registrationName, listener, transaction) {
      ...
      var container = ReactMount.findReactContainerForID(id);
      if (container) {
        var doc = container.nodeType === ELEMENT_NODE_TYPE ? container.ownerDocument : container;
        listenTo(registrationName, doc);
      }
      ...
    }
    // 在监听事件的内部,我们能发现如下:
    target.addEventListener(eventType, callback, false);
    

    这里有所有 React 支持的事件:中文文档-事件系统

    多个子组件使用同一个回调的情况

    // 父组件
    var MyContainer = React.createClass({
      getInitialState: function () {
        return {
          totalChecked: 0
        };
      },
      onChildChanged: function (newState) {
        var newToral = this.state.totalChecked + (newState ? 1 : -1);
        this.setState({
          totalChecked: newToral
        });
      },
      render: function() {
        var totalChecked = this.state.totalChecked;
        return (
          <div>
            <div>How many are checked: {totalChecked}</div>
            <ToggleButton text="Toggle me"
              initialChecked={this.state.checked}
              callbackParent={this.onChildChanged}
              />
            <ToggleButton text="Toggle me too"
                initialChecked={this.state.checked}
                callbackParent={this.onChildChanged}
              />
            <ToggleButton text="And me"
              initialChecked={this.state.checked}
              callbackParent={this.onChildChanged}
              />
          </div>
        );
      }
    });
    
    // 子组件
    var ToggleButton = React.createClass({
      getInitialState: function () {
        return {
          checked: this.props.initialChecked
        };
      },
      onTextChange: function () {
        var newState = !this.state.checked;
        this.setState({
          checked: newState
        });
        // 这里要注意:setState 是一个异步方法,所以需要操作缓存的当前值
        this.props.callbackParent(newState);
      },
      render: function () {
        // 从【父组件】获取的值
        var text = this.props.text;
        // 组件自身的状态数据
        var checked = this.state.checked;
    
        return (
            <label>{text}: <input type="checkbox" checked={checked} onChange={this.onTextChange} /></label>
        );
      }
    });
    

    图片描述

    这是非常容易理解的,在父组件中我们增加了一个【totalChecked】来替代之前例子中的【checked】,当子组件改变的时候,使用同一个子组件的回调函数给父组件返回值。

    三、没有任何嵌套关系的组件之间传值

    如果组件之间没有任何关系,组件嵌套层次比较深(个人认为 2 层以上已经算深了),或者你为了一些组件能够订阅、写入一些信号,不想让组件之间插入一个组件,让两个组件处于独立的关系。对于事件系统,这里有 2 个基本操作步骤:订阅(subscribe)/监听(listen)一个事件通知,并发送(send)/触发(trigger)/发布(publish)/发送(dispatch)一个事件通知那些想要的组件。

    下面讲介绍 3 种模式来处理事件,你能点击这里来比较一下它们。

    简单总结一下:

    (1) Event Emitter/Target/Dispatcher

    特点:需要一个指定的订阅源

    // to subscribe
    otherObject.addEventListener(‘click’, function() { alert(‘click!’); });
    // to dispatch
    this.dispatchEvent(‘click’);
    

    (2) Publish / Subscribe

    特点:触发事件的时候,你不需要指定一个特定的源,因为它是使用一个全局对象来处理事件(其实就是一个全局
    广播的方式来处理事件)

    // to subscribe
    globalBroadcaster.subscribe(‘click’, function() { alert(‘click!’); });
    // to dispatch
    globalBroadcaster.publish(‘click’);
    

    (3) Signals

    特点:与Event Emitter/Target/Dispatcher相似,但是你不要使用随机的字符串作为事件触发的引用。触发事件的每一个对象都需要一个确切的名字(就是类似硬编码类的去写事件名字),并且在触发的时候,也必须要指定确切的事件。(看例子吧,很好理解)

    
    // to subscribe
    otherObject.clicked.add(function() { alert(‘click’); });
    // to dispatch
    this.clicked.dispatch();
    

    如果你只想简单的使用一下,并不需要其他操作,可以用简单的方式来实现:

    // 简单实现了一下 subscribe 和 dispatch
    var EventEmitter = {
        _events: {},
        dispatch: function (event, data) {
            if (!this._events[event]) { // 没有监听事件
              return;
            }
            for (var i = 0; i < this._events[event].length; i++) {
                this._events[event][i](data);
            }
        },
        subscribe: function (event, callback) {
          // 创建一个新事件数组
          if (!this._events[event]) {
            this._events[event] = [];
          }
          this._events[event].push(callback);
        }
    };
    
    otherObject.subscribe('namechanged', function(data) { alert(data.name); });
    this.dispatch('namechanged', { name: 'John' });
    
    

    如果你想使用 Publish/Subscribe 模型,可以使用:PubSubJS

    React 团队使用的是:js-signals 它基于 Signals 模式,用起来相当不错。

    Events in React

    使用 React 事件的时候,必须关注下面两个方法:

    componentDidMount
    componentWillUnmount
    

    在处理事件的时候,需要注意:

    在 componentDidMount 事件中,如果组件挂载(mounted)完成,再订阅事件;当组件卸载(unmounted)的时候,在 componentWillUnmount 事件中取消事件的订阅。

    (如果不是很清楚可以查阅 React 对生命周期介绍的文档,里面也有描述。原文中介绍的是 componentWillMount 个人认为应该是挂载完成后订阅事件,比如Animation这个就必须挂载,并且不能动态的添加,谨慎点更好)

    因为组件的渲染和销毁是由 React 来控制的,我们不知道怎么引用他们,所以EventEmitter 模式在处理组件的时候用处不大。

    pub/sub 模式可以使用,你不需要知道引用。

    下面来一个例子:实现有多个 product 组件,点击他们的时候,展示 product 的名字。

    (我在例子中引入了之前推荐的 PubSubJS 库,如果你觉得引入代价太大,也可以手写一个简版,还是比较容易的,很好用哈,大家也可以体验,但是我还是不推荐全局广播的方式)

    // 定义一个容器
    var ProductList = React.createClass({
        render: function () {
          return (
            <div>
              <ProductSelection />
              <Product name="product 1" />
              <Product name="product 2" />
              <Product name="product 3" />
            </div>
          );
        }
    });
    // 用于展示点击的产品信息容器
    var ProductSelection = React.createClass({
      getInitialState: function() {
        return {
          selection: 'none'
        };
      },
      componentDidMount: function () {
        this.pubsub_token = PubSub.subscribe('products', function (topic, product) {
          this.setState({
            selection: product
          });
        }.bind(this));
      },
      componentWillUnmount: function () {
        PubSub.unsubscribe(this.pubsub_token);
      },
      render: function () {
        return (
          <p>You have selected the product : {this.state.selection}</p>
        );
      }
    });
    
    var Product = React.createClass({
      onclick: function () {
        PubSub.publish('products', this.props.name);
      },
      render: function() {
        return <div onClick={this.onclick}>{this.props.name}</div>;
      }
    });
    

    图片描述

    ES6: yield and js-csp

    ES6 中有一种传递信息的方式,使用生成函数(generators)和 yield 关键字。可以看一下https://github.com/ubolonton/js-csp

    (这里我写一个简单的 DEMO 介绍一下这种新的传递方式,其实大同小异)

    function* list() {
        for(var i = 0; i < arguments.length; i++) {
            yield arguments[i];
        }
        return "done.";
    }
    
    var o = list(1, 2, 3);
    
    var cur = o.next;
    while(!cur.done) {
        cur = o.next();
        console.log(cur);
    }
    

    以上例子来自于屈屈的一篇博客:ES6 中的生成器函数介绍 屈屈是一个大牛,大家可以经常关注他的博客。

    通常来说,你有一个队列,对象在里面都能找到一个引用,在定义的时候锁住,当发生的时候,立即打开锁执行。js-csp 是一种解决办法,也许以后还会有其他解决办法。

    结尾

    在实际应用中,按照实际要解决的需求选择解决办法。对于小应用程序,你可以使用 props 和回调的方法进行组件之间的数据交换。你可以通过 pub/sub 模式,以避免污染你的组件。在这里,我们不是在谈论数据,只是组件。对于数据的请求、数据的变化等场景,可以使用 Facebook 的 Flux、Relay、GraphQL 来处理,都非常的好用。

    文中的每一个例子我都验证过了,主要使用最原始的引入文件方式,创建服务使用的 http-server 包,大家也可以尝试自己来一次。

    展开全文
  • React组件详解

    2018-07-19 23:08:31
    众所周知,组件作为React的核心内容,是View的重要组成部分,每一View页面都由一或多个组件构成,可以说组件React应用程序的基石。在React组件构成中,按照状态来分可以分为有状态组件和无状态组件。 所谓...

    3.6.1 React组件简介

    众所周知,组件作为React的核心内容,是View的重要组成部分,每一个View页面都由一个或多个组件构成,可以说组件是React应用程序的基石。在React的组件构成中,按照状态来分可以分为有状态组件和无状态组件。
    所谓无状态组件,就是没有状态控制的组件,只做纯静态展示的作用,无状态组件是最基本的组件形式,它由属性props和渲染函数render构成。由于不涉及到状态的更新,所以这种组件的复用性也最强。
    有状态组件是在无状态组件的基础上增加了组件内部状态管理,有状态组件通常会带有生命周期lifecycle,用以在不同的时刻触发状态的更新,有状态组件被大量用在业务逻辑开发中。

    目前,React支持三种方式来定义一个组件,分别是:
    - ES5的React.createClass方式;
    - ES6的React.Component方式;
    - 无状态的函数组件方式。

    在ES6出现之前,React使用React.createClass方式来创建一个组件类,它接受一个对象作为参数,对象中必须声明一个render方法,render函数返回一个组件实例。例如:

    import React from 'react';
    
    const TextView = React.createClass({  
    //初始化组件状态
    getInitialState () {
        return {
        };
      },
    
      render() {
        return (
          <div>我是一个Text</div>
        );
      }
    });
    
    export default TextView; 

    不过,随着React版本的持续升级,ES5的React.createClass方式暴露的问题也越来越多。例如,使用React.createClass创建的组件,事件函数会自动绑定相关的函数,这样会导致不必要的性能开销,而React.Component则是有选择性的绑定有需要函数。
    随着ES6语法的普及,React.createClass正逐渐被React.Component方式所替代。并且,使用React.Component方式创建的组件更符合面向函数编程的思想,可读性也更高。例如,下面是使用React.Component方式创建TextView的实例。

    import React,{Component} from 'react'
    
    class TextView extends Component {
    //初始化组件状态
    constructor(props) {
        super(props);    //传递props给component
        this.state = {
        };
      }
    
        render() {
            return (
                <div>我是一个Text</div>
            );
        }
    }
    
    export default TextView;

    通过React.createClass和React.Component方式创建的组件都是有状态的组件,而无状态组件则是通过无状态的函数创建的。无状态组件是React在0.14版本推出的一种新的组件形式,它是一种只负责展示的纯组件。
    在React开发中,随着应用复杂度的不断提升和组件数量的增加,组件的管理和维护成为不得不面对的问题,于是一种只负责展示的纯组件出现了。无状态组件的特点是不需要管理组件状态state,数据直接通过props传入即可,这也符合React单向数据流的思想。
    对于无状态组件的函数式声明方式,不仅可以提高代码的可阅读性,还能大大的减少代码量,提高代码的复用率,箭头函数则是函数式编程的最佳搭档。

    const Todo = (props) => (
      <li
        onClick={props.onClick}
        style={{textDecoration: props.complete ? "line-through" : "none"}}>
        {props.text}
      </li>
    )

    对于上面定义的Todo组件,输入输出数据完全由props决定,如果props为Object类型时,还可以使用ES6提供的解构赋值。例如:

    const Todo = ({ onClick, complete, text, ...props }) => (
      <li
        onClick={onClick}
        style={{textDecoration: complete ? "line-through" : "none"}}
        {...props}
      >
        {props.text}
      </li>
    )

    无状态组件一般会搭配高阶组件(简称OHC)一起使用,高阶组件主要用来托管State,Redux框架就是通过store来管理数据源和组件的所有状态,其中所有负责展示的组件都使用无状态函数式的写法,无状态组件也被大规模的使用在大型应用程序中。
    虽然,无状态组件具有诸多的优势,但也不是万能的。比如,无状态组件在被React调用之前,组件是不会被实例化的,所以它不支持ref特性。

    3.6.2 ES5与ES6组件对比

    相比React.createClass方式,React.Component带来了诸多语法上的改进:
    1. import
    ES6使用import方式替代ES5的require方式来导入模块,其中import { }可以直接从模块中导入变量名,此种写法更加简洁直观。
    2. 初始化state
    在ES6的语法规则中,React的组件使用的类继承的方式来实现,去掉了ES5的getInitialState的hook函数,state的初始化则放在constructor构造函数中声明。
    3. this绑定
    使用React.createClass方式创建的组件,事件函数会自动绑定this函数,但是此种方式会带来不必要的性能开销,增加了代码过时的可能性。而使用React.Component方式创建组件时,事件函数并不会自动绑定this函数,需要开发者手动绑定,从而减少了不必要的性能开销。
    4. 默认属性和状态
    使用React.createClass方式创建组件时,有关组件props的属性类型及组件默认属性会作为组件实例的属性进行配置,其中defaultProps可以使用组件的getDefaultProps方法来获取。例如:

    const Demo = React.createClass({
        propTypes: { 
            name: React.PropTypes.string
        },
        getDefaultProps(){   
            return {
                name: ' '    
            }
        }
        …
    })

    而使用React.Component方式创建组件时,配置组件的属性时,是通过类的静态属性来配置的。例如:

    class Demo extends React.Component {
    //类的静态属性
        static propTypes = {   
            name: React.PropTypes.string
        };
        //类的静态属性
        static defaultProps = {
            name: ' '
        };
        ...
    }

    同时,React.createClass方式创建的组件,组件的状态通过getInitialState方法来进行配置。而React.Component方式创建的组件,其状态state则是在constructor函数中像初始化组件属性一样进行声明的。

    3.6.3 组件的props

    React组件化的开发思路一直为人所称道,而组件最核心的两个概念莫过于props与state,组件的最终呈现效果正是props和state作用的结果。其中,props是组件对外的接口,而state则是组件对内的接口。一般情况下,props是不变的,其基本的使用方法如下。

    {this.props.key}

    在典型的React数据流模型中,props是父子组件交互的唯一方式,下面的例子演示了如何在组件中使用props。

    class HelloMessage extends Component{
        constructor(props){
            super(props);
            this.state = {
                name: 'jack'
            }
        }
    
        render(){
            return (
                <h1> Hello {this.props.name}</h1>
            )
        }
    }
    export default Message;

    在上面的例子中,通过构造函数为属性设置初始值,当然也可以不设置初始值,当需要使用name属性的时候可以通过{this.props.name}方式获取。在ES5语法中,如果想要为组件的属性设置默认值,需要通过getDefaultProps()方法来设置。例如:

    var HelloMessage = React.createClass({
      //设置初始值
      getDefaultProps: function() {
        return {
          name: 'jack'
        };
      },
      render: function() {
        return <h1>Hello {this.props.name}</h1>;
      }
    });
    
    ReactDOM.render(
      <HelloMessage />,
      document.getElementById('example')
    );

    props作为父子组件沟通的桥梁,为组件的通信和传值提供了重要手段,下面是一个父子组件传值的实例。

    //子组件
    export default class Child extends Component {
    
        constructor(props){
            super(props);
            this.state={
                counter:props.age||0
            }
        }
    
        render() {
            return (
                <h1>Hello {this.props.name}</h1>
            )
        }
    }
    
    Child.propTypes={
        name:PropTypes.string.isRequired,
        age:PropTypes.number
    }
    
    Child.defaultProps={
        age:0
    }

    当父组件需要向子组件传递值时,只需要引入子组件,然后使用组件提供的props属性即可。例如:

    //父组件
    export default class Father extends Component {
    
        render() {
            return (
                <div>
                    <Child name="jack" age={30}/>
                    <Child name="tom" age={20}/>
                </div>
            )
        }
    }

    在上面的实例中,子组件props接受的数据格式由PropTypes进行检测,并且使用isRequired关键字来标识该属性是否是必须的。props使用PropTypes来保证传递数据的类型和格式,当向props传入无效数据时,JavaScript的控制台会给出警告提示。

    3.6.4 组件的state

    如果说props是组件对外的接口,那么state则是组件对内的接口,state作为组件的私有属性,只能被本组件去访问和修改。而props对于使用它的组件来说是只读的,如果想要修改props,只能通过组件的父组件修改。
    React把组件看成是一个特殊的状态机,通过与用户的交互实现不同状态,进而渲染界面,让用户界面和数据保持一致。在React中,如果需要使用state,就需要在组件的constructor初始化相关的state。例如:

    constructor(props) {
       super(props);
       this.state={
          key:value,
          ...
       }
    }

    如果要更新组件的state,则需要调用setState方法。

    this.setState({
         key:value
     }) ;

    需要注意的是,在调用setState函数执行更新操作时,组件的state并不会立即改变,因为setState()是异步的。setState操作只是把要修改的状态放入一个队列中,出于性能原因,React可能会对多次的setState状态修改进行合并修正,所以当我们使用{this.state}获取状态state时,可能并不是我们需要的那个state。同理,也不能依赖当前的props来计算组件的下一个状态,因为props一般也是从父组件的State中获取,依然无法确定组件在状态更新时的值。
    同时,在调用setState修改组件状态时,只需要传入需要改变的状态变量即可,而不必传入组件完整的state,因为组件state的更新是一个浅合并的过程。例如,一个组件的state由title和content构成。

    this.state = {
      title : 'React',
      content : 'React is an wonderful JS library!'
    }

    当需要修改title的状态时,只需要调用setState()修改title的内容即可。例如:

    this.setState({title: 'React Native'});

    由于state的更新是一个浅合并的过程,所以合并后的state只会修改新的title到state中,同时保留content的原有状态。合并后的内容如下:

    {
      title : 'React Native ',
      content : 'React is an wonderful JS library!'
    }

    3.6.5 组件的ref

    在React典型的数据流模型中,props作为父子组件交互的最基本也是最重要的方式,主要通过传递props值来使子组件重新render,从而达到父子组件通信的目的。当然,在某些特殊的情况下修改子组件的时候就需要是要另一种方式(例如和第三方库的DOM整合或者某个DOM元素focus问题上),即是ref方式。
    React提供的ref属性,其本质就是调用ReactDOM.render()返回的组件实例,用来表示为对组件真正实例的引用。具体使用时,可以将它绑定到组件的render()上,然后就可以用它输出组件的实例。
    ref不仅可以挂载到组件上,还可以作用于DOM元素上。具体来说,挂载组件使用class定义,表示对组件实例的引用,此时不能在函数式组件上使用ref属性,因为它们不能获取组件的实例。而挂载到DOM元素时则表示具体的DOM元素节点。
    ref支持两种调用方式:一种是设置回调函数,另一种是字符串的方式。其中,设置回调函数是官方的推荐方式,使用它可以更细致的控制refs,使用此种方式,ref属性接受一个回调函数,它在组件被加载或者卸载时被立即执行。具体来说,当给HTML元素添加ref属性时,Refs回调接受底层的Dom元素作为参数,当组件卸载时Refs回调会接受null作为参数。

    class Demo extends React.Component{
      constructor(props) {
        super(props);
        this.state = {           
          isInputshow:false    //控制input是否渲染
        }
      }
    
      inputRefcb(instance){
        if(instance) {                            
          instance.focus();                     
        }
      }
    
      render() {
       {
          this.state.isInputshow ? 
          <div>
            <input ref={this.inputRefcb} type="text" />
          </div>
          : null           
        }
      }
    }

    对于上面的例子,触发回调的时机主要有以下三种情况:

    • 组件被渲染后,回调参数instance作为input的组件实例的引用,回调参数可以立即使用该组件;
    • 组件被卸载后,回调参数instance此时为null,这样做可以确保内存不被泄露;
    • ref属性本身发生改变,原有的ref会再次被调用,此时回调参数instance变成具体的组件实例。

    如果在使用String方式,则可以通过{this.refs.inputRef}的方式来获取组件实例。例如:

    class Demo extends React.Component{
      constructor(props) {
        super(props);
    
        onFocus(){
         this.refs.inputRef.focus()
        }
      }
      render() {
        <div>
          <input ref="inputRef" type="text" />
        </div>
        <input type="button" value="Focus" onClick={this.onFocus} />
      }
    }

    同时,官方明确申明不能在函数式声明组件中使用ref,因为它们不能获取组件的实例。例如,下面的实例是错误的:

    function InputComponent() {return <input />;
    }
    
    class Demo extends React.Component {
      render() {
        // 编译不通过
        return (
          < InputComponent
            ref={(input) => { this.textInput = input; }} />
        );
      }
    }

    在某些情况下,可能需要从父组件中访问子组件的DOM节点,那么可以在子组件中暴露一个特殊的属性给父组件调用,子组件接收一个函数作为prop属性,同时将这个函数赋予到DOM节点作为ref属性,那么父组件就可以将它的ref回调传递给子级组件的DOM。这种方式对于class声明的组件和函数式声明的组件都是适用的。例如:

    function TextInput(props) {
      return (
        <div>
          <input ref={props.inputRef} />
        </div>
      );
    }
    
    class Father extends React.Component {
      render() {
        return (
    //子组件传入inputRef函数
          < TextInput  inputRef={e => this.inputElement = e}  />  
        );
      }
    }

    在上面的例子中,父组件Father将他的ref回调函数通过inputRef属性传递给TextInput,而TextInput将这个回调函数作为input元素的ref属性,此时父组件Father中通过{this.inputElement}得到子组件的input对应的DOM元素。暴露DOM的ref属性除了可以方便在父组件中访问子组件的DOM节点外,还可以实现多个组件跨层级调用。

    展开全文
  • React组件化开发初试 按照官方文档和例程博客,实现了一简单的输入框组件。 如果想了解官方案例,请参考深入理解 React 总结一下,一简单的React.js应用应按照以下步骤构建: 设计组件原型和JSON API; ...

    React组件化开发初试

    按照官方文档和例程博客,实现了一个简单的输入框组件。

    如果想了解官方案例,请参考深入理解 React

    总结一下,一个简单的React.js应用应按照以下步骤构建:

    1. 设计组件原型和JSON API;
    2. 拆分用户界面为一个组件树;
    3. 利用React, 创建应用的一个静态版本;
    4. 识别出最小的(但是完整的)代表UI的state;
    5. 确认state的生命周期;
    6. 添加反向数据流。

    个人认为这里的难点在于如何拆分用户界面,粒度过大不利于组件化的重用,粒度过小的话,显得冗余过多,并且复杂度陡升。本人最开始尝试细化的拆分,尽量让一个组件只完成一个很小的功能需求,但最后反而不知道如何给每个组件分配其任务。

    另一个难点来源于对state的判断,其实如果能够识别出最小的state,读者会发现一个复杂的组件所需的state数量其实很少。

    在React.js中,有两种数据模型,state代表的是会动态变化的状态,props代表的是父级传递而来的属性,state会反过来更新UI,而props只是一次性设置填充。

    第一步:构建原型

    我们的组件要完成以下功能:

    1. 可以定制label和输入框的placeholder的内容
    2. 可以有个属性设置个正则,输入的文字不符合正则的时候输入框就标红
    3. 右侧的大叉icon点击后,消失并清楚输入框内容
    4. 组件需要开放3个对外方法,获取(获取值时trim),设置(设置值时trim),删除,输入框的内容。

    原型比较简单,样式如下:
    LimitedInputBox

    JSON接口数据如下:

    { 
    labelText: "酒店地址",
    hinderText: "请填写酒店地址",
    buttonImgSrc:
    regExp: /^\w{5,8}$/
    }

    我们通过JSON数据设置输入框组件的label标签、Input的placeholder属性和对用户输入的正则验证。

    第二步:拆分用户界面为一个组件树

    在完成用户界面切分的过程中,最开始,如图原型图所示,做了非常细粒度的拆分,拆分结构如下:

    • LimitedInputBox
      • Title
      • InputBox
      • ClearButton

    这里,将LimitedInputBox作为最外面的容器,包裹整个组件,子组件分为三部分,Title即是一个label标签,用来显示该输入框组件的名称(或者说标题),InputBox即是一个input标签,用来接收用户输入,ClearButton可以用一个img标签,用来清除input输入框内容。刚开始准备用另外一个容器包裹InputBoxClearButton,发现这样过于冗余于是两者还是和Title作为同一级组件比较好。

    后来思来,觉得过于拆分了,最小单位与HTML原生标签同一级别,没有意义,因此,最后只保留了一个组件,就称之为LimitedInputBox。即:

    • LimitedInputBox

    第三步:利用React, 创建应用的一个静态版本

    首先,不需要考虑用户交互,直接将数据模型渲染到UI上。即将数据渲染和用户交互两个过程拆分开来。

    这样做比较简单,因为构建静态版本的页面只需要大量的输入,而不需要思考;但是添加交互功能却需要大量的思考和少量的输入。

    为了创建一个渲染数据模型的应用的静态版本,你将会构造一些组件,这些组件重用其它组件,并且通过 props 传递数据。 props 是一种从父级向子级传递数据的方式。

    本例中,的props就是第一步构建的JSON数据。

    考虑到第二步中我采取了两种拆分方式,这里给出相应的代码:

    /** author : River He**/
    
    //细分结构
    var data = {
        labelText: "酒店地址",
        hinderText: "请填写酒店地址",
        buttonImgSrc: "eliminate.png",
        regExp: /^\w{5,8}$/
    };
    
    var Title = React.creatClass({
        render: function() {
        return (
            <label>{this.props.labelText}</label>
        );
      }
    });
    
    var InputBox = React.createClass({
        render: function() {
      return (
        <input 
            placeholder={this.props.hinderText} 
            regExp={this.props.regExp}>
        </input>
      );
      }
    });
    
    var ClearButton = React.createClass({
        render: function() {
        return (
            <img src={this.props.buttonImgSrc}></>
        );
      }
    });
    
    var LimitedInputBox = React.createClass({
        render: function() {
        return (
            <Title labelText={this.props.data.labelText} />
          <InputBox 
            hinderText={this.props.data.hinderText}
            regExp={this.props.data.regExp}
          />
          <ClearButton buttonImgSrc={this.props.data.buttonImgSrc} />
        );
      }
    });
    
    ReactDOM.render(
        <LimitedInputBox data={data} />,
      document.getElementById('example')
    );
    /** author : river he**/
    
    //粗划分
    var data = {
        labelText: "酒店地址",
        hinderText: "请填写酒店地址",
        buttonImgSrc: "eliminate.png",
        regExp: /^\w{5,8}$/
    };
    
    var LimitedInputBox = React.createClass({
        render: function() {
    
            return (
                <div>
                <label >{this.props.data.labelText}</label>
                <input 
                    placeholder={this.props.data.hinderText} 
                    regExp={this.props.data.regExp}>
                </input>
                <img src={this.props.buttonImgSrc ></img>
                </div>
            );
        }
    });
    
    ReactDOM.render(
        <LimitedInputBox data={data} />,
        document.getElementById('example')
    );

    第四步:识别出最小的代表UI的state

    为了使 UI 可交互,需要能够触发底层数据模型的变化。 React 通过 state 使这变得简单。

    为了正确构建应用,首先需要考虑应用需要的最小的可变 state 数据模型集合。此处关键点在于精简:不要存储重复的数据。构造出绝对最小的满足应用需要的最小 state 是有必要的,并且计算出其它强烈需要的东西。

    本案例较简单,state很明显是inputText,即input输入框中的值。

    而对于一般情况,可以简单地对每一项数据提出三个问题:

    1. 是否是从父级通过props传入的?如果是,可能不是state。
    2. 是否会随着时间改变?如果不是,可能不是state。
    3. 能够根据组件中其他的state数据或者props计算出来吗?如果是,就不是state。

    第五步:确认state的生命周期

    找出了state之后,需要继续找出那些组件会被state更新,即哪些组件应该拥有state数据模型,本案例同样很简单,因为输入框input要判断用户输入是否符合正则表达式要求,因此与input相关的组件都应该拥有state。按照第一种细分,InputBox组件应该拥有state,按照第二个细分,因为只存在一个组件LimitedInputBox,故其应该拥有state

    //细分结构
    var data = {
        labelText: "酒店地址",
        hinderText: "请填写酒店地址",
        buttonImgSrc: "eliminate.png",
        regExp: /^\w{5,8}$/
    };
    
    var Title = React.creatClass({
        render: function() {
        return (
            <label>{this.props.labelText}</label>
        );
      }
    });
    
    var InputBox = React.createClass({
        render: function() {
      return (
        <input 
            placeholder={this.props.hinderText}
            value={this.props.inputText} 
            regExp={this.props.regExp}>
        </input>
      );
      }
    });
    
    var ClearButton = React.createClass({
        render: function() {
        return (
            <img src={this.props.buttonImgSrc}></>
        );
      }
    });
    
    var LimitedInputBox = React.createClass({
      getInitialState: function() {
        return {
          inputText: ''
        };
      },
    
      render: function() {
        return (
            <Title labelText={this.props.data.labelText} />
          <InputBox 
            hinderText={this.props.data.hinderText}
            inputText={this.state.inputText}
            regExp={this.props.data.regExp}
          />
          <ClearButton buttonImgSrc={this.props.data.buttonImgSrc} />
        );
      }
    });
    
    ReactDOM.render(
        <LimitedInputBox data={data} />,
      document.getElementById('example')
    );
    /** author : river he**/
    
    //粗划分
    var data = {
        labelText: "酒店地址",
        hinderText: "请填写酒店地址",
        buttonImgSrc: "eliminate.png",
        regExp: /^\w{5,8}$/
    };
    
    var LimitedInputBox = React.createClass({
    
        //初始化state
        getInitialState: function() {
            return {
                inputText: ''
            };
        },
    
        render: function() {
    
            return (
                <div>
                <label >{this.props.data.labelText}</label>
                <input 
                    placeholder={this.props.data.hinderText}
                    regExp={this.props.data.regExp}
                    value={this.state.inputText}
                    ></input>
                <img 
                    src={this.props.data.buttonImgSrc}
                </img>
                </div>
            );
        }
    });
    
    ReactDOM.render(
        <LimitedInputBox data={data} />,
        document.getElementById('example')
    );

    第六步:添加反向数据流

    前面构建了渲染正确的基于propsstate的沿着组件树从上至下单项数据流动的应用。然后我们需要构建反向的数据流动方式:组件树中层级很深的表单组件更新父级中的state

    如果尝试在前面的输入框中输入,React会忽略你的输入。这是有意的,因为已经设置了inputvalue属性,使其总是和LimitedInputBox(对于粗分的情况,就是其本身)传递过来的state一致。

    但是我们希望的是,无论用户何时改变了表单,都要更新state来反应用户的输入。由于组件只能更新自己的stateLimitedInputBox将会传递一个回调函数给InputBox,此函数将会在state应该被改变时触发。我们可以使用inputonChange事件来监听用户输入,从而确定何时触发回调函数。

    LimitedInputBox传递的回调函数将会调用setState(),然后应用将会被更新。

    /**author : River He*/
    
    //细分结构
    var data = {
        labelText: "酒店地址",
        hinderText: "请填写酒店地址",
        buttonImgSrc: "eliminate.png",
        regExp: /^\w{5,8}$/
    };
    
    var Title = React.createClass({
        render: function() {
            return (
                <label>{this.props.labelText}</label>
            );
        }
    });
    
    var InputBox = React.createClass({
    
        handleChange: function() {
            this.props.onUserInput(
                this.refs.inputBox.value
            );
        },
    
        fitRegExp: function(inputText) {
            if(inputText.match(this.props.regExp) != null) {
                // console.log("true");
                return true;
            } else {
                // console.log("false");
                return false;
            }
        },
    
        render: function() {
            return (
                <input
                    style={this.fitRegExp(this.props.inputText)?({border: '1px solid green'}):({border: '1px solid red'})} 
                    placeholder={this.props.hinderText}
                    value={this.props.inputText} 
                    regExp={this.props.regExp}
                    ref="inputBox"
                    onChange={this.handleChange}>
                </input>
            );
        }
    });
    
    var ClearButton = React.createClass({
    
        handleClick: function() {
            this.props.onClear();
        },
    
        render: function() {
            return (
                <img 
                    src={this.props.buttonImgSrc} 
                    onClick={this.handleClick}
                />
            );
        }
    });
    
    var LimitedInputBox = React.createClass({
        getInitialState: function() {
            return {
              inputText: ''
            };
        },
    
        handleUserInput: function(inputText) {
            this.setState({
                inputText: inputText
            });
        },
    
        handleClear: function() {
            this.delText();
        },
    
        getText: function() {
            return this.state.inputText;
        },
    
        setText: function(text) {
            this.setState({
                inputText: text
            });
        },
    
        delText: function() {
            this.setState({
                inputText: ''
            });
        },
    
        render: function() {
            return (
                <div>
                    <Title labelText={this.props.data.labelText} />
                    <InputBox 
                        hinderText={this.props.data.hinderText}
                        inputText={this.state.inputText}
                        regExp={this.props.data.regExp}
                        onUserInput={this.handleUserInput}
                    />
                    <ClearButton 
                        buttonImgSrc={this.props.data.buttonImgSrc} 
                        onClear={this.handleClear}
                    />
                </div>
            );
        }
    });
    
    ReactDOM.render(
        <LimitedInputBox data={data} />,
      document.getElementById('example')
    );
    /** author : river he**/
    
    //粗分结构
    var data = {
        labelText: "酒店地址",
        hinderText: "请填写酒店地址",
        regExp: /^\w{5,8}$/
    };
    
    var LimitedInputBox = React.createClass({
    
        //初始化state
        getInitialState: function() {
            return {
                inputText: ''
            };
        },
    
        // handleKeyUp: function(inputText) {
        //  this.setState({
        //      inputText: this.refs.input.value.trim()
        //  });
        // },
        //处理输入框变化
        handleChange: function() {
            this.setState({
                inputText: this.refs.input.value.trim()
            });
        },
    
        //处理删除事件
        handleClear: function() {
            this.delText();
        },
    
        //删除方法
        delText: function() {
            // console.log(this.refs.input.text);
            this.setState({
                inputText: ''
            });
        },
    
        //获取用户输入
        getText: function() {
            return this.state.inputText;
        },
    
        //设置输入框内容
        setText: function(text) {
            this.setState({
                inputText: text.trim()
            });
        },
    
        //判断输入内容否符合设置的正则表达式
        fitRegExp: function(inputText) {
            // console.log("start fitRegExp");
            if(inputText.match(this.props.data.regExp) != null) {
                // console.log("true");
                return true;
            } else {
                // console.log("false");
                return false;
            }
        },
    
        render: function() {
    
            return (
                <div>
                <label >{this.props.data.labelText}</label>
                <input 
                    style={this.fitRegExp(this.state.inputText)?
                    ({border: '1px solid green'}):({border: '1px solid red'})} 
                    placeholder={this.props.data.hinderText} 
                    regExp={this.props.data.regExp}
                    // onKeyUp={this.handleKeyUp}
                    onChange={this.handleChange}
                    value={this.state.inputText}
                    ref='input'
                    ></input>
                <img src="eliminate.png" onClick={this.handleClear}></img>
                </div>
            );
        }
    });
    
    ReactDOM.render(
        <LimitedInputBox data={data} />,
        document.getElementById('example')
    );

    以上就是一个简单的输入框组件,相应的html模板和css如下,没有什么样式,希望读者谅解,有时间改下css。

    <!-- limitedInputBox.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <script src="../build/react.js"></script>
        <script src="../build/react-dom.js"></script>
        <script src="../build/browser.min.js"></script>
        <!-- <script type="text/babel" src="limitedInputBox.js"></script> -->
        <script src="inputWidget.js" type="text/babel"></script>
        <link rel="stylesheet" href="limitedInputBox.css">
        <title>LimitedInputBox</title>
    </head>
    <body>
        <div id="example"></div>
    </body>
    </html>
    //limitedInputBox.css
    img {
        height: 14px;
        width: 14px;
    }

    JS Bin on jsbin.com

    展开全文
  • react-redux react-redux的作用是连接(connect)store和容器组件的。store是redux提供的,容器组件react提供的。 5.1 组织应用的组件 组织应用的组件 容器组件 展示组件 容器组件:位于应用最顶层的...
  • react开发需要引入多依赖文件:react.js、react-dom.js,分别又有开发版本和生产版本,create-react-app里已经帮我们把这些东西都安装好了。把通过CRA创建的工程目录下的src目录清空,然后在里面重新创建一index...
  • react组件异步加载

    2019-06-10 11:42:08
    我们在用react的时候,希望组件异步加载,提高性能,那么应该怎么实现呢? 往下看。。。 解释一波: react异步加载的方法很多,这里只说一种,因为这一种直接使用react的特性就可以了,不需要另外配置webpack,...
  • 最近公司的项目在用react,所以才开始接触reactreact和vue一样,都是组件化的框架,那么子组件和父组件之间怎么传值呢? 父组件向子组件传值 ,父组件通过属性的形式向子组件传递参数,子组件用过props接受父组件...
  • 虽然antd里面的组件已经很简便了,但是遇到众多功能类似的页面,每次都复制大量的代码还是会耗费很大的时间而且不易维护,看起来很不清爽,于是找了时间就把他们做了次封装。 尽量涵盖了大多数的...
  • 前期我们说了父子组件互相通过props传递数据的方法,这应该都可以理解. 其实今天说的这组件直接调用子组件方法, 也类似. 先看代码,比较直观. import React, {Component} from 'react'; export default ...
  • 其值由React控制的输入表单元素称为“受控组件”。 受控组件有两特点:1. 设置value值,value由state控制,2. value值一般在onChange事件中通过setState进行修改 什么时候使用受控组件? 需要对组件的value值...
  • 相比于普通组件,全局组件实在普通组件的基础上,将普通组件创建至DOM即可。 // Test未普通组件 import React, {Component} from 'react'; import ReactDOM from 'react-dom'; class Test extends Component{ show ...
  • 高亮显示关键字组件react-highlight : 结果: 相关react代码: HTML样式代码引入: 总体浏览:
  • react拖拽添加新组件

    2019-07-10 05:09:40
    2.这次拖拽添加新组件,js通过dom操作,直接在网页中某个元素上append一元素就可以了,但是react显然不能这么做,例如,动态添加一自定义组件或者图表。 3.解决方法:定义一数组this.state={chartList:[]},...
  • react中不依赖redux等这些进行状态管理的话,主要是有父组件给子组件传值,子组件给父组件传值,还有兄弟组件之前的传值 这是一评论功能的小栗子,非常适合来说明组件之前传值的问题 首先上项目文件目录结构 ...
  • React组件模式

    2018-07-10 14:02:17
    什么是组件根据React官网的介绍,“组件机制允许你将UI切分成独立、可复用的部分,并针对每部分单独考虑一些事情”当你第一次运行npm install react时,你得到了一东西:组件以及它的API。就像JavaScript中...
  • react的无状态组件和纯组件 一:无状态组件        无状态组件可以通过减少继承Component而来的生命周期函数而达到性能优化的效果。 从本质上来说,无状态组件就是一单纯的...
  • react的高阶组件

    2019-05-15 21:52:33
    react的高阶组件 高阶组件的作用就是实现组件复用,节省内存 先定义一函数式组价,传入一参数,这参数就是组件 组件内返回一class类组件,类名可以写也可以不写 类组件内部可以写方法,数据,然后将参数...
  • React组件的 key

    2018-07-06 15:44:29
    react key概述key的作用react中的key属性,它是一特殊的属性,它是出现不是给开发者用的(例如你为一个组件设置key之后不能获取组件的这key props),而是给react自己用的。简单来说,react利用key来识别组件,...
1 2 3 4 5 ... 20
收藏数 90,746
精华内容 36,298
热门标签
关键字:

2个组件 react