还不懂 Hook?那你是真的Low了

1,818 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

回想起 React 16.8 的发布,已经将近3年之久,那时还沉浸在 类组件有状态,函数式组件无状态中,我记得还有面试题说 函数式组件和类组件的区别是什么?现在想起有点可笑,哈哈,勾起一点短暂的回忆

介绍

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。也就是说函数式组件也能拥有状态,并且比class组件的使用更加简洁,请看看:React Hook

不得不说,Hook 拯救了 React,我甚至认为如果没有 Hook,React就根本没人用了,哈哈(这只是我的个人看法,勿喷(● ̄(エ) ̄●)~)

所以本文主要讲解下 useStateuseEffectuseContextuseReduceruseMemouseCallbackuseRefuseImperativeHandle 这8个API

这里主要讲解下 有关这个 Api 的使用方法与使用结过,毕竟我是个实现派~

在线示例 Domesy/Hook

src=http___www.5577.com_up_2017-8_15027640263941144.jpg&refer=http___www.5577.jpeg

useState

在 class 组件中,我们定义变量在 constructor 中设置 this.state 设置变量,而在 Hook 中我们使用 useState

作用:用来声明状态变量,相当于 class 中的 this.state 和 this.setState 的作用

image.png

代码示例:

import React, { useState } from 'react';
import { Button } from 'antd';

const Mock: React.FC<any> = () => {
  const [count, setCount ] = useState<number>(0)

  return (
    <div style={{display: 'flex', justifyContent: 'space-between', paddingRight: 200}}>
      <Button type='primary' onClick={() => setCount(count + 1)}>加1</Button>
      <div>{count}</div>
    </div>
   );
};

export default Mock;

useEffect

  • 副作用(Side Effect)是指 function 做了和本身运算返回值无关的事,如请求数据、修改全局变量,打印、数据获取、设置订阅以及手动更改 React 组件中的 DOM 都属于副作用操作都算是副作用
  • 整合原有的生命周期方法,通过第二个参数来接收

作用:副作用,主要是将组件中的 componentDidMount componentDidUpdate componentWillUnmount 到一个方法中,杜绝了频繁定义的繁琐

image.png 代码演示

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

  const Mock: React.FC<any> = () => {
    const [count, setCount ] = useState<number>(0)

    return (
      <div>
        <Button type='primary' onClick={() => setCount(count + 1)}>加一</Button>
        <Test count={count} />
      </div>
    );
  };

  const Test: React.FC<{count: number}> = ({count}) => {

    const [count1, setCount1 ] = useState<number | false>(false)
    const [count2, setCount2 ] = useState<number | false>(false)

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

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

    return <div style={{display: 'flex', justifyContent: 'space-between', marginRight: 200, marginTop: 50}}>
      <div>只执行一次: {count1}</div>
      <div>执行多次: {count2}</div>
    </div>
  }

  export default Mock;

useContext

  • useContext 实现跨层级传递,实现数据共享
  • 作用就是怼他包含的组件树提供全局共享的数据的一种技术
  • 需要createContext的帮助,通过 CountContext.Provider 包裹的组件,才能通过 useContext 获取对应的值

作用:上下文,对应的Context,其本意就是设置全局共享数据,使所有组件可跨层级实现共享

image.png

代码示例

import React, { useState, createContext, useContext } from 'react';
  import { Button } from 'antd';

  const CountContext = createContext(-1)

  const Mock: React.FC<any> = () => {
    const [count, setCount ] = useState<number>(0)

    return (
      <div>
        <Button type='primary' onClick={() => setCount(count + 1)}>加1</Button>
        <CountContext.Provider value={count}>
          <Test1 />
        </CountContext.Provider>
      </div>
    );
  };

  const Test1: React.FC<any> = () => {
    const count = useContext(CountContext)

    return <div style={{marginTop: 20}}>
      子组件获取到的count: {count}
      <Test2 />
    </div>
  }

  const Test2: React.FC<any> = () => {
    const count = useContext(CountContext)

    return <div style={{marginTop: 20}}>
      孙组件获取到的count: {count}
    </div>
  }

  export default Mock;

useReducer

  • 设置统一状态
  • 接收两个参数,分别为 state action,然后返回一个状态的 count(属性值) 和 dispatch(方法)

作用: 作用类似于 redux, 增强函数体

image.png

详细代码

import React, { useState, useReducer } from 'react';
  import { Button } from 'antd';

  const Mock: React.FC<any> = () => {
    const [count, dispatch] = useReducer((state:any, action: any)=> {
      switch(action?.type){
        case 'add':
          return state + action?.payload;
        case 'sub':
          return state - action?.payload;
        default:
          return state;
      }
    }, 0);

    return (
      <div>
        <div style={{display: 'flex', justifyContent: 'flex-start'}}>
          <Button type='primary' onClick={() => dispatch({type: 'add', payload: 1})}>加1</Button>
          <Button type='primary' onClick={() => dispatch({type: 'sub', payload: 1})} style={{marginLeft: 24}}>减1</Button>
        </div>
        <div style={{marginTop: 20}}>count: {count}</div>
      </div>
    );
  };

  export default Mock;

useMemo

  • useMemo 的使用和 useEffect 的使用方式基本一致
  • 当组件进行更新时,虽然子组件不会改变状态,但还是会进行刷新,而 useMemo 只监听特定的值,也就是说,当这个值没有发生变化时,不会更新
  • 在 useMemo 函数内通过复杂计算获取当前值得时候,不需要再父组件每次更新的时候重新计算,只要在依赖项发生变化的时候计算即可

作用: 当一个父组件中调用了一个子组件的时候,父组件的 state 发生变化,会导致父组件更新,而子组件虽然没有发生改变,但也会进行更新。useMemo 就是函数组件为了防止这种不必要的而采取的手段,所以一般 useMemo 是优化手段 image.png

