React Hooks學習指南

原文: The Guide to Learning React Hooks
做者: Eric Bishardjavascript

####第一章:關於React Hooks 它發佈於2018年10月份的16.7.0-alpha.0測試版本中,當時Facebook已經在生產中使用了一個月,確保了社區不會面臨重大的漏洞和問題。因爲對於破壞向後兼容的大型重構每每會出現問題,因此React採用了漸進遷移策略( gradual migration and adoption strategy)容許新的API和模式與舊的API和模式共存。
Hooks是對核心庫的添加。這意味着它是可選以及向後兼容的。他們在GITHUB以前發表了對評論過程的請求。若是您想使用它們,只需安裝最新版本的React。
這種Hooks模式提供了一種對於類組件的替代寫法,能夠簡單的使用狀態以及生命週期方法。Hooks使得函數組件也可使用一些只有類組件才能使用的東西,好比咱們能夠經過useState, useEffect 以及 useContext訪問 React local state, effectscontext。 其它的Hooks還有useReducer, useCallback, useMemo, useRef, useImperativeHandle, useLayoutEffect 以及 useDebugValuecss

因此怎樣使用Hooks

最有效的展現方式就是舉一個對比的例子,一種使用類組件的方式寫,須要訪問狀態以及生命週期方法;另外一種使用函數組件的方式實現一樣的功能。
下面我提供了一個與ReactJS文檔中的示例相似的工做示例,可是您能夠修改和測試它,在咱們學習的每一個階段均可以使用StackBlitz演示來親自修改測試。因此讓咱們中止談論,開始學習React Hooks。前端

使用Hooks的優勢

Hooks對於開發者又很對優勢,它改變了咱們書寫組件的方式。能夠幫助咱們寫出更清晰、更簡潔的代碼——就像咱們進行了代碼節食,咱們減輕了不少體重,看起來更好,感受更好。它使咱們的下頜線突出,使咱們的腳趾感到更輕。就像下面這樣。 java

好了,不開玩笑了。Hooks確實減小了代碼體積,它減小並使咱們的代碼更加可讀、簡潔和清晰。爲了證實這一點,咱們來看一段類組件的版本的代碼,它和用Hooks重寫過的有什麼不一樣。
能夠看出,代碼量少了多少。使用Hooks不只減小了差很少5行代碼,並且提高了可讀性以及可測性。將現有的代碼改爲Hooks的方式確實能夠減小代碼量提升可讀性,但我仍是須要提醒你要慎重。記住Hooks是向後兼容的,而且能夠與舊版本共存,因此不須要當即重寫整個代碼庫。

Hooks的五條重要規則

在咱們建立本身的Hooks以前,讓咱們回顧一些咱們必須始終遵循的主要規則。react

  1. 不要從循環、條件或嵌套函數內部調用Hooks
  2. 只在最頂層使用 Hook
  3. 只在 React 函數中調用 Hook
  4. 不要在普通的 JavaScript 函數中調用 Hook
  5. 在自定義 Hook 中調用其餘 Hook 若是須要,可使用ES Lint插件在團隊中強制執行這些規則。一樣在同一頁上也有關於爲何須要這些規則的很好的解釋。

Hooks for State and Effects

這個GIF中展現的類組件和函數組件之間的不一樣,咱們會在後面詳細解釋。 git

使用useState展現類和函數計數組件的不一樣

下面咱們先看一下在React文檔中展現的計數組件例子。它是一個很簡單的組件,包括一個按鈕,只要點擊按鈕,它就將狀態向前推動一步,並更新state.count以進行渲染。
首先,咱們先看一下類組件,使用setState更新狀態。數據庫

import React from 'react';

class Counter extends React.Component {
  constructor() {
    this.state = { count: 0 };
    this.incrementCount = this.incrementCount.bind(this);
  }

  incrementCount() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={this.incrementCount}>Click Me</button>
      </div>
    );
  }
}

export default Counter;
複製代碼

首先要注意的是,咱們須要使用類語法,聲明一個constructor,在這裏面能夠引用this關鍵詞。在構造器中有一個state屬性,使用setState()方法更新狀態。
下面咱們看下函數組件使用Hooks怎麼來實現。npm

import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  const incrementCount = () => setCount(count + 1);

  return (
    <div> <p>You clicked {count} times</p> <button onClick={incrementCount}>Click Me</button> </div>
  )
}

export default Counter;
複製代碼

在這個函數組件中,咱們引進了一個useState屬性,並無其它的類語法或者構造器。它的賦值設置了默認值,不只提供count屬性,還提供了一個修改該狀態的函數setCount。這個setCount是一個函數方法,能夠隨便命名。
組件方法incrementCount更加易讀,能夠之間引用咱們的state值,而不是引用this.statejson

useEffect方法的對比

當更新狀態的時候,有時候會發生一些反作用。在咱們的計數組件中,咱們可能須要更新數據庫、修改本地存儲或者修改document的title。在React JS文檔中,後一個示例用於使事情儘量簡單。因此讓咱們並更新咱們的例子,使用新的鉤子useffect產生一個反作用。
讓咱們將這個反作用添加到咱們現有的例子中,而後再看一下使用類和使用鉤子的方法。首先看下使用類組件的實現。數組

import React from 'react';

class Counter extends React.Component {
  constructor() {
    this.state = { count: 0 };
    this.incrementCount = this.incrementCount.bind(this);
  }
  incrementCount() {
    this.setState({ count: this.state.count + 1 });
  }
  
  componentDidMount() { document.title = `You clicked ${this.state.count} times`; }
  componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={this.incrementCount}>Click Me</button>
      </div>
    );
  }
}

export default Counter;
複製代碼

而後,使用Hooks實現一樣的方法。

import React, { Component, useState, useEffect } from 'react';
function Counter() {
  const [count, setCount] = useState(0);
  const incrementCount = () => setCount(count + 1);

  useEffect(() => {
    document.title = `You clicked ${count} times`
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={incrementCount}>Click me</button>
    </div>
  )
}

export default Counter;
複製代碼

如今咱們引入了額外的行爲,咱們開始看到更多的證據代表,如何切換到鉤子提供了一種更乾淨的方式來處理狀態和反作用。在類組件中使用兩個方法才能實現的做用,在函數組件中,使用一個useEffect方法就能夠實現。只需去掉類語法和一個額外的方法就可使咱們的代碼更具可讀性。徹底值得。

根據你的須要能夠屢次調用useState 和 useEffect

就像使用setState,你也能夠屢次調用useState。讓咱們換一個示例,它顯示了一個稍微複雜的狀況,咱們在頁面上顯示了一個名稱,一些容許更更名稱的輸入,咱們但願同時控制名字和姓氏。咱們須要建立兩個屬性,每一個屬性都有各自的更新和設置方法。只需對每一個調用useState來設置默認值。
在下面的GIF中,您能夠看到它是什麼樣子的,以及它在基於類的版本中是什麼樣子的,咱們將在下面進一步探討。

正如您所指望的,咱們還爲每一個名稱提供了一個更新函數,以便您能夠獨立處理對它們的更改。 咱們看下基於類的組件。

import React, { Component } from 'react';

export default class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      firstName: 'Harry',
      lastName: 'Poppins'
    };
    this.handleFirstNameChange = this.handleFirstNameChange.bind(this);
    this.handleLastNameChange = this.handleLastNameChange.bind(this);
  }

  handleFirstNameChange = (e) => this.setState({ firstName: e.target.value });
  handleLastNameChange = (e) => this.setState({ lastName: e.target.value });

  render() {
    return (
      <div>
        <input value={this.state.firstName} onChange={this.handleFirstNameChange}/><br />
        <input value={this.state.lastName} onChange={this.handleLastNameChange}/>
        <p>
          <span>{this.state.firstName} {this.state.lastName}</span>
        </p>
      </div>
    );
  }
}
複製代碼

