首页 > 解决方案 > 编写通用的 React 输入钩子

问题描述

我正在尝试编写一个通用的 React Hook 来允许我更新对象。

我参考了:Input Hook -(来源:https ://rangle.io/blog/simplifying-controlled-inputs-with-hooks/ )并做了一些更改:

import { useState } from "react";

export const useForm = initialObject => {
  const [values, setValues] = useState(initialObject);

  return {
    values: values || initialObject,
    setValues,
    reset: () => setValues({}),
    bind: {
      onChange: (event) => {
        setValues({
          ...values,
          [event.target.id]: event.target.value
        })
      }
    }
  };
};

这适用于单级对象:

{ name: '', type: '' }

但对于具有嵌套值的对象:

{ name: '', type: '', price: { dollar: 5, cents: 20  } }

我太确定我应该如何替换[event.target.id]来读取嵌套级别的对象。

有人可以建议吗?

更新:

import { useState } from "react";

export const useForm = initialObject => {
  const [values, setValues] = useState(initialObject);

  return {
    values: values || initialObject,
    setValues,
    reset: () => setValues({}),
    bind: {
      onChange: (event) => {
        // ###need make this part generic###
        // event.target.id will be "price.dollar"
        values['price']['dollar'] = event.target.value;
        setValues({
          ...values
        })
      }
    }
  };
};

标签: javascriptreactjsreact-hooks

解决方案


一般来说,你的钩子应该接受 anamevalue更新你的本地状态。显然你的钩子总是收到一个event并且你提取event.target.id作为name字段的和event.target.value作为字段的值。我建议您更新您的钩子以接收 aname和 avalue作为参数,并让使用该钩子的组件定义什么是namevalue

根据您的钩子,您可以像这样更新嵌套对象。请看一下这个例子。

import React, { useState } from "react";
import ReactDOM from "react-dom";

const useForm = initialObject => {
  const [values, setValues] = useState(initialObject);

  return {
    values: values || initialObject,
    setValues,
    reset: () => setValues({}),
    bind: {
      onChange: event => {
        setValues({
          ...values,
          [event.target.id]: event.target.value
        });
      }
    }
  };
};

const App = () => {
  const { values, bind } = useForm({
    name: "",
    type: "",
    price: { dollar: 5, cents: 20 }
  });
  return (
    <div>
      Hook state:
      <pre>{JSON.stringify(values, null, 4)}</pre>
      <div>
        <div>
          <label>
            Name : <br />
            <input id="name" onChange={bind.onChange} />
          </label>
        </div>
        <div>
          <label>
            Type : <br />
            <input id="type" onChange={bind.onChange} />
          </label>
        </div>
        <div>
          <label>
            Price - Dollar : <br />
            <input
              id="dollar"
              type="number"
              onChange={e => {
                bind.onChange({
                  target: {
                    id: "price",
                    value: { ...values.price, [e.target.id]: e.target.value }
                  }
                });
              }}
            />
          </label>
        </div>
        <div>
          <label>
            Price - Cents : <br />
            <input
              id="cents"
              type="number"
              onChange={e => {
                bind.onChange({
                  target: {
                    id: "price",
                    value: { ...values.price, [e.target.id]: e.target.value }
                  }
                });
              }}
            />
          </label>
        </div>
      </div>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

更新(2019 年 11 月 25 日):

但是,您可以按以下方式更新您的钩子

const useForm = initialObject => {
  const [values, setValues] = useState(initialObject);

  return {
    values: values || initialObject,
    setValues,
    reset: () => setValues({}),
    bind: {
      onChange: event => {
        setValues({
          ...values,
          [event.target.id]: event.target.value
        });
      },
      onNestedChange: (event, name) => {
        setValues({
          ...values,
          [name]: {
            ...values[name],
            [event.target.id]: event.target.value,
          }
        })
      }
    }
  };
};

然后在您的输入中,您可以编写如下:

<div>
  <label>
    Price - Dollar : <br />
    <input
      id="dollar"
      type="number"
      onChange={e => bind.onNestedChange(e, 'price')}
    />
  </label>
</div>
<div>
  <label>
    Price - Cents : <br />
    <input
      id="cents"
      type="number"
      onChange={e => bind.onNestedChange(e, 'price')}
    />
  </label>
</div>

这样你就为嵌套对象创建了另一个绑定方法,也许你可以添加另一个被调用array的东西。希望这能给你一些关于如何改进钩子的想法。顺便说一句,有很多方法可以做到这一点,这只是一个例子。可能有更好的方法来做到这一点。

更新 2(2019 年 11 月 25 日):

我已经更新了你的useForm钩子,现在你可以将嵌套对象属性设置为你的状态。但是,我没有使用数组进行测试,它可能会导致问题。

const useForm = initialObject => {
  const [values, setValues] = useState(initialObject);
  // Copied and modified from https://stackoverflow.com/a/18937118/11125492
  const nestedObjectSet = (obj, path, value) => {
    let schema = obj; // a moving reference to internal objects within obj
    const pList = path.split(".");
    const len = pList.length;
    for (let i = 0; i < len - 1; i++) {
      let elem = pList[i];
      if (!schema[elem]) schema[elem] = {};
      schema = schema[elem];
    }
    schema[pList[len - 1]] = value;
  };
  // handleOnChange update state value
  const handleOnChange = event => {
    let newValues = Object.assign({}, values);
    nestedObjectSet(newValues, event.target.name, event.target.value);
    setValues(newValues);
  };
  return {
    values: values || initialObject,
    setValues,
    reset: () => setValues({}),
    bind: {
      onChange: handleOnChange
    }
  };
};

你可以这样使用它。请注意,我已将key对象的 更改为从event.target.idevent.target.namekey应该设置在而name不是id

const App = () => {
  const { values, bind } = useForm({
    name: "",
    type: "",
    price: { dollar: 5, cents: 20 }
  });
  return (
    <div>
      Hook state:
      <pre>{JSON.stringify(values, null, 4)}</pre>
      <div>
        <div>
          <label>
            Name : <br />
            <input name="name" {...bind} />
          </label>
        </div>
        <div>
          <label>
            Type : <br />
            <input name="type" {...bind} />
          </label>
        </div>
        <div>
          <label>
            Price - Dollar : <br />
            <input name="price.dollar" type="number" {...bind} />
          </label>
        </div>
        <div>
          <label>
            Price - Cents : <br />
            <input name="price.cents" type="number" {...bind} />
          </label>
        </div>
      </div>
    </div>
  );
};

沙盒演示链接:https ://codesandbox.io/s/react-useform-hook-nested-object-cqn9j?fontsize=14&hidenavigation=1&theme=dark


推荐阅读