React.Js - 回调生成器中的 useCallback

React.Js - useCallback inside a callback generator

我有一堆复选框组件,我想在每次切换复选框组件时更新父组件的状态。现在我正在这样做:

   const selChangeHandler = useCallback(
    (checkboxId, isSel) => {
      if (isSel) setSelectedCheckboxes((prevState) => [...prevState, checkboxId]);
      else
        setSelectedCheckboxes((prevState) =>
          prevState.filter((x) => x != checkboxId)
        );
    },
    [setSelectedCheckboxes]
  );

这个函数被传递到每个复选框。我不认为这是一个特别好的方法,因为每个复选框都可以向数组添加任何内容,而不仅仅是它的 id。所以我尝试了这个但是我得到了一个错误:

  const GetSelChangeHandler = (checkboxId) => {
return useCallback(
  (isSel) => {
    if (isSel) setSelectedCheckboxes((prevState) => [...prevState, checkboxId]);
    else
      setSelectedCheckboxes((prevState) =>
        prevState.filter(
          (x) => x != checkboxId
        )
      );
  },
  [setSelectedCheckboxes]
); };

我执行此内联并将返回的处理程序作为回调道具传递给我的复选框组件。我不确定错误是什么,但与违反钩子规则有关。这样做的正确方法是什么?

Rules of Hooks

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

你不应该在 GetSelChangeHandler 中使用 useCallback(),以防你有条件地调用它。

const GetSelChangeHandler = (checkboxId) => {
  return (isSel) => {
    if (isSel) setSelectedCheckboxes((prevState) => [...prevState, checkboxId]);
    else
      setSelectedCheckboxes((prevState) => prevState.filter(x => x !=checkboxId));
  };
};

您不能在循环或条件中调用挂钩。像 useToggle 自定义挂钩这样的东西怎么样,它可以更轻松地管理许多复选框的状态? 运行下面的代码,点击一些,看看应用状态如何反映变化。

function useToggle(initState = false) {
  const [state, setState] = React.useState(initState)
  return [state, _ => setState(!state)]
}

function App() {
  const [inStock, toggleInStock] = useToggle(true)
  const [freeShip, toggleFreeShip] = useToggle(true)
  const [onSale, toggleOnSale] = useToggle(true)
  return <div>
    In Stock: <Checkbox checked={inStock} onClick={toggleInStock} /><br/>
    Free Shipping: <Checkbox checked={freeShip} onClick={toggleFreeShip} /><br/>
    On Sale: <Checkbox checked={onSale} onClick={toggleOnSale} on="" /><br/>
    <pre>{JSON.stringify({ inStock, freeShip, onSale })}</pre>
  </div>
}

function Checkbox({ checked, onClick, on = "✅", off = "❌" }) {
  // use <input type="checkbox"> or any custom representation
  return <div className="checkbox" onClick={onClick}>
    {checked ? on : off}
  </div>
}

ReactDOM.render(<App/>, document.querySelector("#app"))
.checkbox { display: inline-block; cursor: pointer; }
pre { background-color: #ff8; padding: 0.5rem; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

如果复选框的数量未知,useToggles 变体可以解决问题。

function useToggles(init = {}) {
  const [states, setStates] = React.useState(init)
  return [
    states,
    key => event => setStates({...states, [key]: !Boolean(states[key])})
  ]
}

function App() {
  const [checkboxes, toggle] = useToggles({
    inStock: true,
    freeShip: true,
    onSale: true
  })
  return <div>
    In Stock:
    <Checkbox checked={checkboxes.inStock} onClick={toggle("inStock")}/><br/>
    Free Shipping:
    <Checkbox checked={checkboxes.freeShip} onClick={toggle("freeShip")}/><br/>
    On Sale:
    <Checkbox checked={checkboxes.onSale} onClick={toggle("onSale")} on=""/><br/>
    <pre>{JSON.stringify(checkboxes)}</pre>
  </div>
}

function Checkbox({ checked, onClick, on = "✅", off = "❌" }) {
  // use <input type="checkbox"> or any custom representation
  return <div className="checkbox" onClick={onClick}>
    {checked ? on : off}
  </div>
}

ReactDOM.render(<App/>, document.querySelector("#app"))
.checkbox { display: inline-block; cursor: pointer; }
pre { background-color: #ff8; padding: 0.5rem; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>