其他分享
首页 > 其他分享> > React Hooks

React Hooks

作者:互联网

Hooks

概念

Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。Hook是 React 16.8 (当前版本18,项目使用17)的新增特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

优势:

基础Hook

useState

1、参数:useState() 方法里面唯一的参数就是初始 state

2、返回值:当前 state 以及更新 state 的函数[count,setCount]

3、使用方式:

(1)使用直接调用count即可

(2)更新值使用setCount(3)

import React, { useState } from 'react';

function Example() {
  	// 声明一个叫 "count" 的 state 变量
  	const [count, setCount] = useState(0);
    
    return (
        <div>
            <p>You clicked {this.state.count} times</p>
            <button onClick={() => this.setState({ count: this.state.count + 1 })}>
                Click me
            </button>
        </div>
    );
}

useEffect

Effect Hook 可以让你在函数组件中执行副作用操作,所谓副作用就是:数据获取,设置订阅以及手动更改 React 组件中的 DOM等等操作。

通过这个钩子可以告诉 React 组件需要在渲染后执行某些操作。React 会保存传递的函数(effect),并且在执行 DOM 更新之后调用它。

注意事项:

1、useEffect 会在每次渲染后都执行

2、调用useEffect需要在组件内部(如下:Example内)

3、无需清除effect的写法

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

4、需清除effect的写法(通过返回函数来解决)

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  // 这里的useEffect返回了一个函数cleanup
  // 这个函数会在effect清除是执行
  // 对于这种添加和删除订阅的业务就可以用这种方式
  // 当我们关闭或刷新浏览器是都会触发cleanup清除订阅的业务,有效防止内存泄漏风险
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

5、跳过 Effect 进行性能优化(减少不必要的渲染)

通过传入第二个参数解决,只有在第二个参数发生变化时才会重新渲染。

如果要使用此优化方式,需确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量(就是这个变量需要在外部声明并在effect中使用),否则你的代码会引用到先前渲染中的旧变量。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

useContext

实现组件通信的一种手段。

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop 决定。

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext providercontext value 值。即使祖先使用 React.memoshouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。

React.createContextreact使用context实现跨组件通信的基础(创建)

使用方式:

1、创建

const MyContext = React.createContext(themes.light);

2、传值(长辈组件)

function App() {
  return (
    <MyContext.Provider value={themes.dark}>
      <div>
      	<MyButton />
      </div>
    </MyContext.Provider>
  );
}

3、接收(孩子组件)

function MyButton() {
  // useContext 接收值
  const params = useContext(MyContext);
  return (
    <div>
      接收到的context:{params}
    </div>
  );
}

其他Hook

useReducer

useMemo

useMemo是用来减少没必要的重复渲染,从而提高执行效率

代码示例:在下面这个代码中,当num发生变化的时候会重新触发渲染,导致list重复渲染

import React, { useState } from "react";

export default function App() {
  const [len] = useState(10);
  const [num, setNum] = useState(0);
  const list = [];
  for (let i = 0; i < len; i++) {
    console.log(1234);
    list.push(<li>{i}</li>);
  }
  return (
    <div>
      <ul>{list}</ul>
      <input
        type="text"
        value={num}
        onChange={(e) => {
          setNum(e.target.value);
        }}
      />
    </div>
  );
}

使用useMemo优化后代码:在num发生变化后,list组件不会重新渲染,触发渲染的条件来自于useMemo的第二个参数,只有数组中的变量发生变化才会触发list的重新渲染

import React, { useState, useMemo } from "react";

export default function App() {
  const [len] = useState(10);
  const [num, setNum] = useState(0);
  const list = useMemo(() => {
    const list = [];
    for (let i = 0; i < len; i++) {
      console.log(1234);
      list.push(<li>{i}</li>);
    }
    return list;
  }, [len]);

  return (
    <div>
      <ul>{list}</ul>
      <input
        type="text"
        value={num}
        onChange={(e) => {
          setNum(e.target.value);
        }}
      />
    </div>
  );
}

