React Hooks的使用

740 阅读7分钟
  • 初识Hook

    Hook 是 React 16.8 的新增特性,它可以让我们在不编写class的情况下使用state以及其他的React特性(比如生命周期)。(函数式组件与类组件的区别注意:Hooks只在函数式组件中使用
    使用准则:

    1. 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
      应为react是通过链表的形式存储hooks的在读取hooks的时候会按照hooks的顺序读取,如果放在条件语句中,hooks读取可能会错乱,同时放在条件语句中的hooks运行也会报错
    2. 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
  • useState

    useState会帮助我们定义一个state变量,useState是一种新方法,它与class里面的this.state提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而state中的变量会被React保留。

    1. useState接受唯一一个参数在第一次组件被调用时使用来作为初始化值。(如果没有传递参数,那么初始化值为undefined
    2. useState是一个数组,可以通过数组的解构,来完成赋值
    3. useState使用示例
      • 多个状态的情况下
         const [count, setCount] = useState(0);
         const [age, setAge] = useState(18);
         const [friends, setFriends] = useState(["kobe", "lilei"]);
        
      • 复杂状态的情况下
  • Effect Hook

    1. useEffect作用
      • 完成一些类组件中生命周期的功能
      • 监听state变化
    2. useEffect用法
      • useEffect接收参数(两个)
        1. 执行的回调函数;
        2. 该useEffect在哪些state发生变化时,才重新执行;(受谁的影响) 如果第二个参数不传那么每次渲染、挂载和卸载都会执行参数一的回调函数
      • 模仿于componentDidMountcomponentWillUnmount功能
      • state监听
  • useContext

    useContext等效于类中的contextType = MyContext<MyContext.Consumer>useContext(MyContext)仅允许您读取上下文并订阅其更改。您仍需要树中的<MyContext.Provider>来提供此上下文的值。

    • 使用方式
    • 示例代码
    • 使用优点
      当多个context的时候,结构更加清晰不需要嵌套多个Consumer
  • useReducer

    useState的替代品,如果state的处理逻辑比较复杂,或者修改这次的state需要依赖上一次state的时候可以使用

    • 具体用法:
      const [state, dispatch] = useReducer(reducer, initialState);
    • 代码示例
      注意:useReducer不能代替redux,useReducer只能说可以公用一个reducer,但是数据不是共享的,redux数据是共享的,两者之间有本质上的区别
  • useCallback

    useCallback会返回一个函数的memoized(记忆的)值,如果依赖不变的情况下多次定义的时候返回的值都是相同的
    注意:这里的返回一个记忆值是上一个函数的缓存,一般来说在组件的第二次渲染中,原来的函数被垃圾收集(释放内存空间),然后创建一个新的函数。但是使用useCallback时,原来的函数不会被垃圾收集,并且会创建一个新的函数,所以从内存的角度来看,这会变得更糟。

    • 具体用法
    • 使用useCallback性能优化
      如果一个函数单纯的只在当前组件中使用完全没有必要使用useCallback,应为这种情况下除了useCallback做了更多的工作之外,它们完全相同。我们不仅需要定义函数,还要定义一个数组([])并调用 React.useCallback,它本身会设置属性和运行逻辑表达式等
      但是当一个父组件A中含有一个子组件B,B组件中的一个按钮需要触发A组件中的一个函数,这个时候你就需要给B组件传递一个函数,当点击按钮是触发函数,这时这个回调函数就可以使用useCallback包裹,只要依赖项没有变化不管A组件再怎么渲染传递给B组件的函数的内存地址都不会变化,B组件再用memo包裹的情况下,B组件就不会跟随A组件渲染多次,这样就达到了性能优化的目的如下示例:
      import React, {useState, useCallback, memo} from 'react';
      
      /**
       * useCallback在什么时候使用?
       * 场景: 在将一个组件中的函数, 传递给子元素进行回调使用时, 使用useCallback对函数进行处理.
       */
      
      const HYButton = memo((props) => {
        console.log("HYButton重新渲染: " + props.title);
        return <button onClick={props.increment}>HYButton +1</button>
      });
      
      export default function CallbackHookDemo02() {
        console.log("CallbackHookDemo02重新渲染");
      
        const [count, setCount] = useState(0);
        const [show, setShow] = useState(true);
      
        const increment1 = () => {
          console.log("执行increment1函数");
          setCount(count + 1);
        }
      
        const increment2 = useCallback(() => {
          console.log("执行increment2函数");
          setCount(count + 1);
        }, [count]);
      
        return (
          <div>
            <h2>CallbackHookDemo01: {count}</h2>
            {/* <button onClick={increment1}>+1</button>
            <button onClick={increment2}>+1</button> */}
            <HYButton title="btn1" increment={increment1}/>
            <HYButton title="btn2" increment={increment2}/>
      
            <button onClick={e => setShow(!show)}>show切换</button>
          </div>
        )
      }
      
      

    注意:useCallback和memo要配合使用否则适得其反

    性能优化不是免费的。 它们总是带来成本,但这并不总是带来好处来抵消成本。 所以要负责人的进行优化

  • useMemo

    useMemo类似于useCallback,除了它允许你将memoization应用于任何值类型(不仅仅是函数)
    它通过接受一个返回值的函数来实现这一点,然后只在需要检索值时调用该函数(通常这只有在每次渲染中依赖项数组中的元素发生变化时才会发生一次)其他特性用法和useCallback雷同

  • useRef

    useRefreact hook中的作用, 正如官网说的, 它像一个变量, 类似于this , 它就像一个盒子, 你可以存放任何东西. createRef每次渲染都会返回一个新的引用,而useRef每次都会返回相同的引用
    使用方式
    const refContainer = useRef(initialValue);
    具体用法:

    1. 引入DOM(或者组件,但是需要是class组件)元素;
    export default function RefHookDemo() {
    
     const titleRef = useRef();
     const inputRef = useRef();
    
     function changeDOM() {
       titleRef.current.innerHTML = "Hello World";
       inputRef.current.focus();
     }
    
     return (
       <div>
         <h2 ref={titleRef}>RefHookDemo01</h2>
         <input ref={inputRef} type="text"/>
    
         <button onClick={e => changeDOM()}>修改DOM</button>
       </div>
     )
    }
    
    1. 保存一个数据,这个对象在整个生命周期中可以保存不变;
    export default function RefHookDemo() {
     const [count, setCount] = useState(0);
    
     const numRef = useRef(count);
    
     useEffect(() => {
       numRef.current = count;
     }, [count])
    
     return (
       <div>
         {/* <h2>numRef中的值: {numRef.current}</h2>
         <h2>count中的值: {count}</h2> */}
         <h2>count上一次的值: {numRef.current}</h2>
         <h2>count这一次的值: {count}</h2>
         <button onClick={e => setCount(count + 10)}>+10</button>
       </div>
     )
    }
    
  • useImperativeHandle

    useImperativeHandle之前先回顾一下refforwardRef结合使用,通过forwardRef可以将ref转发到子组件;子组件拿到父组件中创建的ref,绑定到自己的某一个元素中;forwardRef的做法本身没有什么问题,但是我们是将子组件的DOM直接暴露给了父组件:父组件可以拿到DOM后进行任意的操作,比如一种情况子组件中有个元素input,父组件只需要控制input获取焦点。
    这个时候就可以使用useImperativeHandle通过useImperativeHandle可以值暴露固定的操作:通过useImperativeHandle的Hook,将传入的ref和useImperativeHandle第二个参数返回的对象绑定到了一起所以在父组件中,使用inputRef.current时,实际上使用的是返回的对象 示例代码

    import React, { useRef, forwardRef, useImperativeHandle } from 'react';
    
     const TDInput = forwardRef((props, ref) => {
       const inputRef = useRef();
    
       useImperativeHandle(ref, () => ({
         focus: () => {
           inputRef.current.focus();
         }
       }), [inputRef])
    
       return <input ref={inputRef} type="text"/>
     })
    
     export default function UseImperativeHandleHookDemo() {
       const inputRef = useRef();
    
       return (
         <div>
           <TDInput ref={inputRef}/>
           <button onClick={e => inputRef.current.focus()}>聚焦</button>
         </div>
       )
     }
    
  • useLayoutEffect

    useEffect的作用类似区别在于useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新;useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新;如果希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect
    代码示例

    import React, { useState, useEffect, useLayoutEffect } from 'react'
    
    export default function LayoutEffectCounterDemo() {
      const [count, setCount] = useState(10);
    
      useLayoutEffect(() => {
        if (count === 0) {
          setCount(Math.random() + 200)
        }
      }, [count]);
    
      return (
        <div>
          <h2>数字: {count}</h2>
          <button onClick={e => setCount(0)}>修改数字</button>
        </div>
      )
    }
    
    
  • 自定义Hook

    自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook
    当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。而组件和 Hook 都是函数,所以也同样适用这种方式。
    代码示例

    
    const Home = (props) => {
     useLoggingLife("Home");
     return <h2>Home</h2>
    }
    
    const Profile = (props) => {
     useLoggingLife("Profile");
     return <h2>Profile</h2>
    }
    
    export default function CustomLifeHookDemo01() {
     useLoggingLife("CustomLifeHookDemo01");
     return (
       <div>
         <h2>CustomLifeHookDemo01</h2>
         <Home/>
         <Profile/>
       </div>
     )
    }
    //useLoggingLife就是一个自定义hooks
    function useLoggingLife(name) {
     useEffect(() => {
       console.log(`${name}组件被创建出来了`);
    
       return () => {
         console.log(`${name}组件被销毁掉了`);
       }
     }, []);
    }