07-04-函数组件及Hooks

课程目标

  • 掌握函数组件的创建与使用;
  • 掌握 React 常见 Hooks 使用;
  • 掌握如何自定义 Hook。

课程内容

函数式组件

  • 函数式组件,本质就是一个常规函数,接收一个参数 props 并返回一个 reactNodes,即 return 该组件需要输出的视图;
  • 函数式组件中没有 this 和生命周期函数;
  • 使用函数式组件时,应该尽量减少在函数中声明子函数,否则组件每次更新时都会重新创建这个函数。

React Hooks(钩子)

-- React Hooks 是 React 16.8 中的新增功能,它们使您无需编写类即可使用状态和其它 React 功能。

常用 Hook

useState
  • const [state, setState] = useState(initialState);
  • const [状态,修改该状态的方法] = useState(初始值);
    • 在同一组件中可以使用 useState 定义多个状态;
    • 注意 useState 返回的 setState 方法不会进行对象的浅合并,即 setState 方法只接收一个参数,该参数代表的就是更新完之后的新状态;
    • 会受到批处理影响,多次调用 setState 会被合并只执行一次;
    • 注意 useState 返回的 setState 方法同样是异步方法。
    import { useState } from "react";
    
    function Child(props) {
      const [count, setCount] = useState(5);
      const [num, setNum] = useState(0)
      console.log('render');
      return (  
        <>
          

    Child:{props.info}

    Count: {count}

    Num: {num}

    ); } export default Child;
  • 只在 React 函数中调用 Hook:
    • React 函数组件中;
    • React 自定义 Hook 中;
  • 只在最顶层使用 Hook:在 React 中要保证 hook 的执行顺序在组件更新前后一致,否则会报错 React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render,原因且看下面 hook 实现原理:
    // useState 实现原理初版
    
    
    
        
        
        
        Document
    
    
    
    // hook 为什么要保持调用顺序
    
    
    
      
      
      
      Document
    
    
      
useEffect
  • 可以完成的作用等同于类组件中的生命周期函数:componentDidMount、componentDidUpdate 和 componentWillUnmount;
  • 需要清除的副作用。
    // 基本使用
    import { useEffect, useState } from "react";
    
    function Effect() {
      /**
       * useEffect 副作用钩子:用于在 React 函数中来处理副作用【数据请求、DOM 操作】
        useEffect( () => {
          // effect 函数
          return () => {
            // cleanUp 函数, 可选
          }
        }, [依赖数据])  // 依赖函数也是可选的
    
        挂载阶段:
          从上向下一行行执行代码,如果碰到 useEffect,则将对应的 effect、cleanUp 函数分别存储到一个队列中,当组件挂在完成后,按添加顺序执行 effect 函数;cleanUp 函数不执行;
    
        更新阶段:
          从上向下一行行执行代码,如果碰到 useEffect,则将对应的 effect 函数存储到一个队列中;当组件更新完成之后,找到之前存储的 cleanUp 队列,依次执行;然后再按照添加顺序依次执行 effect 队列、获取 cleanUp 函数并存储到一个队列中;如果有依赖参数,则对应的依赖参数发生变化才会执行更新阶段的代码;如果没有依赖参数,则只要组件有更新就会执行更新阶段代码;
    
        卸载阶段:
          找到对应的 cleanUp 队列,依次执行。
    
        依赖参数:(只对更新阶段有影响)
          1、不写依赖参数 useEffect(()=>{}),组件有更新,就会执行 cleanUp 函数 和 effect 函数;
          2、有具体依赖参数(1-多个)useEffect(()=>{},[a[,b,...]),则组件更新时,其依赖参数有变化,会执行其 cleanUp 函数和 effect 函数;
          3、有具体依赖参数(0个)useEffect(()=>{},[]),则组建更新时,不会执行其 cleanUp 和 effect 函数,只有加载完成和卸载阶段会执行这里的代码。
    
      */
    
      const [count, setCount] = useState(5);
        useEffect( () => {
          console.log('effect-1');
      
          return () => {
            console.log('cleanUp-1');
          }
        });
    
        useEffect( () => {
          console.log('effect-2');
      
          return () => {
            console.log('cleanUp-2');
          }
        });
    
      console.log('render');
    
      return (  
        <>
          

    Count: {count}

    ); } export default Effect;
  • 和类组件的生命周期进行对应:能对应到的生命周期如下
    • 挂在完成之后,做某事;
    • 更新完成之后,做某事;
    • 挂载完成和更新完成之后,做某事;
    • 即将卸载时,做某事。
    import { useEffect, useRef } from "react";
    
    function EffectLife() {
      useEffect( () => {
        console.log('2--挂载完成之后');
    
        return () => {
          console.log('即将卸载时做某事');
        }
      }, []);
      useEffect( () => {
        console.log('3--更新、挂载完成之后都会做某事');
      });
    
      // 只在更新阶段做某事【需要使用到 useRef】
      const isMount = useRef(false); 
      useEffect( () => {
        if(isMount.current) {
          console.log('更新阶段做某事');
        } else {
          isMount.current = true;
        }
      })
    
      console.log('1--render');
      return ( 
        <>
          

    Effect Life

    ); } export default EffectLife;
useRef
  • 用户关联原生 DOM 节点;
  • 或者用来记录组件更新前的一些数据。
    import { useEffect, useRef, useState } from "react";
    
    function RefTest() {
      // const ref = useRef(init);
      // 1.关联节点实例
      // 2.传递组件更新前的一些数据,当 useRef 中存储的是某项数据时,该数据并不会随着组件的更新而自动更新
      const [count, setCount] = useState(5888);
      const ref = useRef(count);
      const countRef = useRef();
      useEffect( () => {
        ref.current = count;
        console.log(ref, countRef.current.innerHTML);
      })
    
      
      return (  
        <>
          
    Count: {count}
    ); } export default RefTest;