useCallback

useCallback的作用是用来避免子组件不必要的reRender(避免不必要的重复渲染),不是用来解决组件中有过多内部函数导致的性能问题

使用实例:

import {useCallback, useState} from "react";
 
export const UseCallbackExample = () => {
    const [count, setCount] = useState(0);
    const logCount = useCallback(() => {
        console.log(count);
    }, [count]);
 
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                click me
            </button>
            <button onClick={logCount}>log count</button>
        </div>
    );
}

useMemo与useCallback

  1. useCallbackuseMemo主要用来做数据缓存,它们的更新依赖于第二个参数是否发生了变化。
  2. useMemouseCallback是在页面渲染前执行的hooks,因此这两个hooks中执行的内容应该是纯函数,不会影响页面元素的更新,页面也就不会陷入死循环。

不同:

  1. useMemo返回的是一个变量的值,useCallback返回的是一个函数。对应到function组件最后要return的html代码部分,useMemo就是作为一个值来使用的,而useCallback则是被绑定到onClick上,作为要执行的函数。这就是它俩的本质区别。(useCallback针对函数,useMemo针对值)

  2. useCallback(fn, deps) 等价于 useMemo(() => fn, deps)

useRef

作用:

  1. useRef 用来获取DOM元素对象

    当我们需要获取元素对象的时候, 首先引入useRef, 其次调用useRef()方法接收它的返回值,我们需要获取那个DOM元素就在那个DOM元素上进行绑定,通过ref属性将useRef的返回值绑定到元素身上,这样useRef的返回值,通过useRef返回一个对象,对象内部有个current属性,这个属性就对应着我们需要的元素对象;

    import React, {useRef} from "react";
    
    function Ref (){
        const box = useRef()
    
        return (
            <div>
                <div ref={box}>useRef</div>
                <button onClick={() => console.log(box)}>+1</button>
            </div>
        )
    }
    export default Ref;
    
    
  2. 保存数据

    为什么不用let一个变量来保存数据, 因为在使用定时器更新状态数据时, 数值的每次变化都会引起组件的更新,每次更新都重新let一个变量,所以在进行解绑操作的时候,你的let变量为null,它并没有保存定时器,所以以上场景需要使用useRef进行保存数据,useRef不会因为组件的更新而丢失数据,虽然组件进行了更新,但是通过useRef保存的数据是不会丢失的,这里通过useRef中的current来进行保存也是官方要求的写法,所以如果你想要保存的数据不会因为组件的更新而丢失,就可以使用useRef来保存数据

    // 代码实例——保存数据
    import React, {useRef, useEffect, useState} from "react";
    
    function Ref (){
        let timerId = useRef()
        const [count, setCount] = useState(0)
        useEffect(() => {
            timerId.current = setInterval(() => {
                setCount(count => count + 1)
            }, 1000)
        }, [])
        const stop = () => {
            console.log(timerId)
            clearInterval(timerId.current)
        }
        return (
            <div>
                <div>{count}</div>
                <button onClick={stop}>停止</button>
            </div>
        )
    }
    export default Ref;
    
  3. useRef获取组件的方法

    import React, { useRef } from 'react'
    import { Child } from './child'
    
    export default function App() {
    
        const onRef = useRef();
        const pOnclick = ()=>{
            onRef.current.onclickAction();
        }
        
        return (
            <div>
                <span onClick={pOnclick}>点我调用子组件方法</span>
                <Child onRef={onRef} />
            </div>
        )
    }
    

useImperativeHandle

正常情况下 ref 是不能挂在到函数组件上的,因为函数组件没有实例,但是 useImperativeHandle 为我们提供了一个类似实例的东西。在函数组件中就可以通过useImperativeHandle 将子组件的方法暴露出去供其他使用。

结合useRef第三点可以实现父组件调用子组件方法

