20230524----重返学习-useEffect()与useLayoutEffect()-TaskOA任务管理系统

day-077-seventy-seven-20230524-useEffect()与useLayoutEffect()-TaskOA任务管理系统

useEffect()与useLayoutEffect()

useEffect()与useLayoutEffect()的作用:在函数组件中使用生命周期函数。

Hook函数useEffect()细节

  1. useEffect必须处于函数组件的顶层,不能嵌入到判断/循环等操作中。

    // useEffect必须处于函数组件的顶层「不能嵌入到判断/循环等操作中」
    if (x > 12) {
      useEffect(() => {
          console.log('AAA')
      })
    } 
    //修改为
    useEffect(() => {
      if (x > 12) {
          console.log('AAA')
      }
    }, [x]) 
    
    import { useState, useEffect, useLayoutEffect } from 'react'
    import { Button } from 'antd'
    
    export default function Demo() {
      let [x, setX] = useState(10),
        [y, setY] = useState(20),
        [z, setZ] = useState(30)
    
        // useEffect必须处于函数组件的顶层,不能嵌入到判断/循环等操作中。
        if(x>12){
          useEffect(()=>{
            console.log(`AAA`);
          })
        }
    
      return <div className="demo" style={{ padding: '50px' }}>
        <p>{x} - {y} - {z}</p>
        <Button type='primary' size='small' onClick={() => setX(x + 1)}>修改X</Button>
        <Button type='primary' size='small' onClick={() => setY(y + 1)}>修改Y</Button>
        <Button type='primary' size='small' onClick={() => setZ(z + 1)}>修改Z</Button>
      </div>
    }
    
    • 应修改为:

      import { useState, useEffect, useLayoutEffect } from 'react'
      import { Button } from 'antd'
      
      export default function Demo() {
        let [x, setX] = useState(10),
          [y, setY] = useState(20),
          [z, setZ] = useState(30)
      
        // 会报错;
        // useEffect必须处于函数组件的顶层,不能嵌入到判断/循环等操作中。
        // if (x > 12) {
        //   useEffect(() => {
        //     console.log(`AAA`);
        //   })
        // }
      
        useEffect(() => {
          if (!(x > 12)) {
            return
          }
      
          console.log(`AAA`);
        })
      
      
      
        return <div className="demo" style={{ padding: '50px' }}>
          <p>{x} - {y} - {z}</p>
          <Button type='primary' size='small' onClick={() => setX(x + 1)}>修改X</Button>
          <Button type='primary' size='small' onClick={() => setY(y + 1)}>修改Y</Button>
          <Button type='primary' size='small' onClick={() => setZ(z + 1)}>修改Z</Button>
        </div>
      }
      
  2. 在useEffect传递的callback函数中,不写返回值或者只能返回一个函数。

    // 在useEffect传递的 callback 函数中,不写返回值或者只能返回一个函数,所以导致 callback 不能基于 async 修饰「因为一但修饰,则直接返回 promise 实例」
    const query = async () => {
      await api.query()
    }
    //应修改为:
    useEffect(() => {
      // (async () => {
      //     await api.query()
      // })()
      query()
    }, [])
    
    • 所以导致,callback函数不能基于async修改。
      • 因为一旦修饰,则直接返回promise实例对象。

        // 会报错;
        // 在useEffect传递的callback函数中,不写返回值或者只能返回一个函数。所以导致,callback函数不能基于async修改。
        useEffect(async () => {
          // await api.query()
        },[])
        
        • 修改为:

          useEffect(async () => {
            // 
            (async () => {
              await api.query()
            })()
          }, [])
          
          const query = async () => {
            await api.query()
          }
          useEffect(async () => {
            query()
          }, [])
          

