【全棧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從初級階段跳到了中級階段。靠着本身。這是一些重大的材料。確保你明白這一點,由於咱們會常用咱們今天介紹的這些概念。

相關文章
相關標籤/搜索