import React, { useImperativeHandle } from 'react'

export const Child = ({ onRef }) => {

    const onclickAction = () => {
        console.log("我是子组件 我被调用了!!!")
    }

    useImperativeHandle(onRef, () => ({
        onclickAction,
    }))

    return (
        <span onClick={onclickAction} />
    )
}

useLayoutEffect

用法同useEffect

区别如下:

总结:

  1. 优先使用 useEffect,因为它是异步执行的,不会阻塞渲染
  2. 会影响到渲染的操作尽量放到 useLayoutEffect中去,避免出现闪烁问题
  3. useLayoutEffectcomponentDidMount是等价的,会同步调用,阻塞渲染
  4. useLayoutEffect在服务端渲染的时候使用会有一个 warning,因为它可能导致首屏实际内容和服务端渲染出来的内容不一致(解决这个问题需要自定义hook判断环境决定使用useEffect还是useLayoutEffect)。

样例:

import * as React from 'react';

function Test() {
  const [state, setState] = React.useState('hello world');
  React.useEffect(() => {
    console.log('useEffect');
    setState('hello useEffect');
  }, []);
  React.useLayoutEffect(() => {
    console.log('useLayoutEffect');
    setState('hello useLayoutEffect');
  }, []);
  return <div>{state}</div>;
}

export default Test;

注意:渲染顺序如下:hello world-->hello useLayoutEffect-->hello useEffect

usedebugvalue

使用实例:

import { useState, useEffect, useDebugValue } from 'react'

const Hook = () => {
  const [ isGone, setIsGone ] = useState(true)
  // 我认为useDebugValue就是用来说明自定义hook标签是做什么的
  useDebugValue('xm isGone', (key) => {
    return `${key}_${new Date().getMinutes()}: ${new Date().getSeconds()}`
  })
  useEffect(() => {
    setIsGone(false)
    return () => {
      setIsGone(true)
    };
  }, [])
  return isGone
}

export default Hook

自定义Hook

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中,本质上只是一种函数代码逻辑的抽取。

自定义 Hook 是一个函数,其名称必须以use开头,函数内部可以调用其他的 Hook。

1、使用公共的userContext

export default function useUserContext() {
  const user = useContext(UserContext);
  const token = useContext(TokenContext);

  return [user, token];
}

import React, { useContext } from 'react';
import useUserContext from '../hooks/user-hook';

export default function CustomContextShareHook() {
  const [user, token] = useUserContext();
  console.log(user, token);
  
  return (
    <div>
      <h2>CustomContextShareHook</h2>
    </div>
  )
}

2、使用公共的数据存储的localStorage

// hooks/local-store-hook.ts
import React,{useState, useEffect} from 'react';
function useLocalStorage(key) {
  const [data, setData] = useState(() => {
    return JSON.parse(window.localStorage.getItem(key))
  });

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(data));
  }, [data]);
  return [data, setData];
}

export default useLocalStorage;
// Customer.tsx
import React, { useState, useEffect } from 'react';

import useLocalStorage from '@/hooks/local-store-hook';

export default function CustomDataStoreHook() {
  const [name, setName] = useLocalStorage("name");

  return (
    <div>
      <h2>CustomDataStoreHook: {name}</h2>
      <button onClick={e => setName("kobe")}>设置name</button>
    </div>
  )
}

Hook规则

只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useStateuseEffect 调用之间保持 hook 状态的正确。

function Form() {
  const [name, setName] = useState('Mary');
   // 在条件语句中使用 Hook 违反第一条规则
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }
  const [surname, setSurname] = useState('Poppins');
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });
}
///////////////////////////////////////////////////////////////////////////////////////////////
// 会出现当下的结果
useState('Mary')           // 1. 读取变量名为 name 的 state(参数被忽略)
// useEffect(persistForm)  // 

标签:const,Hooks,React,useState,组件,return,useEffect
来源: https://www.cnblogs.com/yangguanglei/p/16598355.html