React-和-Bootstrap-Web-开发学习手册-二-

59 阅读31分钟

React 和 Bootstrap Web 开发学习手册(二)

原文:zh.annas-archive.org/md5/59c715363f0dff298e7d1cff58a50a77

译者:飞龙

协议:CC BY-NC-SA 4.0

第五章:React 中的 jQuery Bootstrap 组件

到目前为止,我们已经介绍了如何创建 DOM 元素以及 DOM 如何与 React 组件交互。正如我们所见,每个框架都有不同的方式与 DOM 元素交互,而 React 使用快速的内部合成 DOM 来执行差异并为您计算最有效的 DOM 变化,这是您的组件实际存在的地方。

在本章中,我们将看看 jQuery Bootstrap 组件在 React 虚拟 DOM 中是如何工作的。我们还将涵盖以下主题:

  • 组件生命周期方法

  • 组件集成

  • Bootstrap 模态框

  • 具体示例

这将让您更好地理解如何处理 React 中的 jQuery Bootstrap 组件。

在 Bootstrap 中,我们有很多可重用的组件,使开发人员的生活更轻松。在第一章和第二章中,我们解释了 Bootstrap 的集成。所以让我们从一个小组件开始,将其集成到 React 中。

警报

在 Bootstrap 中,我们有alert组件来根据用户操作在 UI 中显示消息,使您的组件更具交互性。

首先,我们需要将文本包裹在包含close按钮的.alert类中。

Bootstrap 还提供了表示不同颜色的上下文类,根据消息的不同而不同:

  • .alert-success

  • .alert-info

  • .alert-warning

  • .alert-error

用法

Bootstrap 为我们提供了alert组件的预定义结构,这使得将其包含在我们的项目中变得容易:

<div class="alert alert-info alert-dismissible fade in" role="alert">
    <button type="button" class="close" data-dismiss="alert"
    aria-label="Close">
    <span aria-hidden="true">&times;</span>
    </button>
</div>

当我们将close按钮用作我们声明了alert类的包装标记的子元素时,我们需要向该元素添加.alert-dismissible类,就像前面的示例代码中所示的那样。

添加自定义属性data-dismiss="alert"将为我们在alert中提供close功能。

在 React 中的 Bootstrap 警报组件

现在我们将把 Bootstrap alert组件与我们在第四章中开发的 React 受控组件(textarea)集成起来,ReactJS 中的 DOM 交互,在那里我们开发了一个带有受控组件的表单。我们通过一个示例防止用户在textarea中写入超过140个字符的文本。

在以下示例中,我们将看到如何将警报/警告消息与相同的组件绑定。在这里,我们只是扩展了开发的受控组件。

您可能还在第四章中看到了以下截图,显示了带有textarea中注释的受控组件。在括号中,您可以看到定义的字符限制:

React 中的 Bootstrap 警报组件

添加alert组件后,当用户达到最大字符限制时,它将显示在 UI 中。

为此,首先,我们需要将 Bootstrap 组件包装到 React 结构中。让我们通过实际示例来了解一下:

var BootstrapAlert = React.createClass({  
    render: function() { 
        return ( 
            <div className={(this.props.className) + ' alert'}
            role="alert" ref="alertMsg"> 
                <button type="button" className="close"
                data-dismiss="alert" aria-label="Close" onClick=
                {this.handleClose}> 
                <span aria-hidden="true">×</span></button> 
                <strong>Ooops!</strong> You reached the max limit  
            </div>     
        ); 
    } 
}); 

我们创建了一个名为BootstrapAlert的组件,并将 HTML 包装在render方法中。

onClick调用handleClose函数,该函数将处理close事件。这是 React 的默认函数,因为我们在 JavaScript 中有.show().hide()默认函数。

在我们集成 jQuery Bootstrap 组件之前,我们必须了解组件中的 React 生命周期方法。

组件生命周期方法

在 React 中,每个组件都有自己特定的回调函数。当我们考虑 DOM 操作或在 React(jQuery)中集成其他插件时,这些回调函数起着重要作用。让我们看一些组件生命周期中常用的方法:

  • getInitialState(): 此方法将帮助您获取组件的初始状态。

  • componentDidMount:此方法在组件在 DOM 中首次渲染或挂载时自动调用。在集成 JavaScript 框架时,我们将使用此方法执行操作,例如setTimeoutsetInterval,或发送 AJAX 请求。

  • componentWillReceiveProps:此方法将用于接收新的props

注意

没有替代方法,如componentWillReceiveState。如果我们需要在state更改时执行操作,那么我们使用componentWillUpdate

  • componentWillUnmount:此方法在组件从 DOM 中卸载之前调用。清理在componentDidMount方法中挂载的 DOM 内存元素。

  • componentWillUpdate:此方法在更新新的propsstate之前调用。

  • componentDidUpdate:在组件在 DOM 中更新后立即调用此方法。

组件集成

我们现在了解了组件的生命周期方法。现在让我们使用这些方法在 React 中集成我们的组件。请观察以下代码:

componentDidMount: function() { 
    // When the component is mount into the DOM 
    $(this.refs.alertMsg).hide(); 
          // Bootstrap's alert events 
// functionality. Lets hook into one of them: 
    $(this.refs.alertMsg).on('closed.bs.alert', this.handleClose); 
  }, 
  componentWillUnmount: function() {  
      $(this.refs.alertMsg).off('closed.bs.alert', this.handleClose); 
  }, 
  show: function() { 
      $(this.refs.alertMsg).show(); 
  }, 
  close: function() { 
      $(this.refs.alertMsg).alert('close'); 
  }, 
  hide: function() { 
      $(this.refs.alertMsg).hide(); 
  }, 
  render: function() { 
      return ( 
      <div className={(this.props.className) + ' alert'} role="alert"
      ref="alertMsg"> 
          <button type="button" className="close" data-dismiss="alert"
          aria-label="Close" onClick={this.handleClose}> 
          <span aria-hidden="true">x</span></button> 
          <strong>Oh snap!</strong> You reached the max limit  
      </div>      
    ); 
  }, 
}); 

让我们看一下上述代码的解释:

  • componentDidMount()默认情况下使用refs关键字在组件挂载到 DOM 时隐藏alert组件

  • alert组件为我们提供了一些在调用close方法时被调用的事件

  • 当调用close方法时,将调用close.bs.alert

当我们使用componentWillUnmount组件时,也使用 jQuery 的.off来移除事件处理程序。当我们点击关闭(x)按钮时,它会调用 Closehandler 并调用 close

我们还创建了一些控制我们组件的自定义事件:

  • .hide(): 用于隐藏组件

  • .show(): 用于显示组件

  • .close(): 用于关闭警报

请观察以下代码:

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.substr(0, max_Char),char_Left:   
        max_Char - input.length}); 
        if (input.length == max_Char){ 
            this.refs.alertBox.show(); 
        } 
        else{ 
        this.refs.alertBox.hide(); 
        } 
    }, 
    handleClose: function() { 
        this.refs.alertBox.close(); 
    }, 

    render: function() { 
        var alertBox = null; 
        alertBox = ( 
            <BootstrapAlert className="alert-warning fade in" 
            ref="alertBox" onClose={this.handleClose}/> 
        ); 
        return ( 
            <div className="example"> 
            {alertBox} 
                <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> 
            </div> 
        ); 
    } 
}); 
ReactDOM.render( 
    <Teaxtarea />, 
    document.getElementById('alert') 
); 

使用if条件,根据字符长度隐藏和显示警报。handleClose()方法将调用我们之前创建的close方法来关闭警报。

render方法中,我们使用className属性、ref键和onClose属性来渲染我们的组件以处理close方法。

类中的.fade给我们在关闭警报时提供了淡出效果。

现在让我们结合我们的代码,快速在浏览器中查看一下:

'use strict'; 
var max_Char='140'; 
var style = {color: "#ffaaaa"}; 

var BootstrapAlert = React.createClass({  
    componentDidMount: function() { 
        // When the component is added 
        $(this.refs.alertMsg).hide();  
        // Bootstrap's alert class exposes a few events for hooking 
        into modal 
        // functionality. Lets hook into one of them: 
        $(this.refs.alertMsg).on('closed.bs.alert', this.handleClose); 
    }, 
    componentWillUnmount: function() { 
        $(this.refs.alertMsg).off('closed.bs.alert', this.
        handleClose); 
    }, 
    show: function() { 
        $(this.refs.alertMsg).show(); 
    }, 
    close: function() { 
        $(this.refs.alertMsg).alert('close'); 
    }, 
    hide: function() { 
        $(this.refs.alertMsg).hide(); 
    },  
    render: function() { 
        return ( 
            <div className={(this.props.className) + ' alert'}
            role="alert" ref="alertMsg"> 
                <button type="button" className="close" 
                data-dismiss="alert" aria-label="Close" onClick=
                {this.handleClose}> 
                <span aria-hidden="true">×</span></button> 
                <strong>oops!</strong> You reached the max limit  
            </div> 

        ); 
    } 
}); 

var Teaxtarea = React.createClass({ 
    getInitialState: function() { 
        return {value: '', char_Left: max_Char}; 
    }, 
    handleChange: function(event) { 
        var input = event.target.value; 
        this.setState({value: input.substr(0, max_Char),char_Left: 
        max_Char - input.length}); 
        if (input.length == max_Char){ 
            this.refs.alertBox.show(); 
        } 
        else{ 
            this.refs.alertBox.hide(); 
        } 
    }, 
    handleClose: function() { 
        this.refs.alertBox.close(); 
    }, 

    render: function() { 
        var alertBox = null; 
        alertBox = ( 
            <BootstrapAlert className="alert-warning fade in"
            ref="alertBox"/> 
        ); 
        return ( 
            <div className="example"> 
            {alertBox} 
                <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> 
            </div> 
        ); 
    } 
}); 
ReactDOM.render( 
    <Teaxtarea />, 
    document.getElementById('alert') 
);

请观察以下截图:

组件集成

当我们点击关闭(x)按钮时,它会调用Closehandler并调用close事件来关闭警报消息。一旦关闭,您将需要刷新页面才能重新打开它。请观察以下截图:

组件集成

注意

使用console.log(),我们可以验证我们的组件是否已挂载或卸载。

现在让我们看一下 Bootstrap 组件的另一个示例。

Bootstrap 模态框

Bootstrap 模态组件向用户显示少量信息,而不会将您带到新页面。

来自 Bootstrap 网站(getbootstrap.com/javascript)的下表显示了模态框可用的全部选项:

名称类型默认描述
backdrop布尔值truebackdrop允许我们在用户点击外部时关闭模态框。它为backdrop提供了一个静态值,不会在点击时关闭模态框。
keyboard布尔值true按下Esc键关闭模态框。
show布尔值true初始化模态框。
remote路径false自 3.3.0 版本起,此选项已被弃用,并在 4 版本中已删除。建议使用数据绑定框架进行客户端模板化,或者自己调用jQuery.load

Bootstrap 网站(getbootstrap.com/javascript)的以下表格显示了 Bootstrap 模态组件可用的完整事件列表:

事件类型描述
show.bs.modal当调用show$('.modal').show();)实例方法时,立即触发此事件。
shown.bs.modal当模态框对用户可见时触发此事件(我们需要等待 CSS 过渡完成)。
hide.bs.modal当调用hide$('.modal').hide();)实例方法时,立即触发此事件。
hidden.bs.modal当模态框从用户那里隐藏完成时触发此事件(我们需要等待 CSS 过渡完成)。
loaded.bs.modal当模态框使用remote选项加载内容时触发此事件。

每当我们集成任何其他组件时,我们必须了解库或插件提供的组件选项和事件。

首先,我们需要创建一个button组件来打开一个modal弹出窗口:

// Bootstrap's button to open the modal 
var BootstrapButton = React.createClass({ 
    render: function() { 
        return ( 
            <button {...this.props} 
            role="button" 
            type="button" 
            className={(this.props.className || '') + ' btn'} /> 
        ); 
    } 
}); 

现在,我们需要创建一个modal-dialog组件,并将buttondialog组件挂载到 DOM 中。

我们还将创建一些处理showhide模态框事件的事件:

var BootstrapModal = React.createClass({ 
    componentDidMount: function() { 
        // When the component is mount into the DOM 
        $(this.refs.root).modal({keyboard: true, show: false}); 

        // capture the Bootstrap's modal events 
        $(this.refs.root).on('hidden.bs.modal', this.handleHidden); 
    }, 
    componentWillUnmount: function() { 
        $(this.refs.root).off('hidden.bs.modal', this.handleHidden); 
    }, 
    close: function() { 
        $(this.refs.root).modal('hide'); 
    }, 
    open: function() { 
        $(this.refs.root).modal('show'); 
    }, 
    render: function() { 
        var confirmButton = null; 
        var cancelButton = null; 

    if (this.props.confirm) { 
        confirmButton = ( 
            <BootstrapButton 
                onClick={this.handleConfirm} 
                className="btn-primary"> 
                {this.props.confirm} 
            </BootstrapButton> 
        ); 
    } 
    if (this.props.cancel) { 
        cancelButton = ( 
            <BootstrapButton onClick={this.handleCancel} className=
            "btn-default"> 
            {this.props.cancel} 
            </BootstrapButton> 
        ); 
    } 

    return ( 
        <div className="modal fade" ref="root"> 
        <div className="modal-dialog"> 
        <div className="modal-content"> 
        <div className="modal-header"> 
        <button 
            type="button" 
            className="close" 
            onClick={this.handleCancel}> 
            &times; 
        </button> 
        <h3>{this.props.title}</h3> 
        </div> 
        <div className="modal-body"> 
            {this.props.children} 
        </div> 
        <div className="modal-footer"> 
            {cancelButton} 
            {confirmButton} 
</div> 
</div> 
</div> 
</div> 
    ); 
  }, 
  handleCancel: function() { 
      if (this.props.onCancel) { 
          this.props.onCancel(); 
      } 
  }, 
  handleConfirm: function() { 
      if (this.props.onConfirm) { 
          this.props.onConfirm(); 
      } 
  }, 
  handleHidden: function() { 
      if (this.props.onHidden) { 
          this.props.onHidden(); 
      } 
  } 
}); 

componentDidMount()中,我们正在使用一些选项初始化modal组件,并将hidden.bs.modal事件注入modal中。

close()show()函数触发模态框的hide/show事件。

render()方法中,我们包含了带有propsref键的模态框 HTML 模板来操作模板。

handleCancel()handleConfirm()handleHidden()处理我们组件的每个状态。

.modal-*类为我们提供了 Bootstrap 的样式,使我们的应用更加用户友好。

现在我们需要使用render函数来渲染我们的组件:

var ReactBootstrapModalDialog = React.createClass({ 
    handleCancel: function() { 
        if (confirm('Are you sure you want to cancel the dialog 
        info?')) { 
            this.refs.modal.close(); 
        } 
    }, 
    render: function() { 
        var modal = null; 
        modal = ( 
            <BootstrapModal 
                ref="modal" 
                confirm="OK" 
                cancel="Cancel" 
                onCancel={this.handleCancel} 
                onConfirm={this.closeModal} 
                onHidden={this.handleModalDidClose} 
                > 
                This is a React component powered by jQuery and                      Bootstrap! 
            </BootstrapModal> 
        ); 
        return ( 
            {modal} 
            <BootstrapButton onClick={this.openModal} className="btn-
            default"> 
            Open modal 
            </BootstrapButton> 
        ); 
    }, 
    openModal: function() { 
        this.refs.modal.open(); 
    }, 
    closeModal: function() { 
        this.refs.modal.close(); 
    }, 
    handleModalDidClose: function() { 
        alert("The modal has been dismissed!"); 
    } 
}); 

我们在<BootstrapModal>中传递props并渲染<BootstrapButton>

使用this关键字,我们调用一个函数来调用modal事件并在每次事件触发时显示警报:

ReactDOM.render(<ReactBootstrapModalDialog />, 
document.getElementById('modal')); 

让我们快速在浏览器中查看一下我们的组件:

Bootstrap 模态

哎呀!我们出现了一个错误。我认为这可能是因为我们没有将组件包裹在render方法内。它应该始终与一个父元素一起包装:

return ( 
    <div className="modalbtn"> 
        {modal} 
    <BootstrapButton onClick={this.openModal} className="btn-default"> 
        Open modal 
    </BootstrapButton> 
    </div> 
); 

这是我们在做了一些小改动后ReactBootstrapModalDialog组件的样子:

var ReactBootstrapModalDialog = React.createClass({ 
    handleCancel: function() { 
        if (confirm('Are you sure you want to cancel?')) { 
            this.refs.modal.close(); 
        } 
    }, 
    render: function() { 
        var modal = null; 
        modal = ( 
            <BootstrapModal 
                ref="modal" 
                confirm="OK" 
                cancel="Cancel" 
                onCancel={this.handleCancel} 
                onConfirm={this.closeModal} 
                onHidden={this.handleModalDidClose} 
                > 
                This is a React component powered by jQuery and
                Bootstrap! 
            </BootstrapModal> 
        ); 
        return ( 
            <div className="modalbtn"> 
                {modal} 
                <BootstrapButton onClick={this.openModal} 
                className="btn-default"> 
                    Open modal 
                </BootstrapButton> 
            </div> 
        ); 
    }, 
    openModal: function() { 
        this.refs.modal.open(); 
    }, 
    closeModal: function() { 
        this.refs.modal.close(); 
    }, 
    handleModalDidClose: function() { 
        alert("The modal has been dismissed!"); 
    } 
}); 

ReactDOM.render(<ReactBootstrapModalDialog />, document.getElementById('modal')); 

让我们再次在浏览器中快速查看我们的组件:

Bootstrap 模态

现在点击打开模态按钮查看模态对话框:

Bootstrap 模态

如果我们点击取消确定按钮,它将显示警报框,如下截图所示:

Bootstrap 模态

如果我们点击X图标,它将显示警报框,如下截图所示:

Bootstrap 模态

所以,现在我们知道我们可以通过点击(X)图标来关闭模态对话框。

当模态对话框关闭时,它会显示警报,模态已被解除! 请参见下面的截图:

Bootstrap 模态

这是我们的 HTML 文件的样子:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>J-Query Bootstrap Component with React</title>
        <link rel="stylesheet" href="css/bootstrap.min.css">
    </head>
    <body>
        <div class="container">
            <div class="row">
                <div class="col-sm-6">
                    <h2>jQuery Bootstrap Modal with React</h2>
                    <hr/>
                    <div id="modal">
                    </div>
                </div>
            </div>
        </div>
        <script type="text/javascript" src="js/jquery-1.10.2.min.js">
        </script>
        <script type="text/javascript" src="js/bootstrap.min.js">
        </script>
        <script type="text/javascript" src="js/react.min.js"></script>
        <script type="text/javascript" src="js/react-dom.min.js">
        </script>
        <script src="js/browser.min.js"></script>
        <script src="component/modal.js" type="text/babel"></script>
    </body>
</html>

摘要

我们已经看到了如何在 React 中集成 jQuery Bootstrap 组件以及在集成任何第三方插件(如 jQuery)时生命周期方法的工作方式。

我们能够通过处理事件的 props 来检查组件状态,并显示具有适当内容的对话框。我们还看了生命周期方法如何帮助我们集成其他第三方插件。

我们现在了解了组件的生命周期方法以及它们在 React 中的工作方式。我们已经学会了如何在 React 中集成 jQuery 组件。我们已经看到了事件处理机制以及警报和模态组件的示例。

本章中展示的关键示例将帮助您了解或澄清在 React 中集成 jQuery Bootstrap 组件的概念。

让我们继续阅读第六章,Redux 架构,这一章主要讲述在 React 中使用 Redux 架构。

第六章:Redux 架构

在之前的章节中,我们学习了如何创建自定义组件,与 React 进行 DOM 交互,以及如何在 React 中使用 JSX,这些都足以让你对 React 及其在不同平台上的变化有足够的了解,例如添加工单表单应用程序的实际示例。现在我们将进入一个高级水平,这将让你进一步了解 JavaScript 应用程序中的状态管理。

Redux 是什么?

正如我们所知,在单页面应用程序SPAs)中,当我们需要处理状态和时间时,很难掌握随时间变化的状态。在这里,Redux 非常有帮助。为什么?因为在 JavaScript 应用程序中,Redux 处理两种状态:一种是数据状态,另一种是 UI 状态,这是 SPAs 的标准选项。此外,请记住,Redux 可以与 AngularJS、jQuery 或 React JS 库或框架一起使用。

Redux 是什么意思?简而言之,Redux 是在开发 JavaScript 应用程序时处理状态的助手。

我们在之前的例子中看到,数据只能从父级流向子级,这被称为单向数据流。React 也有相同的数据流方向,从数据到组件,因此在这种情况下,React 中的两个组件之间要进行正确的通信会非常困难。

我们可以在下面的图表中清楚地看到:

什么是 Redux?