使用Hooks:

import React, { Component, useState } from 'react';
export default function Greeting() {
  
  const [firstName, setFirstName] = useState("Bat");
  const [lastName, setLastName] = useState("Man");;

  const handleFirstNameChange = (e) => setFirstName(e.target.value);
  const handleLastNameChange = (e) => setLastName(e.target.value);

  return (
    <div>
      <input value={firstName} onChange={handleFirstNameChange} /><br />
      <input value={lastName} onChange={handleLastNameChange} />
      <p>
        Hello, <span>{firstName} {lastName}</span>
      </p>
    </div>
  );
}
複製代碼

我不會再討論全部的差別,但我但願您看到一個稍微複雜一點的例子。但願您開始看到使用Hooks的好處。 讓咱們對這個示例再作一個更改,並使用useffect將咱們的名稱保存到本地存儲,這樣在刷新頁面時不會丟失狀態。
看下基於類的組件。

import React, { Component } from 'react';

export default class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      firstName: window.localStorage.getItem('classFirstName') || '',
      lastName: window.localStorage.getItem('classLastName') || ''
    };
    this.handleFirstNameChange = this.handleFirstNameChange.bind(this);
    this.handleLastNameChange = this.handleLastNameChange.bind(this);
  }
  handleFirstNameChange = (e) => this.setState({ firstName: e.target.value });
  handleLastNameChange = (e) => this.setState({ lastName: e.target.value });

  componentDidUpdate() {
    window.localStorage.setItem('classFirstName', this.state.firstName),
      [this.state.firstName];
    window.localStorage.setItem('classLastName', this.state.lastName),
      [this.state.lastName];
  }

  render() {
    return (
      <div>
        <input value={this.state.firstName} onChange={this.handleFirstNameChange} /><br />
        <input value={this.state.lastName} onChange={this.handleLastNameChange} />
        <p>
          <span>{this.state.firstName} {this.state.lastName}</span>
        </p>
      </div>
    );
  }
}
複製代碼

對比一下Hooks:

import React, { Component, useState, useEffect } from 'react';
export default function Greeting() {

  const [firstName, setFirstName] = useState(() =>
    window.localStorage.getItem('hooksFirstName') || ''
  );
  const [lastName, setLastName] = useState(() =>
    window.localStorage.getItem('hooksLastName') || ''
  );
  const handleFirstNameChange = (e) => setFirstName(e.target.value);
  const handleLastNameChange = (e) => setLastName(e.target.value);

  useEffect(() => {
    window.localStorage.setItem('hooksFirstName', firstName), [firstName];
    window.localStorage.setItem('hooksLastName', lastName), [lastName];
  });

  return (
    <div>
      <input value={firstName} onChange={handleFirstNameChange} /><br />
      <input value={lastName} onChange={handleLastNameChange} />
      <p>
        Hello, <span>{firstName} {lastName}</span>
      </p>
    </div>
  );
}
複製代碼

第三節:Hooks For Context

爲了更好的理解Hooks的另外一個基礎鉤子useContext,咱們須要對Context API有一個深刻的認識,它是 React 16.3發佈的一個特性。像學習大多數東西同樣,有時咱們在前進以前必須徹底理解另外一個概念。若是你熟悉Context API,那麼能夠跳過這一節。若是你第一次接觸Context API,咱們會簡要介紹一下並經過demo展現。首先要在你的應用程序中添加一個上下文,不然不能呢使用useContext
使用上下文環境的一個很好的例子是profile組件,咱們想一下這個組件都須要有哪些東西。當我登陸到xyz.com,有一些數據須要在全部或者部分子組件中使用。咱們假定須要兩個子組件:<user><team>。一個組件用來展現用戶信息和圖片,另外一個組件展現個人團隊。咱們有React、Angular和Vue團隊的成員,所以咱們將使用這些框架名稱做爲團隊名稱。
回到代碼,咱們須要經過組件value屬性,將須要的數據傳入到<provider>組件中。這樣,咱們容許任何組件和她的子組件調用這些數據。
讓咱們瞭解如何經過簡單地將props傳遞給children(在pre-Context API 階段的一個選項)來實現這個組件。在使用「prop透傳」這種方法的時候,就須要一層一層向每一個子組件傳遞數據。這就爲每一個組件建立了物理輸入,容許數據(狀態)從外部流向每一個組件及其子組件。

讓咱們看下pre-context 階段的例子。

import React from 'react';
import { render } from 'react-dom';
import './style.css';

const profileData = {
  company: 'Progress',
  companyImage: 'https://svgshare.com/i/9ir.svg',
  url: 'https://www.telerik.com/kendo-react-ui/',
  userImage: 'https://i.imgur.com/Y1XRKLf.png',
  userName: 'Kendoka',
  fullName: 'Kendō No Arikata',
  team: 'KendoReact'
}

const App = () => (
  <Profile data={profileData} />
)

const Profile = (props) => (
  <div className="profile">
    <img src={props.data.companyImage}/>
    <User data={props.data} />

  </div>
)

const User = (props) => {
  return (
    <div className="user">
      <a href={props.data.url}>
        <img src={props.data.userImage} width="138px" />
      </a>
      <h1 className="profile-userName">{props.data.userName}</h1>
      <p className="profile-fullName">({props.data.fullName})</p>
      <Team data={props.data} />

      
    </div>
  )
}

const Team = (props) => {
  return (
    <div className="team">
      <p className="profile-team">{props.data.team}</p>
    </div>
  )
}

render(<App />, document.getElementById('root'));
複製代碼

若是一個應用有10個組件,每一個組件都有本身的組件樹,這些組件樹又有一個組件樹。您願意手動將props傳遞給可能須要或不須要數據的組件嗎?或者您更願意從組件樹中的任何點使用該數據?Context容許在組件之間共享值,而沒必要顯式地在樹的每一個級別傳遞一個prop。咱們看下 Context API自己如何應用於基於類的組件:

來看下代碼

import React from 'react';
import { render } from 'react-dom';
import './style.css';

const ProfileContext = React.createContext();
class ProfileProvider extends React.Component {
  state = {
    company: 'Progress',
    companyImage: 'https://svgshare.com/i/9ir.svg',
    url: 'https://www.telerik.com/kendo-react-ui/',
    userImage: 'https://i.imgur.com/Y1XRKLf.png',
    userName: 'Kendoka',
    fullName: 'Kendō No Arikata',
    team: 'KendoReact'
  }
  render() {
    return (
      <ProfileContext.Provider value={this.state}>
        {this.props.children}
      </ProfileContext.Provider>
    )
  }
}

const App = () => (
  <ProfileProvider>
    <Profile />
  </ProfileProvider>
)

const Profile = () => (
  <div className="profile">
    <ProfileContext.Consumer>
      {context => <img src={context.companyImage} />}
    </ProfileContext.Consumer>
    <User />
  </div>
)

const User = () => (
  <div className="user">
    <ProfileContext.Consumer>
      {context =>
        <React.Fragment>
          <a href={context.url}>
            <img src={context.userImage} width="138px" />
          </a>
          <h1 className="profile-userName">{context.userName}</h1>
          <p className="profile-fullName">({context.fullName})</p>
        </React.Fragment>
      }
    </ProfileContext.Consumer>
    <Team />
  </div>
)

