【问题标题】:React: Passing a Prop in w/ event for setState反应:在带有事件的 setState 中传递一个 Prop
【发布时间】:2020-01-09 20:25:13
【问题描述】:

我正在使用我一直在使用 onChange 函数更新的嵌套状态对象,如下所示:

  const [someState, setSomeState] = useState({
    customer: [
      {
        name: "Bob",
        address: "1234 Main Street",
        email: "bob@mail.com",
        phone: [
          {
            mobile: "555-5555",
            home: "555-5555"
          }
        ]
      }
    ]
  });

  const updateSomeStatePhone = e => {
    e.persist();
    setSomeState(prevState => {
      prevState.customer[0].phone[0].mobile = e.target.value;
      return {
        ...prevState
      };
    });
  };

 <p>Update Mobile Number<p>
  <select
   value={someState.customer[0].phone[0].mobile}
   onChange={updateSomeStatePhone}
  >
   <option value="123-4567">"123-4567"</option>
  </select>

这样就搞定了。然而,目前,如果我想通过带有下拉/输入字段等的大型表单更新多个状态属性,我必须为这些字段硬编码 6 个不同的 onChange 处理程序。

相反,我希望只有一个 onChange 处理程序,并从我正在更改的 state 属性的表单字段中传递状态,但我无法弄清楚语法:


  const updateSomeState = (e, prop) => {
    e.persist();
    setSomeState(prevState => {
      prevState.prop = e.target.value;
      return {
        ...prevState
      };
    });
  };

 <p>Update Mobile Number<p>
  <select
   value={someState.customer[0].phone[0].mobile}
   onChange={updateSomeState(e, prop)}
  >
   <option value="123-4567">"123-4567"</option>
  </select>

我尝试使用不同类型的语法将传入的 'prop' 值链接到 prevState:

prevState.prop = e.target.value;

prevState.(prop) = e.target.value;

${prevState} + '.' + ${prop} = e.target.value; // Dumb, I know

但该函数永远无法识别我从该函数传入的“道具”。我确信必须有一个简单的方法来做到这一点。任何帮助将不胜感激。