正如我们在前面的图表中所看到的,React 并不遵循两个组件之间的直接通信,尽管它具有提供该策略的功能。然而,这被认为是不良实践,因为它可能导致不准确性,而且这是一种很难理解的古老写法。

但这并不意味着在 React 中无法实现,因为它提供了另一种替代方法,但根据你的逻辑和 React 的标准,你必须加以处理。

要实现两个没有父子关系的组件之间的相同效果,你必须定义一个全局事件系统,让它们进行通信;Flux 可能是最好的例子。

这就是 Redux 的作用,它提供了一种将所有状态存储到一个组件可以访问的地方的方法,这个地方被称为STORE。简而言之,每当任何组件发现任何更改时,它必须首先分派到存储中,如果其他组件需要访问,它必须从存储中订阅。它不能直接授权与该组件的通信,如下图所示:

Redux 是什么?

在前面的图表中,我们可以看到STORE假装成为应用程序中所有状态修改的中介,Redux 通过STORE控制两个组件之间的直接通信,只有一个通信点。

你可能认为组件之间的通信可以通过其他策略实现,但不建议这样做,因为这样做要么会导致错误的代码,要么会难以跟踪:

Redux 是什么?

现在很清楚 Redux 是如何通过将所有状态更改分派到STORE而不是在组件内部进行通信来简化生活的。现在组件只需要考虑分派状态更改;所有其他责任将属于STORE

Flux 模式也是这样做的。你可能听说过 Redux 受 Flux 启发,所以让我们看看它们有多相似:

比较 Redux 和 Flux,Redux 是一个工具,而 Flux 只是一个模式,你不能用来即插即用,也不能下载。我不否认 Redux 与 Flux 模式有一些相似之处,但它并不完全与 Flux 相同。

让我们看一些区别。

Redux 遵循三个指导原则,如下面的描述所示,这也将涵盖 Redux 和 Flux 之间的区别。

单个存储方法

我们在之前的图表中看到,存储假装成为应用程序中所有状态修改的中介,Redux 通过存储控制两个组件之间的直接通信,充当单一的通信点。

这里 Redux 和 Flux 的区别在于:Flux 有多个存储方法,而 Redux 有单个存储方法。

只读状态

在 React 应用程序中,组件不能直接更改状态,而必须通过actions将更改分派到存储中。

在这里,store是一个对象,它有四种方法,如下所示:

  • store.dispatch (action)

  • store.subscribe(监听器)

  • store.getState()

  • replaceReducer(下一个 Reducer)

您可能已经了解 JavaScript 中的getset属性:set属性设置对象,get属性获取对象。但是使用store方法时,只有get方法,因此只有一种方法可以通过动作分派更改来设置状态。

以下代码显示了 JavaScript Redux 的示例:

var action = { 
    type: 'ADD_USER', 
    user: {name: 'Dan'} 
}; 
// Assuming a store object has been created already 
store.dispatch(action); 

在这里,动作意味着dispatch(),其中store方法将发送一个对象来更新状态。在上述代码片段中,action采用type数据来更新状态。您可以根据组件的需要设计不同的方式来设置您的动作。

Reducer 函数用于更改状态

Reducer 函数将处理dispatch动作以改变状态,因为 Redux 工具不允许两个组件之间的直接通信,因此它也不会改变状态,而是将dispatch动作描述为状态更改。

在下面的代码片段中,您将看到Reducer如何通过允许当前状态作为参数并返回新状态来改变state

Javscript: 
// Reducer Function 
varsomeReducer = function(state, action) { 
    ... 
    return state; 
} 

这里的 Reducer 可以被视为纯函数。以下是编写Reducer函数的一些特征:

  • 没有外部数据库或网络调用

  • 根据其参数返回值

  • 参数是不可变的

  • 相同的参数返回相同的值

Reducer 函数被称为纯函数,因为它们除了根据其设置的参数纯粹返回值之外,什么都不做;它们没有其他后果。

Redux 的架构

正如我们所讨论的,Redux 受 Flux 模式的启发,因此也遵循其架构。这意味着状态变化将被发送到存储库,并且存储库将处理动作以在组件之间进行通信。

让我们看看数据和逻辑是如何通过以下图表工作的:

Redux 的架构

观察以下要点以了解 Redux 架构:

  • 您可以在上图中看到,在右下方,组件触发动作。

  • 状态突变将以与 Flux 请求中相同的方式发生,并且可能会有另一个效果作为API请求。

  • 中间件在这里扮演着重要角色,比如处理监听承诺状态的操作以及采取新的行动。

  • Reducers负责作为中间件处理动作。

  • Reducer作为中间件获取所有动作请求,它还与数据相关联。它有权通过定义新状态全局更改应用程序存储中的状态。

  • 当我们说状态改变时,这涉及重新选择其选择器并转换数据并通过组件传递。

  • 当组件获得更改请求时,相应地,它会将 HTML 呈现给 DOM 元素。

在我们继续之前,我们必须了解流程,以确保结构顺畅。

Redux 的架构优势

与其他框架相比,Redux 具有更多的优势:

  • 它可能没有其他副作用

  • 正如我们所知,不需要绑定,因为组件不能直接交互

  • 状态是全局管理的,因此出现管理不善的可能性较小

  • 有时,对于中间件来说,管理其他副作用可能会很困难

从上述观点来看,Redux 的架构非常强大,而且具有可重用性。让我们看一个实际的例子,看看 Redux 如何与 React 一起工作。

我们将在 Redux 中创建我们的 Add Ticket 表单应用程序。

Redux 设置

让我们从 Redux 中的UserList示例开始。首先,创建一个带有应用程序的目录。我们在这个示例中使用 Node.js 服务器和 npm 包,因为 Redux 模块不能独立使用。

安装 Node.js

首先,如果我们尚未在系统中安装 Node.js,我们必须下载并安装 Node.js。我们可以从nodejs.org下载 Node.js。它包括 npm 包管理器。

设置完成后,我们可以检查 Node.js 是否设置正确。打开命令提示窗口并运行以下命令:

**node --version**

您应该能够看到版本信息,这可以确保安装成功。

设置应用程序

首先,我们需要为我们的项目创建一个package.json文件,其中包括项目信息和依赖项。现在,打开命令提示符/控制台,并导航到您创建的目录。运行以下命令:

**Npm init**

这个命令将初始化我们的应用程序,并询问一些问题,以创建一个名为package.json的 JSON 文件。该实用程序将询问有关项目名称、描述、入口点、版本、作者名称、依赖项、许可信息等的问题。一旦执行了该命令,它将在项目的根目录中生成一个package.json文件:

{ 
  "name": "react-redux add ticket form example", 
  "version": "1.0.0", 
  "description": "", 
  "scripts": { 
    "start": "node server.js", 
    "lint": "eslintsrc" 
  }, 
  "keywords": [ 
    "react", 
   "redux", 
   "redux form", 
    "reactjs", 
    "hot", 
    "reload", 
    "live", 
    "webpack" 
  ], 
  "author": "Harmeet Singh <harmeet.singh090@gmail.com>", 
  "license": "MiIT", 
  "devDependencies": { 
    "babel-core": "⁵.8.3", 
    "babel-eslint": "⁴.0.5", 
    "babel-loader": "⁵.3.2", 
    "css-loader": "⁰.15.6", 
    "cssnext-loader": "¹.0.1", 
    "eslint": "⁰.24.1", 
    "eslint-plugin-react": "³.1.0", 
    "extract-text-webpack-plugin": "⁰.8.2", 
    "html-webpack-plugin": "¹.6.1", 
    "react-hot-loader": "¹.2.7", 
    "redux-devtools": "¹.0.2", 
    "style-loader": "⁰.12.3", 
    "webpack": "¹.9.6", 
    "webpack-dev-server": "¹.8.2" 
  }, 
  "dependencies": { 
    "classnames": "².1.3", 
    "lodash": "³.10.1", 
    "react": "⁰.13.0", 
    "react-redux": "⁰.2.2", 
    "redux": "¹.0.0-rc" 
  } 
} 

好的,让我在开始之前向您解释一些主要工具:

  • webpack-dev-server:这是用于应用程序实时重新加载的服务器。

  • babel-loader:这是我们 JavaScript 的编译器。

  • redux-devtools:这是 Redux 开发的强大工具。在开发中使用此工具将帮助我们监视 DOM UI 中的更新。

  • classnames:这是一个模块,将帮助我们根据条件应用类。

  • eslint:这是类似于 JSHint 和 JSLint 用于解析 JavaScript 的工具。

开发工具设置

首先,我们需要创建webpack.config.js并添加以下代码以启用redux-devtools

var path = require('path'); 
varwebpack = require('webpack'); 
varExtractTextPlugin = require('extract-text-webpack-plugin'); 
vardevFlagPlugin = new webpack.DefinePlugin({ 
  __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'true')) 
}); 

module.exports = { 
  devtool: 'eval', 
  entry: [ 
    'webpack-dev-server/client?http://localhost:3000', 
    'webpack/hot/only-dev-server', 
    './src/index' 
  ], 
  output: { 
    path: path.join(__dirname, 'dist'), 
    filename: 'bundle.js', 
    publicPath: '/static/' 
  }, 
  plugins: [ 
    new webpack.HotModuleReplacementPlugin(), 
    new webpack.NoErrorsPlugin(), 
    devFlagPlugin, 
    new ExtractTextPlugin('app.css') 
  ], 
  module: { 
    loaders: [ 
      { 
        test: /\.jsx?$/, 
        loaders: ['react-hot', 'babel'], 
        include: path.join(__dirname, 'src') 
      }, 
      { test: /\.css$/, loader: ExtractTextPlugin.extract
      ('css-loader?module!cssnext-loader') } 
    ] 
  }, 
  resolve: { 
    extensions: ['', '.js', '.json'] 
  } 
}; 

现在,创建一个名为src的目录。在其中,我们需要创建一些文件夹,如下面的截图所示:

开发工具设置

Redux 应用程序设置

在每个 Redux 应用程序中,我们都有 actions、reducers、stores 和 components。让我们从为我们的应用程序创建一些 actions 开始。

Actions

Actions 是从我们的应用程序发送数据到我们的 store 的信息的一部分。

首先,我们需要在 actions 文件夹内创建UsersActions.js文件,并将以下代码放入其中:

import * as types from '../constants/ActionTypes'; 

export function addUser(name) { 
  return { 
    type: types.ADD_USER, 
    name 
  }; 
} 

export function deleteUser(id) { 
  return { 
    type: types.DELETE_USER, 
    id 
  }; 
} 

在上面的代码中,我们创建了两个动作:addUserdeleteUser。现在我们需要在constants文件夹内创建ActionTypes.js,定义type

