四、DOM 与 ReactJS 的交互

在上一章中,我们了解了什么是 JSX 以及如何在 JSX 中创建组件。与许多其他框架一样,React 也有其他原型来帮助我们构建 web 应用程序。每个框架都有不同的方式与 DOM 元素交互。React 使用一个快速的内部合成 DOM 来执行差异,并为组件实际所在的位置计算最有效的 DOM 变异。

React 组件类似于采用道具和状态的函数(这将在后面的部分中解释)。React 组件仅渲染单个根节点。如果要渲染多个节点,则必须将它们包装到单个根节点中。

在开始使用表单组件之前,我们应该先看看道具和状态。

道具与状态

React 组件将原始数据转换为丰富的 HTML,道具和状态与原始数据一起构建,以保持 UI 的一致性。

好的,让我们确定它到底是什么:

  • 道具和状态都是普通的 JS 对象。
  • 它们通过render更新触发。
  • React 通过调用setState(data,callback)来管理组件状态。此方法将数据合并到此状态,并重新呈现组件以使 UI 保持最新。例如,下拉菜单的状态(可见或隐藏)。
  • React 不随时间变化的组件道具(属性)。例如,下拉菜单项。有时组件仅使用此props方法获取一些数据并进行渲染,这会使组件处于无状态。
  • Using props and state together helps you make an interactive app.

    Props and state

参考第三章中的这个实例,ReactJS JSX。您将对状态和属性有更好的工作理解。

在本例中,我们将切换(显示或隐藏)的状态和切换按钮的文本作为属性进行管理。

表单组件

在 React 中,表单组件与其他本地组件不同,因为它们可以通过用户交互进行修改,如<input><textarea><option>

以下是支持的事件列表:

  • onChangeonInputonSubmit
  • onClickonContextMenuonDoubleClickonDragonDragEndonDragEnteronDragExit
  • onDragLeaveonDragOveronDragStartonDroponMouseDownonMouseEnteronMouseLeave
  • onMouseMoveonMouseOutonMouseOveronMouseUp

支持的事件的完整列表可在官方文件中找到 https://facebook.github.io/react/docs/events.html#supported-事件

形态组件中的道具

我们知道,ReactJS 组件有自己的道具和类似状态的表单,它们支持一些通过用户交互影响的道具:

<input><textarea>

| 组件 | 支撑道具 | | <input><textarea> | 值,默认值 | | <input>复选框或收音机的类型 | 选中,默认选中 | | <select> | 选中,默认值 |

在 HTML <textarea>组件中,该值通过子级设置,但在 React 中,该值可以通过value设置。onChange 道具受所有本机组件(如其他 DOM 事件)支持,并且可以侦听所有气泡更改事件。

当用户交互和更改时,onChange道具在浏览器中工作:

  • <input><textarea>value
  • radiocheckbox<input>类型的checked状态
  • <option>组件的selected状态

在本章中,我们将演示如何使用刚才介绍的属性(prop)和状态来控制组件。然后,我们将看看如何从组件中应用它们来控制行为。

受控元件

我们要看的第一个组件是控制用户输入到textarea的组件,当字符达到最大长度时,它阻止用户输入;当用户输入以下内容时,它还将更新剩余字符:

render: function() { 
    return <textarea className="form-control" value="fdgdfgd" />; 
}

在前面的代码中,我们已经声明了textarea的值,所以当用户输入时,它不会影响textarea的值的更改。为了控制这一点,我们需要使用onChange事件:

var style = {color: "#ffaaaa"}; 
var max_Char='140'; 
var Teaxtarea = React.createClass({ 
    getInitialState: function() { 
        return {value: 'Controlled!!!', char_Left: max_Char}; 
    }, 
    handleChange: function(event) { 
        var input = event.target.value; 
        this.setState({value: input}); 
    }, 
    render: function() { 
        return ( 
            <form> 
                <div className="form-group"> 
                    <label htmlFor="comments">Comments <span style=
                    {style}>*</span></label>(<span>
                    {this.state.char_Left}</span> characters left) 
                    <textarea className="form-control" value=
                    {this.state.value} maxLength={max_Char} onChange=
                    {this.handleChange} /> 
                </div> 
            </form> 
        ); 
    } 
}) 

观察以下屏幕截图:

Controlled component

在前面的屏幕截图中,我们正在接受并控制用户提供的值,并更新<textarea>组件的prop值。

this.state()应该只包含表示 UI 状态所需的最小数据量。

但现在我们还要更新<span>textarea的剩余字符:

this.setState({ 
    value: input.substr(0, max_Char),char_Left: max_Char - 
    input.length 
});

在前面的代码中,this控制textarea的剩余值,并在用户输入时更新剩余字符。

非受控部件

正如我们在 ReactJS 中看到的,当使用value属性时,我们可以控制用户输入,因此没有value属性的 <textarea>是一个不受控制的组件:

render: function() { 
    return <textarea className="form-control"/> 
}

这将使用空值呈现textarea,并且允许用户输入呈现元素立即反映的值,因为非受控组件有其自身的内部状态。如果要初始化默认值,需要使用defaultValue道具:

render:function() { 
    return <textarea className="form-control" defaultValue="Lorem 
    lipsum"/> 
} 

它看起来像是我们以前见过的受控组件。

提交时获取表单值

如我们所见,stateprop将为您提供更改组件值和处理该组件状态的控件。

好的,现在让我们在 addticket 表单中添加一些高级功能,这些功能可以验证用户输入并在 UI 上显示票据。

Ref 属性

React 提供ref非 DOM 属性来访问组件。ref属性可以是一个回调函数,它将在安装组件后立即执行。

因此,我们将在表单元素中附加ref属性以获取值:

var AddTicket = React.createClass({ 
    handleSubmitEvent: function (event) { 
        event.preventDefault(); 
        console.log("Email--"+this.refs.email.value.trim()); 
        console.log("Issue Type--"+this.refs.issueType.value.trim()); 
        console.log("Department--"+this.refs.department.value.trim()); 
        console.log("Comments--"+this.refs.comment.value.trim()); 
    }, 
    render: function() { 
        return ( 
        ); 
    } 
});

现在,我们将在return方法中添加表单元素的 JSX:

<form onSubmit={this.handleSubmitEvent}>
    <div className="form-group">
        <label htmlFor="email">Email <span style={style}>*</span>
        </label>
        <input type="text" id="email" className="form-control" 
        placeholder="Enter email" required ref="email"/>
    </div>
    <div className="form-group">
        <label htmlFor="issueType">Issue Type <span style={style}>*
        </span></label>
        <select className="form-control" id="issueType" required
        ref="issueType">
            <option value="">-----Select----</option>
            <option value="Access Related Issue">Access Related 
            Issue</option>
            <option value="Email Related Issues">Email Related
            Issues</option>
            <option value="Hardware Request">Hardware Request</option>
            <option value="Health & Safety">Health & Safety</option>
            <option value="Network">Network</option>
            <option value="Intranet">Intranet</option>
            <option value="Other">Other</option>
        </select>
    </div>
    <div className="form-group">
        <label htmlFor="department">Assign Department <span style=
        {style}>*</span></label>
        <select className="form-control" id="department" required
        ref="department">
            <option value="">-----Select----</option>
            <option value="Admin">Admin</option>
            <option value="HR">HR</option>
            <option value="IT">IT</option>
            <option value="Development">Development</option>
        </select>
    </div>
    <div className="form-group">
        <label htmlFor="comments">Comments <span style={style}>*</span>
        </label>(<span id="maxlength">200</span> characters left)
        <textarea className="form-control" rows="3" id="comments" 
        required ref="comment"></textarea>
    </div>
    <div className="btn-group">
        <button type="submit" className="btn 
        btn-primary">Submit</button>
        <button type="reset" className="btn btn-link">cancel</button>
    </div>
</form>

在前面的代码中,我在表单元素和onSubmit上添加了ref属性,调用函数名handleSubmitEvent。在这个函数中,我们使用this.refs获取值。

现在,打开浏览器,让我们看看代码的输出:

Ref attribute

我们正在成功获取组件的值。数据是如何在我们的组件中流动的,这一点非常清楚。在控制台中,当用户单击提交按钮时,我们可以看到表单的值。

现在,让我们在 UI 中显示此票证信息。

首先,我们需要获取表单的值并管理表单的状态:

var AddTicket = React.createClass({ 
    handleSubmitEvent: function (event) { 
        event.preventDefault(); 

        var values  = { 
            date: new Date(), 
            email: this.refs.email.value.trim(), 
            issueType: this.refs.issueType.value.trim(), 
            department: this.refs.department.value.trim(), 
            comment: this.refs.comment.value.trim() 
        }; 
        this.props.addTicketList(values); 
    }, 
)};

现在我们将创建 AddTicketForm 组件,该组件将负责管理和保存 addTicketList 的状态(值):

