• ReactHook技巧


    ReactHook技巧


    github: dL-hx/ts-with-react

    一 概念

    在这里插入图片描述

    1.什么是hook?

    2.为什么要用hook?

    -: 为函数组件提供状态管理能力, 扩展
    -: 在Function组件中勾入更多React 特性, 以use开头

    二 useState

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qcvXgPA7-1669130695588)(micro%20_frontend/img/image-20221122232339894.png)]

    ./components/LikeButton.tsx

    // rafc  tsrafe
    import React, { useState } from 'react'
    
    const LikeButton: React.FC = () => {
        const [like, setLike] = useState(0)
        const [on, setOn] = useState(true)
    
        return (
            <div>
                <button onClick={() => setLike(like + 1)}>
                    {like} 👍
                </button>
                <button onClick={() => setOn(!on)}>
                    {on ? 'ON':'OFF' }
                </button>
            </div>
        )
    }
    
    export default LikeButton
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    改变函数内组件状态,在每次组件更新时候, 记住状态值

    三 useEffect

    纯函数:输入确定, 输出确定,无副作用

    副作用:网络请求,手动dom操作,订阅数据来源, … 与纯函数界面渲染不同

    两种场景:

      1. 无需清除副作用(Effect)
      1. 需要清除的副作用

    3.1 无需清除副作用(Effect)

    实现: 使用useEffect 修改DOM 完成标题更新

    class实现:

    在这里插入图片描述

    hooks实现:

    // rafc  tsrafe
    import React, { useEffect, useState } from 'react'
    
    const LikeButton: React.FC = () => {
        const [like, setLike] = useState(0)
     ...
        useEffect(()=>{//渲染和更新时候执行.
            document.title = `点击了 ${like}`
        })
    
        return (
            <div>
                <button onClick={() => setLike(like + 1)}>
                    {like} 👍
                </button>
              ...
            </div>
        )
    }
    
    export default LikeButton
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.2 需要清除副作用(Effect)

    dom操作需要清除监听, 防止内存泄漏,卡顿页面

    实现: 使用useEffect 完成鼠标跟踪器(鼠标移动时候显示鼠标位置)

    原理:挂载document上, 点击click,修改state,当前值

    在这里插入图片描述

    class实现:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H70K1K3G-1669133553231)(ReactHooks/image-20221122235414537.png)]

    hooks实现:// 无依赖时候, 会多次重复调用effect 注册,执行

    import React, { useEffect, useState } from 'react'
    
    const MouseTracker: React.FC = () => {
        const [position, setPosition] = useState({ x: 0, y: 0 })
    
        useEffect(() => {
            console.log('add effect',position.x);
    
            const updateMouse = (e: MouseEvent) => {
                console.log('inner');
                setPosition({
                    x: e.clientX,
                    y: e.clientY
                })
            }
    
            document.addEventListener('click', updateMouse)
            return () => {
                console.log('remove effect',position.x);
    
                document.removeEventListener('click', updateMouse)
    
            }
        })
    
        console.log('before render',position.x);
        return (
            <div>
                X :{position.x} {` `} Y :{position.y}
            </div>
        )
    }
    
    export default MouseTracker
    
    • 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
    • 34

    *忘记react声明周期,重新接触react effect,发现每次渲染都会调用effect(有些浪费), 思考如何规避多次调用------>控制effect调用次数

    3.3 控制(Effect)调用次数

    避免每次渲染都会调用,控制次数

    实现:控制useEffect的执行

    修改前面例子代码 ,利用deps,依赖

    function App() {
      const [show, setShow] = useState(true)
      return (
        <div className="App">
          <header className="App-header">
           ...
            <p>
              <button onClick={()=>setShow(!show)}>Toggle MouseTracker</button>
            </p>
            <LikeButton />
            {show&&<MouseTracker />}
          ...
          </header>
        </div>
      );
    }
    
    export default App;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ./components/MouseTracker.tsx

    添加依赖项 [], 挂载, 卸载时候执行callback/ 避免一更新就执行回调

    import React, { useEffect, useState } from 'react'
    
    const MouseTracker: React.FC = () => {
        const [position, setPosition] = useState({ x: 0, y: 0 })
    
        useEffect(() => {
          ...
            document.addEventListener('click', updateMouse)
            return () => {
                ...
    
            }
        },[])
    
        console.log('before render',position.x);
        return (
           ...
        )
    }
    
    export default MouseTracker
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    ./components/LikeButton.tsx

    添加[like] 依赖, 只在依赖项, like发生变化时候执行callback

    // rafc  tsrafe
    import React, { useEffect, useState } from 'react'
    
    const LikeButton: React.FC = () => {
        const [like, setLike] = useState(0)
      ...
    
        useEffect(()=>{
                  console.log('click')
                  document.title = `点击了 ${like}`
        },[like])
    
        return (
            <div>
                <button onClick={() => setLike(like + 1)}>
                    {like} 👍
                </button>
            </div>
        )
    }
    
    export default LikeButton
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    四 自定义hook

    • 将组件逻辑提取到可重用函数

    之前方式:hoc / render props

    实现: 使用自定义hook 抽象鼠标跟踪器

    注:

    1. 必须use 开头,否则识别不上
    2. 每次使用hook ,会在函数内部形成独立的调用空间, 不会共用

    编写hooks

    \hooks\useMousePosition.tsx

    import React, { useEffect, useState } from 'react'
    
    const useMousePosition= () => {
        const [position, setPosition] = useState({ x: 0, y: 0 })
    
        useEffect(() => {
            const updateMouse = (e: MouseEvent) => {
                setPosition({
                    x: e.clientX,
                    y: e.clientY
                })
            }
    
            document.addEventListener('mousemove', updateMouse)
            return () => {
                document.removeEventListener('mousemove', updateMouse)
            }
        },[])
    
        return position
    }
    
    export default useMousePosition
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    调用hooks , 并复用

    在需要的地方引入

    import React, { useState } from 'react';
    ...
    import useMousePosition from './hooks/useMousePosition';
    
    
    function App() {
      const [show, setShow] = useState(true)
    
    + const position = useMousePosition()
    
      return (
        <div className="App">
          <header className="App-header">
            
    +      X :{position.x} {` `} Y :{position.y}
    ...
          </header>
        </div>
      );
    }
    
    export default App;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    4.1 hoc

    发送请求 (展示Loading, 请求结束隐藏Loading), 实现逻辑复用

    hoc: 高阶组件

    高阶组件: 是一个函数,接受一个组件作为参数, 返回一个新组件

    入参: 组件------>返回:新组件

    实现:点击按钮,发送请求, 拿到结果后—>展示图片

    /hocs/withLoader.tsx

    // high order component
    import React from 'react'
    import axios from 'axios'
    
    interface ILoaderState {
      data: any,
      isLoading: boolean
    }
    interface ILoaderProps {
      data: any,
    }
    const withLoader = <P extends ILoaderState>(WrappedComponent: React.ComponentType<P>, url: string) => {
      return class LoaderComponent extends React.Component<Partial<ILoaderProps>, ILoaderState> {
        constructor(props: any) {
          super(props)
          this.state = {
            data: null,
            isLoading: false
          }
        }
        componentDidMount() {
          this.setState({
            isLoading: true,
          })
          axios.get(url).then(result => {
            this.setState({
              data: result.data,
              isLoading: false
            })
          })
        }
        render() {
          const { data, isLoading } = this.state
          return (
            <>
              { (isLoading || !data) ? <p>data is loading</p> :
                <WrappedComponent {...this.props as P} data={data} />
              }
            </>
          )
        }
      }
    }
    
    export default withLoader
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    引入

    App.tsx

    import React, { useState } from 'react';
    ...
    import withLoader from './hocs/withLoader';
    
    import './App.css';
    
    interface IShowResult {
      message:string,
      status:string,
    }
    
    const DogShow:React.FC<{data:IShowResult}>=({data})=>{
      return (
        <>
          <h2>Dog show:{data.status}</h2>
          <img src={data.message}/>
        </>
      )
    }
    
    function App() {
     
      const WrappedDogShow = withLoader(DogShow, 'https://dog.ceo/api/breeds/image/random')
    
      return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
           ...
    
            <WrappedDogShow />
           ...
          </header>
        </div>
      );
    }
    
    export default App;
    
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    在这里插入图片描述

    弊端:添加额外空节点, 逻辑 看着复杂

    4.2 自定义Hook

    完成相同逻辑

    作用:重复代码封装为一个函数, 像调用一个函数一样调用Hook

    /hooks/useURLLoader.tsx

    import { useEffect, useState } from 'react'
    import axios from 'axios'
    /**
     * url: 接口url
     * deps: 依赖项,传入数组, 依赖变化时候更新数据
     */
    const useURLLoader = (url: string, deps: any[] = []) => {
      const [data, setData] = useState<any>(null)
      const [loading, setLoading] = useState(false)
      useEffect(() => {
        setLoading(true)
        axios.get(url).then(result => {
          setData(result.data)
          setLoading(false)
        })
      }, deps)
      return [data, loading]
    }
    
    export default useURLLoader
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    引入

    App.tsx

    import React, { useState } from 'react';
    import useURLLoader from './hooks/useURLLoader';
    
    interface IShowResult {
      message:string,
      status:string,
    }
    
    
    
    function App() {
      const [show, setShow] = useState(true)
    
      const [data, loading] = useURLLoader('https://dog.ceo/api/breeds/image/random', [show])
    
      const dataResult = data as IShowResult // 断言 data 的类型 为  IShowResult
    
      return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p>
              <button onClick={()=>setShow(!show)}>Toggle dog Image</button>
            </p>
           
    
            {
              loading?'🎁...':<img src={dataResult && dataResult.message}/>
            }
            
          </header>
        </div>
      );
    }
    
    export default App;
    
    
    • 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
    • 34
    • 35
    • 36
    • 37

    通用封装 useAxios.tsx

    import { useEffect, useState } from 'react'
    import axios from 'axios'
    
    const useAxios = (config = {}) => {
        // 1. 定义
        const [data, setData] = useState<any>(null)
        const [loading, setLoading] = useState(false)
        const [error, setError] = useState<any>(null)
        useEffect(() => {
            setLoading(true)
            // 2. 设置, 发送请求
            axios(config)
                .then(result => {
                    setData(result.data)
                })
                .catch(err => {
                    setError(err)
                })
                .finally(() => {
                    setLoading(false)
                })
        }, [config])// deps : 中避免出现引用类型, 会出现死循环问题
    
        // 返回
        return [data, loading, error]
    }
    
    export default useAxios
    
    • 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

    五 useRef

    在任意次渲染中, props 与state保持不变,每次渲染都是相互独立

    用法:

    1.缓存数据,保存变量

    2.模拟生命周期, didMount

    3.获取dom

    解决: 定时事件点击后相互独立,缓存数据

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ETRj3ti-1669248468105)(ReactHooks/image-20221124075921862.png)]

    import React, { useState, useEffect, useRef, useContext } from 'react'
    
    const LikeButton: React.FC = () => {
      const [like, setLike] = useState(0)
      const likeRef = useRef(0)
      const didMountRef = useRef(false)
      const domRef = useRef<HTMLInputElement>(null)
    
    // 2. 模拟生命周期    
      useEffect(() => {
        if (didMountRef.current) {
          console.log('this is updated')
        } else {
          didMountRef.current = true
        }
      })
    //3.获取dom
      useEffect(() => {
        if (domRef && domRef.current) {
          domRef.current.focus()
        }
      })
    // 1. 缓存数据,使用同样一份数据进行同步
      function handleAlertClick() {
        setTimeout(() => {
          alert('you clicked on ' + likeRef.current)
        }, 3000)
      }
      return (
        <>
        <input type="text" ref={domRef} />
        <button onClick={() => {setLike(like + 1); likeRef.current++}}>
            {/*优化为    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kkmxsTEW-1669248468107)(ReactHooks/image-20221124080726725.png)]

    六 useContext

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ieAC4TXY-1669516529844)(ReactHooks/image-20221127095552684.png)]

    避免层层传递

    1. context/types.ts定义types.ts
    export interface IThemeProps {
        [key:string]: {color:string; background:string}
    }
    
    • 1
    • 2
    • 3

    2.context/theme.ts(可以抽象为HOOK)

    import React from 'react'
    import { IThemeProps } from './types'
    
    // 定义传递的主题
    export const themes: IThemeProps = {
        'light': {
            color: '#000',
            background: '#eee',
        },
        'dark': {
            color: '#fff',
            background: '#222',
        },
        'green':{
            color: '#fff',
            background: '#0f0',
        },
    }
    
    export const ThemeContext = React.createContext(themes.light)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.App.tsx引用

    import React, { useState } from 'react';
    + import HelloTheme from './components/HelloTheme';
    + import { ThemeContext, themes } from './context/theme'
    
    function App() {
      const dataResult = data as IShowResult // 断言 data 的类型 为  IShowResult
      return (
        <div className="App">
          <ThemeContext.Provider value={themes.green}>
            <header className="App-header">
              <HelloTheme/>
    ...
            </header>
          </ThemeContext.Provider>
    
        </div>
      );
    }
    
    export default App;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4./components/HelloTheme.tsx调用

    HelloTheme.tsx

    import React, { useContext } from "react";
    + import { ThemeContext } from '../context/theme'
    
    interface IHelloProps {
        message?: string
    }
    
    const HelloTheme: React.FC<IHelloProps> = (props) => {
    +   const theme = useContext(ThemeContext)
    +    const style = {
            background: theme.background,
            color: theme.color
        }
        return (
            <div style={style}>
                {props.message}
            </div>
        )
    }
    
    HelloTheme.defaultProps = {
        message: "Hello World"
    }
    
    export default HelloTheme
    
    • 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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Pl6Yq5W-1669516529847)(ReactHooks/image-20221127102335201.png)]

    总结—HOOKS规则

      1. 只在最顶层使用Hook
      1. 只在React 函数中调用Hook

    其他HOOKS

    Hook API 索引 – React (docschina.org)

    useReducer

    useCallback: 记住某些函数进行优化

    https://usehooks.com/

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iYtWpZiV-1669516529849)(ReactHooks/image-20221127103353690.png)]

  • 相关阅读:
    又一个新项目完结,炸裂!
    常见的安全设备详解
    合理运用ChatGPT使用Python编写一个桌面便签应用
    第七章:单链表与双链表(数组模拟)
    练习一:用python发邮件
    Flink 窗口处理函数 WindowFunction
    yarn 设置淘宝镜像配置
    Jumpserver堡垒机使用与二次开发详解
    若依框架解读(微服务版)—— 3.验证码与登录
    kong安装及使用
  • 原文地址:https://blog.csdn.net/qq_35812380/article/details/127992215