const Team = () => (
  <ProfileContext.Consumer>
    {context =>
      <div className="team">
        <p className="profile-team">{context.team}</p>
      </div>
    }
  </ProfileContext.Consumer>
)

render(<App />, document.getElementById('root'));
複製代碼
Context API入門

咱們如今須要瞭解如何使用useState獲取數據狀態,使用useEffect替換組件生命週期方法,如何使用useContext提升provider
在生命Context API的例子中咱們使用prop傳遞的方法分享數據。這是個有效的方法,可是在有些狀況下會顯得很笨重。更好的狀況下,能夠是使用Context API
從簡單的prop傳遞示例到更易於維護的Context API示例,咱們最終獲得了一些代碼,雖然這是解決咱們問題的更好方法,但卻進一步在咱們的組件中形成了混亂,使得組件的複用性變差。
咱們看下:

每一個須要獲取數據的地方都要使用 <ProfileContext.Consumer>包裹。這會在JSX中形成額外的混亂。最好能夠在函數組件的頂部建立一個const變量,以便在整個JSX中使用Profile上下文。咱們但願JSX儘量簡單,儘量接近咱們想要的HTML輸出,讓開發人員更容易閱讀。Hooks改善了這種狀況,是一種很是優雅的上下文消費方式。就像我說起的,它容許咱們刪除那些曾經把JSX弄得一團糟的標籤。
這樣看起來好多了。這是Hooks如何改變咱們編寫普通的平常組件的一個例子。
如今來看下使用Hooks重寫以後的Profile組件。不過多解釋代碼內容了-若是你還記得,當咱們調用 useState時,咱們有一組值須要去理解。一個是具體的狀態值,一個是這個值的更新方法。使用 useEffect,某個狀態發生更改時容許改方法。使用 useContext,咱們只是將它指向一個現有上下文,而該屬性如今擁有對該上下文的引用。整個使用方法很簡單。

import React, { Component, useContext } from 'react';
import { render } from 'react-dom';
import './style.css';

// Look Ma, No Provider
const ProfileContext = React.createContext({
  company: 'Progress',
  companyImage: 'https://svgshare.com/i/9ir.svg',
  url: 'https://www.telerik.com/kendo-react-ui/',
  userImage: 'https://i.imgur.com/Y1XRKLf.png',
  userName: 'Kendoka',
  fullName: 'Kendō No Arikata',
  team: 'KendoReact'
});

const Profile = () => {
  const context = useContext(ProfileContext);
  return (
    <div className="profile">
      <img src={context.companyImage}/>
      <User />
    </div>
  )
}

const User = () => {
  const context = useContext(ProfileContext);
  return (
    <div className="user">
      <a href={context.url}>
        <img src={context.userImage} width="138px" />
      </a>
      <h1 className="profile-userName">{context.userName}</h1>
      <p className="profile-fullName">({context.fullName})</p>
      <Team />
    </div>
  )
}

const Team = () => {
  const context = useContext(ProfileContext);
  return (
    <div className="team">
      <p className="profile-team">{context.team}</p>
    </div>
  )
}

const App = () => <Profile />;

render(<App />, document.getElementById('root'));
複製代碼
不須要定義Provider

上面的這個demo,咱們介紹使用useContext,由於咱們只讀取上下文中的數據,咱們直接將數據傳遞給createContext()方法。這種方法頗有效,再也不須要使用< Provider >包裹內容。儘管如此,但這不是我想要的方式-爲了可以改變team屬性,我確實須要建立一個Provider。但我想說明的是,若是您傳入一些數據,而沒有像咱們但願的那樣訪問任何函數,那麼您能夠在沒有提供程序的狀況下設置它。
但有些狀況下咱們須要訪問並修改上下文中的狀態,就須要使用Provider。好比,我但願可以改變咱們的用戶所屬的團隊。
爲了上面這種狀況,咱們又須要建立一個Provider,而不只僅傳遞默認的狀態數據。

使用Provider更新數據

咱們回到以前使用Context API 的例子中,使用setState更新數據。咱們應該可以經過調用一個函數來更新team屬性的值,這個函數將做爲鍵-值對放在咱們的狀態中。

import React from 'react';
import { render } from 'react-dom';
import './style.css';

const ProfileContext = React.createContext();
class ProfileProvider extends React.Component {
  state = {
    company: 'Progress',
    companyImage: 'https://svgshare.com/i/9ir.svg',
    url: 'https://www.telerik.com/kendo-react-ui/',
    userImage: 'https://i.imgur.com/Y1XRKLf.png',
    userName: 'Kendoka',
    fullName: 'Kendō No Arikata',
    team: 'KendoReact',
    changeTeam: (team) => this.setState({
      team: `Kendo${team}`
    })
  }
  render() {
    return (
      <ProfileContext.Provider value={this.state}>
        {this.props.children}
      </ProfileContext.Provider>
    )
  }
}

const App = () => (
  <ProfileProvider>
    <Profile />
  </ProfileProvider>
)

const Profile = () => (
  <div className="profile">
    <ProfileContext.Consumer>
      {context => <img src={context.companyImage} />}
    </ProfileContext.Consumer>
    <User />
  </div>
)

const User = () => (
  <div className="user">
    <ProfileContext.Consumer>
      {context =>
        <React.Fragment>
          <a href={context.url}>
            <img src={context.userImage} width="138px" />
          </a>
          <h1 className="profile-userName">{context.userName}</h1>
          <p className="profile-fullName">({context.fullName})</p>
          <Team />
          <button className="profile-button"
            onClick={() => context.changeTeam('Angular')}>Angular</button>
          <button className="profile-button"
            onClick={() => context.changeTeam('Vue')}>Vue</button>
          <button className="profile-button"
            onClick={() => context.changeTeam('React')}>React</button>
        </React.Fragment>
      }
    </ProfileContext.Consumer>
  </div>
)

const Team = () => (
  <ProfileContext.Consumer>
    {context =>
      <div className="team">
        <p className="profile-team">{context.team}</p>
      </div>
    }
  </ProfileContext.Consumer>
)

render(<App />, document.getElementById('root'));
複製代碼

讓咱們確保用戶只需按一個帶有您要切換到的團隊名稱的按鈕,就能夠在團隊中發起更改。咱們但願這個修改發生在< User>組件中,但按鈕顯示在Profile視圖的底部。

經過添加這些按鈕,咱們將須要每一個按鈕處理一次單擊,並將正確的團隊框架類型傳遞給函數,該函數將接受團隊名稱的參數:Vue, Angular 或者 React。
咱們須要一個修改狀態的方法。在狀態中添加一個新的屬性 changeTeam,它的值是名爲 setState的方法。咱們將經過context調用這個方法。
如今咱們能夠改變team的值,也能夠從上下文中讀取。這個模式可讓咱們設置和訂閱屬性值。
我提供了另外一個對於以前的 Context API例子的優化。這個例子仍然沒有使用Hooks,後面咱們將看下如何使用Hooks實現。

import React from 'react';
import { render } from 'react-dom';
import './style.css';