useEffect()基本语法回顾

  1. useEffect(callback) 在第一次渲染完毕和每一次组件更新完毕后,触发callback执行。

    • 等价于 componentDidMount 和 componentDidUpdate。
  2. useEffect(callback,[]) 只在第一次渲染完毕后触发执行。

    • 等价于 componentDidMount。
  3. useEffect(callback,[依赖的状态]) 在第一次渲染完毕后触发执行,以及依赖的状态发生改变后触发执行。

    • 类似于Vue2中watch监听器。
  4. useEffect(() => callback, []) 组件销毁之前,触发callback执行。

    useEffect(() => {
      // 第一次渲染完毕做的事情
      return () => {
        // 组件销毁之前做的事情
      }
    }, [])
    
  5. useEffect(() => callback) 当用户第一次修改了状态,本次闭包触发了()=>callback第一次状态更新返回了第一个callback。之后下次闭包形成。

    • 之后等待用户第二次修改了状态。直到下次闭包触发了第二次状态更新,就会执行第一个callback,之后才是触发了()=>callback第二次状态更新返回了第二个callback
    • 之后等待用户第三次修改了状态。
  6. useEffect(() => callback,[依赖的状态]) 当用户第一次修改了依赖的状态,本次闭包触发了()=>callback第一次状态更新返回了第一个callback。之后下次闭包形成。

    • 之后等待用户第二次修改了依赖的状态。直到下次闭包触发了第二次状态更新,就会执行第一个callback,之后才是触发了()=>callback第二次状态更新返回了第二个callback
    • 之后等待用户第三次修改了依赖的状态
  • 给useEffect传递的callback函数,其内部可以不写返回值,如果设置返回值,则"必须"返回一个函数!

    useEffect(() => {
      return () => {
        // 但凡有状态更新(或者依赖的状态发生改变),在组件更新之前,先把此函数执行 -> 通俗理解:产生新的闭包之前,处理的事情
      }
    }, 不写数组或者设置依赖项)
    

useLayoutEffect()

  • useEffect()与useLayoutEffect():
    • useEffect():基于useEffect()放在effect链表中的callback函数,会在组件渲染与更新完毕后触发执行。

      • 实际上已经GUI线程的绘制effect链表中的执行是两个线程同步进行的。
      • 完毕是指:已经把VirtualDOM渲染为真实DOM,并且浏览器已经把真实DOM绘制到页面中。
        • 也就是说,已经能在浏览器页面中看到该新DOM了。
    • useLayoutEffect:基于useLayoutEffect放在effect链表中的callback函数,会阻碍GUI线程的渲染,也就是在通知effect链表中的callback函数执行的时候,让浏览器停止对真实DOM的绘制,需要先把callback执行完,再去绘制。

      • 实际上是本来GUI线程的绘制effect链表中的执行是两个线程要同步进行的。用了useLayoutEffect,就先执行effect链表中的执行,等effect链表中的执行完毕,之后再进行GUI线程的绘制
    • VirtualDOM的生命周期:

      • 第一次渲染:
        • render返回jsx。
        • 对jsx变成createElement()格式。
        • createElement()格式变成虚拟DOM。
        • 虚拟DOM变成真实DOM。
        • 浏览器把真实DOM渲染到页面上,让用户可以看到。
          • 这一步就是useEffect与useLayoutEffect的区别。

