• React+TS学习和使用(二)


    开启学习react+ts,本篇主要是react函数式组件必备Hook,结合TS一起了解。

    一、Hooks

    1、useState

    App.tsx 中使用 useState 定义数据,以及修改数据的方法,并传递给 Comp.tsx 子组件:

    const [num, setNum] = useState(0);
    
    <Comp1 num={num} />
       
    
    • 1
    • 2
    • 3
    • 4

    子组件接收:

    import React from 'react'
    
    const Comp1: React.FC = function (props) {
        return (
            <>
                <h3>{props.num}</h3>
                <button>累加</button>
            </>
        )
    }
    export default Comp1;
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    很明显,这么接收直接就报错。因为TS强制要求必须指定传参的字段及其类型,因此应当改为:

    import React from 'react'
    
    const Comp1: React.FC = function (props: {num: number}) {
        return (
            <>
                <h3>{props.num}</h3>
                <button>累加</button>
            </>
        )
    }
    export default Comp1;
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    而实际上这是TS中接口的简化写法,完整点应该写为:

    import React from 'react'
    
    interface IProps {
        num: number;
    }
    
    // 使用IProps接口定义字段类型
    const Comp1: React.FC<IProps> = function (props) {
        return (
            <>
                <h3>{props.num}</h3>
                <button>累加</button>
            </>
        )
    }
    
    export default Comp1;
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    A. 事件直接父传子使用

    目前 setNum 依然处于定义了但未使用的状态,因此ESlint又会一直给出提示,因此我们可以把这个累加的效果实现:

    // 父组件:
    <Comp1 num={num} setNum={setNum} />
    
    // 子组件
    interface IProps {
        num: number;
        // 设定setNum为any
        setNum: any
    }
    
    <button onClick={()=>props.setNum(props.num+1)}>累加</button>
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注意:

    这里虽然设置为any可以实现累加,但不建议这么操作。

    因此,真正的做法:

    import React from 'react'
    
    interface IProps {
        num: number;
        setNum: (num:number)=>void;
    }
    
    // 使用IProps接口定义字段类型
    const Comp1: React.FC<IProps> = function(props) {
        return (
            <>
                <h3>{props.num}</h3>
                <button onClick={()=>props.setNum(props.num+1)}>累加</button>
            </>
        )
    }
    
    export default Comp1;
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    B. 事件用子传父的做法

    * 父组件
    import React, {useState, useCallback} from 'react'
    import Comp1 from 'components/Comp1'
    
    const App: React.FC = () => {
        const [num, setNum] = useState(0)
    
    	const toSetNum = (value: number) => setNum(value)
    
        return (
            <>
                <h2>你好世界</h2>
                <Comp1 num={num} toSetNum={()=>toSetNum} />
            </>
        )
    }
    
    export default App;
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    setNum(newValue):代表直接用新值替换初始值

    setNum(preValue => newValue):代表用新值替换旧值

    * 子组件
    import React from 'react'
    
    interface IProps {
        num: number;
        toSetNum: (num:number)=>void;
    }
    
    // 使用IProps接口定义字段类型
    const Comp1: React.FC<IProps> = function(props) {
        return (
            <>
                <h3>{props.num}</h3>
                <button onClick={()=>props.toSetNum(props.num+1)}>累加</button>
            </>
        )
    }
    
    export default Comp1;
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2、useEffect

    React的Class Component中有 componentDidMountcomponentDidUpdatecomponentWillUnmount,但Function Component并没有。

    A、componentDidMount

    useEffect(()=>{
      console.log('componentDidMount')
    }, [])	// 空数组表示不检测任何数据变化
    
        
    
    • 1
    • 2
    • 3
    • 4
    • 5

    B、comopnentDidUpdate

    useEffect(()=>{
      console.log('comopnentDidUpdate')
    }, [num])	// 如果数组中包含了所有页面存在的字段,也可以直接不写
       
    
    • 1
    • 2
    • 3
    • 4

    如果监听路由的变化:

    // 需要先安装路由,而且是react-router-dom@v6.x
    useEffect(()=>{
      console.log('路由变化')
    }, [location.pathname])
    
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    C、componentWillUnmount

    useEffect(()=>{
      return ()=>{
        // callback中的return代表组件销毁时触发的事件
      }
    }, [])
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3、memo、useMemo与useCallback

    在Function Component中,也不再区分mountupdate两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。useMemouseCallback都是解决上述性能问题的。

    来看下面这段代码:

    import React, { useState, useMemo, useCallback } from "react";
    
    const Sub = () => {
      console.log("Sub被渲染了");	// 这行代码在父组件App2更新时,它也被迫一直更新
      return <h3>Sub组件</h3>;
    };
    
    export default function App2() {
      const [num, setNum] = useState<number>(0);
    
      const changeNum = () => setNum(num + 1)
    
      return (
        <div>
          <h2>num的值:{num}</h2>
          <button onClick={changeNum}>累加num</button>
          <Sub />
        </div>
      );
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    以上代码中可以测试出来,Sub组件的 console.log 在App2组件更新时,一直被迫触发,这就是典型的性能浪费。

    A. memo

    使用memo这个hook可以解决这一问题:

    import React, { useState, memo } from "react";
    
    // Sub组件需要被memo包裹
    const Sub = memo(() => {
        console.log("Sub被渲染了");
        return <h3>Sub组件</h3>;
      });
    
    export default function App2() {
      const [num, setNum] = useState<number>(0);
        
      const changeNum = () => setNum(num + 1)
    
      return (
        <div>
          <h2>num的值:{num}</h2>
          <button onClick={changeNum}>累加num</button>
          <Sub />
        </div>
      );
    }
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    memo可以缓存组件,当组件的内容不受修改时,可以不更新该组件。

    B. useCallback

    但我们希望num的变化不造成Sub组件的更新:

    import React, { useState, memo, useCallback } from "react";
    
    interface ISubProps {
      changeNum: () => void;
    }
    
    // Sub组件需要被memo包裹
    const Sub = memo((props: ISubProps) => {
      console.log("Sub被渲染了");
      return (
        <>
          <button onClick={props.changeNum}>累加num</button>
          <h3>Sub组件</h3>
        </>
      );
    });
    
    export default function App2() {
      const [num, setNum] = useState<number>(0);
    
      // 将这个changeNum函数使用useCallback包裹一次
      const changeNum = useCallback(()=>{
          setNum((num)=>num+1)
      }, [])
    
      return (
        <div>
          <h2>num的值:{num}</h2>
          <Sub changeNum={changeNum} />
        </div>
      );
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    C. useMemo

    useMemo与useCallback大致相同,只是useMemo需要在回调函数中再返回一个函数,我们称之为高阶函数:

    import React, { useState, memo, useMemo } from "react";
    
    interface ISubProps {
      changeNum: () => void;
    }
    
    // Sub组件需要被memo包裹
    const Sub = memo((props: ISubProps) => {
      console.log("Sub被渲染了");
      return (
        <>
          <button onClick={props.changeNum}>累加num</button>
          <h3>Sub组件</h3>
        </>
      );
    });
    
    export default function App2() {
      const [num, setNum] = useState<number>(0);
    
      // 将这个changeNum函数改为useMemo
      const changeNum = useMemo(() => {
        return () => setNum((num) => num + 1);
      }, []);
    
      return (
        <div>
          <h2>num的值:{num}</h2>
          <Sub changeNum={changeNum} />
        </div>
      );
    }
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    4、自定义hook

    React中的hook允许我们自定义,来尝试一个简单的:

    自定义一个hook,将所有的小写字母改大写。

    import React from 'react'
    
    const word = "Hello World";
    
    function useBigWord(w: string){
        return w.toUpperCase();
    }
    
    const App3 = () => {
        const bigWord = useBigWord(word)
        return (
            <div>
                <h3>小写:{word}</h3>
                <h3>大写:{bigWord}</h3>
            </div>
        )
    }
    
    export default App3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    实现logstash从rabbitmq取数据存elasticsearch
    竟敢冒充国家级黑客组织!亚信安全还原攻击事件真实面貌
    八大学习方法(金字塔模型、费曼学习法、布鲁姆学习模型)
    IDEA生成Java Doc帮助文档
    318. 最大单词长度乘积
    导出数据为zip压缩文件,解压后为json文件
    【全民编程】《软件编程-讲课视频》【零基础入门到实战应用】
    MATLAB基本绘图功能使用
    【OpenCV实现图像:OpenCV进行OCR字符分割】
    Web优化躬行记(6)——优化闭环实践
  • 原文地址:https://blog.csdn.net/aiyang1214878408/article/details/125429731