React躬行记(15)——React Hooks

  Hook(钩子)是React v16.8新引入的特性,能以钩子的形式为函数组件附加类组件的状态、生命周期等特性。React的类组件有难以拆分、测试,状态逻辑分散,难以复用等问题,虽然能够经过渲染属性(Render Props)和高阶组件来提取状态逻辑,但会造成层层嵌套,而使用Hook后的函数组件就能避免这些问题。html

  Hook本质上是一种特殊的JavaScript函数,名称以use为前缀,在使用它时须要遵循两条规则,以下所列:react

  (1)在循环、条件语句或嵌套函数中调用Hook是不容许的,必须在函数的最顶层调用,确保Hook的调用顺序。数组

  (2)只能在React的函数组件或自定义的Hook中调用Hook。浏览器

  这两条规则能够结合后文的分析慢慢体会,接下来会详细讲解几个内置的Hook,而且会介绍如何自定义Hook,文中的示例来源于官网缓存

1、State Hook(状态钩子)

  先来看一个简单的类组件,Btn组件会渲染出一个按钮,每次点击按钮,其文本会加一。服务器

import React from "react"; class Btn extends React.Component { constructor() { super(); this.state = { count: 0 }; this.dot = this.dot.bind(this); } dot() { this.setState({ count: this.state.count + 1 }) } render() { return <button onClick={this.dot}>{this.state.count}</button>;
 } }

  而后将Btn组件改为相同功能的函数形式,以下代码所示,没有了构造函数和render()方法,经过useState()为函数组件附加状态。异步

import { useState } from "react"; function Btn() { const [count, setCount] = useState(0); return (<button onClick={() => setCount(count + 1)}>{count}</button>);
}

  useState()是一个钩子函数,它的参数是状态的初始值,返回一个数组,包含两个元素:当前状态和更新状态的函数。经过数组解构的方式声明了一个名为count的状态变量和一个名为setCount的函数,至关于类组件中的this.state.count和this.setState()。在点击事件中读取状态或调用更新状态的函数都不须要this。函数

  注意,useState()能够被屡次调用,React会根据useState()的出现顺序保证状态的独立性,而且与this.setState()不一样的是,更新状态是替换而不是合并。性能

2、Effect Hook(反作用钩子)

  在React组件中有两种常见的反作用:无需清除和须要清除,接下来会逐个讲解。测试

1)无需清除

  在React更新DOM以后会运行一些无需清除的反作用,例如向服务器请求数据、变动DOM结构、记录日志等。在类组件中,这些反作用常在componentDidMount()和componentDidUpdate()生命周期方法中执行。以上一节的Btn组件为例,在更新计数后,修改页面标题,以下所示(只列出了核心代码)。

class Btn extends React.Component { componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } }

  注意,两个函数中的代码是重复的,由于不少状况下,在组件挂载和更新时会执行相同的操做,而React并未提供每次渲染以后可回调的函数。

  接下来用useEffect()钩子函数实现相同功能,一样只列出了核心代码,以下代码所示。useEffect()使得相同功能的反作用不用再分散到不一样的生命周期中,即按照用途分离反作用。

import { useEffect } from "react"; function Btn() { useEffect(() => { document.title = `You clicked ${count} times`; }); }

  useEffect()可接收两个参数,第一个参数是回调函数,叫作Effect,在每次渲染(包括第一次挂载和后续的DOM更新)以后Effect都会被执行,其中每次接收的Effect都是新的,不用担忧状态过时的问题;第二个参数是可选的数组(由Effect的依赖项组成),用于控制Effect的执行,而是否执行Effect将取决于数组中的元素是否发生了变化,例如将count变量做为数组的元素(以下代码所示),当count的值与从新渲染后的count的值同样时,React会忽略这个Effect,优化性能。

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

  当把一个空数组([])传给useEffect()时,Effect只会运行一次,即仅在组件挂载和卸载时运行。因为Effect不依赖state或props中的任意值,所以永远都不须要重复执行。

  useEffect()至关于componentDidMount()、componentDidUpdate()和componentWillUnmount()三个生命周期方法的组合,但与componentDidMount()或componentDidUpdate()不一样,使用useEffect()会异步执行反作用,可避免阻塞浏览器更新视图。

2)须要清除

  有些反作用是必须清除的,例如订阅的外部数据源,将其清除后,可防止内存泄露。在类组件中,一般会在componentDidMount()中设置订阅,并在componentWillUnmount()中执行清除。

  假设有一个ChatAPI模块,用于订阅好友的在线状态,以下所示(只有关键部分),其中componentDidMount()和componentWillUnmount()处理的是关联的反作用。

class FriendStatus extends React.Component { 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 }); } }

  接下来用函数组件实现相同的功能,一样只有关键部分的代码。因为添加和移除订阅的逻辑有很强的紧密性,所以useEffect()将它们组织在一块儿。当Effect返回一个函数时,React将在执行清除操做时调用它,以下所示。

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

  注意,React会在执行当前Effect以前对上一个Effect进行清除,也就是说,反作用并不只在组件卸载时被执行。

3、自定义Hook

  自定义的Hook用于保存组件中可复用的逻辑,它的参数和返回值都没有特殊要求,相似于一个普通的函数,但为了遵循Hook的规则,其名称必须以use开头。接下来将以前的FriendStatus组件中订阅好友在线状态的逻辑抽离到自定义的useFriendStatus()中,其参数为friendID,返回值为好友当前的状态,以下所示。

function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }

  在FriendStatus组件中调用自定义的Hook,其内部逻辑将变得很是简洁,以下所示。

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

4、其它Hook

  除了上面所讲解的两个内置Hook,React还提供了其它功能的Hook,例如useContext()、useCallback()、useMemo()、useLayoutEffect()等,具体可参考官方的API索引

1)useContext()

  接收一个由React.createContext()建立的Context对象,返回该Context的当前值(即要传送的数据)。调用了useContext()的组件会在Context值发生变化时从新渲染。

2)useCallback()

  包含两个参数,第一个是回调函数,第二个是依赖项数组,返回回调函数的记忆版本。当某个依赖项发生改变时,会更新回调函数。注意,依赖项数组不会做为参数传给回调函数。

3)useMemo()

  包含回调函数和依赖项数组两个参数,回调函数的返回值就是useMemo()的返回值,它会被缓存,而且仅在某个依赖项发生改变时才从新计算它。以前的useCallback(fn, deps)至关于useMemo(() => fn, deps)。

4)useLayoutEffect()

  函数签名与useEffect()相同,但调用时机不一样,它会在全部的DOM更新以后同步调用Effect,也就是在浏览器更新视图以前调用Effect。

相关文章
相关标签/搜索