阅读 304

React Hooks(30分钟全掌握)

介绍React Hooks

Hooks是React 16.8新增加的特性,Hooks可以让你不写class组件就能使用state和其他React特性。

例子

    import React, {useState} from 'react';
    
    function Example() {
        const [count, setState] = useState(0);
        
        return (
            <div>
                <p>你点击了{count}次</p>
                <button onClick={()=>setCount(count + 1)}>
                </button>
            </div>
        )
    }
复制代码

useState是要学的第一个'Hooks',上面就是简单的使用案例。

接下来是讲为什么在React中加入Hooks并且怎么帮助你写出更好的应用。如果你想学Hooks请点击下一个章节Hooks概览

Hooks概览

Hooks是向后兼容。上面的例子👆中,按钮每点击一次数字加1。其中useState是一个Hook。
在函数组件中调用useState是为了在其中加入state(即函数内部状态)。在重新渲染的时候React将会保留这个state。 useState返回一对值:当前的状态 和 更新状态的函数(即dispatch函数)。你可以在事件处理函数中或其他地方调用dispatch函数更新状态.
useState类似于在类组件中this.setState,不同是Hook没有将新旧状态合并在一起。下一章将会对useStatethis.setState作对比。

useState唯一的参数就是初始化值。在上面的例子中,初始化值是0.注意:不同于this.setState,这里的state不用必须是一个Object。初始化值仅在第一次渲染的时候用到。

声明多个state变量

    const [name, setName] = useState('paprika');
    const [position, setPosition] = useState('frontend developer');
    const [plans, setPlans] = useState([{title: 'dance'}]);
复制代码

数组解构让我们可以给通过调用useState声明的state变量赋任意的名称。这些名字不是useState的API。而是React假设如果你多次调用useState,在每次渲染的时候你都将会以相同的顺序使用这些状态。之后会解释如何这样做以及什么时候这种假设有用。

什么是Hook?

Hooks是让你勾住React state和在函数组件中使用生命周期特性的函数。
Hooks在class组件中不会起作用-(让你不用class就可以用react的state和其他特性)
注意:官方不推荐全部重写已经存在的组件,但是可以在新的组件中尝试使用Hooks。(但是国外有人说:2018年class是代名词,2019年class已成为过去,他们的项目已经用Hooks扩展,不再用class了)

React提供了少量像useState这样的内置Hook。你也可以写自己的Hooks以便在不同组件之间实现状态重用。我们首先看内置Hooks。

Effect Hooks

之前你可能已经进行了数据获取,订阅和手动修改dom。我们称这些操作是:副作用(side effect)。因为这些操作影响其他组件并且在渲染的时候无法完成。

useState是一个Effect Hook并在功能组件中添加了执行副作用的功能。它和在class组件中componentDidMount componentDidUpdate componentWillUnmount的目的一样。但是合并成了一个单独的API,下下章将会做个对比。

例如:React更新dom之后,组件设置标题

    import React, {useState, useEffect} from 'react';
    
    function Example() {
        const [count, setCount] = useState(0);
        
        useEffect(() => {
            document.title = `你点击了${count}次`;
        })
        
        return (
            <div>
              <p>You clicked {count} times</p>
              <button onClick={() => setCount(count + 1)}>
                Click me
              </button>
            </div>
        );
    }
复制代码

当你调用 useEffect,React就会在刷新DOM更改之后调用你的effect函数。Effects定义在组件内部,所以有权访问props 和 state。默认React在每次渲染之后执行effect函数,包括第一次渲染。(后文比较useEffect和class的生命周期)

useEffect可以选择返回一个函数来清理已经不需要的内容。下面例子,组件用了一个effect去订阅在线朋友数,通过取消订阅来移除它。

    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';
    }
复制代码

这个例子,当组件卸载 以及 后续渲染重新运行effect之前,React将会取消订阅 ChatAPI。(跳过重新订阅的方法是提供id)

就像useState,你可以在一个组件用多个effect:

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让你通过相关部分组织副作用(例如:添加和删除订阅),而不是通过生命周期强行拆分。

Hooks规则

Hooks是JavaScript函数,但是加了两个规则:

  1. 仅在顶层调用Hooks。不要在循环内部、判断和嵌套函数中调用Hooks。
  2. 仅在React函数组件调用Hooks。在普通js函数中不要调用Hooks。(你自己的Hooks可以调用)

官方提供linter插件去自动执行这些规则。理解规则对于运行好Hooks至关重要。

构建你自己的Hooks

有时你可能想在不同组件之间重用状态逻辑。传统有两种解决方法: 高阶组件 和 组件注入(render props)。自定义组件可以做到这些并且不用添加额外的东西。

上面介绍了FriendStatus组件,其调用useStateuseEffectHooks去订阅朋友在线状态。假设我们想要在类外一个组件重用订阅逻辑。

首先,在提取逻辑到自己的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>
  );
}
复制代码

不同组件的state是完全独立。Hooks是重用逻辑的一种方法,而不是状态本身。事实上,每次调用Hooks都是完全独立的状态,因此,你可以在一个组件用两次Hooks。

自定义Hooks更多是一个约定而不是特性。如果一个函数以use开始命名 并且 调用了其他Hooks,我们就叫它自定义Hook。这个useSomething命名约定是linter插件用hooks查找bug的关键。

你可以用自定义Hooks覆盖广泛的用例,像表单处理、动画、声明订阅、定时器和其他更多的我们没想到的。期待更多的场景由社区提供。

其他Hooks

你可能发现其他不常用的内置Hooks很有用,例如useContext让你引入上下文而不嵌套。

function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}
复制代码

useReducer允许您使用reducer管理复杂组件的本地状态:

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer);
  // ...
复制代码
关注下面的标签,发现更多相似文章
评论