23

紫棋的一句想要我帮你唱hook吗?让hook红了一把!
做为一个开发,尤其是前端开发,在电视上听到这个词还是有点小兴奋的(虽然彼hook非此hook)。玩够这个梗,是不是也要了解一下自己的react hook?

clipboard.png

一、why hook

使用react有一段时间了,组件化编程也早已成为习惯。常用的两种编写组件的方式就是就是class组件函数组件
class组件:通过继承React.Component来构建组件,虽然提供了state状态和完备的生命周期函数,但是也有很多不方便的地方。

  • 很多事件需要在挂载期componentDidMount更新期componentDidUpdate重复书写。有些副作用还需要在卸载期componentWillUnmount卸载。代码重复度很高,而且难以理解,一旦忘记了就会引起不少bug。
 componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  • 通过class书写自然会碰到 this 的问题,要想使用this函数不能忘记绑定。代码十分冗余。

函数组件:通过函数直接书写的组件,看起来是简洁了许多,但是不能使用state,也没有生命周期函数、更不能使用react的一些其他特性。只能通过父组件传递进来,任人鱼肉,只能惨淡的沦为展示组件

那么,函数组件就只能沦为展示的花瓶吗?能结合class组件能使用react特性的优点和函数组件简洁优雅的特性吗?好在react hook来了,函数组件的春天来了。

React 16.8 新增Hook特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

二、 what's hook

是不是感觉hook挺神奇的?hook是什么?怎么使用?不用惊讶,hook就是一个钩子函数,钩着react的一些api供给函数组件使用。

先看一个官方的useState例子:

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量。
  const [count, setCount] = useState(0);

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

引入一个hook(useState函数),传入一个初始值0,然后函数返回了一个数组,通过数组的结构赋值,取得state, count = 0和修改count值的函数setCount。当button发生点击事件时,触发setCount函数,并且传入新的count值完成对state值的修改,并展示到页面上。代码转换成class组件如下:

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

看明白了吗?

Hook概念

Hook 是一些可以让你在函数组件里“钩入” React state生命周期等(context、refs)特性的函数。
React 内置了一些像 useState 这样的 Hook。你也可以创建你自己的 Hook 来复用不同组件之间的状态逻辑。

hook使用规则

一个组件中可以使用多个hook,每个hook会独立存在,内部状态不会共用。每次更新组件时,hook会按顺序从上到下执行。

function Form() {
  // 1. Use the name state variable
  const [name, setName] = useState('Mary');

  // 2. Use the age state variable
  const [age, setAge] = useState('12');


  // 3. Use the surname state variable
  const [surname, setSurname] = useState('Poppins');

  // ...
}
第一次执行结果:
设置状态name Mary;
设置状态age 12;
设置状态surname Poppins;

第二次执行结果:
设置状态name Mary;
设置状态age 12;
设置状态surname Poppins;

那么,react是如何知道哪个 state 对应 哪一个useState?很简单, React 靠的是 Hook 调用的顺序。

react hook 使用,要遵循下面两个规则:

  • 只在最顶层使用 Hook
    react依靠hook调用顺序来对应state,所以调用顺序不能变。

    function Form() {
      // 1. Use the name state variable
      const [name, setName] = useState('Mary');
    
      // 2. Use the age state variable
      if(!name){
          const [age, setAge] = useState('12');
       }
    
      // 3. Use the surname state variable
      const [surname, setSurname] = useState('Poppins');
    
      // ...
    }
    第一次执行结果:
    设置状态name Mary;
    设置状态age 12;
    设置状态surname Poppins;
    
    第二次执行结果:
    设置状态name Mary;
    设置状态age 12;//判断后会被忽略
    设置状态surname 12;//此时设置为12,与需求已不符
    

    也就是说只能在顶层使用hook。

  • 只在 React 函数中调用 Hook

    在class组件中是无法使用hook函数的,只能在函数组件中使用。
    在自定义hook中也可以使用。

内置Hook

上面使用的useState Hook是一种可以让你在函数组件内使用state的内置hook,除此之外react还提供了许多内置hook:
基础 Hook

useState
useEffect
useContext

额外的 Hook

useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue

下面看一下 useState、useEffect两个常用的hook,其他的使用频率较低,使用方法也都基本一致其他hook

三、state Hook

我们想在函数组件里加入state,点击+,完成数字累加功能:

import React , {useState} from 'react' 


function Item(){
    const [count,setCount] = useState({name:'tom',age:11})
    return <>
        {`${count.name}已经${count.age}岁了`}
        <div onClick={()=>{setCount({name:'janny',age:count.age+1})}}>+</div>
    </>
}

export default Item;

从上面代码可以看出来,useState hook提供了在函数组件里使用state的能力。十分简洁。
首先,通过react引入useState钩子函数;
接着,在函数组件内调用useState,并传入state的初始值,可以是个值,也可以是对象。useState函数的返回值个数组,这里通过数组的结构赋值取得count(变量名可以随意定义)和修改count的方法setCount(函数名也可以随意定义)。
取值state:可以通过变量count直接取值
修改state:通过给setCount函数传递值来修改