var AddTicketsForm = React.createClass({  
    getInitialState: function () { 
        return { 
            list: {} 
        }; 
    }, 
    updateList: function (newList) { 
        this.setState({ 
            list: newList 
        }); 
    }, 

    addTicketList: function (item) { 
        var list = this.state.list; 

        list[item] = item; 
        //pass the item.id in array if we are using key attribute. 
        this.updateList(list); 
    }, 
    render: function () { 
        var items = this.state.list; 
        return ( 
            <div className="container"> 
            <div className="row"> 
            <div className="col-sm-6"> 
            <List items={items} /> 
            <AddTicket addTicketList={this.addTicketList} /> 
        </div> 
        </div> 
        </div> 
        ); 
    } 
});

让我们看看前面的代码:

  • getInitialState:初始化 <List />组件的默认状态
  • addTicketList:保存该值并与状态一起传递到updateList
  • updateList:用于更新票据列表,使我们的 UI 同步

现在我们需要创建<List items={items} />组件,它在提交表单时迭代列表:

var List = React.createClass({  
    getListOfIds: function (items) { 
        return Object.keys(items); 
    }, 
    createListElements: function (items) { 
        var item; 
        return ( 
            this 
            .getListOfIds(items) 
            .map(function createListItemElement(itemId) { 
                item = items[itemId]; 
                return (<ListPanel item={item} />);//key={item.id} 
            }.bind(this)) 
            .reverse() 
        ); 
    }, 
    render: function () { 
        var items = this.props.items; 
        var listItemElements = this.createListElements(items); 

        return ( 
            <div className="bg-info"> 
                {listItemElements} 
            </div> 
        ); 
    } 
});

让我们了解一下前面的代码:

  • getListOfIds:这将遍历该项中的所有键,并返回我们已与<ListPanel item={item}/>组件映射的列表
  • .bind(this)this关键字将作为第二个参数传递,该参数在调用函数时给出适当的值

render方法中,我们只是呈现元素列表。此外,我们还可以根据render方法中的长度添加一个条件:

<p className={listItemElements.length > 0 ? "":"bg-info"}> 
    {listItemElements.length > 0 ? listItemElements : "You have not
    raised any ticket yet. Fill this form to submit the ticket"} 
</p> 

它将验证长度,并根据返回值 TRUE 或 FALSE 显示消息或应用 Bootstrap 类.bg-info

现在我们需要创建一个<ListPanel />组件,在 UI 中显示票据列表:

var ListPanel = React.createClass({ 
    render: function () { 
        var item = this.props.item; 
        return ( 
            <div className="panel panel-default"> 
            <div className="panel-body"> 
            {item.issueType}<br/> 
            {item.email}<br/> 
            {item.comment} 
            </div> 
            <div className="panel-footer"> 
            {item.date.toString()} 
            </div> 
            </div> 
        ); 
    } 
}); 

现在,让我们结合我们的代码,在浏览器中查看结果:

var style = {color: "#ffaaaa"}; 
var AddTicketsForm = React.createClass({  
    getInitialState: function () { 
        return { 
            list: {} 
        }; 
    }, 
    updateList: function (newList) { 
        this.setState({ 
            list: newList 
        }); 
    }, 

    addTicketList: function (item) { 
        var list = this.state.list; 
        list[item] = item; 
        this.updateList(list); 
    }, 
    render: function () { 
        var items = this.state.list; 
        return ( 
            <div className="container"> 
            <div className="row"> 
            <div className="col-sm-12"> 
            <List items={items} /> 
            <AddTicket addTicketList={this.addTicketList} /> 
            </div> 
            </div> 
            </div> 
        ); 
    }  
}); 

//AddTicketsForm components code ends here

var ListPanel = React.createClass({
    render: function () {
        var item = this.props.item;
        return (
        <div className="panel panel-default">
            <div className="panel-body">
                {item.issueType}<br/>
                {item.email}<br/>
                {item.comment}
            </div>
        <div className="panel-footer">
            {item.date.toString()}
        </div>
        </div>
        );
    }
});

// We'll wrap ListPanel component in List

var List = React.createClass({
    getListOfIds: function (items) {
        return Object.keys(items);
    },
    createListElements: function (items) {
        var item;
        return (
            this
            .getListOfIds(items)
            .map(function createListItemElement(itemId) {
                item = items[itemId];
                return (
                    <ListPanel item={item} />
                );//key={item.id}
            }.bind(this))
            .reverse()
        );
    },
    render: function () {
        var items = this.props.items;
        var listItemElements = this.createListElements(items);
        return (
            <p className={listItemElements.length > 0 ? "":"bg-info"}>
            {listItemElements.length > 0 ? listItemElements : "You
            have not raised any ticket yet. Fill this form to submit
            the ticket"}
            </p>
        );
    }
});

在前面的代码中,我们正在迭代这些项,并作为道具在组件中传递:

var AddTicket = React.createClass({
    handleSubmitEvent: function (event) {
        event.preventDefault();
        var values  = {
            date: new Date(),
            email: this.refs.email.value.trim(),
            issueType: this.refs.issueType.value.trim(),
            department: this.refs.department.value.trim(),
            comment: this.refs.comment.value.trim()
        };
        this.props.addTicketList(values);
    },
    render: function() {
    return (

// Form template

ReactDOM.render( 
    <AddTicketsForm />, 
    document.getElementById('form') 
);

以下是我们 HTML 页面的标记:

<link rel="stylesheet" href="css/bootstrap.min.css">
<style type="text/css">
    div.bg-info {
        padding: 15px;
    }
</style>
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-sm-6">
                <h2>Add Ticket</h2>
                <hr/>
            </div>
        </div>
    </div>
    <div id="form">
    </div>
    <script type="text/javascript" src="js/react.js"></script>
    <script type="text/javascript" src="js/react-dom.js"></script>
    <script src="js/browser.min.js"></script>
    <script src="component/advance-form.js" type="text/babel"></script>
</body>

在提交之前,打开浏览器,让我们查看表单的输出:

Ref attribute

以下屏幕截图显示了提交表单后的外观:

Ref attribute

这看起来不错。我们的第一个全功能 React 组件已准备就绪。

不要访问任何组件内部的refs,也不要将它们附加到无状态函数。

观察以下屏幕截图:

Ref attribute

我们收到此警告消息是因为 React 的key(可选)属性接受唯一 ID。每次提交表单时,它都会迭代List组件以更新 UI。例如:

createListElements: function (items) { 
    var item; 

    return ( 
        this 
        .getListOfIds(items) 
        .map(function createListItemElement(itemId,id) { 
        item = items[itemId]; 
            return (<ListPanel key={id} item={item} />); 
        }.bind(this)) 
        .reverse() 
    ); 
},

React 提供附加模块来解决此类警告并生成唯一 ID,但它仅在 npm 中可用。在接下来的章节中,我们将展示如何使用 React npm 模块。以下是一些常用加载项的列表:

  • TransitionGroupCSSTransitionGroup:用于处理动画和过渡
  • LinkedStateMixin:方便与用户表单输入数据和组件状态交互
  • cloneWithProps:更改组件的道具并进行浅拷贝
  • createFragment:用于创建一组外部键控的子项
  • Update:一个帮助函数,可以轻松处理 JavaScript 中的数据
  • PureRenderMixin:性能助推器
  • shallowCompare:一个辅助函数,用于对道具和状态进行粗略比较

Bootstrap 助手类

Bootstrap 提供了一些帮助器类,为您提供更好的用户体验。在AddTicketsForm表单组件中,我们使用了 Bootstrap 助手类*-info,它可以帮助您用颜色向屏幕阅读器传达消息的含义。其中一些是*-muted*-primary*-success*-info*-warning*-danger

要更改文本的颜色,我们可以使用.text*

<p class="text-info">...</p>

要更改背景色,我们可以使用.bg*

<p class="bg-info">...</p>

插入符号

要显示指示下拉菜单方向的插入符号,我们可以使用:

<span class="caret"></span>

Clearfix

在父元素上使用clearfix可以清除子元素的浮动:

<div class="clearfix">... 
    <div class="pull-left"></div> 
    <div class="pull-right"></div> 
</div> 

总结

在本章中,我们看到了道具和状态如何在组件交互以及 DOM 交互中发挥重要作用。Refs 是与 DOM 元素交互的好方法。这将不方便通过流式 React 道具和状态来实现。在 refs 的帮助下,我们可以调用任何公共方法并向特定的子实例发送消息。

本章中显示的关键示例将帮助您理解和明确有关道具、状态和 DOM 交互的概念。

最后一个示例介绍了带有多个 JSX 组件和 Bootstrap 的高级添加票证表单,这将为您提供有关创建 React 组件以及如何使用 REF 与它们交互的更多想法。您可以使用它,并像使用 HTML 一样轻松地使用它。

如果您仍然不确定状态和道具是如何工作的,以及 React 如何与 DOM 交互的,我建议您再次阅读本章,这也将有助于您了解未来的章节。

如果您已经完成了,那么让我们继续看第 5 章jQuery Bootstrap 组件和 React,都是关于 React 中的 Redux 架构的。