React一学就会(5): 函数组件的高级特性

经过四节课的洗礼,你应该已经对Ract的用法有了一个比较好的理解,这节课我们来全面的聊一聊函数组件的高级特性 Hooks , Hook是函数组件所特有的功能。
请在src目录下创建目录:Test05

Hooks的引用

React 中所有的Hooks都是以use开头的一种函数,但是它是符合 React 的设计规则,能实现特定的功能的。下面详细介绍Hooks的用法及如何自定义Hooks.

useState

这个 Hooks 是用来在函数组件中实现状态的管理。比如,下面的示例表示通过 useState 来管理和记录单击的次数。

import React, { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

解构出来的变量有两个: countsetCountuseState 根据指定的值初始化状态 count, 更改count 只能通过setCount来更改,我们给buttononClick事件传递了一个匿名的箭头函数,在这个函数里调用了 setCount(count + 1) 来改变状太变量 count, 从而实现了组件UI的更新。只要组件检测到任何的状态的变化,就会立即更新相应的UI
状态也可以同时声名多个,如下所示:

function ExampleWithManyStates() {
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}

数组的解构语法允许我们通过调用useState来给我们声明的状态变量提供不同的名称。

useEffect

useEffect官方说法是为函数组件提供执行副作用的功能。 这句话很抽象。相当于把类组件中的三个生命周期(componentDidMountcomponentDidUpdatecomponentWillUnmount)合为一处的作法。比如:

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

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // 其它的更新操作
    document.title = `你单击了 ${count}`;
  });

  return (
    <div>
      <p>你单击了 {count}</p>
      <button onClick={() => setCount(count + 1)}>
        单击这里
      </button>
    </div>
  );
}

上面的useEffect 会在组件 componentDidMountcomponentDidUpdate 时执行。当你调用 useEffect 时,相当于告诉 React 在刷新对 DOM 的更改后运行你的 “effect” 函数。 useEffect是在组件内部声明的,因此它们可以访问组件的 props 和状态。默认情况下,React 在每次渲染后(包括第一次渲染)都会运行useEffect。那么如何让它执行清理工作呢, 就像我们在类组件中的生命周期 componentWillUnmount中写的代码一样。 看下面的示例:

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

上面是一个显示好友的在线状态的一个组件。 假定, 有一个对象 ChatAPI, 它提供一个订阅好友在线状态的函数功能 subscribeToFriendStatus, 同时它也提供了一个去除订阅的一个功能函数:unsubscribeFromFriendStatus, 我们希望我们组件加载后就开始订阅好友的状态,当组件卸载时就要清除订阅功能。useEffect 是一个高阶函数, 它的参数是要传入一个函数, 这个函数要么返回 null, 要么返回一个函数。 在上面的示例中,我们传入了一个箭头函数,这个箭头函数返回了另一个函数。 说到这儿或放你已经猜了它的精妙之所在了,对,我们传入的这个函数的函数体内的代码会在组件的 加载更新 这两个生命周期内执行。而这个 返回的函数(比如上面的 retun () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); })就是在组件卸载时执行的过程。 所以,上面的示例实现的功能是 在 FriendStatus 组件被加载后 执行 ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange), 当这个组件 卸载后执行 ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange) 去除订阅功能。 这样你能理解了吧。你在useEffect参数中返回了一函数,那么在组件卸载时执行它。就这么简单。
和useState一样,你也可以在组件中使用多个useEffect, 这样你可以更好的分割功能块,让代码更清晰明了。如下所示。

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

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...

使用Hooks规则

HooksJavaScript 函数,但它们施加了两个额外的规则:

  1. 仅在顶层调用 Hooks。不要在循环、条件或嵌套函数中调用 Hook。
  2. 仅从 React 函数组件调用 Hooks。不要从常规 JavaScript 函数调用 Hooks。
  3. 还有一个有效的地方可以调用 Hooks —— 我们自己的自定义 Hooks。
自定义 Hooks

有时,我们希望在组件之间重用一些有状态逻辑。以上面的示例为例,假设我们还想在另一个组件中重用订阅逻辑,我们也许会将这个逻辑提到到一个名为:useFriendStatus。

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

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

它根据参数friendID,返回我们的好友是否在线的一个功能。现在我们可以从两个组件中使用它:

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