是不是很简单?以后终于可以在函数组件里使用state了,拥有控制自己主权的能力了!除了使用state,还有一种场景也是我们经常碰到的,那就是函数组件没有生命周期,一些副作用的操作没法完成!别着急,react又内置了极好的effect hook!

四、effect Hook

当我们点击+时,不仅想完成数字累加,而且想要在title里显示我们的修改,如果在class组件里,我们可以在componentDidMount生命周期里通过setState完成,但是函数组件里没法完成,即使现在有useState Hook也没有生命周期函数,没法完成!那就在来一个hook吧,引入effect Hook。

useEffect hook不仅提供了生命周期,而且useEffect Hook可以看做componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。也就是在这一个钩子函数里可以完成很多事!

1.无需清除的副作用

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

function Item(){
    const [count,setCount] = useState({name:'tom',age:11})
    useEffect(()=>{
       document.title = count.age
    })
    return <>
        {`${count.name}已经${count.age}岁了`}
        <div onClick={()=>{setCount({name:'tom',age:count.age+1})}}>+</div>
    </>
}

export default Item;

好好感受一下!useEffect就像是一个生命周期函数。这个功能在 class 组件中,需要在两个生命周期函数(componentDidMount、componentDidUpdate)中编写重复的代码。在函数useEffect内进行副作用操作就行了。因为每次挂载、更新后useEffect都会执行。

在 React 组件中有两种常见副作用操作需要清除的和不需要清除的。上面这种就是不需要清除的副作用。

2.需要清除的副作用

假如,我们觉得只用手指点击太累了,想弄个计时器帮我们执行。

let timer = null;

function Item(){
    const [count,setCount] = useState({name:'tom',age:11})
    useEffect(()=>{
        clearInterval(timer);//清除
        timer = setInterval(()=>{
            console.log(1)
            setCount({name:'tom',age:count.age+1})
        },1000)
       document.title = count.age
    })
    return <>
        {`${count.name}已经${count.age}岁了`}
        <div onClick={()=>{setCount({name:'tom',age:count.age+1})}}>+</div>
    </>
}

这样就很棒,换上自动挡就很舒服!但是当我们切换路由,移除这个组件后,出去看看风景回来!发现定时器没有停,自己玩的很嗨...哎呦喂!

clipboard.png

这就是需要清除的副作用,class组件里我们可以通过在componentWillUnmount生命周期函数里清除,所以这里我们也要清除这些需要清除的副作用!

function Item(){
    const [count,setCount] = useState({name:'tom',age:11})
    useEffect(()=>{
        clearInterval(timer)
        timer = setInterval(()=>{
            console.log(1)
            setCount({name:'tom',age:count.age+1})
        },1000)
       document.title = count.age;
       return function(){//add
        clearInterval(timer)
       }
    })
    return <>
        {`${count.name}已经${count.age}岁了`}
        <div onClick={()=>{setCount({name:'tom',age:count.age+1})}}>+</div>
    </>
}

useEffect清除副作用的方式也很简单,return 一个函数,在函数里清除就可以了。

3.优化 useEffect

上面说了useEffect hook在每次挂载、更新后都会执行。这不就是跟setState在性能方面造成的问题一样严重吗?class组件 通过在 componentDidUpdate 进行优化,useEffect怎么优化!

可以通过useEffect函数的第二个参数进行优化。

1.传递空数组,表示函数只执行一次!这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。effect 内部的 props 和 state 就会一直拥有其初始值。更接近大家熟悉的 componentDidMount 。

useEffect(()=>{
 ...
},[]) 

2.传递包含state的数组

会通过===比较count的前一次渲染的值这一次要渲染的值,如果全等则React 会跳过这个 effect,这就实现了性能的优化。一定要将所有需要比较的state都放进数组,否则将不会进行比较,也就会直接跳过

useEffect(()=>{
 ...
},[count]) 

五、自定义Hook

react内置了多hook,我们也可以对这些hook进行业务方面的封装,写出属于自己的hook!我们将上面的叠加计时封装成一个自定义hook:数字每秒叠加,并且修改显示在title上。

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

function useTitle(){//自定义hook
    let timer = null;
    const [count,setCount] = useState({name:'tom',age:11})
    useEffect(()=>{
        clearInterval(timer)
        timer = setInterval(()=>{
            setCount({name:'tom',age:count.age+1})
        },1000)
       document.title = count.age;
       return function(){
        clearInterval(timer)
       }
    },[count])
    
    return count;//返回一个对象
}


function Item(){//自定义hook使用
   const count = useTitle()
    return <>
        {`${count.name}已经${count.age}岁了`}
    </>
}

将一些公共代码提取出来,最后返回需要在页面显示的数据就成了

六、总结

到这里,react hook已经有所了解了吧!就是让函数组件能够像class组件一样自由自在的使用react的特性的函数。可以不影响之前任何业务,在项目中完全可选100%向后兼容的react新特性!快在项目中用起来吧!

如有不妥!欢迎指正

搁浅
693 声望323 粉丝

想进BAT的前端er