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

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

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

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

用户交互

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

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

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处理事件提供了一种方法。post

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

<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从初级阶段跳到了中级阶段。靠着本身。这是一些重大的材料。确保你明白这一点,由于咱们会常用咱们今天介绍的这些概念。

相关文章
相关标签/搜索