ref的处理

  • ref的处理

    • 在类组件中:

      1. ref的设置方式:
        • 字符串:
          --> this.refs.AAA
        • 函数:<div ref={refObject=>this.box=refObject}> --> this.box
        • 基于React.createRef处理:
      2. ref的作用:
        • 给元素设置ref,其目的是获取这个真实DOM,操作非受控数组
        • 给子组件设置ref
          • 子组件是类组件:则是为了获取子组件的实例
            • 拿到实例后,就可以直接调用其实例上的属性和方法了!
          • 子组件是函数组件:默认会报错,说明函数组件不能直接设置ref。
    • 在函数组件中:

      1. ref的设置方式:

        • 字符串方式已经被淘汰了。

          <span ref="myRef">01</span>
          
          • 这样使用字符串方式声明 ref 并直接访问它的方式在函数组件中是不可行的!

          • 间接的做法:

            import React, { useRef } from "react";
            export default function Demo() {
              const refs = useRef({});
            
              // 创建一个字符串 ref
              const myRef = useRef(null);
            
              // 将字符串 ref 存储到对象中
              refs.current["myRef"] = myRef;
            
              // 使用字符串方式获取 ref
              const getRef = (refName) => {
                return refs.current[refName];
              };
            
              const handleClick = () => {
                // 通过字符串方式获取 ref
                const ref = getRef("myRef");
                console.log(`getRef("myRef")`,ref.current); // 输出 ref 对应的值
              };
            
              return (
                <div>
                  <span ref={myRef}>....</span>
                  <button onClick={handleClick}>获取 ref 值</button>
                </div>
              );
            }
            
        • 设置为函数。

          let spanBox1 = null
          <span ref={refObject => spanBox1 = refObject}>01</span>
          console.log('spanBox1', spanBox1);//第一个span。
          
        • 设置为ref对象。

          let spanBox2 = createRef()
          <span ref={spanBox2}>02</span>
          console.log('spanBox2.current', spanBox2.current);//第二个span。
          
          let spanBox3 = useRef()
          <span ref={spanBox3}>03</span>
          console.log('spanBox3.current', spanBox3.current);//第三个span。
          
        import { useState, useEffect, createRef,useRef } from "react";
        
        export default function Demo() {
          let spanBox1 = null
          let spanBox2 = createRef()
          let spanBox3 = useRef()
          useEffect(() => {
            console.log('spanBox1', spanBox1);//第一个span。
            console.log('spanBox2.current', spanBox2.current);//第二个span。
            console.log('spanBox3.current', spanBox3.current);//第三个span。
          }, [])
          return (
            <div>
              <span ref={refObject => spanBox1 = refObject}>01</span>
              <span ref={spanBox2}>02</span>
              <span ref={spanBox3}>03</span>
            </div>
          );
        }
        
      2. createRef()useRef():

        • createRef可以用在任何组件中,useRef只能作用在函数组件中!
        • 在函数组件中,使用createRef和useRef是有区别的:
          • 使用createRef,则在每一次组件渲染和更新的时候即函数重新执行的时候,遇到createRef都会重新创建一个新的ref对象!这样比较浪费性能。

            import { useState, useEffect, createRef, useRef } from "react";
            let prev
            export default function Demo() {
              let [, setRandom] = useState(0)
              let spanBox = createRef()
            
              if (!prev) {
                prev = spanBox//把第一次渲染创建的ref对象赋值给prev。
              } else {
                // 在组件更新后,函数会重新执行,遇到createRef,又创建了一个ref对象。此时我们对比两次创建的是否是相同的对象!!
                console.log(`prev===spanBox`, prev === spanBox);//false;
              }
              useEffect(() => {
                console.log('spanBox.current', spanBox.current);
              })
              return (
                <div>
                  <span ref={spanBox}>...</span>
                  <button onClick={() => setRandom(+new Date())}>更新</button>
                </div>
              );
            }
            
          • 使用useRef,在每一次组件渲染和更新的时候,并不会创建新的ref对象,而是把之前第一次创建的ref对象直接拿过来使用,节约性能。

            • 所以在函数组件中,我们使用useRef()来代替createRef()

              import { useState, useEffect, createRef, useRef } from "react";
              let prev
              export default function Demo() {
                let [, setRandom] = useState(0)
                let spanBox = useRef()
              
                if (!prev) {
                  prev = spanBox//把第一次渲染创建的ref对象赋值给prev。
                } else {
                  // 在组件更新后,函数会重新执行,遇到useRef,依旧返回了之前的旧ref对象。此时我们对比两次创建的是否是相同的对象!!
                  console.log(`prev===spanBox`, prev === spanBox);//true;
                }
                useEffect(() => {
                  console.log('spanBox.current', spanBox.current);
                })
                return (
                  <div>
                    <span ref={spanBox}>...</span>
                    <button onClick={() => setRandom(+new Date())}>更新</button>
                  </div>
                );
              }
              
      3. 设置REF的作用。

        • 给元素设置,其目的是获取真实DOM。
        • 给子组件设置。
          • 类组件:获取子组件的实例。
          • 函数组件:不能直接设置,需要经过处理。
            • 好像也可以用给prop传一个对象类型的ref值,不过prop好像不能改值;
      • 让函数组件更新,只能通过useState()拿到一个修改状态值的函数,之后调用那个函数,才让视图更新。
  • 拿到组件实例:

    • 给类组件类型的子组件设置ref,其目的是获取子组件实例。

      import { useState, useEffect, createRef, useRef,Component } from "react";
      // 创建子组件-类组件
      class Child extends Component{
        render(){
          return <div className="child-box">
            <span>子组件-类组件</span>
          </div>
        }
      }
      // 父组件
      export default function Demo() {
        let children = useRef()
        useEffect(()=>{
          console.log(`children.current`,children.current);
        },[])
        return (
          <div>
            {/* //给类组件类型的子组件设置ref,其目的是获取子组件实例。这样想操作子组件中的那些东西,在子组件内部,就把这些东西挂载到实例上就可以了。 */}
            <Child ref={children}></Child>
          </div>
        );
      }
      
      • 这样想操作子组件中的那些东西,在子组件内部,就把这些东西挂载到实例上就可以了。

        import { useState, useEffect, createRef, useRef, Component } from "react";
        //给类组件类型的子组件设置ref,其目的是获取子组件实例。
        // 创建子组件-类组件
        class Child extends Component {
          spanBox = createRef()
          state = {
            x: 100,
          }
          submit = () => { }
          render() {
            return <div className="child-box">
              <span ref={this.spanBox}>子组件-类组件</span>
            </div>
          }
        }
        // 父组件
        export default function Demo() {
          let children = useRef()
          useEffect(() => {
            console.log(`children.current`, children.current);
          }, [])
          return (
            <div>
              <Child ref={children}></Child>
            </div>
          );
        }
        
    • 给函数类型的子组件设置ref,默认是报错的。

      <Child x="10" ref={children}>Child是函数类型组件</Child>
      
      • 报错:Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

        import { useState, useEffect, createRef, useRef, Component } from "react";
        // 创建子组件-函数
        const Child=function Child(props)  {
          console.log(props);
            return <div className="child-box">
              <span>子组件-类组件</span>
            </div>
        }
        // 父组件
        export default function Demo() {
          let children = useRef()
          useEffect(() => {
            console.log(`children.current`, children.current);
          }, [])
          return (
            <div>
              <Child x="10" ref={children}></Child>
            </div>
          );
        }
        
      • 我们需要把函数子组件,基于React.forwardRef进行处理,实现ref的转发

        • 就是把调用子组件的时候,设置的属性(包含ref属性),都作为实参传递给子组件。

          const Child = forwardRef(
            function Child(props, ref) {
              // props是传递的属性;ref是调用子组件的时候,设置的ref属性值。
              //...
            }
          )
          
      • 此时在子组件内部,如果我们把转发的ref值,赋值给子组件的某个元素的ref属性,这样在父组件中就可以直接获取到子组件这个元素的真实DOM了。

      • 在子组件内部,还可以基于useImperativeHandle()这个API,把子组件内部的某些内容直接返回,这样在父组件中也就可以调用这些信息了!

