React教程: 第10天 交互性

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

这篇文章是30 Days of React系列的一部分。
在本系列中,我们将从最基本的开始,逐步了解开始React所需的所有知识。如果你曾经想学习反应,这是开始的地方!

30 Days of React PDF版本下载:下载超过300页的 PDF

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

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

User interaction(用户交互)


浏览器是事件驱动的应用程序。 用户在浏览器中进行的一切都会触发一个事件,从点击按钮,甚至只是移动鼠标。 在简单的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(请注意事件名称是驼峰命名的)。

class MouseMover extends React.Component {
  state = {
    x: 0,
    y: 0
  };

  handleMouseMove = e => {
    this.setState({
      x: e.clientX,
      y: e.clientY
    });
  };

  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.state.x || this.state.y
          ? "The mouse is at x: " + this.state.x + ", y: " + this.state.y
          : "Move the mouse over this box"}
      </div>
    );
  }
}

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

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

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

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

class Header extends React.Component {
  render() {
    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="searchInput"
          placeholder="Search ..." />

        <div className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

让我们稍微更新一下,这样就可以将动态className属性传递给<input/>元素

class Header extends React.Component {
  render() {
    // Classes to add to the <input /> element
    let searchInputClasses = ["searchInput"];
    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 ..." />

        <div 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
    })
  }
  // ...
}

让我们添加一个if语句来更新searchinputclass,如果this.state.searchVisibletrue的话

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

最后,我们可以在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="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>
    )
  }
}

尝试点击搜索图标并观看输入字段出现并消失(动画效果由CSS动画处理)。原文中点击下面的搜索图标就会出现输入框的,我这里实现不了,想看效果的可以去原文试试。原文链接在本文章的开头有。
在这里插入图片描述

Input events(输入事件)


无论何时在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.props;
    let searchClasses = ["searchInput"];
    if (searchVisible) {
      searchClasses.push("active");
    }

    return (
      <form>
        <input
          type="search"
          className={searchClasses.join(" ")}
          placeholder="Search ..."
        />
      </form>
    );
  }
}

现在我们已经将一些代码从Header组件移动到SearchForm,让我们更新它的render方法来合并SearchForm

class Header extends React.Component {
  // ...
  render() {
    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 />

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

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

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

class SearchForm extends React.Component {
  // ...
}

SearchForm.propTypes = {
  searchVisible: PropTypes.bool
}
  
SearchForm.defaultProps = {
  searchVisible: false
};

如果您忘记在页面中包含PropTypes包,只需在页面中添加以下script标记

<script src="https://unpkg.com/prop-types@15.6/prop-types.min.js"></script>

最后,让我们将searchVisible状态值作为prop从Header传递给SearchForm

class Header extends React.Component {
  render() {
    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} />

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

原文中点击下面的搜索图标就会出现输入框的,我这里实现不了,想看效果的可以去原文试试。原文链接在本文章的开头有。
在这里插入图片描述
现在我们在 <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>
        <input
          type="search"
          className={searchClasses.join(" ")}
          onChange={this.updateSearchInput.bind(this)}
          placeholder="Search ..."
        />
      </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
    });
  }
  // ...
}
Controlled vs. uncontrolled(控制与不受控制)

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

如果我们要验证字段或操作 <input />组件的值,我们将必须创建一个所谓的controlled组件,这真的只是意味着我们使用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(event) {
    event.preventDefault();
  }
  // ...
  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 {
  // ...
}

SearchForm.propTypes = {
  onSubmit: PropTypes.func.isRequired,
  searchVisible: PropTypes.bool
}

SearchForm.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() 函数。

让我们将onSubmit属性添加到Header组件中的SearchForm

class Header extends React.Component {
  // ...
  render() {
    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.onSearch}/>

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

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

Implementing search(实现搜索)


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

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

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

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

下面是我们的Panel组件:

class Content extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      activities: data,
    };
  }

  render() {
    const { activities } = this.state; // ES6 destructuring

    return (
      <div>
        <Header
          title="Github activity" />
        <div className="content">
          <div className="line" />
          {/* Timeline item */}
          {activities.map(activity => (
            <ActivityItem key={activity.id} activity={activity} />
          ))}
        </div>
      </div>
    );
  }
}

无论如何,我们的Panel组件本质上是我们之前在第7天构建的Content组件的副本。请确保在页面中包含ActivityItem组件。也别忘了包含Moment.js在您的文件中,因为ActivityItem使用它来格式化日期。在页面中添加以下script标记

<script src="https://unpkg.com/moment@2.24.0/min/moment.min.js"></script>

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

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

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

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

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

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

class Panel extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      activities: data,
    };
  }

  handleSearch(val) {
    // handle search here
  }

  render() {
    const { activities } = this.state; // ES6 destructuring
    return (
      <div>
        <Header
          title="Github activity"
          onSearch={this.handleSearch.bind(this)}
        />
        <div className="content">
          <div className="line" />
          {/* Timeline item */}
          {activities.map(activity => (
            <ActivityItem key={activity.id} activity={activity} />
          ))}
        </div>
      </div>
    );
  }
}

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

让我们更新handleSearch方法来实际执行搜索:

class Panel extends React.Component {
  // ...
  handleSearch(val) {
    // resets the data if the search value is empty
    if (val === "") {
      this.setState({
        activities: data
      });
    } else {
      const { activities } = this.state;
      const filtered = activities.filter(
        a => a.actor && a.actor.login.match(val)
      );
      this.setState({
        activities: filtered
      });
    }
  }
  // ...
}

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

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

尝试搜索auser

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

在下一节中,我们将跳出并查看构建纯组件。


上一章:样式
下一章:纯组件


本教程系列的完整源代码可以在 GitHub repo, 上找到,其中包含所有样式和代码示例。
如果在任何时候你感到困扰,有进一步的问题,请随时通过以下方式与我们联系:
在原文文章末尾评论这篇文章
发送电子邮件至 [email protected]
加入我们的 gitter room
发推文给我们 @fullstackreact
REACT.JS DOM 应用 移动 JAVASCRIPT
版权声明 本译文仅用于学习、研究和交流目的,欢迎非商业转载。转载请注明出处、译者和众成翻译的完整链接。
原文链接:全栈React: React 30天


如果有疑问可以直接留言评论,如果觉得对你有帮助,可以小小的赞赏一杯奶茶钱,谢谢!!

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值