我怎样才能实现不仅一个而且多个设置切换?

How can I implement not only one but multi setting toggles?

我使用 Shopify Polaris 的设置开关。https://polaris.shopify.com/components/actions/setting-toggle#navigation

而且我不仅要实现一个设置,还要实现多个设置 toggles.But 我不想总是重复相同的 handleToggle() 和值(contentStatus,textStatus),就像沙盒 A、B、C 下面那样。 ..

import React, { useCallback, useState } from "react";
import { SettingToggle, TextStyle } from "@shopify/polaris";

export default function SettingToggleExample() {
  const [activeA, setActiveA] = useState(false);
  const [activeB, setActiveB] = useState(false);

  const handleToggleA = useCallback(() => setActiveA((active) => !active), []);
  const handleToggleB = useCallback(() => setActiveB((active) => !active), []);

  const contentStatusA = activeA ? "Deactivate" : "Activate";
  const contentStatusB = activeB ? "Deactivate" : "Activate";
  const textStatusA = activeA ? "activated" : "deactivated";
  const textStatusB = activeB ? "activated" : "deactivated";

  const useHandleToggle = (active, setActive) => {
    const handleToggle = useCallback(() => setActive((active) => !active), []);

    const contentStatus = active ? "Disconnect" : "Connect";
    const textStatus = active ? "connected" : "disconnected";
    handleToggle();
    return [contentStatus, textStatus];
  };

  useHandleToggle(activeA, setActiveA);

  return (
    <>
      <SettingToggle
        action={{
          content: contentStatusA,
          onAction: handleToggleA
        }}
        enabled={activeA}
      >
        This setting is <TextStyle variation="strong">{textStatusA}</TextStyle>.
      </SettingToggle>
      <SettingToggle
        action={{
          content: contentStatusB,
          onAction: handleToggleB
        }}
        enabled={activeB}
      >
        This setting is <TextStyle variation="strong">{textStatusB}</TextStyle>.
      </SettingToggle>
    </>
  );
}

https://codesandbox.io/s/vigorous-pine-k0dpib?file=/App.js

所以我想我可以使用自定义挂钩。但它不起作用。所以如果你给我一些建议会很有帮助。

我的第一次重构尝试是在公共处理程序上使用参数

const handleToggle = useCallback((which) => {
  which === 'A' ? setActiveA((activeA) => !activeA) 
   : setActiveB((activeB) => !activeB)
},[])

...

<SettingToggle
  action={{
    content: contentStatusA,
    onAction: () => handleToggle('A')
  }}
  enabled={activeA}
>

它的功能,但感觉有点幼稚。对于更多 React-ish,reducer 可能是可行的方法。


带减速机

这看起来更简洁,如果您需要更多开关,它肯定更可扩展。

function reducer(state, action) {
  switch (action.type) {
    case "toggleA":
      const newValueA = !state.activeA;
      return {
        ...state,
        activeA: newValueA,
        contentStatusA: newValueA ? "Deactivate" : "Activate",
        textStatusA: newValueA ? "activated" : "deactivated"
      };
    case "toggleB":
      const newValueB = !state.activeB;
      return {
        ...state,
        activeB: newValueB,
        contentStatusB: newValueB ? "Deactivate" : "Activate",
        textStatusB: newValueB ? "activated" : "deactivated"
      };
    default:
      throw new Error();
  }
}

const initialState = {
  activeA: false,
  activeB: false,
  contentStatusA: "Activate",
  contentStatusB: "Activate",
  textStatusA: "deactivated",
  textStatusB: "deactivated"
};

export default function SettingToggleExample() {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <>
      <SettingToggle
        action={{
          content: state.contentStatusA,
          onAction: () => dispatch({type: 'toggleA'})
        }}
        enabled={state.activeA}
      >
        This setting is <TextStyle variation="strong">{state.textStatusA}</TextStyle>.
      </SettingToggle>
      <SettingToggle
        action={{
          content: state.contentStatusB,
          onAction: () => dispatch({type: 'toggleA'})
        }}
        enabled={state.activeB}
      >
        This setting is <TextStyle variation="strong">{state.textStatusB}</TextStyle>.
      </SettingToggle>
    </>
  );
}

