【全栈React】第10天: 交互性

本文转载自:众成翻译
译者:iOSDevLog
链接:http://www.zcfy.cc/article/3823
原文:https://www.fullstackreact.com/30-days-of-react/day-10/

今天,我们将介绍如何添加交互性到我们的应用,使其具有吸引力和交互性。

通过这一点,我们构建了少数几个组件,而没有添加用户交互。 今天我们将要改变它。

用户交互

浏览器是事件驱动的应用程序。 用户在浏览器中进行的一切都会触发一个事件,从点击按钮,甚至只是移动鼠标。 在简单的JavaScript中,我们可以监听这些事件并附加一个JavaScript函数与它们进行交互。

例如,我们可以使用JS附加一个函数到mousemove浏览器事件:

export const go = () => {
  const ele = document.getElementById('mousemove');
  ele.innerHTML = 'Move your mouse to see the demo';
  ele.addEventListener('mousemove', function(evt) {
    const { screenX, screenY } = evt;
    ele.innerHTML = '<div>Mouse is at: X: ' +
          screenX + ', Y: ' + screenY +
                    '</div>';
  })
}

这导致以下行为:

将鼠标移到该文本上

然而,在React中,我们不必在原始JavaScript中与浏览器的事件循环进行交互,因为React为我们使用props处理事件提供了一种方法。

例如,要从React上面的(相当不起眼的)演示中收听mousemove 事件,我们将设置onMouseMove (请注意事件名称是驼峰命名的)。

<div onMouseMove={(evt) => console.log(evt)}>
  Move the mouse
</div>

React提供了很多props ,我们可以设置监听不同的浏览器事件,例如点击,触摸,拖动,滚动,选择事件等等(参见事件文档列出所有这些)。

要看看其中的一些在行动中,以下是一些小的演示,一些props ,我们可以传递我们的元素。 列表中的每个文本元素设置其列出的属性。 尝试使用列表查看事件在元素中的调用和处理方式。

我们将在我们的应用中使用 onClick 属性相当多,所以熟悉它是一个好主意。 在我们的活动列表标题中,我们有一个搜索图标,我们还没有与显示一个搜索框关联起来。

我们_想要_的交互是在用户点击搜索图标时显示搜索。 回想一下,我们的Header组件是这样实现的:

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }

  // toggle visibility when run on the state
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }

  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];

    // Update the class array if the state is visible
    if (this.state.searchVisible) {
      searchInputClasses.push("active");
    }

    return (
      <div className="header">
        <div className="menuIcon">
          <div className="dashTop"></div>
          <div className="dashBottom"></div>
          <div className="circle"></div>
        </div>

        <span className="title">
          {this.props.title}
        </span>

        <input
          type="text"
          className={searchInputClasses.join(' ')}
          placeholder="Search ..." />

        {/* Adding an onClick handler to call the showSearch button */}
        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

当用户点击 <div className="fa fa-search searchIcon"></div>元素时,我们需要运行一个函数来更新组件的状态,以便searchInputClasses对象更新。 使用onClick处理程序,这很简单。

我们让这个组件有状态(它需要跟踪搜索字段是否应该显示)。 我们可以使用constructor() 函数(构造函数)将我们的组件转换为状态:

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }
  // ...
}

什么是constructor函数(构造函数)?

在JavaScript中,constructor 函数是一个在创建对象时运行的函数。它返回对创建实例的prototype的Object函数的引用。

在纯英文中,构造函数是JavaScript运行时创建新对象时运行的函数。我们将使用构造函数方法在对象创建时正确运行对象时设置实例变量。

当使用ES6类语法创建对象时,我们必须在任何其他方法之前调用super() 方法。调用super() 函数调用父类的 constructor() 函数。我们将使用_相同参数_调用它,因为我们类的 constructor() 函数被调用。

当用户点击按钮时,我们将要更新状态来表示searchVisible 标志被更新。由于我们希望用户能够第二次点击搜索图标后关闭/隐藏 <input />字段,所以我们将_切换_该状态,而不是将其设置为true。

我们创建这个方法来绑定我们的点击事件:

class Header extends React.Component {
  // ...
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }
  // ...
}

