常用React Hooks 方法

image.png

useState

使用状态

const [n, setN] = React.useState(0)
const [user, setUser] = React.useState({name: 'Jack', age: 18})

  • 注意事项1: 不可局部更新

如果state是一个对象,能否部分setState?
答案是不行,因为setState不会帮我们合并属性
那么useReducer会合并属性吗?也不会!
因为React认为这应该是你自己要做的事情

function App(){
    const [user, setUser] = React.useState({name: 'Jack', age: 18})
    const onClick = () =>{
        //setUser不可以局部更新,如果只改变其中一个,那么整个数据都会被覆盖
        // setUser({
        //  name: 'Frank'
        // })
        setUser({
            ...user, //拷贝之前的所有属性
            name: 'Frank' //这里的name覆盖之前的name
        })
    }
    return (
        

{user.name}

{user.age}

) }
  • 注意事项2: 地址要变

setState(obj) 如果obj内存地址不变,那么React就认为数据没有变化,不会更新视图,了解内存地址与 桟堆概念

  • useState接受函数

const [state, setState] = useState(() => {return initialState})
该函数返回初始state,且只执行一次

  • setState接受函数

setxxxx(i => i + 1)
如果你能接受这种形式,应该优先使用这种形式
修改之后的状态 会在函数组件 再次更新完之后 拿到最新的值,如果想立刻拿到最新的值,使用useRef

useRef

useRef这个hooks函数,除了传统的用法之外,它还可以“跨渲染周期”保存数据。

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

export default function App(props){
  const [count, setCount] = useState(0);

  const doubleCount = useMemo(() => {
    return 2 * count;
  }, [count]);

  const couterRef = useRef();

  useEffect(() => {
    document.title = `The value is ${count}`;
    console.log(couterRef.current);
  }, [count]);
  
  return (
    <>
      
    
  );
}

代码中用useRef创建了couterRef对象,并将其赋给了button的ref属性。这样,通过访问couterRef.current就可以访问到button对应的DOM对象。

然后再来看看它保存数据的用法。

在一个组件中有什么东西可以跨渲染周期,也就是在组件被多次渲染之后依旧不变的属性?第一个想到的应该是state。没错,一个组件的state可以在多次渲染之后依旧不变。但是,state的问题在于一旦修改了它就会造成组件的重新渲染。

那么这个时候就可以使用useRef来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染。

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

export default function App(props){
  const [count, setCount] = useState(0);

  const doubleCount = useMemo(() => {
    return 2 * count;
  }, [count]);

  const timerID = useRef();
  
  useEffect(() => {
    timerID.current = setInterval(()=>{
        setCount(count => count + 1);
    }, 1000); 
  }, []);
  
  useEffect(()=>{
      if(count > 10){
          clearInterval(timerID.current);
      }
  });
  
  return (
    <>
      
    
  );
}

在上面的例子中,我用ref对象的current属性来存储定时器的ID,这样便可以在多次渲染之后依旧保存定时器ID,从而能正常清除定时器。

useReducer():action 钩子

React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux。

Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState。

useReducers()钩子用来引入 Reducer 功能。

const [state, dispatch] = useReducer(reducer, initialState);

上面是useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。

下面是一个计数器的例子。用于计算状态的 Reducer 函数如下。

const myReducer = (state, action) => {
  switch(action.type)  {
    case('countUp'):
      return  {
        ...state,
        count: state.count + 1
      }
    default:
      return  state;
  }
}

组件代码如下。

function App() {
  const [state, dispatch] = useReducer(myReducer, { count:   0 });
  return  (
    

Count: {state.count}

); }

由于 Hooks 可以提供共享状态和 Reducer 函数,所以它在这些方面可以取代 Redux。但是,它没法提供中间件(middleware)和时间旅行(time travel),如果你需要这两个功能,还是要用 Redux。

useEffect():副作用钩子

useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。

useEffect()的用法如下。

useEffect(()  =>  {
  // Async Action
}, [dependencies])

上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()。