const ProfileContext = React.createContext();
class ProfileProvider extends React.Component {
  state = {
    company: 'Progress',
    companyImage: 'https://svgshare.com/i/9ir.svg',
    url: 'https://www.telerik.com/kendo-react-ui/',
    userImage: 'https://i.imgur.com/Y1XRKLf.png',
    userName: 'Kendoka',
    fullName: 'Kendō No Arikata',
    team: 'KendoReact',
    changeTeam: (team) => this.setState({
      team: `Kendo${team}`
    })
  }
  render() {
    return (
      <ProfileContext.Provider value={this.state}>
        {this.props.children}
      </ProfileContext.Provider>
    )
  }
}

const App = () => (
  <ProfileProvider>
    <Profile />
  </ProfileProvider>
)

const Profile = () => (
  <div className="profile">
    <ProfileContext.Consumer>
      {context => <img src={context.companyImage} />}
    </ProfileContext.Consumer>
    <User />
  </div>
)

const User = () => (
  <div className="user">
    <ProfileContext.Consumer>
      {context =>
        <React.Fragment>
          <a href={context.url}>
            <img src={context.userImage} width="138px" />
          </a>
          <h1 className="profile-userName">{context.userName}</h1>
          <p className="profile-fullName">({context.fullName})</p>
          <Team />
          <button className="profile-button"
            onClick={() => context.changeTeam('Angular')}>Angular</button>
          <button className="profile-button"
            onClick={() => context.changeTeam('Vue')}>Vue</button>
          <button className="profile-button"
            onClick={() => context.changeTeam('React')}>React</button>
        </React.Fragment>
      }
    </ProfileContext.Consumer>
  </div>
)

const Team = () => (
  <ProfileContext.Consumer>
    {context =>
      <div className="team">
        <p className="profile-team">{context.team}</p>
      </div>
    }
  </ProfileContext.Consumer>
)

render(<App />, document.getElementById('root'));
複製代碼

接下來咱們看另外一個例子,包括一個相似按鈕的設置,以及一個用於修改teams狀態的方法。實際上,在這個Hooks版本中,按鈕語法和state對象沒有區別。最大的優勢就是移除了<ProfileContext.Consumer>,咱們只需在每一個功能組件中建立一個const,它將保存對咱們上下文的引用:

const context = useContext(ProfileContext);
複製代碼

咱們只須要像之前那樣調用上下文及其方法。

import React, { Component, useContext } from 'react';
import { render } from 'react-dom';
import './style.css';

const ProfileContext = React.createContext();

class ProfileProvider extends Component {
  state = {
    company: 'Progress',
    companyImage: 'https://svgshare.com/i/9ir.svg',
    url: 'https://www.telerik.com/kendo-react-ui/',
    userImage: 'https://i.imgur.com/Y1XRKLf.png',
    userName: 'Kendoken',
    fullName: 'Kendoken No Michi',
    team: 'KendoReact',
    toggleTeam: (team) => this.setState({
      team: `Kendo${team}`
    })
  }
  render() {
    return (
      <ProfileContext.Provider value={this.state}>
        {this.props.children}
      </ProfileContext.Provider>
    )
  }
}

let Profile = () => {
  const context = useContext(ProfileContext);
  return (
    <div className="profile">
      <img src={context.companyImage} />
      <User />
    </div>
  )
}

let User = () => {
  const context = useContext(ProfileContext);
  return (
    <div className="user">
      <a href={context.url}>
        <img src={context.userImage} width="138px" />
      </a>
      <h1 className="profile-userName">{context.userName}</h1>
      <p className="profile-fullName">({context.fullName})</p>
      <Team />
      <button className="profile-button"
        onClick={() => context.toggleTeam('Angular')}>Angular</button>
      <button className="profile-button"
        onClick={() => context.toggleTeam('Vue')}>Vue</button>
      <button className="profile-button"
        onClick={() => context.toggleTeam('React')}>React</button>
    </div>
  )
}

let Team = () => {
  const context = useContext(ProfileContext);
  return (
    <div className="team">
      <p className="profile-team">{context.team}</p>
    </div>
  )
}

class App extends Component {
  render() {
    return (
      <ProfileProvider>
        <Profile />
      </ProfileProvider>
    );
  }
}

render(<App />, document.getElementById('root'));
複製代碼

我會再舉一個例子,把咱們當前的profile組件和「Change Team」按鈕重構成放進它們本身的獨立組件中,並將提供Context API的組件轉爲函數組件-使用useState替代this.state。注意,在最後一個例子中,咱們移除了ProfileContext.Consumer標籤,以及引入了useContext。如今咱們全部的組件都改爲函數組件了。

import React, { Component, useContext, useState } from 'react';
import { render } from 'react-dom';
import './style.css';

const ProfileContext = React.createContext();
const ProfileProvider = (props) => {
  const userInformation = {
    company: 'Progress',
    companyImage: 'https://svgshare.com/i/9ir.svg',
    url: 'https://www.telerik.com/kendo-react-ui/',
    userImage: 'https://i.imgur.com/Y1XRKLf.png',
    userName: 'Kendoken',
    fullName: 'Kendoken No Michi',
    team: 'KendoReact',
    toggleTeam: (property, value) => {
      setUserInfo(
        {...userInfo,[property]: value}
      );
    }
  }
  const [userInfo, setUserInfo] = useState(userInformation);
  return (
    <ProfileContext.Provider value={userInfo}>
      {props.children}
    </ProfileContext.Provider>
  )
}

const Profile = () => {
  const context = useContext(ProfileContext);
  return (
    <div className="profile">
      <img src={context.companyImage} />
      <User />
    </div>
  )
}

const User = () => {
  const context = useContext(ProfileContext);
  return (
    <div className="user">
      <a href={context.url}>
        <img src={context.userImage} width="138px" />
      </a>
      <h1 className="profile-userName">{context.userName}</h1>
      <p className="profile-fullName">({context.fullName})</p>
      <Team />
      <ChangeTeam />
    </div>
  )
}

const Team = () => {
  const context = useContext(ProfileContext);
  return (
    <div className="team">
      <p className="profile-team">{context.team}</p>
    </div>
  )
}

const ChangeTeam = () => {
  const context = useContext(ProfileContext);
  return (
    <>
      <button className="profile-button"
        onClick={() => context.toggleTeam('team', 'Kendo for Angular')}>Angular</button>
      <button className="profile-button"
        onClick={() => context.toggleTeam('team', 'KendoVue')}>Vue</button>
      <button className="profile-button"
        onClick={() => context.toggleTeam('team', 'KendoReact')}>React</button>
    </>
  )
}

const App = () => (
  <ProfileProvider>
    <Profile />
  </ProfileProvider>
)

render(<App />, document.getElementById('root'));
複製代碼

第四節:Hooks for Reducers

在前面的幾節咱們瞭解了幾個基礎的React Hooks。如今,讓咱們把咱們學到的知識應用到一個更高級的演示中並學會使用useReducer鉤子。在這以前須要保證你對useState有必定了解,若是沒有接觸過,能夠回到前面的部分看一下介紹。
Redux是除了setState以外使用reducer處理單向數據的最流行方法之一,React團隊鼓勵Redux管理state。然而,從16.9 版本發佈以後,React如今有了useReducer,它爲咱們提供了一種強大的方法來使用reducer,而不依賴Redux庫做爲依賴項來管理UI狀態。

Reducers入門

讓咱們討論Redux狀態reducer和JavaScript方法Array.prototype.reduce之間的區別。
求和函數是數組原型的典型例子。當咱們在只包含數字的數組中調用reducer時,咱們能夠返回一個數值,將數組中的全部值相加。reducer能夠輸入一個初始值做爲可選的第二個參數。讓咱們簡要地看一看一些代碼,這些代碼演示了JavaScript的Array.prototype中的reduce方法。