每个组件的状态是完全独立的。Hooks是一种重用状态逻辑的方法,而不是状态本身。事实上,对 Hook 的每次调用都有一个完全隔离的状态,因此我们甚至可以在一个组件多次使用相同的自定义 Hook

自定义Hook与其说是功能,不如说是一种约定。如果一个函数的名称以 “use” 开头,并且它调用了其他 Hook,我们说它是一个自定义的 Hook。这种强制命名约定是我们的React内部插件能够使用 Hooks 在代码中查找错误的方式。

useState的相关补充

看下面这个示例

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

我们将通过将此代码与等效类示例进行比较来开始学习

等效的类示例组件

我们前面的章节已经介绍过类组件的创建方法了。所以下面的示例你应该能理解

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}
Hooks 和 函数组件

再次强调,Hooks在类组件中不起作用。在函数的组件中使用 Hooks 合法位置:

const Example = (props) => {
  // 我们可认在这儿使用 Hooks!
  return <div />;
}
function Example(props) {
  // 我们可认在这儿使用 Hooks!
  return <div />;
}
什么是Hooks

我们的新示例首先从 React 导入 Hook:useState

import React, { useState } from 'react';

function Example() {
  // ...
}

Hook 是一个特殊的函数,可以让你“挂钩”到 React 功能。例如,useState是一个 Hook,可让您将 React 状态添加到函数组件。

我什么时候会使用 Hook?如果你编写了一个函数组件,并意识到你需要向它添加一些状态,那么以前你必须把它转换为一个类。现在,您可以在现有函数组件中使用 Hook。

state的声明

在类组件中,我们必须在构造函数中声明

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  ...
}

在函数组件中,我们没有this ,因此我们无法赋值或读取this.state 。取而代之的是useState,我们直接在组件中调用 Hook:

import React, { useState } from 'react';

function Example() {
  // 定义一个新的状态变量
  const [count, setCount] = useState(0);
  ...
}

1. 调用 useState 有什么作用?

它声明了一个“状态变量count”。我们的变量是被调用的,但我们可以称它为其他任何变量,比如 banana 。这是一种在函数调用之间“保留”某些值的方法,useState 是一种使用与类中提供的this.state 完全相同的功能的新方法。通常,当函数退出时,变量会“消失”,但 React 会保留状态变量。

2. 我们传递给 useState 作为参数的是什么?

Hook 的唯一参数是初始状态。与类不同,状态不必是对象。

3. useState 返回什么?

它返回一对值:当前状态更新它的函数。这就是我们写const [count, setCount] = useState(), 这与类中this.state.类似,只是你把它们countthis.setState成对地得到。

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);
  ...
}

我们声明一个名为 count 的状态变量,并将其设置为 0 。React 将在重新渲染之间记住它的当前值,并为我们的函数提供最新的值。如果我们想更新当前的count,我们可以调用 setCount

读取状态

当我们想显示一个类中的当前计数时,我们读到this.state.count:

  <p>You clicked {this.state.count} times</p>

在一个函数中,我们可以直接使用count:

  <p>You clicked {count} times</p>
更新状态

在一个类中,我们需要调用this.setState()来更新状态count:

  <button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Click me
  </button>

在函数中,我们已经有 setCountcount 作为变量,所以我们不需要this:

  <button onClick={() => setCount(count + 1)}>
    Click me
  </button>
回顾

现在让我们逐行回顾一下我们学到的东西,并检查我们的理解。

 1:  import React, { useState } from 'react';
 2:
 3:  function Example() {
 4:    const [count, setCount] = useState(0);
 5:
 6:    return (
 7:      <div>
 8:        <p>You clicked {count} times</p>
 9:        <button onClick={() => setCount(count + 1)}>
10:         Click me
11:        </button>
12:      </div>
13:    );
14:  }
  • 第 1 行:我们从 React 导入 Hook。useState 它允许我们在函数组件中保持本地状态。
  • 第 4 行:在组件Example内部,我们通过调用 useState 声明一个新的状态变量。它返回一对值,我们为其命名为countsetCount。我们之所以调用变量,是因为它包含按钮点击次数。我们通过作为唯一参数将其初始化为 0 。第二个返回项本身就是一个函数。它让我们更新count,所以我们将其命名为 setCount
  • 第 9 行:当用户单击时,我们使用一个新值进行调用setCount。然后,React 将重新渲染组件Example,并将新值count传递给它。