export constADD_USER = 'ADD_USER'; 
export constDELETE_USER = 'DELETE_USER';  

Reducers

Reducers 处理描述发生了什么事情的 actions,但管理应用程序状态是 reducers 的责任。它们存储先前的stateaction,并return下一个state

export default function users(state = initialState, action) { 
  switch (action.type) { 
    case types.ADD_USER: 
    constnewId = state.users[state.users.length-1] + 1; 
      return { 
        ...state, 
        users: state.users.concat(newId), 
        usersById: { 
          ...state.usersById, 
          [newId]: { 
            id: newId, 
            name: action.name 
          } 
        }, 
      } 

     case types.DELETE_USER: 
     return { 
       ...state, 
       users: state.users.filter(id => id !== action.id), 
       usersById: omit(state.usersById, action.id) 
     } 

     default: 
     return state; 
  } 
} 

Store

我们已经定义了 actions 和 reducers,它们代表了关于发生了什么的事实,以及何时需要根据这些 actions 更新状态。

store是将 actions 和 reducers 结合在一起的对象。store 有以下职责:

  • 保存应用程序状态

  • 通过getState()dispatch(action)允许访问和更新状态。

  • 通过subscribe(监听器)注册和取消注册监听器

以下是 container 文件夹中UserListApp.js的代码:

constinitialState = { 
  users: [1, 2, 3], 
  usersById: { 
    1: { 
      id: 1, 
      name: 'Harmeet Singh' 
    }, 
    2: { 
      id: 2, 
      name: 'Mehul Bhatt' 
    }, 
    3: { 
      id: 3, 
      name: 'NayanJyotiTalukdar' 
    } 
  } 
}; 
import React, { Component, PropTypes } from 'react'; 
import { bindActionCreators } from 'redux'; 
import { connect } from 'react-redux'; 

import * as UsersActions from '../actions/UsersActions'; 
import { UserList, AddUserInput } from '../components'; 

@connect(state => ({ 
userlist: state.userlist 
})) 
export default class UserListApp extends Component { 

  static propTypes = { 
    usersById: PropTypes.object.isRequired, 
    dispatch: PropTypes.func.isRequired 
  } 

  render () { 
    const { userlist: { usersById }, dispatch } = this.props; 
    const actions = bindActionCreators(UsersActions, dispatch); 

    return ( 
      <div> 
        <h1>UserList</h1> 
        <AddUserInputaddUser={actions.addUser} /> 
        <UserList users={usersById} actions={actions} /> 
      </div> 
    ); 
  } 
} 

在上面的代码中,我们使用UserList的静态 JSON 数据初始化组件的状态,并使用getstatedispatch(action),然后更新 store 信息。

提示

在 Redux 应用程序中,我们只会有一个单一的 store。当我们需要拆分我们的数据处理逻辑时,我们将使用 reducer 组合而不是多个 store。

Components

这些都是普通的 React JSX 组件,所以我们不需要详细介绍它们。我们已经添加了一些功能状态组件,除非我们需要使用本地状态或生命周期方法,否则我们将使用它们:

在这个(AddUserInput.js)文件中,我们正在创建一个 JSX 输入组件,从中获取用户输入:

export default class AddUserInput extends Component { 
  static propTypes = { 
    addUser: PropTypes.func.isRequired 
  } 

  render () { 
    return ( 
      <input 
      type="text" 
      autoFocus="true" 
      className={classnames('form-control')} 
        placeholder="Type the name of the user to add" 
        value={this.state.name} 
        onChange={this.handleChange.bind(this)} 
        onKeyDown={this.handleSubmit.bind(this)} /> 
    ); 
  } 

  constructor (props, context) { 
    super(props, context); 
      this.state = { 
        name: this.props.name || '', 
      }; 
  } 
} 

UserList.js中,我们正在创建一个列表组件,其中我们迭代Input组件的值:

export default class UserList extends Component { 
  static propTypes = { 
    users: PropTypes.object.isRequired, 
    actions: PropTypes.object.isRequired 
  } 

  render () { 
    return ( 
      <div className="media"> 
        { 
          mapValues(this.props.users, (users) => { 
            return (<UsersListItem 
              key={users.id} 
              id={users.id} 
              name={users.name} 
               src={users.src} 
              {...this.props.actions} />); 
          }) 
        } 
      </div> 
    ); 
  } 
}

UserList组件中迭代值后,我们将在 Bootstrap 的media布局中显示该列表:

export default class UserListItem extends Component { 
  static propTypes = { 
    id: PropTypes.number.isRequired, 
    name: PropTypes.string.isRequired, 
    onTrashClick: PropTypes.func.isRequired 
  } 

  render () { 
    return ( 
      <div> 
      <div className="clearfix"> 
            <a href="#" className="pull-left"> 
            <img className="media-object img-thumbnail" 
            src={"http://placehold.it/64x64"}/> 
            </a> 
            <div className={`media-body ${styles.paddng10}`}> 
                  <h3className="media-heading"> 
                  <strong><a href="#">{this.props.name}</a></strong> 
                  </h3> 
            <p> 
                  Loremipsum dolor sit amet, consecteturadipiscingelit. 
                  Praesentgravidaeuismod ligula,
                  vel semper nuncblandit sit amet.  
            </p> 

            <div className={`pull-right ${styles.userActions}`}> 
            <button className={`btnbtn-default ${styles.btnAction}`} 
            onClick={()=>this.props.deleteUser(this.props.id)} 
            > 
            Delete the user <iclassName="fafa-trash" /> 
            </button> 
            </div> 
          </div> 
        </div> 
      </div> 
    ); 
  } 
}  

现在,我们需要将我们的组件包装在容器文件夹中的UserListApp.js中:

import { UserList, AddUserInput } from '../components'; 
@connect(state => ({ 
  userlist: state.userlist 
})) 
export default class UserListApp extends Component {  
  static propTypes = { 
    usersById: PropTypes.object.isRequired, 
    dispatch: PropTypes.func.isRequired 
  } 

  render () { 
    const { userlist: { usersById }, dispatch } = this.props; 
    const actions = bindActionCreators(UsersActions, dispatch); 

    return ( 
      <div> 
        <h1>UserList</h1> 
        <AddUserInput addUser={actions.addUser} /> 
        <UserList users={usersById} actions={actions} /> 
      </div> 
    ); 
  } 
}

现在,让我们将UserListApp组件包装到容器文件夹中的App.js中的 Redux 存储中:

import UserListApp from './UserListApp'; 
import * as reducers from '../reducers'; 

const reducer = combineReducers(reducers); 
const store = createStore(reducer); 

export default class App extends Component { 
  render() { 
    return ( 
      <div> 
        <Provider store={store}> 
          {() => <UserListApp /> } 
        </Provider> 

        {renderDevTools(store)} 
      </div> 
    ); 
  } 
} 

现在转到根目录,打开 CMD,并运行以下命令:

要安装此应用程序所需的软件包,请运行以下命令:

**Npm install**

完成后,运行以下命令:

**Npm start**

观察以下屏幕截图:

Components

看起来很棒。右侧面板是 Redux DevTool,它提供了 UI 的更新。我们可以轻松地看到在此列表中删除或添加用户的更新。

以下屏幕截图显示了从UserList中删除用户:

Components

以下屏幕截图显示了添加用户的过程:

Components

注意

请参阅第六章的源代码,Redux 架构,以便更好地理解应用程序的流程。

总结

我们现在可以看到 Redux 架构的重要性及其在 React 应用程序中的作用。我们还在本章中学习了状态管理,看看存储如何全局处理状态更改请求,Redux 有助于避免组件之间的直接交互。

本章主要讨论 Redux 架构及其细节。为了澄清,我们已经看到了提供对 Redux 架构中数据和逻辑流程理解的图表。Redux 架构受 Flux 的启发,但它有自己的特点和优势。我们希望图表和实际示例有助于让您了解 Redux 架构。

现在,我们将继续进行下一章,讨论如何在 React 中进行路由。

第七章:使用 React 进行路由

在之前的章节中,我们已经了解了 Redux 架构以及如何处理两种状态,即数据状态和 UI 状态,以创建单页面应用程序或组件。目前,如果需要,我们的应用程序 UI 将与 URL 同步,我们需要使用 React 路由器使我们的应用程序 UI 同步。

React 路由器的优势

让我们看一下 React 路由器的一些优势:

  • 以标准化结构查看声明有助于我们立即了解我们的应用视图

  • 延迟加载代码

  • 使用 React 路由器,我们可以轻松处理嵌套视图和渐进式视图分辨率

  • 使用浏览历史功能,用户可以向后/向前导航并恢复视图的状态

  • 动态路由匹配

  • 导航时视图上的 CSS 过渡

  • 标准化的应用程序结构和行为,在团队合作时非常有用

注意

React 路由器不提供任何处理数据获取的方式;我们需要使用asyncProps或另一种 React 数据获取机制。

在本章中,我们将看看如何创建路由,以及包含参数的路由。在开始之前,让我们计划一下我们的员工信息系统EIS)需要哪些路由。请查看以下屏幕截图:

React 路由器的优势

上述屏幕截图来自第二章 使用 React-Bootstrap 和 React 构建响应式主题供您参考。

在第二章 使用 React-Bootstrap 和 React 构建响应式主题中,我们为我们的应用程序创建了响应式主题布局。现在我们将在其中添加路由以导航到每个页面。

  • 主页:这将是我们的主页,将显示员工的个人资料信息

  • 编辑个人资料:在这里,我们将能够编辑员工的信息

  • 查看工单:在这个页面,员工将能够查看他提交的工单

  • 新工单:在这里,员工可以提交工单

这些都是我们必要的路由;让我们看看如何创建它们。

安装路由器

React 路由器已经作为 React 库之外的不同模块打包。我们可以在 React 路由器 CDN 上使用 React 路由器 CDN:cdnjs.cloudflare.com/ajax/libs/react-router/4.0.0-0/react-router.min.js

我们可以像这样将其包含在我们的项目中:

var { Router, Route, IndexRoute, Link, browserHistory } = ReactRouter 

或者我们可以使用 React 的npm包:

**$ npm install --save react-router**

使用 ES6 转译器,比如 Babel:

import { Router, Route, Link } from 'react-router'

不使用 ES6 转译器:

var Router = require('react-router').Router 
var Route = require('react-router').Route 
var Link = require('react-router').Link 

好的,现在让我们设置我们的项目并包括 React 路由器。

应用程序设置

React 路由器看起来与其他 JS 路由器不同。它使用 JSX 语法,这使得它与其他路由器不同。首先,我们将创建一个示例应用程序,而不使用npm包,以更好地理解路由器的概念。