const votesByDistrict = [250, 515, 333, 410];
    const reducer = (accumulator, currentValue) => {
      return accumulator + currentValue;
    }

    console.log(votesByDistrict.reduce(reducer));
    // expected output: 1508

    // and below we simply add a value to start from:

    console.log(votesByDistrict.reduce(reducer, 777));
    // expected output: 2285
複製代碼

sum函數是最簡單的例子,可是在這個reduce中,您能夠在花括號之間迭代地執行任何工做。能夠把它想象成一個食譜。給定相同的輸入,老是產生相同的結果。正在討論的方法能夠成爲純函數。這個概念是很重要的,尤爲是用來處理狀態管理的時候。
讓咱們再看一個reduce示例,幫助咱們更好地理解它們。沒必要在每次迭代中都累積一個值,還能夠在每次迭代中進行比較。就像求和運算同樣,咱們每次都會存儲一個值,但咱們不會將這些值相加,而是存儲到目前爲止最高的值。每次迭代,咱們將比較數組中的下一個項與目前爲止的最高值,若是它較大,它將替換它,若是不是,繼續迭代,而不更新最高值。

const todos = [
    { name: 「dishes」, priority: 2 },
    { name: 「laundry」, priority: 3 ),
    { name: 「homework」, priority: 1 }
    ];

    let reducer = (highest, todo) => {
    return highest.priority > todo.priority
      ? Highest
          : todo;
    }

    todos.recuce(reduce, {})
    // output: { name: 「laundry」, priority: 3 }
複製代碼

這個例子演示了使用reducer的另外一種方法。在每次迭代中能夠作你任意想作的。這個概念很簡單,咱們獲取一個項目數組(在本例中是對象數組),執行一個操做並將其處理爲一個返回值。 React Hooks Reducer相似於JavaScript數組Reducer,返回一些東西的累積——在咱們的例子中,就是React state。基於全部之前和當前的操做以及過去發生的狀態修改,咱們的Reducer接收一個狀態和一個做爲參數的操做,狀態根據action.type被處理,而且咱們在運行與該特定action.type的大小寫相匹配的指令後返回新狀態。
就像咱們在現實生活中烹調一些東西,好比波爾多風格的波爾多醬同樣,咱們從許多配料開始:黃油、蔥、小牛肉、胡椒,固然還有葡萄酒。全部這些原料在平底鍋中混合,而後文火燉。若是重複並給出相同的步驟,使用相同的成分、相同的量、相同的爐子和相同的溫度,咱們每次都應該獲得相同的結果。

State 和 Actions概述

咱們將構建一個Todo應用程序。首先,咱們但願todo列表有一個初始todo項,該項簡單地說:「 Get Started」。

當咱們添加一個新的todo項時,首先要分派一個操做。
此操做由 Reducer函數處理。咱們的操做類型是 ADD_TODO。當 reducer函數注意到類型變爲 ADD_TODO時,它的做用是把舊的狀態取出來,把它展開,而後把新的todo項附加到最後,咱們就獲得了新的狀態。
另外一個須要處理的操做是 COMPLETE_TODO或者換個更好的名字 TOGGLE_COMPLETE。由於咱們真正想作的是像開關同樣打開和關閉那個狀態。
在這個例子中, reducer不會向列表中添加任何新項,它會修改現有todo項的一個屬性。若是咱們從一個todo開始,上面寫着「Get Started」,而後添加一個新的todo:「Take a break」,咱們的狀態如今應該有兩項:

{
      id: 1,
      name: 'Get started',
      complete: false
    },
    {
      id: 2,
      name: 'Take a break',
      complete: false
    }
複製代碼

注意,每一項都包括幾個屬性,其中一個就是id。這是一個擁有惟一值的鍵,咱們使用它來定位一個特定的todo,並在不影響其餘todo屬性值的狀況下更改該todo的一個屬性。TOGGLE_COMPLETE用來將complete屬性由false修改成true。 完成此操做後,任何更改都將向下傳播到使用該狀態的任何組件,從而觸發它們更新。
由於列表中的completed初始值都是false,若是咱們觸發TOGGLE_COMPLETED方法更新id爲1的項,狀態變爲下面這樣:

{
      id: 1,
      name: 'Get started',
      complete: true // We changed it!
    },
    {
      id: 2,
      name: 'Take a break',
      complete: false
    }

複製代碼

在使用Hooks以前,若是不引入第三方庫,很難處理reducer操做。如今,咱們能夠在任何React應用程序中輕鬆實現reducer模式,而沒必要包含其餘依賴項。
這使得處理內部狀態比之前更加容易。這不會取代Redux的全部用法,但它爲React開發人員提供了一種清晰、簡潔的Redux風格的方法,能夠在不安裝任何依賴項的狀況下當即管理內部狀態。

狀態管理

一般在Redux中,決定如何對狀態進行分類以及存儲在哪裏對於初學者是最大的問題之一。這是他們的Redux常見問題解答中的第一個問題,如下是他們所說的:並無正確的答案。有些用戶更喜歡將每一條數據都保存在Redux中,以便隨時維護其應用程序的徹底可序列化和受控版本。其餘人更喜歡在組件的內部狀態中保持非關鍵或UI狀態,例如「此下拉列表當前是否打開」。
Hooks在應用程序層很是強大,咱們能夠跟蹤諸如「是下拉式打開」和「是菜單關閉」之類的內容。咱們能夠以Redux風格的方式妥善管理UI數據,而沒必要脫離React核心。
在惟一的責任是不斷地改變和附加狀態的一臺機器中,reducer是每種操做的不一樣部分。它的邏輯將增長一個計數器或管理一個複雜的對象,這些對象的變化將對當前狀態產生影響。讓咱們從功能組件中訪問它和設置狀態是這個謎題的最後一部分,同時也是新謎題的第一部分。
讓咱們看看如何管理很是簡單的todo類型應用程序。這是一個很好的示範例子。如下是咱們的Todo應用程序的規則。
咱們將須要定義一些部分來設計一個使用useReducer的簡單的真實案例。咱們須要經過添加、完成和清除等操做來跟蹤狀態的修改和更新。使用熟悉的Redux模式,咱們一般會將這些進程中的每個與分配器處理的特定操做類型相關聯:

  • 一個容許咱們進入任務的表單域
  • 一個當咱們提交時處理表單的分配器
  • 一個包含全部內容的實際任務組件
  • 一個處理狀態變化的Reducer
    讓咱們從添加和組合全部這些片斷開始。我不會從建立一個React項目開始,有不少方式能夠實現。我會提供一些關鍵代碼,你能夠複製下來隨意處理。 在咱們的簡單示例中,咱們將建立Todo組件做爲應用程序的實際根級組件,該組件以下所示:
import React from 'react';
    import { render } from 'react-dom';
    import './style.css';

    const Todo = () => {
      return (
        <>
          Todo Goes Here
        </>
      );
    }

    render(<Todo />, document.getElementById('root'));

複製代碼