乍一看,这似乎有很多东西要接受。不要着急!如果您迷失在解释中,请再次查看上面的代码并尝试从上到下阅读它。我们保证,一旦你试图“忘记”状态在类中是如何工作的时候,并用新的眼光看待这段代码时,它就会有很有意义。

提示:方括号是什么意思?
当我们声明状态变量时,您可能已经注意到了方括号:

 const [count, setCount] = useState(0);

左边的名字不是 React API 的一部分。您可以命名自己的状态变量:

  const [fruit, setFruit] = useState('banana');

这种 JavaScript 语法称为“数组解构”。这意味着我们正在创建两个新变量,其中设置为 fruit返回的第一个值,并且setFruit是第二个值。它等效于以下代码

  var fruitStateVariable = useState('banana'); // 返回一对值
  var fruit = fruitStateVariable[0]; // 第一个值
  var setFruit = fruitStateVariable[1]; // 第二个值

useEffect的相关补充

Effect Hook 允许您在函数组件中执行副作用:

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

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

此代码段基于上一页中的计数器示例,但我们添加了一项新功能:我们将文档标题设置为包含单击次数的自定义消息。

在 React 组件中获取数据、设置订阅和手动更改 DOM 都是副作用的例子。无论你是否习惯于将这些操作称为“副作用” 。

React 组件中有两种常见的副作用:不需要清理的副作用和需要清理的副作用。让我们更详细地看一下这种区别。

有时,我们想在 React 更新 DOM 后运行一些额外的代码。网络请求、手动 DOM 突变和日志记录是不需要清理的常见效果示例。我们之所以这样说,是因为我们可以运行它们并立即忘记它们。让我们比较一下类和钩子如何让我们表达这种副作用。

没有清理的效果类示例

在 React 类组件中,render 方法本身不应该引起副作用。现在还为时过早——我们通常希望在 React 更新 DOM 之后执行我们的效果, 这就是为什么在React类中,我们将副作用放入componentDidmountcomponentDidupdate中。回到我们的示例,这是一个React Counter类组件,在React后立即更新文档标题对DOM进行更改:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  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.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

请注意,我们必须在类组件中的这两种生命周期方法之间复制代码

这是因为在许多情况下,我们都希望执行相同的副作用,而不管组件是否已经加载,还是已更新

现在,让我们看看如何使用useEffect做同样的事情。

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

function Example() {
  const [count, setCount] = useState(0);

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect有什么作用?

通过使用此Hook,您可以告诉React,渲染后您的组件需要做点什么。React会记住您传递的功能(我们将其称为“effect”),并在执行DOM更新后回调。在此effect中,我们设置了文档标题.

为什么在组件中调用useEffect?

将useEffect放在组件中,使我们可以从中访问count状态变量。我们不需要特殊的API来阅读它 - 它已经在功能范围中了。

每次渲染后useEffect会运行吗?

是的!默认情况下,它在第一个渲染和每个更新之后都运行。

详细说明
function Example() {
  const [count, setCount] = useState(0);

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

我们声明了状态变量count,然后告诉 React 我们需要useEffect。我们将一个函数传递给 Hook。我们传递的这个函数就是我们的效果。在我们的效果中,我们使用浏览器 API 设置文档标题。我们可以读取最新的效果,因为它在我们的功能范围内。当 React 渲染我们的组件时,它会记住我们使用的效果,然后在更新 DOM 后运行我们的效果。每次渲染都会发生这种情况,包括第一次渲染。

有经验的 JavaScript 开发人员可能会注意到,传递给的函数在每次渲染时都会有所不同。这是有意为之的。事实上,这就是让我们从useEffect内部读取值而不必担心它变得陈旧的原因。每次重新渲染时,我们都会安排不同的效果,替换前一个效果。在某种程度上,这使得效果的行为更像是渲染结果的一部分 —— 每个效果都“属于”一个特定的渲染。

清理副作用类示例
class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}
使用useEffect示例
import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

为什么我们从效应中返回一个函数?

这是useEffect的可选清理机制。每个effect都可能返回一个函数,该函数在它之后进行清理。这样,我们就可以使添加和删除订阅的逻辑彼此接近。它们是相同effect的一部分!

React 究竟什么时候清理效果?

React 在组件卸载时执行清理。但是,正如我们之前所了解的,效果针对每个渲染运行,而不仅仅是一次。这就是为什么 React 在下次运行效果之前也会清理上一次渲染中的效果。

其它注意事项请关注官方文档

  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码蚁先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值