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

时间:2019-04-07 01:43:02

标签: javascript reactjs react-hooks

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

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()函数的对象,然后将每个键/值添加到状态保持对象中,如下所示:

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

st.a返回1,而st.setA返回function bound dispatchAction()的意义上讲,这似乎是可行的(我假设这就是useState()[1]返回)。

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

(在下面的示例中,我将countcool用作var而不是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的工作方式可能有些奇怪。

任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:1)

尝试这样的事情

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);
  }
};

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

答案 1 :(得分:1)

您正在破坏rules of hooks

  

仅位于顶层的呼叫挂钩

     

请勿在循环,条件或嵌套函数中调用Hook。相反,请始终在您的React函数的顶层使用挂钩。

     

仅来自React函数的呼叫挂钩

     

不要从常规JavaScript函数中调用Hook。

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

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

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

  useEffect(() => {

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

  }, []);

//...

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

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

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

因此,您可以像在基于类的组件中使用setState一样简单地使用上述自定义钩子。

检查this CodeSandbox fork和以下文章:

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