React常用Hook系列

Hook 常用Api系列:

1.useState

它与class组件里面的 this.state 提供的功能完全相同
useState接收一个初始值,这个初始值可以是对象,也可以是简单数据类型。setState 函数用于更新状态。它接受一个新的状态值,并排队等待重新渲染该组件。

先贴上一个我自己手写的useState粗糙源码,帮助大家更好的理解:

 /*
			  首先,我们需要在外部声明一个state空数组和下标
			  作用是再函数调用时,值不会被刷新 
			 */
			 let state=[];
			 let index=0;
			 //useState函数     //默认值
			 function myUseState(initialValue) {
			   let currentIndex=index;	//引入中间变量currentIndex就是为了保存当前操作的下标index。
			 
			   state[currentIndex] = state[currentIndex]===undefined? initialValue:state[currentIndex];  //判断,给state存入值
			   //将state对应下标的值进行更新
			   const setState = (newValue) => {
			     state[currentIndex] = newValue; 
			     render(); 
			   };
			   
			   index+=1;  //如果有多个myUseState,每次更新完state值后,index值+1
			   //返出为数组
			   //第一个参数是当前值,第二个是赋值后的返出的值
			   return [state[currentIndex], setState];
			 }
			 
			 //render渲染,选然后初始化index
			 const render = () => {
			   index=0;	//重要的一步,必须在渲染前后将index值重置为0,不然index会一种增加1
			   ReactDOM.render(<App />, document.getElementById("root"));
			 };
			  
			 // 使用myUseState
			 const App = () => {
			   const [num, setNum] = myUseState(0);
			   const [age, setAge] = myUseState(0);
			   return (
			 	  <div classNam="App">
				  
			 		 <p>num的值:{num}</p>
			 		 <button onClick={()=>{setNum(n+1)}}> 对Num+1</button> 
					 
			 		 <p>age的值:{Age}</p>
			 		 <button onClick={()=>{setAge(m+1)}}> 对Age的值1</button> 
			 	  </div>
			 	  );
			 };
			 ReactDOM.render(<App />, document.getElementById("root"));

有细心小伙伴会发现:App用了state和index,那其他组件用什么?放在全局作用域重名了怎么办?

这里有两个方法:
解决办法1: 每个组件都创建一个state和index。
解决办法2: 放在组件对应的虚拟节点对象上
注意: React的节点应该是FiberNode,state的真实名称为memorizedState,index的实现使用了链表

useState只有一个参数: 接收一个初始化状态的值(设置初始值),再第一次被组件调用时使用来作为初始化值(如果不设置则默认为undefined);

useState的返回值: 返回一个数组,数组包含两个元素;
元素一: 当前状态的值(第一次调用为初始化值);
元素二: 是一个设置状态值变化的函数;
不过我们如果总是使用索引来获取这两个元素总是不方便的, 因此在开发中我们通常是会对数组进行解构(当然要取什么名字是自定义的)

使用它们会有两个额外的规则:
·只能在函数组件的顶层调用 Hook。不能在循环语句、条件判断语句或者子函数中调用。
·只能在 React 的函数组件和自定义hook中调用 Hook。不能在其他 JavaScript 函数中调用。

2.useEffect && useLayoutEffect

useEffect 的回调函数是【异步宏任务】,在下一轮事件循环才会执行。根据 JS 线程与 GUI 渲染线程互斥原则,在 JS 中页面的渲染线程需要当前事件循环的宏任务与微任务都执行完,才会执行渲染线程,渲染页面后,退出渲染线程,控制权交给 JS 线程,再执行下一轮事件循环。

好处:这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因为绝大多数操作不应阻塞浏览器对屏幕的渲染更新