最后,我们可以在icon元素上附加一个点击处理程序(使用onClick 属性)来调用我们新的 showSearch() 方法。 我们的 Header组件的整个更新的源代码如下所示:

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }

  // toggle visibility when run on the state
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }

  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];

    // Update the class array if the state is visible
    if (this.state.searchVisible) {
      searchInputClasses.push("active");
    }

    return (
      <div className="header">
        <div className="fa fa-more"></div>

        <span className="title">
          {this.props.title}
        </span>

        <input
          type="text"
          className={searchInputClasses.join(' ')}
          placeholder="Search ..." />

        {/* Adding an onClick handler to call the showSearch button */}
        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

尝试点击搜索图标并观看输入字段出现并消失(动画效果由CSS动画处理)。

输入事件

无论何时在React中构建表单,我们将使用React提供的输入事件。最值得注意的是,我们最常使用 onSubmit()onChange()属性。

我们更新我们的搜索框演示,以便在更新时捕获搜索字段内的文本。每当一个 <input /> 有一个 onChange()属性被设置时,它会在该字段_改变_的每个时间调用函数。当我们点击它并开始输入时,该函数将被调用。

使用这个属性,我们可以捕捉到我们这个字段的价值。

让我们创建一个新的子组件来包含一个 <form />元素而不是更新我们的 <Header />组件,。通过将表单处理职责移到自己的表单中,我们可以简化 <Header />代码,当我们的用户提交表单(这是一个通常的反应模式)时,我们可以调用头文件的父组件。

我们创建一个我们称之为SearchForm的新组件。这个新组件是一个有状态的组件,因为我们需要保持搜索输入的值(跟踪它的变化):

class SearchForm extends React.Component {
  // ...
  constructor(props) {
    super(props);

    this.state = {
      searchText: ''
    }
  }
  // ...
}

现在,我们已经在 <Header />组件中写入了表单的HTML,所以让我们从我们的 Header组件中获取它,并将它从我们的SearchForm.render()函数中返回:

class SearchForm extends React.Component {
  // ...
  render() {
    const { searchVisible } = this.state;
    let searchClasses = ['searchInput']
    if (searchVisible) {
      searchClasses.push('active')
    }

    return (
      <form className='header'>
        <input
          type="search"
          className={searchClasses.join(' ')}
          onChange={this.updateSearchInput.bind(this)}
          placeholder="Search ..." />

        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </form>
    );
  }
}

请注意,我们在我们的 <input />字段上丢失了样式。 由于我们不再在我们的新 <input />组件中具有searchVisible状态,所以我们不能再使用它来对其进行风格化了。 _无论如何_,我们可以从我们的Header组件传递一个支持,该组件告诉SearchForm将输入渲染为可见。

我们定义searchVisible 属性(当然使用React.PropTypes),并更新render函数以使用新的prop值来显示(或隐藏)搜索<input />。 我们还将为字段的可见性设置一个默认值为false(因为我们的Header显示/隐藏它很好):

class SearchForm extends React.Component {
  static propTypes = {
    onSubmit: React.PropTypes.func.isRequired,
    searchVisible: React.PropTypes.bool
  }
  // ...
}

现在我们在 <input />元素上有我们的样式,让我们添加用户在搜索框中键入的功能,我们将要捕获搜索字段的值。 我们可以通过将onChange参数附加到 <input />元素上来实现这个工作流,并在每次更改 <input />元素时传递一个函数来调用它。

class SearchForm extends React.Component {
  // ...
  updateSearchInput(e) {
    const val = e.target.value;
    this.setState({
      searchText: val
    });
  }
  // ...
  render() {
    const { searchVisible } = this.state;
    let searchClasses = ['searchInput']
    if (searchVisible) {
      searchClasses.push('active')
    }

    return (
      <form className='header'>
        <input
          type="search"
          className={searchClasses.join(' ')}
          onChange={this.updateSearchInput.bind(this)}
          placeholder="Search ..." />

        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </form>
    );
  }
}

当我们键入字段时,将会调用updateSearchInput() 函数。 我们将通过更新状态来跟踪表单的值。 在updateSearchInput() 函数中,我们可以直接调用this.setState() 来更新组件的状态。

该值在event对象的目标上保存为`event.target.value’。

class SearchForm extends React.Component {
  // ...
  updateSearchInput(e) {
    const val = e.target.value;
    this.setState({
      searchText: val
    });
  }
  // ...
}

控制与不受控制

我们正在创建所谓的不受控制的组件,因为我们没有设置 <input />元素的值。 我们现在不能对输入的文本值提供任何验证或后处理。

如果我们要验证字段或操作 <input />组件的值,我们将必须创建一个所谓的控件组件,这真的只是意味着我们使用value'传递一个值 属性。 受控组件版本的render()` 函数将如下所示:

class SearchForm extends React.Component {
  render() {
    return (
      <input
        type="search"
        value={this.state.searchText}
        className={searchInputClasses}
        onChange={this.updateSearchInput.bind(this)}
        placeholder="Search ..." />
    );
  }
} 

到目前为止,我们无法真正提交表单,所以我们的用户无法真正搜索。 我们来改变一下 我们需要将component包含在一个DOM元素中,这样我们的用户可以按回车键提交表单。 我们可以使用 <form />元素上的onSubmit支持来捕获表单提交。

我们来更新render()函数来反映这个变化。

class SearchForm extends React.Component {
  // ...
  submitForm(e) {
    e.preventDefault();

    const {searchText} = this.state;
    this.props.onSubmit(searchText);
  }
  // ...
  render() {
    const { searchVisible } = this.props;
    let searchClasses = ['searchInput']
    if (searchVisible) {
      searchClasses.push('active')
    }

    return (
      <form onSubmit={this.submitForm.bind(this)}>
        <input
          type="search"
          className={searchClasses.join(' ')}
          onChange={this.updateSearchInput.bind(this)}
          placeholder="Search ..." />
      </form>
    );
  }
}

我们立即在submitForm()函数上调用event.preventDefault()。这将阻止浏览器冒泡,从而使整个页面的默认行为重新加载(浏览器提交表单时的默认功能)。

现在当我们键入 <input />字段并按回车键,submitForm() 函数被调用的事件对象。

那么好的,我们可以提交表单和内容,但是什么时候我们实际上进行搜索?为了演示目的,我们将把搜索文本传递给父子组件链,以便 Header 可以决定搜索_什么_。

SearchForm 组件当然不知道它正在搜索什么,所以我们必须把责任传递给链。我们将会使用这种回调策略。

为了将搜索功能传递给链,我们的“SearchForm”将需要接受在提交表单时调用的函数。我们来定义一个我们称之为 SearchForm 的属性,我们可以传递给我们的SearchForm 组件。作为好的开发人员,我们还会为这个onSubmit函数添加默认的prop值和propType。因为我们想要确定onSubmit() 是被定义的,所以我们将把onSubmit的prop设置成一个必需的参数:

class SearchForm extends React.Component {
  static propTypes = {
    onSubmit: React.PropTypes.func.isRequired,
    searchVisible: React.PropTypes.bool
  }
  // ...
  static defaultProps = {
    onSubmit: () => {},
    searchVisible: false
  }
  // ...
}

当表单提交时,我们可以直接从props调用这个函数。 由于我们在跟踪我们状态下的搜索文本,所以我们可以在该状态下使用searchText值调用该函数,因此onSubmit() 函数只能获取值并且不需要处理事件。

class SearchForm extends React.Component {
  // ...
  submitForm(event) {
    // prevent the form from reloading the entire page
    event.preventDefault();
    // call the callback with the search value
    this.props.onSubmit(this.state.searchText);
  }
}

现在,当用户按下enter时,我们可以通过我们的Header 组件来调用props 中传递的onSubmit() 函数。

我们可以在我们的 Header 组件中使用这个 SearchForm 组件,并传递我们定义的两个属性(searchVisibleonSubmit):

import React from 'react';
import SearchForm from './SearchFormWithSubmit'

class Header extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchVisible: false
    }
  }

  // toggle visibility when run on the state
  showSearch() {
    this.setState({
      searchVisible: !this.state.searchVisible
    })
  }

  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];

    // Update the class array if the state is visible
    if (this.state.searchVisible) {
      searchInputClasses.push("active");
    }

    return (
      <div className="header">
        <div className="menuIcon">
          <div className="dashTop"></div>
          <div className="dashBottom"></div>
          <div className="circle"></div>
        </div>

        <span className="title">
          {this.props.title}
        </span>

        <SearchForm
          searchVisible={this.state.searchVisible}
          onSubmit={this.props.onSubmit} />

        {/* Adding an onClick handler to call the showSearch button */}
        <div
          onClick={this.showSearch.bind(this)}
          className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

export default Header

现在我们有一个搜索表单组件,可以在我们的应用中使用和重用。 当然,我们还没有搜索任何东西。 我们来解决这个问题,实现搜索。

[](#implementing-search)实现搜索

要在我们的组件中实现搜索,我们希望将搜索责任从我们的 Header 组件传递到容器组件,我们称之为 Panel

首先,让我们实现一个从 Panel 容器到Header 组件的子组件中将回调传递给父组件的模式。

Header 组件上,我们来更新一个属性的propTypes ,我们将它定义为onSearch属性:

class Header extends React.Component {
  // ...
}
Header.propTypes = {
  onSearch: React.PropTypes.func
}

Header 组件的’submitForm()’函数里面,调用这个onSearch() 属性,我们将传入它:

class Header extends React.Component {
  // ...
  submitForm(val) {
    this.props.onSearch(val);
  }
  // ...
}
Header.propTypes = {
  onSearch: React.PropTypes.func
}

请注意,我们的虚拟树如下所示:

<Panel>
  <Header>
    <SearchForm></SearchForm>
  </Header>
</Panel> 

<SearchForm />更新时,它会传递它的意识,搜索输入的变化到它的父组件<Header />,当它将向上传递到<Panel />组件。 这种方法在React应用中是_very common_,并为我们的组件提供了一套很好的功能隔离。

回到我们在第7天构建的Panel 组件中,我们将把一个函数作为HeaderonSearch() 属性传递给Header。 我们在这里说的是,当提交搜索表单时,我们希望搜索表单回调到头部组件,然后调用 Panel 组件来处理搜索。

由于Header 组件不能控制内容列表,所以Panel组件可以像我们在这里定义一样,我们_必须_将职责更多地传递给他们。

无论如何,我们的Panel 组件本质上是我们之前使用的Content组件的副本:

class Panel extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false, // <~ set loading to false
      activities: data,
      filtered: data,
    }
  }

  componentDidMount() {this.updateData();}
  componentWillReceiveProps(nextProps) {
    // Check to see if the requestRefresh prop has changed
    if (nextProps.requestRefresh !== this.props.requestRefresh) {
      this.setState({loading: true}, this.updateData);
    }
  }

  handleSearch = txt => {
    if (txt === '') {
      this.setState({
        filtered: this.state.activities
      })
    } else {
      const { activities } = this.state
      const filtered = activities.filter(a => a.actor && a.actor.login.match(txt))
      this.setState({
        filtered
      })
    }
  }

  // Call out to github and refresh directory
  updateData() {
    this.setState({
      loading: false,
      activities: data
    }, this.props.onComponentRefresh);
  }

  render() {
    const {loading, filtered} = this.state;

    return (
      <div>
        <Header
          onSubmit={this.handleSearch}
          title="Github activity" />
        <div className="content">
          <div className="line"></div>
          {/* Show loading message if loading */}
          {loading && <div>Loading</div>}
          {/* Timeline item */}
          {filtered.map((activity) => (
            <ActivityItem
              key={activity.id}
              activity={activity} />
          ))}

        </div>
      </div>
    )
  }
}

