React Hook

168 阅读7分钟

Hook简介

在不编写class的情况下使用state以及其它特性
  • 完全可选的.无需重写任何已有代码.
  • 100%向后兼容.
  • v6.8.0后可用

Hook为已知的React概念提供了更直接的API: props,state,context,refs以及生命周期.

在组件之间复用状态逻辑很难.

React没有提供将可复用性行为"附加"到组件的途径.(例如把组件链接到store).有一些解决此类问题的方案: 比如render props和高阶组件.但是这类方案需要重新组织你的组件结构. React需要为共享状态逻辑提供更好的原生途径.

使用Hook从组件中提取出状态逻辑,使得这些逻辑可以单独测试并复用.Hook可以在无需修改组件逻辑的情况下复用状态逻辑.

复杂组件变得难以理解

组件起初很简单,但是逐渐会被状态逻辑和副作用充斥.每个生命周期常常包含一些不相关的逻辑.

Hook将组件中相互关联的部分呢拆分成更小的函数(比如设置订阅或请求数据),而非强制按照生命周期划分.也可以使用reducer来管理组件的内部状态,使其更加可预测.

难以理解的class

1.必须理解this的工作原理. 2.不能忘记绑定事件处理器. 3.class不能很好的被压缩. 4.使热重载出现不稳定的情况.

为了解决这些问题,Hook可以在非class的情况下可以使用更多的React特性.React组件一直高更像是函数.而Hook则拥抱了函数.

Hook概览

引入Hook的原因:Hook解决了编写和维护成千上万的组件时遇到的各种各样看起来不相关的问题.

State Hook

import React, { useState } from 'react';

function Example() {
    const [count, setCount] = useState(0);
    
    ruturn (<div>
        <button onClick={() => setCount(count + 1)}></button>
    </div>)
}

useState,通过在函数组件里调用它来给组件添加一些内部state.React会在重复渲染时保留这个state.useState会返回一对值: 当前状态和一个额让你更新它的函数.它类似class组件的this.setStaet,但是它不会把新的stte和旧的state进行合并.

useState唯一的参数就是初始state.不同于this.state,这里的state不一定要是一个对象.这个初始state参数只有在第一次渲染时会被用到.

声明多个state变量
function ExampleWithManyStates() {
    const [age, setAge] = useState(42);
    const [fruit, setFruit] = useState('banana');
    const [todos, setTodos] = useState([{text: 'Learn Hooks'}])
}
什么是Hook?

在函数组件里"钩入"Rect state及生命周期等特性的函数.

Effect Hook

在组件中执行数据获取,订阅或者手动修改DOM,这些操作称为"副作用".

useEffect就是一个Effect Hook,给函数组件增加了操作副作用的能力.它跟class组件中的componentDidMount,componentDidUpdate和componentWillUnmount具有相同的用途,只不过被合并成一个API.

import React, { useState, useEffect } from 'react';
function Example() {
    const [count, setCount] = useState(0);
    // 相当于componentDidMount和componentDidUpdate;
    useEffect(()=>{
       document.title = `You clicked ${count}times`; 
    });
    
    return (<div>
        <button onClick={()=> setCount(count + 1)}></button>
    </div>)
}

当调用useEffect时,Rect会在完成对DOM的更改后运行你的"副作用”函数.由于副作用函数是在组件内声明的,所以它可以访问到组件的props和state.默认情况下,React会在每次渲染后调用副作用函数---包括第一次渲染的时候.

副作用函数还可以通过返回一个函数来指定如何清除副作用.

例如,在下面的组件中使用副作用函数来订阅好友的在线状态,并通过取消订阅来进行清除操作.

import Reat, { 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.unsubscribeToFriendStatus(props.friend.id, handleStatusChange);
        };
    });
    
    if (isOnline === null) {
        return 'Loading...'
    }
    return isOnline ? 'Online' : 'Offline';
}

1.可以在组件中多次使用useEffect. 2.通过使用Hook,可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里.

Hook使用规则
  • 只能在函数最外层调用Hook.不要在循环,条件判断或者子函数中调用.
  • 只能在React的函数组件中调用Hook.
  • 自定义的Hook中.
自定义Hook

在组件之间重用一些状态逻辑.目前为止.有两种主流解决方案:高阶组件和render props.自定义Hook可以在不增加组件的情况下达到同样的目的.

将逻辑抽取到useFriendStatus的自定义Hook里.

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;
}

Hook是一种复用状态逻辑的方式,它不复用state本身.事实上Hook的每次调用都有一个完全独立的state,因此可以在单个组件中多次调用同一个自定义的Hook.

自定义Hook更像是一种约定.如何函数以use开头并调用其它Hook.则为自定义Hook.

其它Hook

useContext可以在不使用组件嵌套就可以订阅React的Context.

function Example() {
    const locale = useContext(LocaleContext);
    const theme = useContext(ThemeContext);
}

useReducer()可以通过reducer来管理组件本地的复杂state.

function Todos() {
    const [todos, dispatch] = useReducer(todosReducer);
}

使用State Hook

Hook和函数组件

const Example = (props) => {
    return <div />
}  
Hook是什么?

Hook是一个特殊的函数,它可以让你"钩入"Reactd的特性.例如useState是允许你在React函数组件中添加state的Hook.

什么时候会用Hook?

在函数组件中添加一些state时.可以使用useState

声明State变量

在class中,我们通过在构造函数中设置this.state为{ count: 0 }来初始化count state为0:

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);}

一般来说,在函数退出后变量就会"消失",而state中的变量会被React保留.

useState需要那些参数? useState()方法里面唯一的参数就是初始化state.如果我们想在state中存储两个不同的变量,只需调用useState()两次即可.

useState方法的返回值是什么? 返回值为当前state以及更新state的函数.这就是我们所写const [count,setCount] = useState()的原因.

我们声明一个叫count的state变量,然后把它设为0,React会在重复渲染时记住它当前的值,并且提供最新的值给我们的函数.我们可以通过调用setCount来更新当前的count.

state只在组件首次渲染的时候被创建.在下一次重新渲染时,useState返回给我们当前的state.

读取State

在class中显示

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

在函数中

<p>You clicked { count } times</p>

更新State 在class中,调用this.setState()来更新count值:

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

在函数中,我们不需要this

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

当使用useState定义state变量的时候,它返回一个有两个值的数组.第一个值是当前的state,第二个值是更新state的函数.使用数组解构来访问.

使用多个state变量

将state变量声明为一对[somgthing,setSomething]也很方便,因为如果我们想使用多个state变量,它允许我们给不同的state变量取不同的名称.

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

使用Effect Hook

Hook规则

自定义Hook

Hook API