【问题讨论】:

    标签: json reactjs events nested state


    【解决方案1】:

    使用 lodash 的 _.set 助手。

    const updateSomeState = (e, prop) => {
        e.persist();
        setSomeState(prevState => {
          let customers = [...prevState.customers] // make a copy of array
          let customer = {...customers[0]} // make a copy of customer object
          _.set(customer, prop, e.target.value)
          customers[0] = customer;
          return {
            ...prevState, customers 
          };
        });
      };
    

    顺便说一句,在您现有的 updateSomeStatePhone 中,您正在修改应该是不可变的 prevState 对象。

    【讨论】:

    • 使用这个比reducer有什么优势吗?
    • @yummylumpkins 更少的样板代码。如果向客户添加新属性,则无需更改更新代码。我们可以使用 _.set 编写 reducer 来消除样板代码,但一般来说,在处理更复杂的状态时,我会使用 reducer,因为状态组件之间存在交互/相互依赖。
    【解决方案2】:

    它必须是一个useState 钩子吗?我建议使用useReducer 或使用多个useState 挂钩对其进行简化。

    多个useState 钩子

    import React from "react";
    import ReactDOM from "react-dom";
    
    import "./styles.css";
    
    function App() {
      const [name, setName] = React.useState("");
      const [address, setAddress] = React.useState("");
      const [email, setEmail] = React.useState("");
      const [mobile, setMobile] = React.useState("");
      const [home, setHome] = React.useState("");
    
      const getResult = () => ({
        customer: [
          {
            name,
            address,
            email,
            phone: [
              {
                mobile,
                home
              }
            ]
          }
        ]
      });
    
      // Do whatever you need to do with this
      console.log(getResult());
    
      return (
        <>
          <input
            value={name}
            placeholder="name"
            onChange={e => setName(e.target.value)}
          />
          <br />
          <input
            value={address}
            placeholder="address"
            onChange={e => setAddress(e.target.value)}
          />
          <br />
          <input
            value={email}
            placeholder="email"
            onChange={e => setEmail(e.target.value)}
          />
          <br />
          <input
            value={mobile}
            placeholder="mobile"
            onChange={e => setMobile(e.target.value)}
          />
          <br />
          <input
            value={home}
            placeholder="home"
            onChange={e => setHome(e.target.value)}
          />
        </>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    

    单个useReducer(带简化状态)

    import React from "react";
    import ReactDOM from "react-dom";
    
    import "./styles.css";
    
    const reducer = (state, action) => {
      const { type, value } = action;
    
      switch (type) {
        case "SET_NAME":
          return { ...state, name: value };
        case "SET_ADDRESS":
          return { ...state, address: value };
        case "SET_EMAIL":
          return { ...state, email: value };
        case "SET_MOBILE":
          return { ...state, phone: [{ ...state.phone[0], mobile: value }] };
        case "SET_HOME":
          return { ...state, phone: [{ ...state.phone[0], home: value }] };
        default:
          throw Error(`Unexpected action: ${action.type}`);
      }
    };
    
    const initialState = {
      name: "",
      address: "",
      email: "",
      phone: [
        {
          mobile: "",
          home: ""
        }
      ]
    };
    
    function App() {
      const [state, dispatch] = React.useReducer(reducer, initialState);
    
      // Do what you need with state
      console.log(state);
    
      return (
        <>
          <input
            value={state.name}
            placeholder="name"
            onChange={({ target: { value } }) =>
              dispatch({ type: "SET_NAME", value })
            }
          />
          <br />
          <input
            value={state.address}
            placeholder="address"
            onChange={({ target: { value } }) =>
              dispatch({ type: "SET_ADDRESS", value })
            }
          />
          <br />
          <input
            value={state.email}
            placeholder="email"
            onChange={({ target: { value } }) =>
              dispatch({ type: "SET_EMAIL", value })
            }
          />
          <br />
          <input
            value={state.phone.mobile}
            placeholder="mobile"
            onChange={({ target: { value } }) =>
              dispatch({ type: "SET_MOBILE", value })
            }
          />
          <br />
          <input
            value={state.phone.home}
            placeholder="home"
            onChange={({ target: { value } }) =>
              dispatch({ type: "SET_HOME", value })
            }
          />
        </>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    

    【讨论】:

    • 非常感谢您的回复。问题虽然。 . . 与@ckedar 的 lodash 推荐相比,使用 reducer 有什么优势吗?这似乎是访问嵌套项目的最干净的方式。
    • 嗯,看来他已经删除了他的评论。没关系。 . .
    • 我还注意到 reducer 函数是在组件外部声明的。如果我将这个组件与许多其他带有减速器的类似组件一起导入,这会导致问题吗?
    • 一个优点是使用减速器,您可以更灵活地精确控制状态。您还可以为每个操作/字段执行独特的操作并添加新操作(例如:清除或重置)。而且它不需要添加另一个依赖项(因为它是 vanilla React)。
    • 这里还有一个问题。如果我需要到达上面列出的原始状态的第二个“客户”数组对象来设置名称(state.customer[1].name)怎么办?我似乎找不到合适的语法。我有以下内容:``` case "SET_CUST1_NAME": return { ...state, customer: [ { ...state.customer[1], name: value } ] }; ```
    【解决方案3】:

    useReducer 是执行此操作的更好选择。网上都有例子。

    为什么你不应该使用useState 来传递一个对象是因为它不像setState。底层对象引用是相同的。因此,react 永远不会触发状态更改。如果您想对对象使用相同的useState。您可能必须实现自己的版本来扩展它(下面的示例),或者您可以直接使用useReducer 挂钩来实现相同的目的。

    这里有一个useState 的示例,供您注意每次更改的状态更新。

    const [form, setValues] = useState({
        username: "",
        password: ""
    });
    
    const updateField = e => {
        setValues({
          ...form,
          [e.target.name]: e.target.value
        });
    };
    

    注意那里的...form。您可以在每次更新时执行此操作,也可以使用自己的实用程序或 useReducer,正如我所提到的。

    现在谈到您的代码,还有其他问题。

    1. 您将phone 用作可以是对象的数组。或者更好但单独的属性也可以。无害。

    2. 如果你有customers 作为一个数组,你必须遍历记录。不仅仅是通过硬编码来更新索引。如果只有一个客户,最好不要保留数组,而只保留一个对象。假设它是一组客户,并且您正在循环访问它,这里是更新mobile 的方法。

    const updatedCustomers =  state.customers.map(item => {
    
       const { phone } = item;
       return { ...item, phone: { mobile: e.target.value }}; 
       // returns newCustomer object with updated mobile property
    });
    
    // Then go ahead and call `setSomeState ` from `useState`
    setSomeState(...someState, { customer: updatedCustomers });// newState in your case is 
    

    相反,我宁愿只有一个 onChange 处理程序,然后传入 我是状态属性的表单字段中的状态 改变,但我无法弄清楚语法

    如果您还没有从第一个示例中弄清楚这一点。以下是简短的步骤。

    1. 为您的 HTML 元素指定名称属性。
    2. 然后改用[e.target.name]
       return { ...item, phone: { [e.target.name]: e.target.value }}; 
    
    

    【讨论】:

    • 非常感谢您的回复。我将研究 useReducers。不幸的是,我无法更改正在使用的状态对象的结构。 . .否则,我肯定看到重组状态和使用您建议的方法的价值。
    • 我没有说你强制使用它。我根据 useState 和 use Reducer 向您解释了使用什么和什么不是的原因。另外,我指出了您不使用循环来更新您应该更新的方式。另一种情况,如果它是一次更新。感谢您至少喜欢我的回答。
    猜你喜欢
    • 1970-01-01
    • 2020-05-03
    • 2018-12-05
    • 2021-06-04
    • 1970-01-01
    • 2018-12-08
    • 1970-01-01
    • 1970-01-01
    • 2023-01-11
    相关资源
    最近更新 更多