它包括一個表單和未提交的輸入字段。也添加了一些樣式表和json數據。咱們可使用它來植入一些todo項,以測試咱們的列表呈現方式以及數據的形狀是否符合HTML。
你能夠從這裏(stackblitz.com/edit/todos-…
如今咱們已經完成了這個項目,咱們將經過從React導入useReducer鉤子來進行第一個更改。更新代碼中的第一行。

import React, { useReducer } from 'react';
複製代碼

如今咱們須要向useReducer添加調用,它須要stateaction 做爲輸入。咱們將其分配給一個數組對象,這個數組對象是一個元組(兩個值)-這是在進行解構,由於useReducer()將其做爲返回值進行匹配: 在Todo組件的return語句上方添加如下行:

const [todos, dispatch] = useReducer(todoReducer, initialState);
複製代碼

items將是todo項的實際列表,dispatch將是用於更改該項列表的實際reducer。在return語句中,咱們爲items數組中的每一個項建立一組div。
咱們的應用程序還有一個問題,由於咱們尚未建立一個名爲todoReducer的函數。讓咱們將代碼添加到設置initialState賦值的行的正下方。

const todoReducer = (state, action) => {
      switch (action.type) {
        case 'ADD_TODO': {
          return (action.name.length)
            ? [...state, {
              id: state.length ? Math.max(...state.map(todo => todo.id)) + 1 : 0,
              name: action.name,
              complete: false
            }]
            : state;
        }
        default: {
          return state;
        };
      }
    }

複製代碼

一開始這彷佛很複雜。它所作的就是創建一個函數來執行狀態和動做。經過switch還判斷action.type。一開始,咱們只有一個操做,但咱們也但願設置默認的catch all,此默認值將返回當前狀態。
可是若是它捕捉到一個真正的ADD_TODO,咱們將返回當前狀態,展開,並將有效數據附加到末尾。棘手的部分是分配新的ID。咱們在這裏所作的是獲取todo的現有列表,並返回最大id加1,不然爲零。
既然我已經設置了一個初始狀態,咱們很高興進入下一步。咱們須要確保當咱們按enter鍵時在輸入字段中鍵入時,咱們輸入的值被髮送到一個函數,該函數將進行處理。 所以,首先讓咱們用類名todo input替換div,以下所示:

<div className="todo-input">
      <form onSubmit={addTodo}>
        <input ref={inputRef} type="search" id="add-todo" placeholder="Add Todo..." />
      </form>
    </div>

複製代碼

這確保當咱們點擊enter時,咱們將表單信息發送給一個名爲addTodo()的函數。咱們還使用ref屬性引用輸入,併爲該元素提供inputRef的引用值。隨着這些更新,咱們須要作更多的事情。
1)咱們須要建立一個名爲inputRef的屬性,它調用useRef鉤子2)咱們須要建立一個名爲addTodo()的函數 讓咱們從建立inputRef屬性開始。在todo組件的頂部,添加如下屬性:

const inputRef = useRef();
複製代碼

咱們將使用ref屬性獲取對輸入的引用,這將容許咱們稍後訪問其值。他的引用將由todo函數組件中的本地屬性支持,但這只是對useRef鉤子的調用。調用建立的inputRef屬性,使用inputRef.value獲取輸入的值。
你須要像咱們引入useReducer同樣導入另外一個鉤子。

import React, { useReducer, useRef } from 'react';
複製代碼

最後,咱們須要建立addTodo()函數,該函數將使用此引用並負責分配ADD_TODO類型的操做。在返回的正上方添加如下函數:

function addTodo(event) {
      event.preventDefault();
      dispatch({
        type: 'ADD_TODO',
        name: inputRef.current.value,
        complete: false
      });
        inputRef.current.value = '';
    }

複製代碼

在函數內部,爲了防止咱們點擊提交表單時頁面刷新。咱們調用了preventDefault ()方法。
而後,咱們使用inputRef從表單中獲取輸入值來觸發ADD_TODO操做。全部todo項最初的completed都是false。最後,咱們將inputRef值設置爲空。這將清除輸入字段。
最後,在ADD_TODO觸發以前,咱們還須要進行一次更新。在JSX內部,咱們仍然在initialState上進行映射。咱們須要從下面的行中更改:

{initialState.map((todo) => (
複製代碼

改成:

{todos.map((todo) => (
複製代碼

如今咱們應該有一個工做的useReducer鉤子,它利用addTodo函數將操做分派給todoReducer

添加完成的待辦事項

讓咱們在這個項目中也有一個useffect的工做示例。每次簽出待辦事項時,咱們都將更新document.title以顯示列表中已完成待辦事項的計數或數量。
addTodo()函數的正上方,讓咱們添加邏輯來計算咱們有多少已完成的todo。而後,當document.title更改時,咱們須要一個useffect方法來更新它:

const completedTodos = todos.filter(todo => todo.complete);
    useEffect(() => {
      // inputRef.current.focus();
      document.title = `You have ${completedTodos.length} items completed!`;
    })
複製代碼

要作到這一點,咱們還須要引入鉤子:

import React, { useReducer, useRef, useEffect } from 'react';
複製代碼

咱們尚未完成,咱們如今須要添加一個事件,該事件將調用函數,該函數將分派完成的任務。向div添加一個onClick處理程序,類名爲todo name

<div className="todo-name" onClick={() => toggleComplete(todo.id)}>
      {todo.name}
    </div>
複製代碼

接下來,咱們須要一個函數來處理這個點擊事件。它很簡單,只發送一個簡單的id和操做類型。將此添加到addTodo()函數的正下方:

function toggleComplete(id) {
      dispatch({ type: 'TOGGLE_COMPLETE', id });
    }
複製代碼

最後,咱們添加下面代碼到todoReducer中:

case 'TOGGLE_COMPLETE': {
      return state.map((item) =>
        item.id === action.id
          ? { ...item, complete: !item.complete }
          : item
      )
    }

複製代碼

我還設置了一個樣式,咱們將根據todo的完整值是否爲true來添加或刪除該樣式。在todos.map代碼下面,讓咱們更改以下所示的代碼行:

<div key={todo.id} alt={todo.id} className="column-item">

複製代碼

改成:

<div className={`column-item ${todo.complete ? 'completed' : null}`}
  key={todo.id}>
複製代碼

咱們再也不須要alt屬性,因此咱們刪除了它。如今,當咱們單擊todo時,它將分派一個操做,並將該特定todo的completed值設置爲true,如今,咱們的過濾器將經過useffect方法來獲取這個值,該方法反過來更新document.title。咱們還將添加類名completed,而且完成的todo將變得不透明,以表示完成的todo。
在這個時候,除了delete功能,以及清除列表中全部todo的按鈕以外,咱們幾乎全部的東西都在起做用。爲了完成咱們的演示,咱們將重複咱們已經學到的使最後兩個功能工做的內容。

刪除一個Todo項

首先,爲todos HTML中的close圖標添加onClick()事件:

<div className="todo-delete" onClick={() => deleteTodo(todo.id)}>
      &times;
    </div>
複製代碼

咱們將添加操做函數來處理這些操做,它沒必要是它們本身的函數,咱們能夠直接從onClick()傳遞,或者咱們能夠設置一個相似的switch語句來處理全部分配。咱們能夠採起任何咱們想要的方法。爲了演示的目的,我想逐個添加它們。
如今咱們建立一個函數來處理dispatch:

function deleteTodo(id) {
      dispatch({ type: 'DELETE_TODO', id });
    }

複製代碼

如今咱們只需在reducer的switch語句中添加一個case來處理reduce。在這裏,咱們使用數組的.filter()方法從列表中刪除一個知足id的todo項並返回狀態。

case 'DELETE_TODO': {
      return state.filter((x) => x.id !== action.id);
    }

複製代碼
清除全部Todos

對於清除todo操做沒什麼特別的,咱們只須要返回一個空數組。下面是實現這一點所需的三段不一樣的代碼。 將onClick()添加到HTML按鈕:

onClick={() => clearTodos()}

複製代碼

添加一個方法處理dispatch:

function clearTodos() {
      dispatch({ type: 'CLEAR_TODOS' });
    }
複製代碼

在咱們的reducer方法裏添加一個case:

case 'CLEAR_TODOS': {
      return [];
    }

複製代碼
Reducers總結

咱們如今已經使用useReducer構建了Todo應用程序的基礎。當處理數據子級別稍微複雜一點的狀態時,此模式將很是有用。咱們瞭解了純函數以及爲何它們是reducer的核心,容許咱們返回可預測的狀態,如今使用這種模式在覈心React庫中更容易實現。

第五節:自定義React Hooks

讓咱們學習如何建立一個定製的React鉤子,以及使用鉤子時必須記住的全部規則。
Hooks只是功能!任何函數均可以成爲Hooks。我以爲ReactJS文檔站點上的文檔不夠簡單。這不是敲打他們,我只是以爲,若是我能嘗試用更簡單的方式來解釋,更多的人會受益。

重溫 Effect Hook

若是您對基本Hooks有足夠的瞭解,能夠直接跳到建立自定義Hooks。沒必要再回顧全部的基本Hooks,我想咱們只須要從新訪問其中一個:useffect鉤子。我在閱讀ReactJS.org文檔的Hooks時瞭解到,有兩種方法可使用useffect。無需清除的 effect和須要清除的effect。我但願在這個階段使用Hooks的任何人要麼知道這些術語,要麼花幾分鐘來讀一下官方文檔。
在類和Hooks可用以前,effect被放在許多生命週期方法中,好比:componentDidMount 或者 componentDidUpdate。若是在這兩種方法中都有重複的代碼(執行相同的處理和更新效果),如今咱們能夠在功能組件中執行這些操做,只需一個鉤子就能夠完成。
useffect告訴React咱們的組件須要在組件呈現以後作一些事情。它在第一次渲染以後和每次更新以後運行。在我以前的文章中,我只討論了沒有清理的反作用,因此我想很快地介紹如何容許功能組件在清理時產生反作用。
下面是一個示例,說明如何在不進行任何清理的狀況下運行useffect:

useEffect(() => {
      document.title = `You clicked ${count} times`;
    });
複製代碼

若是確實須要清理才能運行,能夠從useffect返回函數。這是可選的,它容許您在效果以後和任何新效果運行以前運行一些代碼。訂閱某些內容的狀況可能須要取消訂閱,做爲效果清理過程的一部分。React將在卸載時執行此清理。

useEffect(() => {
      console.log("Subscribe to Something); return function cleanup() { console.log("Unsubscribe to Something);
      };
    });

複製代碼

以上效果將在每一個渲染上運行一次以上。React在運行下一個渲染的效果以前清除上一個渲染的效果,這應該注意。有關爲何在每次更新時都運行hook的解釋,請查看ReactJS文檔。不過,請記住,若是此行爲致使性能問題,則能夠選擇退出。 咱們還能夠經過使用可選參數跳過效果來優化性能。例如,可能咱們不想運行subscribe/unsubscribe效果,除非某些id已更改。看看下面的例子,瞭解如何作到這一點,這是至關簡單的!

useEffect(() => {
      console.log("Subscribe to Something); return () => { console.log("Unsubscribe to Something);
      };
    }, [props.something.id]); // only if something.id changes

複製代碼

Hooks,特別是useffect,如今容許您根據代碼正在作什麼而不是它在什麼生命週期方法中拆分代碼。當咱們只有類和生命週期方法時,咱們有時不得不混合關注點。如今,使用多個useffect方法,React能夠按指定的順序應用每一個效果。這對於在應用程序中組織代碼是一個巨大的好處。

建立自定義Hook

我真的很喜歡亞當·拉基斯(Adam Rackis)最近在推特上發表的一篇文章:「Hooks的創做水平遠遠超過咱們所看到的任何東西。」關於Hooks,我想讓你瞭解的是,咱們在類中看到的全部偉大的變化,以及咱們如何有這麼多的組合選項,如今Hooks中都有了這些。這意味着,如今當涉及到React中功能組件的組成時,咱們的手是不受約束的。對於React開發人員來講,這是一個巨大的進步。
自定義鉤子就是JavaScript函數,其名稱以單use做爲前綴。自定義鉤子是一個普通函數,但咱們使用不一樣的標準。經過在開頭添加use這個詞,咱們知道這個函數遵循Hooks的規則。
有了對Hook的更好理解,讓咱們把咱們知道的做爲一段簡單的代碼,咱們的文檔標題更新,並建立一個簡單的自定義Hook。
彷佛咱們須要在幾個頁面上或在應用程序中的許多不一樣功能組件中執行某些操做。當信息更改時,咱們但願用某種類型的字符串更新文檔標題。另外,咱們不想在每一個功能組件中重複這個邏輯。咱們將從在同一頁面的本地將此代碼提取到鉤子開始,而後查看如何將同一鉤子導入到多個組件並共同定位。很簡單吧?
若是這是真的,那麼咱們的自定義鉤子也能夠調用React Core基本鉤子之一,好比useffect。讓咱們再檢查一次更新文檔標題的功能組件。

import React, { Component, useState, useEffect } from 'react';

    function Counter() {
      const [count, setCount] = useState(0);
      const incrementCount = () => setCount(count + 1);
      useEffect(() => {
        document.title = `You clicked ${count} times`
      });

      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={incrementCount}>Click me</button>
        </div>
      )
    }

    export default Counter;

複製代碼

所以,咱們但願在這裏建立一個自定義鉤子,將一段文本傳遞到鉤子中,鉤子會爲咱們更新文檔標題。首先讓咱們看看建立此自定義掛鉤所需的代碼:

const useDocumentTitle = (title) => {
      useEffect(() => {
        document.title = title;
      }, [title])
    }

複製代碼

在上面你能夠看到咱們須要這個鉤子做爲參數的是一個字符串,咱們稱之爲title。在鉤子中,咱們調用React Core的基本useffect鉤子,並設置標題。useffect的第二個參數將爲咱們執行該檢查,而且僅當標題的本地狀態與咱們傳入的不一樣時才更新標題。你的意思是,建立自定義鉤子和建立函數同樣簡單?是的,它的核心很是簡單,並且該函數能夠引用任何其餘鉤子。該死的…建立自定義鉤子比咱們想象的要容易!
讓咱們回顧一下咱們的總體功能組件如今的樣子。你會看到我把useffect的舊調用註釋掉了,上面是咱們如何使用自定義鉤子來代替它。

import React, { Component, useState, useEffect } from 'react';

    const useDocumentTitle = title => {
      useEffect(() => {
        document.title = title;
      }, [title])
    }

    function Counter() {
      const [count, setCount] = useState(0);
      const incrementCount = () => setCount(count + 1);
      useDocumentTitle(`You clicked ${count} times`);
      // useEffect(() => {
      //   document.title = `You clicked ${count} times`
      // });

      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={incrementCount}>Click me</button>
        </div>
      )
    }

    export default Counter;

複製代碼

讓咱們進一步清理一下,看看若是這個鉤子是由某個npm包提供的,而不是被複制粘貼在文件的頂部,咱們能夠如何使用它。

import React, { Component, useState } from 'react';
    import useDocumentTitle from '@rehooks/document-title';

    function Counter() {
      const [count, setCount] = useState(0);
      const incrementCount = () => setCount(count + 1);
      useDocumentTitle(`You clicked ${count} times`);

      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={incrementCount}>Click me</button>
        </div>
      )
    }

    export default Counter;

複製代碼

這真是太棒了,但我也但願您注意到,我沒必要在個人功能組件中導入useffect,由於咱們從「@rehooks/document title」導入的鉤子會處理這個問題。因此若是我不須要useffect,我能夠從組件導入中省略它。
我但願這說明了建立自定義React鉤子的基本原理,而且您甚至能夠經過這樣一個簡單的例子看到它的威力。

管理KendoReact組件的控制狀態

Hooks很是適合處理特定類型的應用程序狀態。例如控制狀態、本地組件狀態和會話狀態。在使用KendoReact UI(www.telerik.com/kendo-react…)組件時,我想利用Hooks,但我想從簡單開始。咱們將重構,再也不使用類,而是使用函數組件。咱們將查找演示使用this.statethis.setState的實例,由於當咱們將組件轉換爲函數時,咱們將再也不須要使用this關鍵字,咱們將不須要使用構造函數或調用setState
所以,讓咱們開始重構KendoReact演示,演示如何使用咱們的KendoReact對話框。 若是您看看下面演示的main.jsx(stackblitz.com/edit/kendor…)頁面,咱們能夠肯定在使用功能組件和React鉤子時會發生變化的幾個目標區域。用綠色突出顯示的代碼和行須要修改,用紅色突出顯示的行能夠徹底刪除。

  1. 在第6行有一個類定義,咱們須要把它轉換成函數組件
  2. 第7行有一個構造器,第8行調用了super()方法,第10行有一些綁定。在使用Hooks的函數組件中這些都是不須要的。
  3. 在第9行中,咱們建立一個狀態實例,並給它一個默認值true,這將是對useState鉤子的調用。
  4. 在第13行,咱們須要重命名toggleDialog函數並將其切換到ES6箭頭函數樣式語法,第14行到第16行只需調用useState()賦值提供的更新方法setVisible,它將引用的值將是可見的,而不是this.state.visible
  5. 在第19行中,咱們有一個render()調用,這在函數組件中是沒必要要的
  6. 在第2二、2三、26和27行咱們提到了這一點。而this.state須要引用visibletoggleVisible而不是toggleDialog,稍後我將解釋爲何要重命名該函數。
    首先要作的就是將組件轉爲函數組件,移除constructor構造器,刪除supr()引用以及toggleDialog()函數綁定。這裏可使用多種語法選項,我更喜歡ES6箭頭函數樣式:
const multiply = (x, y) => { return x * y };
複製代碼

在咱們的組件中,第6行如今看起來以下:

const DialogWrapper = () => {

複製代碼

讓咱們來設置一個鉤子來代替state對象。咱們將不建立名爲state的對象,而是設置對useState()的調用,並將其返回值解構爲一個變量,該變量將保存咱們的狀態和更新/設置方法來更新該狀態。咱們的狀態名稱將是可見的,其更新方法將被稱爲setVisible。咱們將刪除整個構造函數並將其替換爲這一行:

const [visible, setVisible] = useState(true);
複製代碼

由於咱們使用的是useState()基本鉤子,因此還須要導入它。咱們的React導入如今看起來像:

import React, { useState } from 'react';

複製代碼

接下來,咱們須要一個在這個組件中調用setVisible的函數來切換它的值。咱們將其命名爲toggleVisible,而不是toggleDialog,由於咱們在一個功能組件中,因此以前使用的語法將不起做用。相反,我將更新爲ES6箭頭函數樣式。此函數只需將可視狀態設置爲與當前狀態相反的狀態。

const DialogWrapper = () => {;
      const [visible, setVisible] = useState(true);
      const toggleVisible = () => setVisible(!visible);

複製代碼

如今咱們須要去掉render()塊及其兩個大括號。此外,咱們須要刪除對this.toggleDialogthis.state.visible的全部引用,並相應地將它們更改成toggleVisiblevisible。如今在return()中,咱們將進行如下更改:

return (
      <div>
      <Button className="k-button" onClick={toggleVisible}>Open Dialog</Button>
      {visible && <Dialog title={"Please confirm"} onClose={toggleVisible}>
        <p style={{ margin: "25px", textAlign: "center" }}>Are you sure you want to continue?</p>
        <DialogActionsBar>
        <Button className="k-button" onClick={toggleVisible}>No</Button>
        <Button className="k-button" onClick={toggleVisible}>Yes</Button>
        </DialogActionsBar>
      </Dialog>}
      </div>
    );
複製代碼

一樣,咱們剛剛更新了return()中的代碼,以不引用this關鍵字並使用新的函數名toggleVisible
咱們已經成功地將KendoReact演示轉換爲使用功能組件和基本useState掛鉤。讓咱們使用一個叫作githrisk的很棒的工具來看看咱們的總體變化是什麼樣子的:

總結

我但願本指南能幫助您更好地理解Hooks的基礎知識,並容許您在這些示例的基礎上建立新的和使人驚奇的東西。若是它對你有用,請分享和傳播。
我想讓你對Hooks的建立有一個很好的理解,我認爲這能夠經過回顧Sophie Alpert在React Conf 2018上的演講獲得最好的解釋。
在過去,一些React開發人員在什麼時候使用和什麼時候不使用類方面遇到了困惑。這個問題能夠追溯到幾年前,在一個案例中,丹阿布拉莫夫的一篇文章中寫道:如何使用React類在晚上睡覺。
儘管咱們有時可能在當前的React中使用它們,或者在未來處理遺留代碼時遇到它們,但這個問題如今正在處理中,咱們已經看到開發人員有很強的看法,而且大多使用功能組件。
當談到React團隊正在作些什麼,以便更容易地構建優秀的UI,並改進React中的開發人員體驗時,Sophie Alpert提出了一個很好的問題。
爲何React仍然很糟糕?
如下是React Conf 2018大會上著名演講的答案:

重用邏輯

在React hook以前,咱們使用了不少高階組件和渲染道具來實現這一點,這將須要您在使用這些模式時常常從新構建應用程序,並致使包裝地獄(末日金字塔風格的嵌套)。

巨大的部件

因爲在不一樣的生命週期方法中分割出不一樣的邏輯片斷,咱們的組件中常常出現混亂。

混淆類

這是我將留給你的許多引語中的第一個引語。
課程對人類來講很難,但不只僅是人類,課程對機器來講也很難——索菲·阿爾伯特
理解JavaScript中的類可能很棘手,並且在hook以前,還須要使用類組件來訪問狀態和生命週期方法。簡單地定義類組件須要至關多的樣板文件。鉤子有助於解決這些問題,出於這個緣由,我想留給你一些其餘值得注意的引用,咱們的無畏反應和社區領袖!
Hooks容許您始終使用函數,而不是在函數、類、HOC和渲染道具之間切換——Dan Abramov 若是你想讓世界變得更美好,看看React Hooks,而後作出改變。--邁克爾傑克遜
Hooks提供了一種處理React中問題的新方法——Dave Ceddia 有了React鉤子,咱們擁有了兩個世界中最好的:可使用狀態的乾淨功能組件——David Katz 鉤子是React,是React要去的地方,是React V2——邁克爾傑克遜 React鉤子從根本上簡化了我建立、編寫、讀取和原型組件的方式——Zach Johnson 這些就是人們所說的關於React Hooks的一些事情。

若是你但願瞭解更多前端知識,請關注個人公衆號「前端記事本」

相關文章
相關標籤/搜索