坏处页面会有闪烁此时需要使用useLayoutEffect来解决这个问题(在下面),产生二次渲染问题,第一次渲染的是旧的状态,接着下一个事件循环中,执行改变状态的函数,组件又携带新的状态渲染,在视觉上,就是二次渲染。

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做
·componentDidMount 组件挂载 当数组为空时useEffect只执行一次
·componentDidUpdate 组件更新 当数组不为空有监听对象时,挂载和更新useEffect都会执行
·componentWillUnmount 组件将要摧毁 当return一个处理逻辑时,useEffect在销毁组件时执行

这三个函数的组合。

useEffect传递两个参数,第一个参数是逻辑处理函数,第二个参数是一个数组
useEffect的第二个参数存放变量(可以不传,也不会报错,但是浏览器会无限循环执行处理逻辑的函数),当数组内存放变量发生改变时,第一个参数预设的逻辑处理函数将会被执行

useEffect((逻辑处理函数,数组(空包含了state)) => {
/** 执行逻辑 */
},[])

在第二个参数为空数组或者有变量的时候,会出现以下情况:

1.第二个参数如果时空数组,逻辑处理函数里面的逻辑只会在组件挂载时执行 一次 , 相当于 componentDidMount

2.第二个参数如果不为空数组,如下

const [age, setAge] = useState(1);
const [name, setNmae] = useState(2);
useEffect(() => {
/** 执行逻辑 */
},[age,name])

此时逻辑处理函数会在组件挂载时执行一次和(age或者name变量在栈中的值发生改变时执行一次) 就相当于componentDidMount 和 componentDidUpdate 的结合

第二个参数监听常见踩雷 :

1.useEffect执行函数里面改变了useEffect监测的变量:
逻辑处理的时候对监听的变量进行赋值操作,当监听元素的地址发生变化时,useEffect会再一次的执行,如此循环往复,形成无限循环咯


const [age, setAge] = useState(1);

useEffect(() => {
  /** 执行逻辑 */
  setAge(age+1)
},[age,name])

2.useEffect监测不到依赖数组/对象(引用类型)元素的变化:

const [a, setA] = useState({
name: 'ikun',
age: '18',
})

const changeA = () => {
	//形参,表示a的值
	setA((old) => {
	old.name = 'lovekunkun'
	return old
	})
}

/**当changeA执行却没有打印 a*/
useEffect(() => {
/** 执行逻辑 */
console.log(a)
},[a])

原因是引用类型存放在堆内存内,而useEffect监听不到堆内存内的变化,所以会失效,所有的引用类型都需要注意这一点。但是可以通过 useReducer组合,实现引用类型的useEffect。
代码如下:

import { useEffect,useReducer } from "react";
//定义好obj
const obj = {
  num: 1,
  age: 2,
};
 
//定义reducer函数
function reducer(state, action) {
  //解构action
  const { num, age } = state;
  //写好type返出
  if (action.type === 'cheng') {
    return { num: num *age, age };
  } else if (action.type === 'age') {
    console.log('pppp')
    return { num, age: action.age };
  } else {
    throw new Error();
  }
}

export default  function Counter() {
  
  const [state, dispatch] = useReducer(reducer, obj);
  const { num, age } = state;
 
  useEffect(() => {
    //写好一个定时器
    const id = setInterval(() => {
      dispatch({ type: 'cheng' });
    }, 1000);
    //当销毁组件时,清除定时器
    return () => clearInterval(id);
    //我们直接监听dispa
  }, [dispatch]);
 
  return (
    <>
      <h1>{num}</h1>
      <input value={age} onChange={e => {
        dispatch({
          type: 'age',
          age: Number(e.target.value)
        });
      }} />
    </>
  );
}

这里我们采用了监听dispatch,而dispatch它是预先定义好的,不会更改,通过dispatch再去发送action,更新的逻辑就交给reducer去处理

useLayoutEffect

useLayoutEffect 与 componentDidMount、componentDidUpdate 生命周期钩子是【异步微任务】,在渲染线程被调用之前就执行。这意味着回调内部执行完才会更新渲染页面,没有二次渲染问题。

