React:循环对象以绑定 useState 函数不会更新我的应用程序的状态

React: Looping over an object to bind useState functions is not updating the state of my application

我正在尝试创建一个小型库来清理此模式..

const [a, setA] = useState(1)
const [b, setB] = useState(2)
const [c, setC] = useState(3)
const [d, setD] = useState(4)
const [e, setE] = useState(5)
// ...

进入看起来像这样的东西...

s({
  a: 1,
  b: 2,
  c: 3,
  d: 4,
  e: 5
}, true)

我的想法是遍历提供给 s() 函数的对象,并将每个 key/value 添加到一个状态保持对象,如下所示:

st = {
  a: 1,
  setA: THE 2nd ITEM IN THE ARRAY THE useState() FUNCTION RETURNS
  // repeat this pattern for b, c, d, e...
}

它似乎在 st.a returns 1st.setA returns a function bound dispatchAction() 的意义上起作用(我'我假设这就是 useState()[1] returns).

但是当您单击下面演示中的按钮时,它不会更新任何 st

(在下面的示例中,我使用 countcool 作为变量而不是 a, b, c, d, e

import React, { useState } from 'react'
import ReactDOM from 'react-dom'

// State holder. This could be pulled out to Context for global state.
const st = {}

const s = (obj, setDefaults = false) => {
  if (setDefaults) {
    // Loop over s({...}, true) to create default state.
    Object.keys(obj).map(k => {
      st[k] = useState(obj[k])[0]
      st[`set${k.charAt(0).toUpperCase()}${k.slice(1)}`] = useState(obj[k])[1]
      return null
    })
  } else {
    // Update state using st[setXyz(...)]

    // FIXME: This doesn't update the state.
    // It seems like it would since it is calling things like setCount(2)
    // using the setCount that has a function bound to it in the `st` object.

    for (let k in obj) {
      st[`set${k.charAt(0).toUpperCase()}${k.slice(1)}`](obj[k])
    }

    console.log(st)
  }
}

const App = () => {
  // Set default state object.
  // It's prettier than 20 lines of `const [x, setX] = useState(1)`
  s(
    {
      count: 0,
      cool: true,
    },
    true,
  )

  return (
    <div>
      <button
        onClick={() =>
          // Update state all in one object.
          s({
            count: 2,
            cool: false,
          })
        }
      >
        Click
      </button>

      {/* Access state via the st variable. */}
      <pre>{JSON.stringify(st, null, 2)}</pre>
    </div>
  )
}

const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)

https://codesandbox.io/s/14xjzl8xx7 <-- 现场演示

我怀疑我没有正确绑定,或者 React Hooks 的工作方式可能有些奇怪。

如有任何帮助,我们将不胜感激。

尝试这样的事情

const s = (obj, setDefaults = false) => {
  if (setDefaults) {
    // Loop over s({...}, true) to create default state.
    Object.keys(obj).map(k => {
      st[k] = useState(obj[k]);
      return null;
    });
  } else {
    // Update state using st[setXyz(...)]

    // FIXME: This doesn't update the state.
    // It seems like it would since it is calling things like setCount(2)
    // using the setCount that has a function bound to it in the `st` object.

    for (let k in obj) {
      st[k][1](obj[k]);
      console.log(st[k], obj[k]);
    }

    console.log(st);
  }
};

最终工作link:https://codesandbox.io/s/qx6r8387x4

你正在破坏 rules of hooks:

Only Call Hooks at the Top Level

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function.

Only Call Hooks from React Functions

Don’t call Hooks from regular JavaScript functions.

对于使用较大状态的组件,我建议您改为使用 useReducer 来处理它。

例如,您可以使用 useReducer 挂钩来获得一个 setState 函数,该函数可以一次将多个 属性 值合并到您的状态:

//...
const App = () => {
  const [state, setState] = useReducer(
    (state, newState) => ({ ...state, ...newState }),
    st
  );

  useEffect(() => {

    setState({
      count: 0,
      cool: true
    });
    
  }, []);

//...

如您所见,您可以在组件安装时在 useReducer 标志上设置初始状态(在上例的 useEffect 调用中)。

您还可以将此状态行为提取到自定义挂钩中,我发现它在重构基于 class 的组件时非常有用,因为它模仿了 React.Component setState 方法的行为:

function useSetState(initialState) {
  const [state, setState] = useReducer(
    (state, newState) => ({...state, ...newState}),
    initialState,
  )
  return [state, setState]
}

因此您可以像在基于 class 的组件中使用 setState 一样简单地使用上面的自定义挂钩。

检查 this CodeSandbox fork 和以下文章:

此外,忘记提及您的“状态持有者”对象,我建议您仅作为组件的“初始状态”,这就是为什么在我的示例中我将其传递给 useReducer 挂钩。因为我们让 React 处理状态,所以我们不应该直接改变这个对象,你的组件的“当前”状态将存在于我们在我的示例中渲染的 state 标识符中。