带有包装组件

子组件可以消除 'A' 和 'B' 后缀

function reducer(state, action) {
  switch (action.type) {
    case "toggle":
      const newValue = !state.active;
      return {
        ...state,
        active: newValue,
        contentStatus: newValue ? "Deactivate" : "Activate",
        textStatus: newValue ? "activated" : "deactivated"
      };
    default:
      throw new Error();
  }
}

const initialState = {
  active: false,
  contentStatus: "Activate",
  textStatus: "deactivated",
};

const ToggleWrapper = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <SettingToggle
      action={{
        content: state.contentStatus,
        onAction: () => dispatch({ type: "toggle" })
      }}
      enabled={state.active}
    >
      This setting is <TextStyle variation="strong">{state.textStatus}</TextStyle>.
    </SettingToggle>
  )
}

export default function SettingToggleExample() {

  return (
    <>
      <ToggleWrapper />
      <ToggleWrapper />
    </>
  );
}

对每个切换使用简单的布尔值

如果您将活动状态对象合并到一个数组中,那么您可以动态更新任意数量的设置。这是一个可能看起来像的例子:

import React, { useCallback, useState } from "react";
import { SettingToggle, TextStyle } from "@shopify/polaris";

export default function SettingToggleExample() {
  // define stateful array of size equal to number of toggles
  const [active, setActive] = useState(Array(2).fill(false));

  const handleToggle = useCallback((i) => {
    // toggle the boolean at index, i
    setActive(prev => [...prev.slice(0,i), !prev[i], ...prev.slice(i+1)])
  }, []);

  return (
    <>
      {activeStatuses.map((isActive, index) =>
        <SettingToggle
          action={{
            content: isActive ? "Deactivate" : "Activate",
            onAction: () => handleToggle(index)
          }}
          enabled={isActive}
        >
          This setting is <TextStyle variation="strong">{isActive ? "activated" : "deactivated"}</TextStyle>.
        </SettingToggle>
      }
    </>
  );
}

当然,您以后可能希望为其中的每一个添加一个标签,因此最好在函数范围之外定义一个 defaultState 对象并用它替换 Array(2).fill(false)。然后,除了布尔值 active 属性 之外,您还可以为每个切换设置一个字符串 label 属性,可以将其添加到 .map(...) 中每个切换的旁边。

为每个切换添加标签

根据您的跟进,这里是在 CodeSandbox 中也找到的实现,用于每个切换带有标签的状态(包括此处关于防止 link 衰减的答案):

import React, { useCallback, useState } from "react";
import { SettingToggle, TextStyle } from "@shopify/polaris";

const defaultState = [
  {
    isActive: false,
    label: "A"
  },
  {
    isActive: false,
    label: "B"
  },
  {
    isActive: false,
    label: "C"
  }
];

export default function SettingToggleExample() {
  const [active, setActive] = useState(defaultState);

  const handleToggle = useCallback((i) => {
    // toggle the boolean at index, i
    setActive((prev) => [
      ...prev.slice(0, i),
      { ...prev[i], isActive: !prev[i].isActive },
      ...prev.slice(i + 1)
    ]);
  }, []);

  return (
    <div style={{ height: "100vh" }}>
      {active?.map(({ isActive, label }, index) => (
        <SettingToggle
          action={{
            content: isActive ? "Deactivate" : "Activate",
            onAction: () => handleToggle(index)
          }}
          enabled={isActive}
          key={index}
        >
          This {label} is 
          <TextStyle variation="strong">
            {isActive ? "activated" : "deactivated"}
          </TextStyle>
          .
        </SettingToggle>
      ))}
    </div>
  );
}