【问题标题】:React transclusion and communication with container componentReact 嵌入和与容器组件的通信
【发布时间】:2019-03-22 09:46:39
【问题描述】:

我正在寻找合适的模式/方法来使用 React 开发它,但到目前为止我没有找到任何相关/优雅的东西。

我基本上想写一个表单引擎。对于表单的每个输入,我想要一些通用的行为。

我在 React 文档中读到继承不是正确的方法;好的方法是设计一个通用组件并通过组合对其进行专门化。

在我的顶级组件中,我想写如下内容:

<Form>
    <TextInput .../>
    <TextInput .../>
    <EmailInput .../>
</Form>

每种类型的输入基本上都必须做同样的事情:例如:根据它的验证器检查这个值,等等。

所以,我设计了一个通用组件FormInput,其中包含所有这些标准行为。当我编写 TextInput 组件时,它是这样的:

export default class TextInput extends React.Component {

    constructor(props) {
        super(props);
    }

    render() {
        return (
            <FormInput>
                <input name={this.props.name} 
                       type='text'
                       onChange={this.onChange}
                       onBlur={this.dirty}
                       value={this.state.value}
                />
            </FormInput>
        );
    }

}

现在,问题在于this.onChangethis.dirty 是位于FormInput 组件中的标准行为,所以很明显,我不能像那样直接访问它们...

将转入的内容连接到其容器组件的正确方法是什么?

编辑

只是为了澄清和总结目标,我基本上想制作一个通用组件和一个嵌入的内容/模板。这个问题是我需要将特定的 DOM 模板(在特定组件中)绑定到通用处理程序(在通用组件中)。

提前致谢!

【问题讨论】:

  • 您希望FormInputTextInput 处理更改和验证?
  • @Sagivb.g FormInput 必须托管所有标准内容,例如更改和验证。 TextInput 必须基本上只承载 html &lt;input&gt; 标记及其样式。
  • 所以看来你的层次结构是错误的 imo,FormInput 应该换行 TextInput
  • 很可能是我的层次结构错了!您能否提供一个包含适当包装组件的样本?
  • 另一个问题,Form 不应该为每个孩子input 处理onChangevalue 之类的逻辑吗?

标签: javascript reactjs typescript transclusion


【解决方案1】:

如果我理解正确,您的 onChangedirty 方法是在 FormInput 组件中实现的。在这种情况下,input 元素应该放在组件的 render 方法中,而不是作为子元素传递。

FormInput 的渲染方法应该是这样的:

render(){
    return  <input name={this.props.name} 
                       type={this.props.type}
                       onChange={this.onChange}
                       onBlur={this.dirty}
                       value={this.state.value}
                />
}

FormInput 也应该有 name 和 type 属性才能以这种方式使用它:

 <FormInput name='A name' type='text' />

我猜,最初您试图传递一个包含其他 html 元素的输入。如果是这种情况,按照我的建议,您只需包装新的 FormInput 即可获得相同的结果。

更新:

要在 FormInput 组件中渲染其他类型的表单元素,如果您对条件渲染不满意,您可以尝试其他方式,在带有您的 type 属性的渲染中使用 createElement 方法。

更多信息在这里: Create Element

我以前使用过它,并且在我的项目中运行良好。

【讨论】:

  • 这可能行得通,不幸的是,这是一个相当简单的版本...type={this.props.type} 不足以涵盖任何领域:如果我想实现复选框或单选按钮,或者任何其他需要不同的 DOM?
  • FormInput 可以使用 type prop 有条件地渲染任何表单元素
  • 这可以工作,但感觉不是很漂亮...我很失望,显然没有简单的方法可以访问容器组件...
  • 我了解你 :) 我更新了答案,希望对您有所帮助。
【解决方案2】:

根据您的评论:

FormInput 必须托管所有标准内容,例如更改和验证。 TextInput 必须基本上只承载 html &lt;input&gt; 标记及其样式。

在我看来,您的层次结构似乎是错误的。
我将创建一个 Input 负责原始 input 的外观和 Form 负责嵌套 input 的行为方式以及它们保存的数据。

这是一个运行示例,显然你可以用其他方式设置它的样式,而不是内联样式。

const Input = ({ label, ...rest }) => (
  <div style={{ display: "flex" }}>
    <div style={{ display: "flex", alignItems: "center", margin: "10px 0" }}>
      <label style={{ minWidth: "100px", margin: "0 10px" }}>{label}</label>
      <input style={{ padding: "0.5em" }} {...rest} />
    </div>
  </div>
);

class Form extends React.Component {
  state = {
    fName: "",
    lName: "",
    age: null
  };

  onChange = e => {
    const { name, value } = e.target;
    this.setState({ [name]: value });
  };

  onSubmit = e => {
    e.preventDefault();
    const { onSubmit } = this.props;
    onSubmit(this.state);
  };

  render() {
    const { fName, lName, age } = this.state;
    return (
      <form onSubmit={this.onSubmit}>
        <Input
          type="text"
          name="fName"
          label="First Name"
          value={fName}
          required
          onChange={this.onChange}
        />
        <Input
          type="text"
          name="lName"
          label="Last Name"
          value={lName}
          required
          onChange={this.onChange}
        />
        <Input
          type="number"
          name="age"
          label="Age"
          value={age}
          required
          onChange={this.onChange}
        />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

function App() {
  return (
    <div>
      <Form onSubmit={data => console.log(data)} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

【讨论】:

  • 这个问题与@MiguelML 的回答基本相同。在这里,Input 组件不够灵活:如果我想渲染复选框、单选按钮或选择,我将无法做到这一点……这就是为什么我真的很想拥有一个组件输入类型,将 DOM 模板从位于另一事物中的通用组件中分离出来。也许我的主要错误是FormInput 是一个组件......也许我应该制作/使用除组件之外的其他东西来托管所有通用的东西。
  • 我认为你的问题是你试图将几个原语概括为一个。 inputcheckboxradiotext 等...但 select 不是 input。如果你真的想要一个组件来渲染任何东西,那么你可以有条件地渲染input(具有相关类型)或select。但在我看来,这个组件可能有一个糟糕的 API,你会传递什么? options? value? selectedValue? isOpen?在我看来,过于笼统是一个缺陷
  • 是的,你可能是对的,我正在尝试完成一些过于通用的事情......
【解决方案3】:

我终于明白了!

这是我的解决方案:

export default class TextInput extends React.Component {

    constructor(props) {
        super(props);
    }

    template(state, formInput) {
        return (
             <input name={this.props.name} 
                    type='text'
                    onChange={formInput.onChange}
                    onBlur={formInput.dirty}
                    value={state.value}/>
        );
    }

    render() {
        return (
            <FormInput {...this.props} template={this.template} />
        );
    }

}

终于比预想的要容易了,不过我没用transclusion。

FormInput 组件托管所有内容,从状态到通用代码/行为(如 dirtyonChange)。

TextInput 组件仅实例化 FormInput 并托管一个名为 template 的函数,该函数采用远程状态和通用 FormInput 组件本身来访问其函数。

然后在FormInput,我有以下内容:

render() {
    return this.props.template(this.state, this);
}

通用输入组件调用template 函数并传入渲染所需的内容。

这样,我将视图与其行为分离。

希望你会喜欢它,并希望它对以后的人有所帮助;)

【讨论】:

    猜你喜欢
    • 2017-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-21
    • 1970-01-01
    • 2019-02-09
    • 2018-10-14
    相关资源
    最近更新 更多