好处:没有二次渲染问题,页面视觉行为一致。

坏处:在回调内部有一些运行耗时很长的代码或者循环时,页面因为需要等 JS 执行完之后才会交给渲染线程绘制页面,等待时期就是白屏效果,即阻塞了渲染

在用法上,我们只需要将useEffuct改成useLayoutEffect即可

3.useMemo

作用:
useMemo相当于vue的计算属性,只有当它所依赖的值发生变化的时候,useMemo才会重新返回一个新的值

应用场景:
常用于父子组件内,当父更新一个值传递到某个子组件内后,render会把其它挂载的组件也进行渲染,这就造成了不必要的性能开销,此时使用useMemo可以避免这个问题。

注意:
如果用useMemo返回的数据,只有对应的变量会发生变化,而其它的常量是不会变化的,所以如果要对useMemo的返回值进行一些splice操作的话,是无法起作用的,它无法改变其自己本身已经定义好的数据结构,改变的只是里面的变量。

useMemo的执行顺序为:
useMemo -> render -> useEffect

使用说明 :
同useEffect相同,包含两个参数,第一个是执行回调,第二个是监听的值

    useMemo(() => {
     }, []);

4.useCallBack

作用:
同useMemo相同,但是返出的是一个函数而不是值

应用场景:
监听一些如map,for一类的处理事件的值,避免无意义的开销

使用说明 :

import Title from './26Title'
import Button from './26Button'

function app(){ 
 const [num, setNum] = useState(1);         
 const [age, setAge] = useState(1);         
 
 const incrementAge =useCallback(() => {
    ()=>{
      setAge(age+999)
      console.log("触发了incrementAge")
    }
  }, [age]);
 
  const incrementSalary =useCallback(() => {
    ()=>{
      setNum(num+100)
       console.log("触发了incrementSalary ")
    }
  }, [num]);


  return (
    <>
    <Title />
     <Button
        handleClick={incrementAge}
      >incrementAge</Button>

     <Button
        handleClick={incrementSalary}
      >incrementSalary</Button>
    </>
  );
  }
}

Title

import React from 'react'

function Title() {
  console.log('Rendering Title')
  return (
    <h2>useCallback</h2>
  )
}

export default Title

Button

import React from 'react'

function Button(props: {
  handleClick: () => void
  children: string
}) {
  console.log('Rendering button', props.children)
  return (
    <button onClick={props.handleClick}>
      {props.children}
    </button>
  )
}
export default Button

当刷新进入页面后rending button会打印两次 reding title会打印一次,共计3次。
不使用useCallBack时,每次点击都是三个打印
使用useCallback后,只会显示一个reading button的打印

5.useRef

useRef会返回一个可变的ref对象,它会把初始化参数绑定到current属性上。当然初始化属性可以通过函数返回。

useRef 返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref 对象都是同一个(使用 React.createRef ,每次重新渲染组件都会重新创建 ref)

通过useRef保存的属性可以在渲染前后不发生变化,并且改变.current属性不会触发组件渲染!

使用场景

媒体播放:你还可以使用引用访问媒体资源,如图像、音频或视频,并与它们的渲染方式进行交互。例如,当元素进入视口时,自动播放视频或延迟加载图像。

复杂动画触发:传统上,CSS keyframes 或 timeout 用来确定何时启动动画。在某些情况下(可能更加复杂),可以使用 ref 来观察 DOM 元素并确定何时启动动画。

与 input 元素交互:通过使用引用,可以访问 input 元素并执行聚焦、变化跟踪或自动完成等功能。

与第三方 UI 库交互:ref 可用于与第三方 UI 库创建的元素交互,使用标准 DOM 方法访问这些元素可能比较困难。例如,如果你使用第三方库生成滑块,你可以使用 ref 来访问滑块的 DOM 元素,而不必知道滑块库的源代码结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值