按照以下说明进行设置:

  1. 第二章目录结构和文件复制到第七章中。

  2. 删除现有的 HTML 文件并创建一个新的index.html

  3. 在您的 HTML 中复制此样板代码:

        <!doctype html>
        <html lang="en">
            <head>
                <meta charset="utf-8">
                <title>React Router - Sample application with 
                bootstrap</title>
                <link rel="stylesheet" href="css/bootstrap.min.css">
                <link rel="stylesheet" href="css/font-awesome.min.css">
                <link rel="stylesheet" href="css/custom.css">
                <script type="text/javascript" src="js/react.js"></script>
                <script type="text/javascript" src="js/react-dom.min.js">
                </script>
                <script src="js/browser.min.js"></script>
                <script src="js/jquery-1.10.2.min.js"></script>
                <script src="js/bootstrap.min.js"></script>
                <script src="https://unpkg.com/react-router/umd/
                ReactRouter.min.js"></script>
                <script src="components/bootstrap-navbar.js" type=
                "text/babel"></script>
                <script src="components/sidebar.js" type="text/babel">
                </script>
                <script src="components/sidebar.js" type="text/babel">
                </script>
            </head>
            <body>
                <div id="nav"></div>
                <div class="container">
                    <h1>Welcome to EIS</h1>
                    <hr>
                    <div class="row">
                        <div class="col-sm-3" id="sidebar">
                            <!--left col-->
                        </div>
                        <!--/col-3-->
                        <div class="col-sm-9 profile-desc" id="main">
                        </div>
                        <!--/col-9-->
                    </div>
                </div>
                <!--/row-->
            </body>
        </html> 

  1. 在浏览器中打开index.html。确保输出不显示控制台中的任何错误。

创建路由

由于我们已经创建了 HTML,现在我们需要在之前创建的bootstrap-navbar.js中添加一个 Bootstrap navbar组件。

为了配置路由,让我们在routing.js中创建一个组件,它将与 URL 同步:

var homePage = React.createClass({ 
    render: function() { 
        return (<h1>Home Page</h1>); 
    } 
}); 
ReactDOM.render(( 
    <homePage /> 
), document.getElementById('main')); 

在浏览器中打开它,看起来是这样的:

创建路由

让我们添加Router来渲染我们的homePage组件与 URL:

ReactDOM.render(( 
    <Router> 
        <Route path="/" component={homePage} /> 
    </Router> 
), document.getElementById('main'));  

在前面的例子中,使用<Route>标签定义了一个规则,访问首页将把homePage组件渲染到'main'中。正如我们已经知道的那样,React 路由器使用 JSX 来配置路由。<Router><Route>是不同的东西。<Router>标签应该始终是包裹多个 URL 的主要父标签,而<Route>标签。我们可以声明多个带有属性组件的<Route>标签,使您的 UI 同步。当历史记录发生变化时,<Router>将使用匹配的 URL 渲染组件:

ReactDOM.render(( 
    <Router> 
        <Route path="/" component={homePage} /> 
        <Route path="/edit" component={Edit} /> 
        <Route path="/alltickets" component={allTickets} /> 
        <Route path="/newticket" component={addNewTicket} /> 
    </Router> 
), document.getElementById('main'));

看起来非常简单和清晰,路由器将在视图之间切换路由,而不会向服务器发出请求并将它们渲染到 DOM 中。

页面布局

让我们假设如果我们需要为每个组件都需要不同的布局,比如首页应该有两列,其他页面应该有一列,但它们都共享头部和页脚等公共资产。

这是我们应用程序的布局草图:

页面布局

好的,现在让我们创建我们的主要布局:

var PageLayout = React.createClass({
    render: function() {
        return ( 
            <div className="container">
                <h1>Welcome to EIS</h1>
                <hr/>
                <div className="row">
                    <div className="col-md-12 col-lg-12">
                        {this.props.children}
                    </div>
                </div>
            </div>
        )
    }
}) 

在上面的代码中,我们已经为我们的应用程序创建了主要布局,它使用this.props.children来处理子布局组件,而不是硬编码的组件。现在我们将创建在我们的主要布局组件中呈现的子组件:

var RightSection = React.createClass({
    render: function() {
        return (
            <div className="col-sm-9 profile-desc" id="main">
                <div className="results">
                    <PageTitle/>
                    <HomePageContent/>
                </div>
            </div>
        )
    }
})
var ColumnLeft = React.createClass({
    render: function() {
        return (
            <div className="col-sm-3" id="sidebar">
                <div className="results">
                    <LeftSection/>
                </div>
            </div>
        )
    }
})

在上面的代码中,我们创建了两个组件,RightSectionColumnLeft,来包装和分割我们的组件在不同的部分。

所以在响应式设计中,我们应该很容易管理布局:

var LeftSection = React.createClass({ 
    render: function() { 
        return ( 
            React.DOM.ul({ className: 'list-group' }, 
            React.DOM.li({className:'list-group-item 
            text-muted'},'Profile'), 
            React.DOM.li({className:'list-group-item'}, 
            React.DOM.a({className:'center-block 
            text-center',href:'#'},'Image') 
        ), 
        React.DOM.li({className:'list-group-item text-right'},'2.13.2014', 
        React.DOM.span({className:'pull-left'}, 
        React.DOM.strong({className:'pull-left'},'Joining Date') 
        ), 
        React.DOM.div({className:'clearfix'}) 
        ))                                                             
      ) 
    } 
}) 
var TwoColumnLayout = React.createClass({ 
    render: function() { 
        return ( 
            <div> 
                <ColumnLeft/> 
                <RightSection/> 
            </div> 
        ) 
    } 
}) 
var PageTitle = React.createClass({ 
    render: function() { 
        return ( 
            <h2>Home</h2> 
        ); 
    } 
}); 

在上面的代码中,我们将组件分成了两个部分:<ColumnLeft/><RightSection/>。我们在<TwoColumnLayout/>组件中给出了这两个组件的引用。在父组件中,我们有this.props.children作为一个 prop,但只有当组件是嵌套的时候才起作用,React 会自动负责填充这个 prop。如果组件不是父组件,this.props.children将为 null。

嵌套路由

好的,我们已经创建了特定布局组件,但我们仍然需要看看如何为它们创建嵌套路由,以便将组件传递给具有 props 的父组件。这很重要,以便在我们的 EIS 应用程序中实现一定程度的动态性。这是我们的 HTML,显示当前的样子:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>React Router - Sample application with bootstrap</title>
        <link rel="stylesheet" href="css/bootstrap.min.css">
        <link rel="stylesheet" href="css/font-awesome.min.css">
        <link rel="stylesheet" href="css/custom.css">
    </head>
    <body>
        <div id="nav"></div>
        <div id="reactapp"></div>
        <script type="text/javascript" src="js/react.js"></script>
        <script type="text/javascript" src="js/react-dom.min.js"></script>
        <script src="js/browser.min.js"></script>
        <script src="js/jquery-1.10.2.min.js"></script>
        <script src="js/bootstrap.min.js"></script>
        <script src="https://unpkg.com/react-router/umd/
        ReactRouter.min.js"></script>
        <script src="components/bootstrap-navbar.js" 
        type="text/babel"></script>
        <script src="components/router.js" type="text/babel"></script>    
    </body>
</html>  

让我们再次看一下我们之前创建的路由器:

ReactDOM.render((
    <Router>
        <Route path="/" component={PageLayout}>
            <IndexRoute component={TwoColumnLayout}/>
            <Route path="/edit" component={Edit} />
            <Route path="/alltickets" component={allTickets} />
            <Route path="/newticket" component={addNewTicket} />
        </Route>
    </Router>
), document.getElementById('reactapp'));

现在我们已经在与父级的映射中添加了额外的元素<IndexRoute />,将其视图设置为我们的{TwoColumnLayout}组件。IndexRoute元素负责在应用程序初始加载时显示哪个组件。

不要忘记在{PageLayout}组件中包装。我们还可以在<indexRoute>上定义路径规则,与<Route>相同:

ReactDOM.render((
<Router>
    <Route component={PageLayout}>
        <IndexRoute path="/" component={TwoColumnLayout}/>
        <Route path="/edit" component={Edit} />
        <Route path="/alltickets" component={allTickets} />
        <Route path="/newticket" component={addNewTicket} />
    </Route>
</Router>
), document.getElementById('reactapp'));

观察以下截图:

嵌套路由

看起来不错。如我们在<IndexRoute>中提到的,它总是在第一次加载页面时加载<TwoColumnLayout>。现在让我们导航并查看一些其他页面。

React 还为我们提供了一种使用<IndexRedirect>组件重定向路由的方法:

<Route path="/" component={App}> 
    <IndexRedirect to="/welcome" /> 
    <Route path="welcome" component={Welcome} /> 
    <Route path="profile" component={profile} /> 
</Route> 

观察以下截图:

嵌套路由

您可能已经注意到,我点击了编辑个人资料页面,它呈现了编辑页面组件,但没有在当前活动链接上添加活动类。为此,我们需要用 React 的<Link>标签替换<a>标签。

React 路由

React 路由使用了<link>组件,而不是我们在nav中使用的<a>元素。如果我们使用 React 路由,则必须使用这个。让我们在导航中添加<link>而不是<a>标签,并替换href属性为两个。

<a>标签:

<li className="active"><a href="#/">Home</a></li> 

用这个替换:

<li className="active"><Link to="#/">Home</Link></li> 

让我们在浏览器中查看<link>的行为:

React router

它在控制台中显示错误,因为我们没有在ReactRouter对象中添加Link组件引用:

var { Router, Route, IndexRoute, IndexLink, Link, browserHistory } = ReactRouter 

我们还添加了browserHistory对象,稍后我们会解释。

这是我们的PageLayout组件的样子:

var PageLayout = React.createClass({
render: function() {
return ( 
<main>
    <div className="navbar navbar-default navbar-static-top"
    role="navigation">
        <div className="container">
            <div className="navbar-header">
                <button type="button" className="navbar-toggle"
                data-toggle="collapse"
                data-target=".navbar-collapse">
                <span className="sr-only">Toggle navigation</span>
                <span className="icon-bar"></span>
                <span className="icon-bar"></span>
                <span className="icon-bar"></span>
                </button>
                <Link className="navbar-brand" to="/">
                EIS</Link>
            </div>
            <div className="navbar-collapse collapse">
                <ul className="nav navbar-nav">
                    <li className="active">
                        <IndexLink activeClassName="active" to="/">
                        Home</IndexLink>
                    </li>
                    <li>
                        <Link to="/edit" activeClassName="active">
                        Edit Profile</Link>
                    </li>
                    <li className="dropdown">
                        <Link to="#" className="dropdown-toggle"
                        data-toggle="dropdown">
                        Help Desk <b className="caret"></b></Link>
                        <ul className="dropdown-menu">
                            <li>
                                <Link to="/alltickets">
                                View Tickets</Link>
                            </li>
                            <li>
                                <Link to="/newticket">
                                New Ticket</Link>
                            </li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </div>
    <div className="container">
        <h1>Welcome to EIS</h1>
        <hr/>
        <div className="row">
            <div className="col-md-12 col-lg-12">
                {this.props.children}
            </div>
        </div>
    </div>
</main>
)
}
})

为了激活默认链接,我们使用了<IndexRoute>。这会自动定义默认链接的活动类。activeClassName属性将 URL 与to值匹配并将活动类添加到其中。如果我们不使用activeClassName,则无法自动在活动链接上添加类。让我们快速看一下浏览器:

React router

它按预期工作。让我们在控制台中查看 DOM HTML:

React router

我们只需要覆盖<li> .active上的 Bootstrap 默认样式为<a>

.navbar-default .navbar-nav li>.active, .navbar-default
.navbar-nav li>.active:hover, .navbar-default 
.navbar-nav li>.active:focus { 
    color: #555; 
    background-color: #e7e7e7; 
}  

我们还可以在路由中传递参数来匹配、验证和渲染 UI:

<Link to={`/tickets/${ticket.id}`}>View Tickets</Link>

在路由器中,我们需要添加:

<Route path="tickets/:ticketId" component={ticketDetail} /> 

我们可以添加尽可能多的参数,并且很容易在我们的组件中提取这些参数。我们将以对象的形式访问所有route参数。

React 路由支持 IE9+浏览器版本,但对于 IE8,您可以使用 Node npmreact-router-ie8

NotFoundRoute

React 路由还提供了一种在客户端显示 404 错误的方法,如果路径与路由不匹配:

var NoMatch = React.createClass({ 
   render: function() { 
       return (<h1>URL not Found</h1>); 
   } 
}); 

<Route path="*" component={NoMatch}/>

观察以下截图:

NotFoundRoute

我们可以很容易地处理不匹配的 URL,这太棒了。

这是我们的路由器的样子:

ReactDOM.render(( 
    <Router> 
        <Route path="/" component={PageLayout}> 
            <IndexRoute component={TwoColumnLayout}/> 
                <Route path="/edit" component={Edit} /> 
                <Route path="/alltickets" component={allTickets} /> 
                <Route path="/newticket" component={addNewTicket} /> 
                </Route> 
        <Route path="*" component={NoMatch}/> 
    </Router> 
), document.getElementById('reactapp')); 

以下是我们可以使用的其他link属性列表:

  • activeStyle:我们可以用这个来自定义内联样式。例如:
        <Link activeStyle={{color:'#53acff'}} to='/'>Home</Link>
  • onlyActiveOnIndex:当我们使用activeStyle属性添加自定义内联样式时,我们可以使用这个属性。它只在我们在精确链接上时应用。例如:
        <Link onlyActiveOnIndex activeStyle={{color:'#53acff'}} 
        to='/'>Home</Link>

浏览器历史

React 路由的另一个很酷的功能是它使用browserHistory API 来操作 URL 并创建干净的 URL。

使用默认的hashHistory

  http://localhost:9090/react/chapter7/#/?_k=j8dlzv
  http://localhost:9090/react/chapter7/#/edit?_k=yqdzh0 http://localhost:9090/react/chapter7/#/alltickets?_k=0zc49r
  http://localhost:9090/react/chapter7/#/newticket?_k=vx8e8c

当我们在我们的应用程序中使用browserHistory时,URL 看起来很干净:

 http://localhost:9090/react/chapter7/
 http://localhost:9090/react/chapter7/edit
 http://localhost:9090/react/chapter7/alltickets
 http://localhost:9090/react/chapter7/newticket

现在 URL 看起来干净且用户友好。

查询字符串参数

我们还可以将查询字符串作为props传递给将在特定路由上呈现的任何组件。要访问这些 prop 参数,我们需要在我们的组件中添加props.location.query属性。

要查看这是如何工作的,让我们创建一个名为RouteQueryString的新组件:

var QueryRoute = React.createClass({ 
    render: function(props) { 
        return (<h2>{this.props.location.query.message}</h2>); 
        // Using this we can read the parameters from the 
        request which are visible in the URL's
    } 
}); 
<IndexLink activeClassName='active' to= 
     {{ pathname: '/query', query: { message: 'Hello from Route Query' } }}> 
         Route Query 
</IndexLink> 

在路由器中包含此路由路径:

<Route path='/query' component={QueryRoute} /> 

让我们在浏览器中看看输出:

查询字符串参数

很好,它的工作正常。

现在我们的Router配置看起来是这样的:

ReactDOM.render((
    <Router>
        <Route path="/" component={PageLayout}>
            <IndexRoute component={TwoColumnLayout}/>
            <Route path="/edit" component={Edit} />
            <Route path="/alltickets" component={allTickets} />
            <Route path="/newticket" component={addNewTicket} />
            <Route path='/query' component={QueryRoute} />
        </Route>
        <Route path="*" component={NoMatch}/>
    </Router>
), document.getElementById('reactapp'));

进一步定制您的历史记录

如果我们想要定制历史选项或使用历史记录中的其他增强器,那么我们需要使用 React 的useRouterHistory组件。

useRouterHistory已经使用useQueriesuseBasename从历史工厂预增强。示例包括:

import { useRouterHistory } from 'react-router' 
import { createHistory } from 'history' 
const history = useRouterHistory(createHistory)({ 
    basename: '/base-path' 
}) 

使用useBeforeUnload增强器:

import { useRouterHistory } from 'react-router' 
import { createHistory,useBeforeUnload } from 'history' 
const history = useRouterHistory(useBeforeUnload(createHistory))() 
history.listenBeforeUnload(function () { 
    return 'Are you sure you want to reload this page?' 
}) 

在使用 React 路由之前,我们必须了解 React 路由版本更新。

请访问此链接github.com/ReactTraining/react-router/blob/master/upgrade-guides/v2.0.0.md以获取更新。

以下是路由器中不推荐使用的语法的简短列表:

<Route name="" /> is deprecated. Use <Route path="" /> instead. 
<Route handler="" /> is deprecated. Use <Route component="" /> instead. 
<NotFoundRoute /> is deprecated. See Alternative 
<RouteHandler /> is deprecated. 
willTransitionTo is deprecated. See onEnter 
willTransitionFrom is deprecated. See onLeave 
query={{ the: 'query' }} is deprecated. Use to={{ pathname: '/foo', query: { the: 'query' } }} 

history.isActive被替换为router.isActive

RoutingContext被重命名为RouterContext

摘要

在本章中,我们将我们的应用程序从单个页面转换为多个页面和多路由应用程序,我们可以在其上构建我们的 EIS 应用程序。我们首先规划了应用程序中的主要路由,然后创建了一个组件。

然后,我们看了如何使用<Router><Route>方法设置我们的路由。这是通过var { Router, Route, IndexRoute,IndexLink, Link, browserHistory } = ReactRouter完成的。我们还看了其他方法:<Link><IndexLink><IndexRoute>

这使我们能够设置包含参数的静态和动态路由,使我们的应用程序 UI 与 URL 完美同步。

在下一章中,我们将讨论如何将其他 API 与 React 集成。

第八章:ReactJS API

在之前的章节中,我们学习了 React 路由器,它允许我们创建单页应用程序,并确保我们的 UI 与 URL 同步。我们还介绍了 React 路由器的优势、动态路由匹配以及如何配置路由器中的组件以与匹配的 URL 在 DOM 中呈现。通过 React 路由器浏览器历史功能,用户可以向后/向前导航并恢复应用程序的先前状态。现在我们将检查如何将 React API 与 Facebook、Twitter 和 Git 等其他 API 集成。

React 顶级 API

当我们谈论 React API 时,这是进入 React 库的第一步。不同的 React 用法会提供不同的输出。例如,使用 React 的script标签将使顶级 API 在React全局上可用,使用 npm 的 ES6 将允许我们编写import React from 'react',而使用 npm 的 ES5 将允许我们编写var React = require('react'),因此有多种不同特性初始化 React 的方式。

React API 组件

通常,在处理 React 时,我们正在构建适合其他组件的组件,并且我们假设用 React 构建的任何东西都是一个组件。然而,这并不正确。需要有其他一些方法来编写支持代码,以将外部世界与 React 连接起来。观察以下代码片段:

ReactDOM.render(reactElement, domContainerNode)

render方法用于更新组件的属性,然后我们可以声明一个新元素来再次呈现它。

另一种方法是unmountComponentAtNode,用于清理你的代码。当我们使用 React 组件构建 SAP 时,我们必须插入unmountComponentAtNode以在正确的时间启动,从而清理应用程序的生命周期。观察以下代码片段:

ReactDOM.unmountComponentAtNode(domContainerNode)

我经常观察到开发人员不调用unmountComponentAtNode方法,这导致他们的应用程序出现内存泄漏问题。

挂载/卸载组件

在你的 API 中,建议始终使用自定义包装器 API。假设你有一个或多个根,它将在某个时期被删除,那么在这种情况下,你将不会丢失它。Facebook 就有这样的设置,它会自动调用unmountComponentAtNode

我还建议不要每次调用ReactDOM.render(),而是通过库来编写或使用它的理想方式。在这种情况下,应用程序将使用挂载和卸载来进行管理。

创建一个自定义包装器将帮助您在一个地方管理配置,比如国际化、路由器和用户数据。每次在不同的地方设置所有配置都会非常痛苦。

面向对象编程

如果我们在声明变量下面再次声明它,它将被覆盖,就像ReactDOM.render覆盖了它的声明属性一样:

ReactDOM.render(<Applocale="en-US"userID={1}/>,container); 
// props.userID == 1
// props.locale == "en-US" 
ReactDOM.render(<AppuserID={2}/>,container); 
// props.userID == 2
// props.locale == undefined ??!?

如果我们只覆盖组件中的一个属性,那么建议使用面向对象编程将覆盖所有声明的属性可能会令人困惑。

您可能会认为我们通常使用setProps作为辅助函数,以帮助覆盖选择性属性,但由于我们正在使用 React,我们不能使用它;因此,建议在您的 API 中使用自定义包装器。

在下面的代码中,您将看到一个样板,以帮助您更好地理解它:

classReactComponentRenderer{ 
    constructor(componentClass,container){ 
        this.componentClass=componentClass; 
        this.container=container; 
        this.props={}; 
        this.component=null; 
    } 

    replaceProps(props,callback){ 
        this.props={}; 
        this.setProps(props,callback); 
    } 

    setProps(partialProps,callback){ 
        if(this.componentClass==null){ 
            console.warn( 
                'setProps(...): Can only update a mounted or '+ 
                'mounting component. This usually means you called 
                setProps() on '+'an unmounted component. This is a no-op.' 
            ); 
            return; 
        } 
        Object.assign(this.props,partialProps); 
        varelement=React.createElement(this.klass,this.props); 
        this.component=ReactDOM.render(element,this.container,callback); 
    } 

    unmount(){ 
        ReactDOM.unmountComponentAtNode(this.container); 
        this.klass=null; 
    }
}

在前面的例子中,似乎我们仍然可以在面向对象的 API 中编写更好的代码,但为此我们必须了解自然的面向对象 API 及其在 React 组件中的使用:

classReactVideoPlayer{                                 
    constructor(url,container){ 
        this._container=container; 
        this._url=url; 
        this._isPlaying=false; 
        this._render(); 
    } 

    _render(){ 
        ReactDOM.render( 
            <VideoPlayerurl={this._url}playing={this._isPlaying}/>, 
            this._container 
        ); 
    } 

    geturl(){ 
        returnthis._url; 
    } 

    seturl(value){ 
        this._url=value; 
        this._render(); 
    } 

    play(){ 
        this._isPlaying=true; 
        this._render(); 
    } 

    pause(){ 
        this._isPlaying=false; 
        this._render(); 
    } 

    destroy(){ 
        ReactDOM.unmountComponentAtNode(this._container); 
    }
}

我们可以从前面的例子中了解到命令式API 和声明式API 之间的区别。这个例子还展示了我们如何在声明式 API 或反之上提供命令式 API。在使用 React 创建自定义 Web 组件时,我们可以使用声明式 API 作为包装器。

React 与其他 API 的集成

React 集成只是通过使用 JSX、Redux 和其他 React 方法将 Web 组件转换为 React 组件。

让我们看一个 React 与另一个 API 集成的实际例子。

React 与 Facebook API 集成

这个应用将帮助您集成 Facebook API,并且您将可以访问您的个人资料图片以及您在好友列表中有多少个朋友。您还将看到在各自朋友列表中有多少个赞、评论和帖子。

首先,您必须安装 Node.js 服务器并在系统中添加 npm 包。

如果您不知道如何安装 Node.js,请参阅以下说明。

安装 Node

首先,我们必须下载并安装 Node.js 版本 0.12.10,如果我们还没有在系统上安装它。我们可以从nodejs.org下载 Node.js,它包括 npm 包管理器。

设置完成后,我们可以检查 Node.js 是否设置正确。打开命令提示符并运行以下命令:

**node  -v** 

或者

**node --version**

这将返回 Node.js 安装的版本,如下所示:

Installing Node

您应该能够看到版本信息,这可以确保安装成功。

安装 Node 后,您将拥有babel-plugin-syntax-object-rest-spreadbabel-plugin-transform-object-rest-spread

这两者之间有一个基本的区别:spread只允许您阅读语法,但transform将允许您将语法转换回 ES5。

完成此操作后,您将不得不将插件存储到.babelrc文件中,如下所示:

{ 
  "plugins": ["syntax-object-rest-spread", "transform-object-rest-spread"] 
} 

设置应用程序

首先,我们需要为我们的项目创建一个package.json文件,其中包括项目信息和依赖项。现在,打开命令提示符/控制台并导航到您创建的目录。运行以下命令:

**Npm init**

这个命令将初始化我们的应用程序,并在创建一个名为package.json的 JSON 文件之前询问几个问题。该实用程序将询问有关项目名称、描述、入口点、版本、作者名称、依赖项、许可信息等的问题。一旦执行了该命令,它将在项目的根目录中生成一个package.json文件。

我已经根据我的要求创建了package.json文件,如下所示:

{ 
  "name": "facebook-api-integration-with-react", 
  "version": "1.2.0", 
  "description": "Web Application to check Like, Comments and
  Post of your Facebook Friends, 

在上述代码中,您可以看到应用程序的name,您的应用程序的version和您的应用程序的description。观察以下代码片段:

  "scripts": { 
    "lint": "eslint src/ server.js config/ webpack/", 
    "start": "npm run dev", 
    "build": "webpack -p --config webpack/webpack.config.babel.js
    --progress --colors --define process.env.NODE_ENV='"production"'", 
    "clean": "rimraf dist/", 
    "deploy": "npm run clean && npm run build", 
    "dev": "./node_modules/.bin/babel-node server.js" 
  }, 

从上述代码中,您可以设置您的scripts,以详细说明如何start您的服务器,如何build,如何clean,以及deploydev。请确保您在各自变量中定义的路径是正确的,否则您的应用程序将无法按预期工作。观察以下代码片段:

  "author": "Mehul Bhatt <mehu_multimedia@yahoo.com>", 
  "license": "MIT", 
  "keywords": [ 
    "react", 
    "babel", 
    "ES6", 
    "ES7", 
    "async", 
    "await", 
    "webpack", 
    "purecss", 
    "Facebook API" 
  ], 

上述代码显示了author名称,license(如果适用)以及您的应用程序的keywords。观察以下代码片段:

  "devDependencies": { 
    "babel-cli": "⁶.3.17", 
    "babel-core": "⁶.3.26", 
    "babel-eslint": "⁶.0.0", 
    "babel-loader": "⁶.2.0", 
    "babel-plugin-react-transform": "².0.0-beta1", 
    "babel-plugin-transform-regenerator": "⁶.5.2", 
    "babel-polyfill": "⁶.5.0", 
    "babel-preset-es2015": "⁶.3.13", 
    "babel-preset-react": "⁶.3.13", 
    "babel-preset-stage-0": "⁶.5.0", 
    "css-loader": "⁰.23.0", 
    "enzyme": "².4.1", 
    "eslint": "².12.0", 
    "eslint-config-airbnb": "⁹.0.1", 
    "eslint-plugin-import": "¹.8.1", 
    "eslint-plugin-jsx-a11y": "¹.5.3", 
    "eslint-plugin-react": "⁵.2.0", 
    "express": "⁴.13.3", 
    "file-loader": "⁰.9.0", 
    "imports-loader": "⁰.6.5", 
    "json-loader": "⁰.5.4", 
    "lolex": "¹.4.0", 
    "react-transform-catch-errors": "¹.0.1", 
    "react-transform-hmr": "¹.0.1", 
    "redbox-react": "¹.2.0", 
    "rimraf": "².5.0", 
    "sinon": "¹.17.4", 
    "style-loader": "⁰.13.0", 
    "url-loader": "⁰.5.7", 
    "webpack": "¹.12.9", 
    "webpack-dev-middleware": "¹.4.0", 
    "webpack-hot-middleware": "².6.0", 
    "yargs": "⁴.1.0" 
  }, 
  "dependencies": { 
    "classnames": "².2.5", 
    "jss": "⁵.2.0", 
    "jss-camel-case": "².0.0", 
    "lodash.isequal": "⁴.0.0", 
    "react": "¹⁵.0.2", 
    "react-addons-shallow-compare": "¹⁵.0.2", 
    "react-dom": "¹⁵.0.2", 
    "reqwest": "².0.5", 
    "spin.js": "².3.2" 
  } 
} 

最后,您可以在上述代码中看到您的应用程序的dependencies,这将帮助您设置所需的组件并获取数据,以及前端内容。您还可以看到定义的devDependencies及其版本,这些与您的应用程序相关联。

设置package.json文件后,我们有如下所示的 HTML 标记,名为index.html

<!doctype html> 
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>React Integration with Facebook API</title>
        <meta name="viewport" content="width=device-width, 
        initial-scale=1">
    </head>
    <body>
        <div id=" Api-root"></div>
        <script src="dist/bundle.js"></script> 
    </body>
</html>

config.js中使用唯一 ID 配置您的应用程序:

export default { 
    appId: '1362753213759665', 
    cookie: true, 
    xfbml: false, 
    version: 'v2.5' 
}; 

如前所示,您可以将配置放在一个文件中。您可以将其命名为index.js。该文件包括您的appId,在本地目录中运行应用程序时非常重要。

要获得您的 ID,您必须在 Facebook 上注册您的应用程序developers.facebook.com,然后您将需要按照以下步骤进行操作:

  1. 登录到您的 Facebook 开发者帐户:设置应用程序

  2. 登录后,您将在右侧看到一个名为我的应用程序的下拉菜单。点击它并打开列表菜单。在那里,您将找到添加新应用程序。点击它将打开一个对话框,显示创建新应用程序 ID,如下截图所示:设置应用程序

输入所需的详细信息,然后点击创建应用程序 ID按钮。

  1. 创建应用程序 ID 后,请转到仪表板页面,您将看到类似以下的屏幕:设置应用程序

  2. 仪表板页面上,您左侧的导航显示设置链接。请点击该链接设置应用程序的基本高级设置:设置应用程序

  3. 一旦您能够看到前面的屏幕,您将能够看到您动态生成的应用程序 ID显示名称类别和应用程序密钥自动填充。您还将看到应用程序域。在访问应用程序并通知我们需要在此处定义域时,此字段非常重要。但是,如果您直接将您的localhost写为域,它将不被接受,您的应用程序将出现错误。

为了使您的本地主机可访问,我们必须定义其平台。现在,请向下滚动一点以访问**+** 添加平台

设置应用程序

  1. 一旦您点击**+添加平台**,您将在屏幕上看到以下选项,并且您必须选择网站在本地服务器上运行应用程序:设置应用程序

  2. 在您选择网站作为平台后,将会在屏幕上添加一个字段,如下截图所示:设置应用程序

  3. 一旦你得到了前面的屏幕,你需要将站点 URL定义为http://localhost:3000/,然后以类似的方式,在应用域字段中定义相同的域,如下面的截图所示:设置应用程序

  4. 在做了上述更改之后,请通过点击右下角的保存更改按钮来保存你的更改:设置应用程序

现在你的 ID 已经创建好了,你可以在你的config.js文件中使用它来链接你的应用在本地服务器上运行。

在设置好config.js文件之后,下一步是在应用程序中设置你所需的文件,并将你的动态内容注入到 HTML ID 中。

你可以在index.js文件中导入所需的组件、工具和 CSS,并将其放在不同的文件夹中,这样它就不会与你的配置index.js文件冲突:

import React from 'react'; 
import { render } from 'react-dom'; 
import App from './components/App'; 

import 'babel-polyfill'; 

// import CSS 

import '../vendor/css/base.css'; 
import '../vendor/css/bootstrap.min.css'; 

render( 
  <App />, 
  document.querySelector('#Api-root') 
); 

在前面的代码中,你可以看到我导入了React来支持 React 文件,并导入了所需的 CSS 文件。最后一步,render方法在定义了你的 HTML ID 之后将为你完成这个技巧。确保document.querySelector有正确的选择器,否则你的应用将无法以正确的结构渲染。

你可以在前面的代码中看到,我创建了一个名为App的组件并导入了它。

App.js文件中,我导入了几个组件,这些组件帮助我从我的 Facebook 账户中获取数据,借助 Facebook API 集成。

观察一下App.js文件的代码结构:

/* global Facebook  */ 

import React, { Component } from 'react'; 
import Profile from './Profile'; 
import FriendList from './FriendList'; 
import ErrMsg from './ErrMsg'; 
import config from '../../config'; 
import Spinner from './Spinner'; 
import Login from './Login'; 
import emitter from '../utils/emitter'; 
import { getData } from '../utils/util'; 
import jss from 'jss';

前面导入的 JavaScript 文件已经设置好了获取数据的结构,关于它将如何在你的应用程序中执行。

const { classes } = jss.createStyleSheet({ 
  wrapper: { 
    display: 'flex' 
  }, 
  '@media (max-width: 1050px)': { 
    wrapper: { 
      'flex-wrap': 'wrap' 
    } 
  } 
}).attach(); 

前面的代码定义了常量来为包装器创建样式,在页面在浏览器中渲染时将应用这些样式。

class App extends Component { 

  state = { 
    status: 'loading' 
  }; 

  componentWillMount = () => { 
    document.body.style.backgroundColor = '#ffffff'; 
  }; 

  componentWillUnmount = () => { 
    emitter.removeListener('search'); 
  }; 

  componentDidMount = () => { 
    emitter.on('search', query => this.setState({ query })); 

    window.fbAsyncInit = () => { 
      FB.init(config); 

      // show login 
      FB.getLoginStatus( 
        response => response.status !== 'connected' && 
        this.setState({ status: response.status }) 
      ); 

      FB.Event.subscribe('auth.authResponseChange', (response) => { 
        // start spinner 
        this.setState({ status: 'loading' }); 

        (async () => { 
          try { 
            const { profile, myFriends } = await getData(); 
            this.setState({ status: response.status, profile, myFriends }); 
          } catch (e) { 
          this.setState({ status: 'err' }); 
       } 
     })(); 
   }); 
}; 

前面的代码扩展了组件,包括挂载/卸载的细节,这些细节我们在之前的章节中已经涵盖过了。如果你对这个领域还不确定,那么请重新查看一下。

window.fbAsyncInit将会将 Facebook API 与登录设置同步,并验证登录的状态。

它还将异步获取 Facebook 数据,比如你的个人资料和好友列表,这部分有单独的 JavaScript,将在本章后面进行讲解。

    // Load the SDK asynchronously 
    (function (d, s, id) { 
      const fjs = d.getElementsByTagName(s)[0]; 
      if (d.getElementById(id)) { return; } 
      const js = d.createElement(s); js.id = id; 
      js.src = '//connect.facebook.net/en_US/sdk.js'; 
      fjs.parentNode.insertBefore(js, fjs); 
    }(document, 'script', 'facebook-jssdk')); 
  }; 

  _click = () => { 
    FB.login(() => {}, { scope: ['user_posts', 'user_friends'] }); 
  }; 

定义一个范围数组意味着我们正在访问用户的 Facebook 好友和帖子。

观察下面的截图:

设置应用程序

在上述截图中,您可以看到在创建 Facebook 登录应用程序时App Review选项卡中的默认登录权限访问。我们可以提交批准以访问任何其他用户信息:

  mainRender = () => { 
    const { profile, myFriends, status, query } = this.state; 
    if (status === 'err') { 
      return (<ErrMsg />); 
    } else if (status === 'unknown' || status === 'not_authorized') { 
      return <Login fBLogin={this._click} />; 
    } else if (status === 'connected') { 
      return ( 
        <div className={classes.wrapper}> 
          <Profile {...profile} /> 
          <FriendList myFriends={myFriends} query={query} /> 
        </div> 
      ); 
    } 
    return (<Spinner />); 
  }; 

  render() { 
    return ( 
      <div> 
        {this.mainRender()} 
      </div> 
    ); 
  } 
}  
export default App; 

在上述代码中,mainRender方法将呈现ProfilemyFriends(好友列表)和status,并且它将在render return中返回值。您可以在render方法中看到一个<div>标签;我称之为{this.mainRender()}来在其中注入数据。

正如您所知,这里我们正在处理第三方 API 集成。我们不确定我们将连接到该 API 多长时间以及加载内容需要多长时间。最好有一个内容加载器(旋转器),表示用户需要等待一段时间,因此我们使用以下旋转器来显示页面上内容加载的进度。旋转器的代码也包含在App.js文件中。以下是旋转器的样子:

设置应用程序

您还可以选择自己的自定义旋转器。

一旦您的应用程序页面准备就绪,最终输出应该如下截图所示,您将看到基本的外观和感觉,以及所需的元素:

设置应用程序

一旦您启动本地服务器,上述屏幕将要求您允许继续登录过程。

一旦您按下同意按钮,它将重定向您到 Facebook 登录页面。这可以通过以下代码(Login.js)实现:

import React, { PropTypes } from 'react'; 
import jss from 'jss'; 
import camelCase from 'jss-camel-case'; 
jss.use(camelCase());  

在导入 React PropTypes之后,在以下代码中,您将看到我已经定义了一个常量来为登录页面创建样式。您也可以在这里定义样式,并且可以将它们放入一个 CSS 文件中,并且有一个外部文件调用。

const { classes } = jss.createStyleSheet({ 
  title: { 
    textAlign: 'center', 
    color: '#008000' 
  }, 
  main: { 
    textAlign: 'center', 
    backgroundColor: 'white', 
    padding: '15px 5px', 
    borderRadius: '3px' 
  },     
  wrapper: { 
    display: 'flex', 
    minHeight: '60vh', 
    alignItems: 'center', 
    justifyContent: 'center' 
  }, 
  '@media (max-width: 600px)': { 
    title: { 
      fontSize: '1em' 
    }, 
    main: { 
      fontSize: '0.9em' 
    } 
  } 
}).attach(); 

以下代码显示了登录页面的 HTML 结构,并且还定义了Login.propTypes用于登录按钮:

const Login = ({ fBLogin }) => ( 
  <div className={classes.wrapper}> 
    <div> 
      <h2 className={classes.title}>Please check your friend list 
      on Facebook</h2> 
        <div className={classes.main}> 
          <h4>Please grant Facebook to access your friend list</h4> 
          <button className="btn btn-primary" 
          onClick={fBLogin}>Agree</button> 
        </div> 
    </div> 
  </div> 
);  

Login.propTypes = { 
  fBLogin: PropTypes.func.isRequired 
}; 

export default Login; 

当您点击同意按钮时,您的应用程序将被重定向到 Facebook 登录页面。请参考以下截图:

设置应用程序

一旦您使用您的凭据登录,它将要求您允许访问您的数据,如下截图所示:

设置应用程序

一旦您提供了所需的细节并按下继续按钮,它将给您最终屏幕和最终输出。

设置应用程序

出于安全原因,我已经模糊了我的朋友的个人资料图片和他们的名字,但是您将在您的 Facebook 账户中获得相同的布局。现在您在考虑在您的应用程序中获取朋友列表,对吧?所以,借助以下代码的帮助,我在我的自定义应用程序中获取了一个列表。

FriendList.js被导入到App.js文件中:

import React, { PropTypes } from 'react'; 
import FriendItem from './FriendItem'; 
import { MAX_OUTPUT } from '../utils/constants'; 
import jss from 'jss'; 
import camelCase from 'jss-camel-case';  

jss.use(camelCase()); 

在前面的代码片段中,我们还导入了ReactconstantsFriendItem来获取数据。在这里,我们只是导入了FriendItem,但它将有一个单独的文件来处理这个问题:

const { classes } = jss.createStyleSheet({ 
  nodata: { 
    fontSize: '1.5em', 
    display: 'flex', 
    justifyContent: 'center', 
    alignItems: 'center', 
    textAlign: 'center', 
    color: 'white', 
    minHeight: '100vh', 
  }, 
  wrapper: { 
    flex: '3' 
  }, 
  '@media (max-width: 1050px)': { 
    wrapper: { 
      flex: '1 1 100%' 
    }, 
    nodata: { 
      minHeight: 'auto' 
    } 
  } 
}).attach(); 

前面的代码定义了朋友列表内容的包装器样式。正如我之前所说,您也可以将它们放在一个单独的 CSS 文件中,并进行外部调用,以便您方便。

const emptyResult = (hasFriends, query) => { 
  return ( 
    <div className={classes.nodata}> 
      {hasFriends ? `No results for: "${query}"` : 'No friends to show'} 
    </div> 
  ); 
}; 

在前面的代码中,您可以看到一个条件来验证某人是否有朋友或没有朋友。如果某人在他们的 Facebook 账户中没有朋友列表,它将显示上述消息。

const renderFriends = ({ myFriends, query }) => { 
  const result = myFriends.reduce((prev, curr, i) => { 
    if (curr.name.match(new RegExp(query, 'i'))) { 
      prev.push(<FriendItem key={i} rank={i + 1} {...curr} />); 
    } 

    return prev; 
    }, []); 
    return result.length > 0 ? result : emptyResult
    (!!myFriends.length, query); 
    }; 

    const FriendList = (props) => ( 
      <div className={classes.wrapper}> 
        {renderFriends(props)} 
      </div> 
    ); 

    FriendList.propTypes = { 
      myFriends: PropTypes.array.isRequired, 
      query: PropTypes.string 
    }; 

export default FriendList; 

如果您的账户有朋友,那么您将获得一个包括他们的个人资料图片、点赞、评论和帖子数量的完整朋友列表,因此您也可以通过 React 与 Facebook API 集成。

总结

我们已经探索了如何借助 React 集成 Facebook API,您也可以以类似的方式集成其他 API。

我们使用了常量、工具和扩展组件来实现集成并获得预期的输出。

本章中展示的关键示例将帮助您理解或澄清您对将其他 API 与 React 集成的概念。