我们更新我们的状态以包括一个searchFilter字符串,这将只是搜索值:

class Panel extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      searchFilter: '',
      activities: []
    }
  }
}

为了实际处理搜索,我们需要将onSearch() 函数传递给我们的Header 组件。 我们在我们的Panel组件中定义一个onSearch() 函数,并将其传递给render() 函数中的Header 属性。

class Panel extends React.Component {
  // ...
  // after the content has refreshed, we want to 
  // reset the loading variable
  onComponentRefresh() {this.setState({loading: false});}

  handleSearch(val) {
    // handle search here
  }

  render() {
    const {loading} = this.state;

    return (
      <div>
        <Header
          onSearch={this.handleSearch.bind(this)} 
          title="Github activity" />
        <Content 
          requestRefresh={loading}
          onComponentRefresh={this.onComponentRefresh.bind(this)}
          fetchData={this.updateData.bind(this)} />
      </div>
    )
  }
}

我们在这里所做的就是添加一个handleSearch() 函数并将其传递给标题。 现在当用户键入搜索框时,我们的Panel组件上的handleSearch() 函数将被调用。

为了_实现_搜索,我们需要跟踪这个字符串,并更新我们的updateData() ‘函数来考虑搜索过滤。 首先让我们把searchFilter 设置为状态。 我们也可以强制内容通过将加载设置为true来重新加载数据,因此我们可以在一个步骤中执行此操作:

class Panel extends React.Component {
  // ...
  handleSearch(val) {
    this.setState({
      searchFilter: val,
      loading: true
    });
  }
  // ...
}

最后,我们更新我们的updateData()函数来考虑_搜索_帐户。

class SearchableContent extends React.Component {
  // ...
      this.setState({loading: true}, this.updateData);
  // ...
}

虽然这可能看起来很复杂,但它实际上几乎与我们现有的updateData() 函数完全相同,除了我们更新了我们的fetch()结果以在json集合上调用filter() 方法。

所有的collection.filter() 函数都是运行着每个元素传递的函数,并且过滤_掉_返回伪造值的值,保留返回真值的值。我们的搜索功能只是在Github活动的 actor.login (Github用户)上查找匹配,以查看它是否正确匹配searchFilter 值。

随着updateData() 功能的更新,我们的搜索完整了。

尝试搜索auser

现在我们有一个3层应用组件来处理嵌套子组件的搜索。我们通过这个post从初级阶段跳到了中级阶段。靠着自己。这是一些重大的材料。确保你明白这一点,因为我们会经常使用我们今天介绍的这些概念。

    原文作者:二月红
    原文地址: https://segmentfault.com/a/1190000010516481
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