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

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

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

我知道我可以使用useEffect来做额外的事情,但我必须检查之前的状态值,这需要位代码。我正在寻找一个简单的解决方案,可以使用useState挂钩。


当前回答

我认为你需要的是useState和useCallback:

useState react doc; useCallback react doc;

示例代码

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

const Test = () => {
  const [name, setName] = useState("");
  const testCallback = useCallback(() => console.log(name), [name]);

  return (
    <button onClick={() => {
      setName("Michael")
      testCallback();
    }}>Name</button>
  )
};

export default Test;

其他回答

我有一个用例,我想做一个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];
};

setState()将更改排队到组件状态,并告诉React该组件及其子组件需要使用更新后的状态重新呈现。

setState方法是异步的,实际上,它并不返回承诺。在我们想要更新或调用一个函数的情况下,函数可以在setState函数中调用callback作为第二个参数。 例如,在上面的例子中,您调用了一个函数作为setState回调函数。

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

上面的代码适用于类组件,但对于函数组件,我们不能使用setState方法,因此我们可以利用use effect钩子来实现相同的结果。

显而易见的方法是,你可以使用useEffect,如下所示:

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

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

但这也会在第一次呈现时触发,因此我们可以更改如下代码,检查第一次呈现事件并避免状态呈现。因此,可以通过以下方式实现:

我们可以在这里使用user钩子来标识第一次渲染。

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

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

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

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

你的问题很有道理。让我告诉您,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 OR If块,以便当条件满足时,只执行该代码块。无论如何,当依赖项数组改变时,效果会运行,但效果中的IF代码只会在特定的条件下执行。

编辑

在这里使用promise似乎仍然推迟了重新渲染后的执行,两次触发setState可能是获得最新状态的最佳解决方案。因为setState将被列出,我们只需要在重新渲染之前获得prevState。

最初的发布

我刚刚弄清楚了我们是否可以在这里使用一个Promise让setState变成可等待的。

这是我的实验结果,感觉比使用回调更好

主要是在useEffect中触发解析函数

function useAsyncState(initialState) {
  const [state, setState] = useState(initialState)
  const resolveCb = useRef()

  const handleSetState = (updatedState) => new Promise((resolve, reject) => {
    // force previous promise resolved
    if (typeof resolveCb.current === 'function') {
      resolveCb.current(updatedState)
    }
    resolveCb.current = resolve
    try {
      setState(updatedState)
    } catch(err) {
      resolveCb.current = undefined
      reject(err)
    }
  })

  useEffect(() => {
    if (typeof resolveCb.current === 'function') {
      resolveCb.current(state)
      resolveCb.current = undefined
    }
  }, [state])

  return [state, handleSetState]
}

组件中使用

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

  const increment = useMemoizedFn(async () => {
    const newCount = await setCount(count + 1)
    console.log(newCount)
  })

  console.log('rerender')

  return (
    <div>
      <h3 onClick={increment}>Hi, {count}</h3>
    </div>
  )
}

我探索了use-state with-callback npm库和其他类似的自定义钩子,但最后我意识到我可以做这样的事情:

const [user, setUser] = React.useState(
  {firstName: 'joe', lastName: 'schmo'}
)

const handleFirstNameChange=(val)=> {
  const updatedUser = {
     ...user,
     firstName: val
  }
  setUser(updatedUser)
  updateDatabase(updatedUser)
}