如何在React钩子上使用`setState`回调

时间:2019-05-21 23:18:59

标签: reactjs

反应挂钩引入了useState来设置组件状态。但是我该如何使用钩子替换如下代码的回调:

setState(
  { name: "Michael" },
  () => console.log(this.state)
);

状态更新后,我想做些事情。

我知道我可以使用useEffect来执行其他操作,但是我必须检查状态以前的值,该值需要位代码。我正在寻找一种可以与useState挂钩一起使用的简单解决方案。

16 个答案:

答案 0 :(得分:18)

// This is the class and constructor const App = function() { // This is a property this.created = new Date() // This is a method this.age = function() { return new Date() - this.created } } // This creates the instance const makeApp = function() { return new App() } ,仅在state updates上触发:

useEffect

以上将最佳模拟const [state, setState] = useState({ name: "Michael" }) const isFirstRender = useRef(true) useEffect(() => { if (!isFirstRender.current) { console.log(state) // do something after state has updated } }, [state]) useEffect(() => { isFirstRender.current = false // toggle flag after first render/mounting }, []) 回调,而将 not 激发为初始状态。您可以从中提取自定义挂钩:

setState

答案 1 :(得分:15)

我认为,使用useEffect并不是一种直观的方法。

我为此创建了一个包装器。在此自定义挂钩中,您可以将回调传递给setState参数而不是useState参数。

我刚刚创建了Typescript版本。因此,如果您需要在Javascript中使用此代码,只需从代码中删除一些类型符号即可。

用法

const [state, setState] = useStateCallback(1);
setState(2, (n) => {
  console.log(n) // 2
});

声明

import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react';

type Callback<T> = (value?: T) => void;
type DispatchWithCallback<T> = (value: T, callback?: Callback<T>) => void;

function useStateCallback<T>(initialState: T | (() => T)): [T, DispatchWithCallback<SetStateAction<T>>] {
  const [state, _setState] = useState(initialState);

  const callbackRef = useRef<Callback<T>>();
  const isFirstCallbackCall = useRef<boolean>(true);

  const setState = useCallback((setStateAction: SetStateAction<T>, callback?: Callback<T>): void => {
    callbackRef.current = callback;
    _setState(setStateAction);
  }, []);

  useEffect(() => {
    if (isFirstCallbackCall.current) {
      isFirstCallbackCall.current = false;
      return;
    }
    callbackRef.current?.(state);
  }, [state]);

  return [state, setState];
}

export default useStateCallback;

缺点

如果传递的箭头函数引用了变量外部函数,则它将在状态更新后捕获当前值而不是值。 在上面的用法示例中, console.log(state)将打印1而不是2。

答案 2 :(得分:3)

如果有人还需要的话,我用打字稿编写了自定义钩子。

import React, { useEffect, useRef, useState } from "react";

export const useStateWithCallback = <T>(initialState: T): [state: T, setState: (updatedState: React.SetStateAction<T>, callback?: (updatedState: T) => void) => void] => {
    const [state, setState] = useState<T>(initialState);
    const callbackRef = useRef<(updated: T) => void>();

    const handleSetState = (updatedState: React.SetStateAction<T>, callback?: (updatedState: T) => void) => {
        callbackRef.current = callback;
        setState(updatedState);
    };

    useEffect(() => {
        if (typeof callbackRef.current === "function") {
            callbackRef.current(state);
            callbackRef.current = undefined;
        }
    }, [state]);

    return [state, handleSetState];
}

答案 3 :(得分:2)

您需要使用useEfect钩子来实现此目的

const [counter, setCounter] = useState(0);

const doSomething = () => {
  setCounter(123);
}

useEffect(() => {
   console.log('Do something after counter has changed', counter);
}, [counter]);

答案 4 :(得分:2)

在大家的帮助下,我实现了这个自定义钩子:

非常类似于基于类的 this.setState(state, callback)

const useStateWithCallback = (initialState) => {
  const [state, setState] = useState(initialState);
  const callbackRef = useRef(() => undefined);

  const setStateCB = (newState, callback) => {
    callbackRef.current = callback;
    setState(newState);
  };

  useEffect(() => {
    callbackRef.current?.();
  }, [state]);

  return [state, setStateCB];
};

这样我们就可以像......一样使用它

const [isVisible, setIsVisible] = useStateWithCallback(false);

...

setIsVisible(true, () => console.log('callback called now!! =)');

保持冷静和快乐编码!

答案 5 :(得分:1)

如果要更新以前的状态,则可以在钩子中执行以下操作:

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


setCounter(previousCount => previousCount + 1);

答案 6 :(得分:1)

我遇到了同样的问题,在我的设置中使用useEffect并不能解决问题(我正在从多个子组件的数组更新父状态,并且我需要知道哪个组件更新了数据)。

将setState包装在promise中可以在完成后触发任意操作:

import React, {useState} from 'react'

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

  function handleClick(){
    Promise.resolve()
      .then(() => { setCount(count => count+1)})
      .then(() => console.log(count))
  }


  return (
    <button onClick= {handleClick}> Increase counter </button>
  )
}

export default App;

以下问题使我朝着正确的方向前进: Does React batch state update functions when using hooks?

答案 7 :(得分:1)

您可以使用我知道的以下方法来获取更新后的最新状态:

  1. useEffect
    https://reactjs.org/docs/hooks-reference.html#useeffect
    const [state, setState] = useState({name: "Michael"});
    
    const handleChangeName = () => {
      setState({name: "Jack"});
    }
    
    useEffect(() => {
      console.log(state.name); //"Jack"

      //do something here
    }, [state]);
  1. 功能更新
    https://reactjs.org/docs/hooks-reference.html#functional-updates
    “如果新状态是使用之前的状态计算出来的,你可以将一个函数传递给 setState。该函数将接收之前的值,并返回一个更新的值。”
    const [state, setState] = useState({name: "Michael"});

    const handleChangeName = () => {
      setState({name: "Jack"})
      setState(prevState => {
        console.log(prevState.name);//"Jack"

        //do something here

        // return updated state
        return prevState;
      });
    }
  1. useRef
    https://reactjs.org/docs/hooks-reference.html#useref
    “返回的 ref 对象将在组件的整个生命周期内持续存在。”
    const [state, setState] = useState({name: "Michael"});

    const stateRef = useRef(state);
    stateRef.current  = state;
    const handleClick = () => {
      setState({name: "Jack"});

      setTimeout(() => {
        //it refers to old state object
        console.log(state.name);// "Michael";

        //out of syntheticEvent and after batch update
        console.log(stateRef.current.name);//"Jack"

        //do something here
      }, 0);
    }

在react综合事件处理程序中,setState是一个批量更新的过程,所以每次状态改变都会等待并返回一个新的状态。
“setState() 并不总是立即更新组件。它可能会批量更新或推迟更新。”,
https://reactjs.org/docs/react-component.html#setstate

这是一个有用的链接
Does React keep the order for state updates?

答案 8 :(得分:1)

我们可以编写一个名为 useScheduleNextRenderCallback 的钩子,它返回一个“调度”函数。在我们调用 setState 之后,我们可以调用“schedule”函数,传递我们希望在下一次渲染时运行的回调。

import { useCallback, useEffect, useRef } from "react";

type ScheduledCallback = () => void;
export const useScheduleNextRenderCallback = () => {
  const ref = useRef<ScheduledCallback>();

  useEffect(() => {
    if (ref.current !== undefined) {
      ref.current();
      ref.current = undefined;
    }
  });

  const schedule = useCallback((fn: ScheduledCallback) => {
    ref.current = fn;
  }, []);

  return schedule;
};

示例用法:

const App = () => {
  const scheduleNextRenderCallback = useScheduleNextRenderCallback();

  const [state, setState] = useState(0);

  const onClick = useCallback(() => {
    setState(state => state + 1);
    scheduleNextRenderCallback(() => {
      console.log("next render");
    });
  }, []);

  return <button onClick={onClick}>click me to update state</button>;
};

简化的测试用例:https://stackblitz.com/edit/react-ts-rjd9jk

答案 9 :(得分:0)

您的问题非常有效。让我告诉您,useEffect默认情况下运行一次,并且每次依赖项数组更改后运行。

检查以下示例:

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

const App = () => {
  const [age, setAge] = useState(0);
  const [ageFlag, setAgeFlag] = useState(false);

  const updateAge = ()=>{
    setAgeFlag(false);
    setAge(age+1);
    setAgeFlag(true);
  };

  useEffect(() => {
    if(!ageFlag){
      console.log('effect called without change - by default');
    }
    else{
      console.log('effect called with change ');
    }
  }, [ageFlag,age]);

  return (
    <form>
      <h2>hooks demo effect.....</h2>
      {age}
      <button onClick={updateAge}>Text</button>
    </form>
  );
}

export default App;

如果要使用挂钩执行setState回调,请使用标志变量,并在useEffect内提供IF ELSE或IF块,以便在满足该条件时仅执行该代码块。无论何时,效果都会随着依赖项数组的更改而运行,但是效果内部的IF代码将仅在特定条件下执行。

答案 10 :(得分:0)

setState()使对组件状态的更改排队,并告知React该组件及其子级需要以更新后的状态重新呈现。

setState方法是异步的,事实上,它不返回promise。因此,在我们要更新或调用函数的情况下,可以在setState函数中将该函数作为第二个参数调用回调。 例如,在上述情况下,您已将一个函数称为setState回调。

setState(
  { name: "Michael" },
  () => console.log(this.state)
);

上面的代码对于类组件很好用,但是对于功能组件,我们不能使用setState方法,这可以利用使用效果钩子来达到相同的结果。

一个显而易见的方法是,ypu可以与useEffect一起使用,如下所示:

const [state, setState] = useState({ name: "Michael" })

useEffect(() => {
  console.log(state) // do something after state has updated
}, [state])

但是这也会在第一个渲染上触发,因此我们可以按以下方式更改代码,以便检查第一个渲染事件并避免状态渲染。因此,可以通过以下方式来实现:

我们可以在此处使用用户挂钩来标识第一个渲染。

useRef Hook允许我们在功能组件中创建可变变量。对于访问DOM节点/ React元素和存储可变变量而不触发重新渲染非常有用。

const [state, setState] = useState({ name: "Michael" });
const firstTimeRender = useRef(true);

useEffect(() => {
 if (!firstTimeRender.current) {
    console.log(state);
  }
}, [state])

useEffect(() => { 
  firstTimeRender.current = false 
}, [])

答案 11 :(得分:0)

我有一个用例,我想在设置状态后进行带有某些参数的 api调用 。我不想将这些参数设置为我的状态,所以我做了一个自定义钩子,这是我的解决方法

import { useState, useCallback, useRef, useEffect } from 'react';
import _isFunction from 'lodash/isFunction';
import _noop from 'lodash/noop';

export const useStateWithCallback = initialState => {
  const [state, setState] = useState(initialState);
  const callbackRef = useRef(_noop);

  const handleStateChange = useCallback((updatedState, callback) => {
    setState(updatedState);
    if (_isFunction(callback)) callbackRef.current = callback;
  }, []);

  useEffect(() => {
    callbackRef.current();
    callbackRef.current = _noop; // to clear the callback after it is executed
  }, [state]);

  return [state, handleStateChange];
};

答案 12 :(得分:0)

我不认为用 useRef 来区分挂载与否是一个好方法,通过确定 useState() 中生成的 useEffect() 的值是否为初始值?

const [val, setVal] = useState(null)

useEffect(() => {
  if (val === null) return
  console.log('not mounted, val updated', val)
}, [val])

答案 13 :(得分:0)

接受的答案对我不起作用,如果我有很多处理要做。将逻辑置于事件循环 (setTimeout) 中可解决问题。

在下面的示例中,我在进行大量处理之前设置了一个加载器变量。

    const [counter, setCounter] = useState(0);
    const [showLoader, setShowLoader] = useState(false);

    const doSomething = () => {
      setLoader(true);
      setTimeout(() => {
        // do lot of data processing
        setCounter(123);
      })
    }
    
      useEffect(() => {
        setLoader(false)
      }, [setLoader, setCounter]);

答案 14 :(得分:-1)

这个怎么样:

const [Name, setName] = useState("");
...
onClick={()=>{
setName("Michael")
setName(prevName=>{...}) //prevName is Michael?
}}

答案 15 :(得分:-3)

UseEffect是主要解决方案。但是正如Darryl所述,使用useEffect并将状态作为第二个参数传递有一个缺陷,该组件将在初始化过程中运行。如果只希望回调函数使用更新后的状态值运行,则可以设置一个局部常量,并在setState和回调中都使用它。

CREATE TABLE sangre(    
    id_pruebas VARCHAR2(10) PRIMARY KEY,
    tipo VARCHAR2(5) NOT NULL   
        CHECK (tipo IN ('O-', 'O+', 'A-', 'A+', 'B-', 'B+', 'AB-', 'AB+')),
    fecha DATE NOT NULL,
    id_sal VARCHAR2(10) NOT NULL
        REFERENCES laboratorio(id_sal),
    id_paciente NUMBER(8) NOT NULL
        CHECK (id_paciente >= 0)
        REFERENCES paciente(id_paciente),
    id_enfermero VARCHAR2(10) NOT NULL
        REFERENCES enfermero (id_enfermero)
);