模拟forwardRef

  • forwardRef()源码模拟

    // 模拟forwardRef
    function forwardRef1(FunctionalComponent) {
      // FunctionalComponent是传递进来的函数组件。
      // 返回的是一个做了处理的函数组件---真实源码中返回并不是函数组件,也不是类组件。而是内部专门做处理的一个专门类型。
      return function Proxy() {
        // 在源码中,返回的Proxy会接收到传递的属性以及为其设置的ref。
        return <FunctionalComponent></FunctionalComponent>
        // 在渲染这个组件的时候,把拿到的属性和ref,传递给这个组件!
    
      }
    }
    
    ```jsx
    import { useState, useEffect, createRef, useRef, Component, forwardRef } from "react";
    // 创建子组件-函数
    const Child = forwardRef(
      function Child(props, ref) {
        // props是传递的属性;ref是调用子组件的时候,设置的ref属性值。
        console.log(`props`, props, `;ref`, ref);
        return <div className="child-box">
          <span>子组件-类组件</span>
        </div>
      }
    )
    // 父组件
    export default function Demo() {
      let children = useRef()
      useEffect(() => {
        console.log(`children.current`, children.current);
      }, [])
      return (
        <div>
          <Child x="10" ref={children}></Child>
        </div>
      );
    }
    ```
    
  • forwardRef()的运行机制

    import { useState, useEffect, createRef, useRef, Component, forwardRef } from "react";
    // 创建子组件-函数
    const Child = forwardRef(
      function Child(props, ref) {
        // props是传递的属性;ref是调用子组件的时候,设置的ref属性值。
        console.log(`props`, props, `;ref`, ref);
        return <div className="child-box">
          <span>子组件-类组件</span>
        </div>
      }
    )
    // 父组件
    export default function Demo() {
      let children = useRef()
      useEffect(() => {
        console.log(`children.current`, children.current);
      }, [])
      return (
        <div>
          <Child x="10" ref={children}></Child>
        </div>
      );
    }
    
  • forwardRef()的ref转发流程

    import { useState, useEffect, createRef, useRef, Component, forwardRef } from "react";
    // 创建子组件-函数
    const Child = forwardRef(
      function Child(props, ref) {
        // props是传递的属性;ref是调用子组件的时候,设置的ref属性值。
        // console.log(`props`, props, `;ref`, ref);
        return <div className="child-box">
          <span ref={ref}>子组件-类组件</span>
        </div>
      }
    )
    // 父组件
    export default function Demo() {
      let children = useRef()
      useEffect(() => {
        console.log(`children.current`, children.current);
      }, [])
      return (
        <div>
          <Child x="10" ref={children}></Child>
        </div>
      );
    }
    
  • forwardRef()配合useImperativeHandle()后的ref转发加强-可转发对象

    • 在函数类型的子组件中,我们想把那些东西传给父组件,就把那些东西放在作为forwardRef()入参的函数组件内部中useImperativeHandle()第二个回调函数入参中的return返回的对象中;

      import { useState, useEffect, createRef, useRef, Component, forwardRef, useImperativeHandle } from "react";
      // 创建子组件-函数
      const Child = forwardRef(
        function Child(props, ref) {
          // 基于useImperativeHandle()这个Hook函数,可以把函数类型子组件内部的某些东西返回,赋值给转发过来的ref对象中的current属性!
          useImperativeHandle(ref, () => {
            // 在函数类型的子组件中,我们想把那些东西传给父组件,就把那些东西放在forwardRef()第一个入参的函数组件内部中useImperativeHandle()第二个回调函数中的这个return返回的对象中;
            return {
              n: 100,
              m: 200,
            }
          })
          return <div className="child-box">
            <span>子组件-类组件</span>
          </div>
        }
      )
      // 父组件
      export default function Demo() {
        let children = useRef()
        useEffect(() => {
          console.log(`children.current`, children.current);//{n: 100,m: 200,}
        }, [])
        return (
          <div>
            <Child x="10" ref={children}></Child>
          </div>
        );
      }
      

