簡體   English   中英

帶有條件 React 鈎子的可選受控/不受控 React 組件

[英]optional controlled / uncontrolled React component with conditional React hooks

IMO,React Hooks useState 非常適合可選使用 props 的值或使用自己的狀態的模式,但是當我有條件地使用 hook 時,lint 顯示了一些錯誤。

工作示例

我嘗試使用條件如下的鈎子,但出現 eslint 錯誤React hook useState is called conditionally 根據doc 的解釋React relies on the order in which Hooks are called

const Counter = ({ value, onChange, defaultValue = 0 }) => {
  const [count, onCountChange] =
    typeof value === "undefined" ? useState(defaultValue) : [value, onChange];
  return (
    <div>
      {count.toString()}
      <button
        onClick={() => {
          onCountChange(count + 1);
        }}
      >
        +
      </button>
    </div>
  );
};
function App() {
  const [count, onCountChange] = useState(0);
  return (
    <div className="App">
      <div>
        Uncontrolled Counter
        <Counter />
      </div>
      <div>
        Controlled Counter
        <Counter value={count} onChange={onCountChange} />
      </div>
    </div>
  );
}

我如何使用鈎子來實現與下面的類組件相同的功能?

class CounterClass extends React.Component {
  state = {
    value: this.props.defaultValue || 0
  };
  render() {
    const isControlled = typeof this.props.defaultValue === "undefined";
    const count = isControlled ? this.props.value : this.state.value;

    return (
      <div>
        {count.toString()}
        <button
          onClick={() => {
            isControlled &&
              this.props.onChange &&
              this.props.onChange(this.props.value + 1);
            !isControlled && this.setState({ value: this.state.value + 1 });
          }}
        >
          +
        </button>
      </div>
    );
  }
}

或者這種在一個組件中的 props/state 可選方式是錯誤的?

我從 React JSX <input>組件中學到了"defaultValue""value""onChange" API 命名和想法。

您可以將組件拆分為完全受控和完全不受控。 演示

const CounterRepresentation = ({ value, onChange }) => (
  <div>
    {value.toString()}
    <button
      onClick={() => {
        onChange(value + 1);
      }}
    >
      +
    </button>
  </div>
);

const Uncontrolled = ({ defaultValue = 0 }) => {
  const [value, onChange] = useState(defaultValue);
  return <CounterRepresentation value={value} onChange={onChange} />;
};

// Either use representation directly or uncontrolled
const Counter = ({ value, onChange, defaultValue = 0 }) => {
  return typeof value === "undefined" ? (
    <Uncontrolled defaultValue={defaultValue} />
  ) : (
    <CounterRepresentation value={value} onChange={onChange} />
  );
};

好問題! 我認為這可以通過掛鈎來解決,方法是使useState調用成為無條件的,並且只使您決定呈現哪個狀態和使用哪個更改處理程序的部分成為條件。

我剛剛發布了一個解決這個問題的鈎子: use-optionally-controlled-state

用法:

import useOptionallyControlledState from 'use-optionally-controlled-state';
 
function Expander({
  expanded: controlledExpanded,
  initialExpanded = false,
  onChange
}) {
  const [expanded, setExpanded] = useOptionallyControlledState({
    controlledValue: controlledExpanded,
    initialValue: initialExpanded,
    onChange
  });
 
  function onToggle() {
    setExpanded(!expanded);
  }
 
  return (
    <>
      <button onClick={onToggle} type="button">
        Toggle
      </button>
      {expanded && <div>{children}</div>}
    </>
  );
}

// Usage of the component:

// Controlled
<Expander expanded={expanded} onChange={onExpandedChange} />
 
// Uncontrolled using the default value for the `initialExpanded` prop
<Expander />
 
// Uncontrolled, but with a change handler if the owner wants to be notified
<Expander initialExpanded onChange={onExpandedChange} />

通過使用鈎子來實現這一點,您不必包裝額外的組件,理論上您可以將其應用於同一組件內的多個道具(例如<Prompt isOpen={isOpen} inputValue={inputValue} />組件其中兩個道具都是可選控制的)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM