react hooks

195 阅读11分钟

一、useState的应用  状态管理

function App() {  const [name, setName] = useState("yhl");  const onChange = () => {    setName('wxe')    console.log('name', name)//yhl  }  return (    <div className="App">      {name}      <button onClick={onChange}>更改名字</button>    </div>  );}export default App;

以上为useState的用法,[]表示解构运算,里面第一个是name的值,第二个值setName是改变name的方法,useState括号里的是name的默认值。

注意:

(1)如果状态是一个对象或者数组的话,必须传入传入完整的部分,因为他是替换老状态返回新状态。也就是useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果

(2)useStata是异步回调。也就是对数据更新后,不能立刻得到最新的数据;

解决办法:配合useEffect使用;

  useEffect(() => {    console.log('name2', name)//wxe  }, [name])

创建新的变量保存最新的数据

  const onChange = () => {    const newName = 'wxe'    setName(newName)    console.log('name1', newName)  }

(3)在多个useState()调用中,渲染之间的调用顺序必须相同。

仅从React 函数调用 Hook:必须仅在函数组件或自定义钩子内部调用useState()。不能在循环,条件,嵌套函数等中调用useState()。

(4)使用回调函数更新数据

const [a, setA] = useState({ c: 0 });
      <button onClick={() => {        setA((a) => {          return { c: a.c + 1 }        })      }}>加1</button>

(5)useState存入的值是引用类型,可使用深拷贝解决;

(6)useState如果保存引用数据,useEffect检测不到变化;

const textObj = {name:'dx'}
const [useState1, setUseState1] = useState(textObj)
/** usestate的操作不要放在函数的最外层,这里只是简单的代码展示,你可以将set操作放在某个函数里面 */
setUseState1((oldUseState1) => {
	oldUseState1.age = 18
return oldUseState1

useEffect(() => {
	console.log(useState1)  
},[useState1])
//结果是没有任何反应


const textObj = {name:'dx'}
const [useState1, setUseState1] = useState(textObj)
/** usestate的操作不要放在函数的最外层,这里只是简单的代码展示,你可以将set操作放在某个函数里面 */
setUseState1((oldUseState1) => {
	oldUseState1.age = 18
	/** 返回一个新的对象,useEffectc才能检测得到 */
return {...oldUseState1}

useEffect(() => {
	console.log(useState1)  // {name: "dx", age: 18}
},[useState1])

(7)useState无法保存一个函数,在useState中,函数会自动调用,并且保存函数返回的值,而不能保存函数本身。建议使用useCallback

参考链接:blog.csdn.net/glorydx/art…

useState 源码中的链表实现

import React from 'react';
import ReactDOM from 'react-dom';

let firstWorkInProgressHook = {memoizedState: null, next: null};
let workInProgressHook;

function useState(initState) {
    let currentHook = workInProgressHook.next ? workInProgressHook.next : {memoizedState: initState, next: null};

    function setState(newState) {
        currentHook.memoizedState = newState;
        render();
    }
  	// 这就是为什么 useState 书写顺序很重要的原因
		// 假如某个 useState 没有执行,会导致指针移动出错,数据存取出错
    if (workInProgressHook.next) {
        // 这里只有组件刷新的时候,才会进入
        // 根据书写顺序来取对应的值
        // console.log(workInProgressHook);
        workInProgressHook = workInProgressHook.next;
    } else {
        // 只有在组件初始化加载时,才会进入
        // 根据书写顺序,存储对应的数据
        // 将 firstWorkInProgressHook 变成一个链表结构
        workInProgressHook.next = currentHook;
        // 将 workInProgressHook 指向 {memoizedState: initState, next: null}
        workInProgressHook = currentHook;
        // console.log(firstWorkInProgressHook);
    }
    return [currentHook.memoizedState, setState];
}

function Counter() {
    // 每次组件重新渲染的时候,这里的 useState 都会重新执行
    const [name, setName] = useState('计数器');
    const [number, setNumber] = useState(0);
    return (
        <>
            <p>{name}:{number}</p>
            <button onClick={() => setName('新计数器' + Date.now())}>新计数器</button>
            <button onClick={() => setNumber(number + 1)}>+</button>
        </>
    )
}

function render() {
    // 每次重新渲染的时候,都将 workInProgressHook 指向 firstWorkInProgressHook
    workInProgressHook = firstWorkInProgressHook;
    ReactDOM.render(<Counter/>, document.getElementById('root'));
}

render();

引用链接:juejin.cn/post/684490…

二、useEffect 

利用useEffect可以在函数组件中执行副作用操作。特指那些没有发生在数据向视图转换过程中的逻辑。如:ajax请求、原生dom元素的访问、绑定/解绑事件、添加订阅、设置定时器、本地持久化缓存、记录日志等;

react组件中有两种常见副作用操作:需要清除的和不需要清除的。

无需清除的。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。

useEffect就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它与class组件中的生命周期函数componentDidMount 、componentDidUpdata和componentWillUnmount具有相同的用途,只不过被合成了一个api。

useEffect接受一个函数,该函数会在组件渲染到屏幕之后才执行,该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容。

import React,{Component,useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
function Counter(){
    const [number,setNumber] = useState(0);
    // useEffect里面的这个函数会在第一次渲染之后和更新完成后执行
    // 相当于 componentDidMount 和 componentDidUpdate:
    useEffect(() => {
        document.title = `你点击了${number}次`;
    });
    return (
        <>
            <p>{number}</p>
            <button onClick={()=>setNumber(number+1)}>+</button>
        </>
    )
}
ReactDOM.render(<Counter />, document.getElementById('root')); 

需要清除的副作用。

副作用函数可以通过返回一个函数来指定如何清除副作用。为防止内存泄漏,清除函数会在组件卸载前执行。如果组件多次渲染,则在执行下一个effect之前,上一个effect就已经被清除。

  useEffect(() => {    let timer = setInterval(() => {      console.log(num)      setNum((num) => num + 1)    }, 2000)
 // useEffect 如果返回一个函数的话,该函数会在组件卸载和更新时调用
        // useEffect 在执行副作用函数之前,会先调用上一次返回的函数
        // 如果要清除副作用,要么返回一个清除副作用的函数
    return () => {      console.log('卸载')     clearInterval(timer)     }  })
或者
  // useEffect(() => {  //   let timer = setInterval(() => {  //     console.log(num)  //     setNum((num) => num + 1)  //   }, 2000)  // }, [])  //要么在这里传入一个空的依赖项数组,这样就不会去重复执行  return (    <div className="App">      <p>{num}</p>    </div>  );  return (    <div className="App">      <p>{num}</p>    </div>  );

effect进行性能优化

effect第二个参数是依赖数组项,控制着useEffect的进行。

  • 如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可

  • 如果只想运行一次的effect(组件挂载和卸载时执行),可以传递一个空数组,作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行

  • 不带第二个参数,每次组件重新渲染或者更新都执行清理或者执行effect,可能会导致性能问题,比如两次渲染的数据完全一样。

    useEffect(() => { console.log('name2', name)//wxe }, [name])// 数组表示 effect 依赖的变量,只有当这个变量发生改变之后才会重新执行 efffect 函数

useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

useEffect 和 useLayoutEffect 的区别?

useEffect 在渲染时是异步执行,并且要等到浏览器将所有变化渲染到屏幕后才会被执行。

useLayoutEffect 在渲染时是同步执行,其执行时机与 componentDidMount,componentDidUpdate 一致。useLayoutEffect 则是在 DOM 更新完成后,浏览器绘制之前执行,在绘制之前执行的,会阻塞页面的绘制。

学习链接:useLayoutEffect和useEffect两者区别

两者执行机制

三、useRef

类组件获取dom元素用的是字符串、回调函数和React.createRef();函数组件使用useRef。useRef返回一个可变的ref对象,其current属性被初始化为传入的参数(initialValue)。

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

    const divRef = useRef<HTMLDivElement | null>(null);//ts接口定义 console.log(divRef.current?.style) return (

    {num}

    );

    组件生命周期期间,useRef指向的对象都是一直存在的; 每次渲染时,useRef都指向同一个引用的对象;总结起来就是:useRef生成的对象,在组件生命周期期间内存地址都是不变的。

forwardRef

因为函数组件没有实例,所以无法像类组件一样接受ref属性;

forwardRef可以在父组件中操作子组件的ref对象;forwardRef可以将父组件的ref对象转发到子组件中的dom元素上;子组件接受props和ref作为参数。

import React, { useState, useEffect, useRef } from "react";const Child = React.forwardRef((props: any, ref: any) => {    console.log('ref', ref)    return (        <input type="text" ref={ref} />    )})function Parent() {    let [number, setNumber] = useState<number>(0);    // 在使用类组件的时候,创建 ref 返回一个对象,该对象的 current 属性值为空    // 只有当它被赋给某个元素的 ref 属性时,才会有值    // 所以父组件(类组件)创建一个 ref 对象,然后传递给子组件(类组件),子组件内部有元素使用了    // 那么父组件就可以操作子组件中的某个元素    // 但是函数组件无法接收 ref 属性 <Child ref={xxx} /> 这样是不行的    // 所以就需要用到 forwardRef 进行转发    const inputRef = useRef<HTMLDivElement | null>(null);//{current:''}    function getFocus() {        console.log('inputRef', inputRef)        inputRef.current?.focus();    }    return (        <>            <Child ref={inputRef} />            <button onClick={() => setNumber(number + 1)}>+</button>            <button onClick={getFocus}>获得焦点</button>        </>    )}export default Parent;

四、优化

useCallback:接受一个回调函数参数和一个依赖数组项(子组件依赖父组件的状态,即子组件会使用到父组件的值)。useCallback会返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。

应用场景

通常在将一个组件中的函数,传递给子元素进行回调使用时,使用useCallback对函数进行处理;
1.useCallback会返回一个函数的memoized(记忆的)值
2.在依赖不变的情况下,多次定义的时候,返回的值是相同的

总结如下:

1、父组件传给子组件的是函数,即使父组件改变的是A值,正常情况下也会触发渲染;2、第二项加空数组时虽然不触发重新渲染,但是函数中的state状态值是初始值;
3、当没有第二项值,会触发重复渲染,函数中的state状态值是最新值;
5、当加上useCallback是第二项是依赖项,只有当依赖项发生改变时,子组件才会触发渲染。6、利用ref,当依赖项发生改变时,子组件可以不用渲染,函数内部也可以获取最新状态值。

useMemo

用来缓存数据,当 组件内部某一个渲染的数据,需要通过计算而来,这个计算是依赖与特定的state、props数据,我们就用useMemo来缓存这个数据,以至于我们在修改她们没有依赖的数据源的情况下,多次调用这个计算函数,浪费计算资源。

把创建函数和依赖项数组作为参数传入useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

useMemo 接收一个函数和依赖数组,当数组中依赖项变化的时候,这个函数就会执行,返回新的值。

import React, { FC, memo, useState, useMemo, useCallback } from "react"
interface Props {    [key: string]: any}const Children: 
FC<Props> = memo((props) => {    console.log('props', props)    return (      
  <button onClick={props.onClick}>{props.data.number}</button>    )})
let oldData: any, 
oldAddClick: any;export default function Counter2() {   
 console.log('Counter render');    

const [name, setName] = useState('计数器');  
  const [number, setNumber] = useState(0);    // 父组件更新时,这里的变量和函数每次都会重新创建,那么子组件接受到的属性每次都会认为是新的    // 所以子组件也会随之更新,这时候可以用到 useMemo   
 // 有没有后面的依赖项数组很重要,否则还是会重新渲染    // 如果后面的依赖项数组没有值的话,即使父组件的 number 值改变了,子组件也不会去更新    
//const data = useMemo(()=>({number}),[]);   
 const data = useMemo(() => ({ number }), [number]);    
console.log('data', data)   
 console.log('data===oldData ', data === oldData);  
  oldData = data;    // 有没有后面的依赖项数组很重要,否则还是会重新渲染 
const addClick = useCallback(() => {        setNumber(number + 1);    }, [number]);   
 console.log('addClick', addClick)   
 console.log('addClick===oldAddClick ', addClick === oldAddClick);   
 oldAddClick = addClick;    return (        <>         
   <input type="text" value={name} onChange={(e) => setName(e.target.value)} />            {number}            <Children data={data} onClick={addClick} />        </>    )}

五、useReducer

在 hooks 中提供了的 useReducer 功能,可以增强 ReducerDemo 函数提供类似 Redux 的功能。它和redux中的reducer很像。

  • useState 的替代方案,它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

  • 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。

    let initialState = 0; // 如果你希望初始状态是一个{number:0} // 可以在第三个参数中传递一个这样的函数 ()=>({number:initialState}) // 这个函数是一个惰性初始化函数,可以用来进行复杂的计算,然后返回最终的 initialState const [state, dispatch] = useReducer(reducer, initialState, init);

    import React, { FC, useReducer } from "react"export default function Counter() { const initialState = 0; function reducer(state: any, action: any) { switch (action.type) { case 'increment': return { number: state.number + 1 }; case 'decrement': return { number: state.number - 1 }; default: throw new Error(); } } function init(initialState: any) { return { number: initialState }; } const [state, dispatch] = useReducer(reducer, initialState, init) return ( <> Count: {state.number} <button onClick={() => dispatch({ type: 'increment' })}>+ <button onClick={() => dispatch({ type: 'decrement' })}>- </> )}

    import React, { FC, useReducer } from "react"export default function Counter() { // 第一个参数:应用的初始化 const initialState = { number: 0 }; // 第二个参数:state的reducer处理函数 function reducer(state: any, action: any) { switch (action.type) { case 'increment': return { number: state.number + 1 }; case 'decrement': return { number: state.number - 1 }; default: throw new Error(); } } // 返回值:最新的state和dispatch函数 const [state, dispatch] = useReducer(reducer, initialState) return ( <> Count: {state.number} <button onClick={() => dispatch({ type: 'increment' })}>+ <button onClick={() => dispatch({ type: 'decrement' })}>- </> )}

其他链接:blog.csdn.net/qq_22833925…

六、自定义hooks

useRize.jsx

import React, { useState, useEffect, useCallback } from "react"import React, { useState, useEffect, useCallback } from "react"const Resize = () => {    const [size, setSize] = useState({        width: document.documentElement.clientWidth,        height: document.documentElement.clientHeight    })    const onResize = useCallback(() => {        setSize({            height: document.documentElement.clientHeight,            width: document.documentElement.clientWidth        })    })    useEffect(() => {        window.addEventListener('resize', onResize);        return () => {            window.addEventListener('resize', onResize);        }    }, [onResize])    return size;}export default Resize

// 自定义useCount的Hooksfunction useCount() {  const [count, setCount] = useState(0);  return { count, setCount };}const App = () => {  const { count, setCount } = useCount()  const size = Resize()  return (    <div>      {size.width}      {/* {count}      <button onClick={() => setCount(count + 1)}>add</button> */}    </div>  )