TaskOA任务管理系统

  • 做管理系统,要用UI框架,一般React得用antd;

    • 用antd得考虑汉化,汉化分成组件库本体及日期插件如dayjs。
  • PC端UI组件库:

    • 比较核心的组件,操作也复杂一些,可以当成一个技能来学的。
      • 表格组件
        • 后台管理系统上基本上最主要的了。
      • 表单及表单校验
        • 基本上最重要的了。
      • 弹出层
      • 文件上传
      • 日历
        • 这个主要是在它上面集成其它东西的。
    • 如何看一个UI组件。
      • 如果有案例,先看案例。
      • 结合案例,查看该组件的核心API。
      • 查看一个UI组件库的API列表,思考着能用这些API达到什么自定义的效果。
      • 在控制台上查询页面上已修改得差不多的UI组件,之后查看UI组件的DOM元素,看它的类名,根据类名大概猜出那个类是重要的,根据需要自定义修改样式。
  • 对着一份设计图,前后端都可以同步开始自己的任务。

    • 如果可以,前端先问后端大概能给什么接口,接口的作用具体是什么。
    • 前端可以先对着设计图搭页面。
    • 如果页面已经搭完,那么前端可以先做一些假数据,根据这些假数据进行交互的设计。
    • 后端写完接口,进行联调。

进阶参考

  1. table标签
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值