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的一些事情。

若是你但愿了解更多前端知识,请关注个人公众号“前端记事本”

相关文章
相关标签/搜索