• 内置hooks(1):如何保存组件状态和使用生命周期?


    一、基本hooks的用法

      如果你用过基于类的组件,那对组件的生命周期函数一定不会陌生,例如componentDidMount,componentDidUpdate,等等。如果没有使用过,也没关系,基于hooks去考虑组件的实现,这会是一个非常不同的思路,你完全不用去关心一个组件的生命周期是怎样的。遇到需求直接考虑hooks中去如何实现。

      react提过的hooks其实非常少,一共只有10个,比如useState、useEffect、useCallBack、useMemo、useRef、useContext等等,接下来我们先学习useState和useEffect这两个最核心的hooks。掌握了hooks,那90%的react都可以开发。

      只写hooks的功能其实非常简单,多看看官网文档就可以了,我们要学习的是如何用hooks的思路去实现功能

    二、实践

      1.useState:让函数组件具有持续状态的能力

      state是react组件的一个核心机制,那么useState这个hooks就是用来管理state的,他可以让函数组件具有维持状态的能力。也就是说,在一个函数组件的多次渲染之间,这个state是共享的。下面的例子时显示useState的用法:

    import React,{useState} from 'react';
    
    function Example(){
       //创建一个保存count的state,并给初始0
       const [count,setCount]=useState(0);
       return(
           <div>
               <p>{count}</p>
                <button onClick={()=>setCount(count+1)}>
          +
    </button>
    </div>
    )  
    }

      在这个例子中,我们生命了一个名为count的state,并得到了设置这个count的值的函数setCount.当调用setCount时,count这个state就会被更新,并且触发组件的刷新,那末useState这个hook的用法总结出来就是这样的:

    1.useState(initialState)的参数initialState时创建state的初始值,他可以时任意类型,比如数字、对象、数组等等。

    2.useState()的返回值时一个有着两个元素的数组。第一个数组杨素是用来读取state的值,第二个则是用来设置这个state的值。这里要注意的时,state的变量(例子中的count)时只读的所以我们必须通过第二个数组元素setCount来设置他的值。

    3.如果要创建多个state,那么我们就需要多次调用useState.比如要创建多个state,使用的代码如下:

    //定义一个年龄的state,初始值时42
    const [age,setAge]=useState(42);
    //定义一个水果的state,初始值是banana
    const [fruit,setFruit]=useState('banbana');
    //定义一个一个数组state,初始值是包含一个todo的数组
    const [todos,setTodos]=useState([{text:'Learn Hooks'}])

    从这段代码中可以看到,useState是一个非常简单的hook,他让你很方便地区创建一个状态,并且提供一个特定的方法(比如setAge)来设置这个状态。

      如果你之前用过类组件,那么这里的useState就和组件的setState非常类似。不过两者最大的区别就在于,类组件中的state只能有一个。以我们一般把对象作为一个state,然后再通过不同的属性来表示不同的状态。而函数组件中用useState则可以很容易的创建多个state,所以他更加语义化。

      可以说,state是react组件非常重要的一个机制,那么什么样的值应该保存再state中呢?通常来说,我们要遵循的一个原则就是:state中永远不要保存课可以通过计算得到的值。比如说:

    1.从props传递过来的值。有时候props传递过来的值无法直接使用,而是要通过一定的计算后再在ui展示,比如说排序。那么我们要做的事每次用的时候,都重新排序一下,或者利用某些cache机制,而不是直接放到state中。

    2.从url中读取到的值。比如有些需要读取url中的参数,把他作为组件的一部分状态。那么我们可以在每次需要用的时候从url中读取,而不是读取出来直接放到state中。

    3.从cookie、localStorage中读取的值。通常来说,也是每次要用的时候直接去读取,而不是读出来后放到state里。

      2.useEffect:执行副作用

    useEffect,顾名思义,用于执行一段副作用

      什么是副作用?通常来说,副作用是指一段和当前执行结果无关的代码。比如说要修改函数外部的某个变量,要发起一个请求,等等。也就是说在函数组件的当此执行过程中,

    useEffect中代码的执行不是影响渲染出来的UI的。

    我们先来看看他具体的用法。useEffect可以接收连个参数函数签名如下

    useEffect(callback,dependencies)

      第一个参数为要执行的函数callback,第二个是可选的依赖项数组dependencies。其中依赖项是可选的,如果不指定,那么callback就会在每次函数组件执行完之后都执行;如果指定了,那么只有依赖项中的值发生变化的时候,他才会执行。

      对应到class组件,那么useEffect就涵盖了componentDidMount、componentDidUpdate和componentWillUnmount三个生命周期方法。不过你习惯了使用class组件,那千万不要按照把useEffect对应到某个或者某几个生命周期的用法,需要记住的是,useEffect是每次组件render后完成判断依赖并执行就可以了。

      举个例子,某个组件用于显示一篇Blog文章,那么这个组件会接收一个参数来表示Blog的id。当id发生变化的时候,组件需要发起请求来获取文章内容并展示:

    import React,{useState,useEffect} from"react";
    
    function BlogView({id}){
        //设置一个本地state用于保存blog内容
       const [blogeContent,setBlogContent]=useState(null);
      useEffect(()=>{
         //useEffect的callback要避免直接的async函数,需要封装一下
        const doAsync=async ()=>{
         //当id发生变化时,将当前内容清楚以保持一致性
         setBlogContent(null);
         //发起请求
        const res=await fecch(`/blog-content/${id}/`);
    //将获取的数据放入state
        setBlogContent(await res.text());
    };
    doAsyc();
    
    },[id]);//使用id作为依赖项,变化时则执行副作用
    //如果没有blogContent则认为时在loading状态
    const isloading=!blogContent;
    return <div>
    {isLoading?"Loading...."blogContent}
    </div>
    
    }

      这样,我们就利用useEffect完成了一个简单的数据请求的需求,在这段代码中,我们把ID作为依赖项参数,这样就很自然的在id发生变化时,利用useEffect执行副作用去获取数据。如果在之前的类组件中完成类似的需求,我们就需要在componentDidUpdate这个方法里,自己去判断两次id是否发生变化。如果变了,才去发请求。这样的话,逻辑上就不如useEffect来的直观。

    useEffect还有两个特殊的用法:没有依赖项,以及依赖项作为空数组。代码如下

    1.没有依赖项,则每次render后都会重新执行。例如:

    useEffect(()=>{
       //每次render完一定执行
    console.log(‘re-enderred’)
    
    })

    2.空数组作为依赖项,则只在首次执行时触发,对应到class组件就是componentDidMount.例如:

    useEffect(()=>{
      //组件首次渲染时执行,等价于class组件中的componentDidMount
     console.log('did mount');;
    },[])

    除了这些机制之外,useEffect还允许你返回一个函数,用于在组件销毁的时候做一些清丽的操作。比如移除事件的监听。这个机制就几乎等价于类组件中componentWillUnnoint.举个例子,在组件中,我们需要监听窗口的大小编变化,以便做一些布局上的调整:

    //设置一个size的用于保存当前窗口尺寸
    const [size,setSize]=useState({});
    
    useEffect(()=>{
      //窗口大小变化事件处理函数
      const handler=()=>{
    setSize(getSize());
    };
    //监听resize事件
    window.addEventListener('resize',handler)
    //返回一个callback在组件销毁时调用
    return ()=>{
      //移除resize事件
      window.removueEventLisTener('resize',hander)
    }
    
    })

      总结一下,useEffect让我们能够在下面四种时机去执行一个回调函数产生副作用:

    1.每次render后执行:不提供第二个依赖项参数。比如useEffect(()=>{})。

    2.仅第一次render后执行:提过一个空数组作为依赖项。比如useEffect(()=>{},[])

    3.第一次以及依赖项发生那个变化后执行:提供依赖项数组。比如useEffect(()=>{},[deps]).

    4.组件unMount后执行:返回一个回调函数。比如useEffect(()=>{return ()=>{}},[])

      定义依赖项时,我们需要注意一下三点:

    1.依赖项中定义的变量一定会在回调函数中用到,否则声明依赖项其实时没有意义的,

    2.依赖项一般为一个常量数组,而不是一个变量。因为一般在创建callBack的时候,你其实是非常清楚其中要用到那些依赖项了

    3.react会使用浅比较来对比依赖项是否发生了变化,所以要特别注意数组或者对象类型。如果你是每次创建一个新对象,即使和之前是等价的,也会被认为是依赖项发生了变化,这是一个开始使用hooks是很容易导致bug的地方。例如下代码

    function sample(){
      //这里在每次组件执行时创建了一个新数组
       const todos=[{text:'learn hooks'}];
      useEffect(()=>{
       console.log('todos changed')
    
    },[todos]);  
    }

    代码的愿意可能是todos变化的时候产生一些副作用,但是这里的todos变量是在函数内部创建的,实际上每次产生了一个新数组。所以在作为依赖项的时候进行引用的比较,实际上被认为是发生了变化的。

      掌握hooks的使用规则

    HOOKS本身作为纯粹的js函数,不是通过特殊的API去创建的,而是直接定义一个函数。他需要在降低学习成本和使用成本的同时,还要遵循一定的规则才能正常工作。因而hooks的使用规则包括以下两个:只能在函数组件的顶级作用域使用:只能在函数组件或者hooks中使用。

    Hooks只能在函数组件的顶级作用域使用

      所谓顶级作用域,就是hooks不能再循环、条件判断或者嵌套函数内执行,而必须是再顶层。同时Hooks在组件的多次渲染之间,必须按照顺序被执行。因为在react组件内部,其实是维护了一个对应组件的固定hooks执行列表的,以便在多次渲染之间保持hooks的状态。

      例如:下面代码是可行的,因为hooks一定会被执行到:

    function MyComp(){
        const [count,setCount]=userState(0);
        return <div>
    {count}
    </div>
    }

      而下面代码是错误的,因为在某些条件下hooks是不会被执行到的:

    function MyComp(){
       const [count,setCount]=useState(0);
       if(count>10){
        //错误:不能将hooks用在条件判断里
       useEffect(()=>{
      //...
    },[count])
    //这里可能提前放回组件渲染结果,后面就不能在用hooks了
    if(count==0){
        return 'No content';
    }
    //错误:不能将hooks放在可能的return之后
    const [loding,setLoading]=useState(false);
    //...
    return <div>
    {count}
    </div>
    }
    
    }

      所以hooks的这个规则可以总结为两点:第一,所有hooks必须要被执行到。第二,必须按顺序执行。

    Hooks只能在函数组件或者其他hooks中使用

      hooks作为专门为函数设计的机制,使用的情况只有两种,一种是在函数组件内,另一种是在自定义hooks里面。

    这个规则在函数组件和类组件同时存在的项目中,可能造成一定和困扰,因为hooks简介、直观,我们可能都倾向于hooks来实现逻辑的重用,但是如果一定要在class组件中使用,有一个通用的机制,那就是利用高阶函数组件 模式,将hooks封装成高阶组件,从而让类组件使用。

    例如:

    import React from 'react';
    import { useWindowSize } from '../hooks/useWindowSize';
    
    export const withWindowSize = (Comp) => {
      return props => {
        const windowSize = useWindowSize();
        return <Comp windowSize={windowSize} {...props} />;
      };
    };
    //高阶组件写法
    
    import React from 'react';
    import { withWindowSize } from './withWindowSize';
    
    class MyComp {
      render() {
        const { windowSize } = this.props;
        // ...
      }
    }
    
    // 通过 withWindowSize 高阶组件给 MyComp 添加 windowSize 属性
    export default withWindowSize(MyComp);

    三、总结

      在这里我们学习了useState和useEffect这两个核心hooks的用法,一个用于保存装填,一个用于执行副作用。可以说掌握了这俩个hooks,几乎就能完成大部分的react开发了。

  • 相关阅读:
    hdu2243之AC自动机+矩阵乘法
    jstl
    HDU 3360 National Treasures 奇偶匹配的最低点覆盖
    【UVA】11992
    what is the difference between definition and declaration in c
    UVALive 6485 Electric Car Rally (BFS,PQ)
    Android发展_备份短信
    vxWorks 命令
    【C++智能指针 auto_ptr】
    Get and Post(Unity3D六个发展)
  • 原文地址:https://www.cnblogs.com/qiaozhiming123/p/15908079.html
Copyright © 2020-2023  润新知