useMemo
  • const data = useMemo(cb, []); 类似于 useState,有一个依赖参数。
    // 发现做任何操作,都会执行 maxValue 函数
    import { useState } from "react";
    
    function MemoTest() {
      const [count, setCount] = useState(1);
      const [num, setNum] = useState(5);
      const [val, setVal] = useState('');
      const plusCount = () => {
        console.log('plusCount');
        setCount(count+1);
      };
      const minusCount = () => {
        console.log('minusCount');
        setCount(count-1);
      };
      const plusNum = () => {
        console.log('plusNum');
        setNum(num+1);
      };
      const minusNum = () => {
        console.log('minusNum');
        setNum(num-1)
      };
      const valueChange = ({target}) => {
        console.log('valueChange');
        setVal(target.value)
      };
      const maxValue = () => {
        console.log('maxValue');
        return count>num?"count":"num";
      };
      return (  
        <>
          

    MemoTest

    Count: {count}; ,
    Num: {num}; ,

    当前比较大的值为:{maxValue()}

    {val}

    ); } export default MemoTest;
    import { useMemo, useState } from "react";
    /**
     * useMemo(cb, [])
     * 不是函数,直接等于 cb 的返回值。
     * 发现只有修改 count、num 的值才会打印 maxValue,在输入框中输入内容时不再打印
     */
    
    function MemoTest() {
      const [count, setCount] = useState(1);
      const [num, setNum] = useState(5);
      const [val, setVal] = useState('');
      const plusCount = () => {
        console.log('plusCount');
        setCount(count+1);
      };
      const minusCount = () => {
        console.log('minusCount');
        setCount(count-1);
      };
      const plusNum = () => {
        console.log('plusNum');
        setNum(num+1);
      };
      const minusNum = () => {
        console.log('minusNum');
        setNum(num-1)
      };
      const valueChange = ({target}) => {
        console.log('valueChange');
        setVal(target.value)
      };
      const maxValue = useMemo(() => {
        console.log('maxValue');
        return count>num?"count":"num";
      }, [num, count]);
      return (  
        <>
          

    MemoTest

    Count: {count}; ,
    Num: {num}; ,

    当前比较大的值为:{maxValue}

    {val}

    ); } export default MemoTest;
useCallback
  • 是 useMemo 的一个进化体;
  • 当 useMemo 返回一个函数时,可以使用 useCallback 来改进;
  • 将上面的代码进行改进,修改 plusCount,只有在 count 进行更新时才会进行声明子函数:
    import { useCallback, useMemo, useState } from "react";
    /**
     * useMemo(cb, [])
     * 当 useMemo 返回值是一个函数时,可以使用 useCallback 来优化
     */
    
    function CallbackTest() {
      const [count, setCount] = useState(1);
      const [num, setNum] = useState(5);
      const [val, setVal] = useState('');
      const plusCount = useCallback( () => {
        console.log('plusCount');
        setCount(count+1);
      }, [count]);
      const minusCount = useCallback(() => {
        console.log('minusCount');
        setCount(count-1);
      }, [count]);
      const plusNum = useCallback(() => {
        console.log('plusNum');
        setNum(num+1);
      }, [num]);
      const minusNum = useCallback(() => {
        console.log('minusNum');
        setNum(num-1)
      }, [num]);
      const valueChange = useCallback(({target}) => {
        console.log('valueChange');
        setVal(target.value)
      }, [val]);
      const maxValue = useMemo(() => {
        console.log('maxValue');
        return count>num?"count":"num";
      }, [num, count]);
      return (  
        <>
          

    MemoTest

    Count: {count}; ,
    Num: {num}; ,

    当前比较大的值为:{maxValue}

    {val}

    ); } export default CallbackTest;
  • 使用 useCallback、useMemo,会更麻烦一点,但是性能会有很大提升。

Memo

  • React.memo(Component, [areEqual(prevProps, nextProps]);

Hook 使用规则

  • 只在 React 函数中调用 Hook;
    • React 函数组件中;
    • React 自定义 Hook 中;
  • 只在最顶层使用 Hook。

自定义 Hook

  • 常用来解决(与状态相关的)逻辑复用问题;
  • 自定义 hook 就是一个普通函数,但是该函数必须以 use 开始命名;
    import { useCallback, useMemo, useState } from "react";
    
    function useCount(init) {
      const [count, setCount] = useState(init);
      const plusCount = useCallback(() => {
        setCount(count + 1);
      }, [count]);
      const minusCount = useCallback(() => {
        setCount(count - 1);
      }, [count]);
      return [count, plusCount, minusCount];
    }
    function HookTest() {
      const [count, plusCount, minusCount] = useCount(1);
      const [num, plusNum, minusNum] = useCount(5);
      const [val, setVal] = useState('');
      const valueChange = useCallback(({target}) => {
        console.log('valueChange');
        setVal(target.value)
      }, [val]);
      const maxValue = useMemo(() => {
        console.log('maxValue');
        return count>num?"count":"num";
      }, [num, count]);
      return (  
        <>
          

    MemoTest

    Count: {count}; ,
    Num: {num}; ,

    当前比较大的值为:{maxValue}

    {val}

    ); } export default HookTest;

你可能感兴趣的:(07-04-函数组件及Hooks)