代码示例

  import React, { useState, useMemo } from 'react';
  import { Button } from 'antd';

  const Mock: React.FC<any> = () => {
    const [count, setCount ] = useState<number>(0)

    const add = useMemo(() => {
      return count + 1
    }, [count])

    return (
      <div style={{display: 'flex', justifyContent: 'space-between', paddingRight: 50}}>
        <Button type='primary' onClick={() => setCount(count + 1)}>加1</Button>
        <div>count: {count}</div>
        <div>次数: {add}</div>
      </div>
    );
  };

  export default Mock;

useCallback

通常在将一个组件中的函数,传递给子元素进行回调使用时,使用useCallback对函数进行处理

作用: 与 useMemo 方法类似,只是 useMemo 缓存的是变量, 而 useCallBack 缓存的是函数

image.png image.png

代码示例:

import React, { useState, useCallback } from 'react';
  import { Button } from 'antd';

  const MockMemo: React.FC<any> = () => {
    const [count,setCount] = useState(0)
    const [show,setShow] = useState(true)

    const  add = useCallback(()=>{
      setCount(count + 1)
    },[count])

    return (
      <div>
        <div style={{display: 'flex', justifyContent: 'flex-start'}}>
          <TestButton title="普通点击" onClick={() => setCount(count + 1) }/>
          <TestButton title="useCallback点击" onClick={add}/>
        </div>
        <div style={{marginTop: 20}}>count: {count}</div>
        <Button onClick={() => {setShow(!show)}}> 切换</Button>
      </div>
    )
  }

  const TestButton = memo((props:any)=>{
    console.log(props.title)
    return <Button type='primary' onClick={props.onClick} style={props.title === 'useCallback点击' ? {
    marginLeft: 20
    } : undefined}>{props.title}</Button>
  })

  export default MockMemo;

useRef

  • useRef 类似于类组件的 this
  • 可以传入初始值(initialValue),并且这个对象只有一个 current属性
  • useRef 不会随着渲染,生命周期而改变,这点与 createRef 有着本质区别

作用:useRef 获取当前元素的所有属性,并且返回一个可变的ref对象,并且这个对象只有current属性,可设置initialValue

image.png

代码示例

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

  const Mock: React.FC<any> = () => {
    const scrollRef = useRef<any>(null);
    const [clientHeight, setClientHeight ] = useState<number>(0)
    const [scrollTop, setScrollTop ] = useState<number>(0)
    const [scrollHeight, setScrollHeight ] = useState<number>(0)

    const onScroll = () => {
      if(scrollRef?.current){
        let clientHeight = scrollRef?.current.clientHeight; //可视区域高度
        let scrollTop  = scrollRef.current.scrollTop;  //滚动条滚动高度
        let scrollHeight = scrollRef.current.scrollHeight; //滚动内容高度
        setClientHeight(clientHeight)
        setScrollTop(scrollTop)
        setScrollHeight(scrollHeight)
      }
    }

    return (
      <div >
        <div >
          <p>可视区域高度:{clientHeight}</p>
          <p>滚动条滚动高度:{scrollTop}</p>
          <p>滚动内容高度:{scrollHeight}</p>
        </div>
        <div style={{height: 200, overflowY: 'auto'}} ref={scrollRef} onScroll={onScroll} >
          <div style={{height: 2000}}></div>
        </div>
      </div>
    );
  };

  export default Mock;

image.png

详细代码

import React, { useState, useRef, createRef } from 'react';
  import { Button } from 'antd';

  const CreateMock: React.FC<any> = () => {
    const [renderIndex, setRenderIndex] = React.useState(1);
    const refFromUseRef = React.useRef<number>();
    const refFromCreateRef = createRef<any>();

    if (!refFromUseRef.current) {
      refFromUseRef.current = renderIndex;
    }

    if (!refFromCreateRef.current) {
      // @ts-ignore
      refFromCreateRef.current = renderIndex;
    }

    return <>
      <p>Current render index: {renderIndex}</p>
      <p>refFrom UseRef: {refFromUseRef.current}</p>
      <p>refFrom CreateRef: {refFromCreateRef.current}</p>
      <Button type='primary' onClick={() => setRenderIndex(renderIndex + 1)} >点击</Button>
    </>
  };

  export default CreateMock;

useImperativeHandle

当一个页面很复杂的时候,我们会将这个页面进行模块化,这样会分成很多个模块,有的时候我们需要在最外层的组件上控制其他组件的方法,希望最外层的点击事件,同时执行子组件的点击事件,这时就需要 useImperativeHandle 的帮助

作用: 可以让你在使用 ref 时自定义暴露给父组件的实例值。

image.png

代码示例

import React, { useState, useImperativeHandle, useRef } from 'react';
  import { Button } from 'antd';

  const Children: React.FC<any> = ({cRef}) => {

    const [count, setCount] = useState<number>(0)

    const add = () => {
      setCount((c) => c + 1)
    }

    useImperativeHandle(cRef, () => ({
      add
    }))

    return <div style={{marginBottom: 20}}>
      <p>点击次数:{count}</p>
      <Button type='primary' onClick={() => add()}>加1</Button>
    </div>
  }


  const Mock: React.FC<any> = () => {
    const ref = useRef<any>(null)

    return (
      <div>
        <Children cRef={ref} />
        <Button type='primary' onClick={() => {
          ref.current.add()
        }}>父节点加1</Button>
      </div>
    );
  };

  export default Mock;

以上只是本人对每个Api简单的理解和使用方法,如若不对,欢迎评论区留言指出(● ̄(エ) ̄●)

其他文章