下面看一个例子。

const Person = ({ personId }) => {
  const [loading, setLoading] = useState(true);
  const [person, setPerson] = useState({});

  useEffect(() => {
    setLoading(true); 
    fetch(`https://swapi.co/api/people/${personId}/`)
      .then(response => response.json())
      .then(data => {
        setPerson(data);
        setLoading(false);
      });
  }, [personId])

  if (loading === true) {
    return 

Loading ...

} return

You're viewing: {person.name}

Height: {person.height}

Mass: {person.mass}

}

上面代码中,每当组件参数personId发生变化,useEffect()就会执行。组件第一次渲染时,useEffect()也会执行。

useContext

如果需要在组件之间共享状态,可以使用useContext()。

现在有俩个组件Navbar和Messages,我们希望它们之间共享状态。

使用方法如下:

第一步在它们的父组件上使用React的Context API,在组件外部建立一个Context。

const TestContext = React.createContext({)};

组件封装代码如下:


    

上面的代码中,TestContext.Provider提供了一个Context对象,这个对象是可以被子组件共享的。

Navbar组件的代码如下:

const Navbar = () => {
    const { username } = useContext(TestContext);
    return (
        

{username}

) }

上面代码中,useContext()钩子函数用来引入Context对象,从中获取username属性。

Message组件的代码也类似:

const Messages = () => {
    const { username } = useContext(TestContext);
    return (
        

1 message for {username}

) }

整体代码如下:

import React, { useContext } from "react";
import ReactDOM from "react-dom";

const TestContext= React.createContext({});

const Navbar = () => {
  const { username } = useContext(TestContext)

  return (
    

{username}

) } const Messages = () => { const { username } = useContext(TestContext) return (

1 message for {username}

) } function App() { return (
); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

useMemo

我们来看一个反例:

import React from 'react';
 
 
export default function WithoutMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    function expensive() {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }
 
    return 

{count}-{val}-{expensive()}

setValue(event.target.value)}/>
; }

这里创建了两个state,然后通过expensive函数,执行一次昂贵的计算,拿到count对应的某个值。我们可以看到:无论是修改count还是val,由于组件的重新渲染,都会触发expensive的执行(能够在控制台看到,即使修改val,也会打印);但是这里的昂贵计算只依赖于count的值,在val修改的时候,是没有必要再次计算的。在这种情况下,我们就可以使用useMemo,只在count的值修改时,执行expensive计算:

export default function WithMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
    const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }, [count]);
 
    return 

{count}-{expensive}

{val}
setValue(event.target.value)}/>
; }

上面我们可以看到,使用useMemo来执行昂贵的计算,然后将计算值返回,并且将count作为依赖值传递进去。这样,就只会在count改变的时候触发expensive执行,在修改val的时候,返回上一次缓存的值。

useCallback

useCallback跟useMemo比较类似,但它返回的是缓存的函数。我们看一下最简单的用法:

const fnA = useCallback(fnB, [a])

import React, { useState, useCallback } from 'react';
 
const set = new Set();
 
export default function Callback() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        console.log(count);
    }, [count]);
    set.add(callback);
 
 
    return 

{count}

{set.size}

setVal(event.target.value)}/>
; }

我们可以看到,每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。

知道useCallback有什么样的特点,那有什么作用呢?

使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        return count;
    }, [count]);
    return 

{count}

setVal(event.target.value)}/>
; } function Child({ callback }) { const [count, setCount] = useState(() => callback()); useEffect(() => { setCount(callback()); }, [callback]); return
{count}
}

不仅是上面的例子,所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景。

多谈一点
useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。

参考文章:
阮一峰老师:http://www.ruanyifeng.com/blog/2019/09/react-hooks.html
优质博客:
https://blog.csdn.net/sinat_17775997/article/details/94453167
https://www.jianshu.com/p/1252be39c702
https://www.jianshu.com/p/7e778adec7d1
https://www.jianshu.com/p/d6244228a427

你可能感兴趣的:(常用React Hooks 方法)