从嵌套组件中获取包含组件

时间:2019-07-11 21:43:40

标签: javascript reactjs

我正在编写一个ControlledInput组件,并且为了使用ControlledInput访问该组件的状态,我在binder中有一个ControlledInput道具。 / p>

使用该组件时出现了一个小问题:

  render() {
    const CI = props => <ControlledInput binder={this} {...props} />;
    return (
      <div style={styles.container}>
        <h1>NEW RECIPE</h1>
        <ControlledInput binder={this} label={"Title"} />
      </div>
    );
  }

以上实现完全正常。但是,请注意我定义的const CI。我尝试使用它,所以我可以只写<CI label={"Title"}/>而不必写binder,因为在给定的{{1 }} 方法。

使用binder的问题在于,当我在输入中键入内容时,输入“模糊”并且必须重新选择它。这似乎是因为ControlledInput方法在每个渲染上都创建了render

我希望我已经清楚地解释了这一点,因为我的头很痛。

无论如何,为什么发生这种情况对我来说很有意义。而且我知道一种解决方案是将<CI label={"Title"}/>放在render函数之外。但是然后我不得不将其称为CI,这开始破坏目的。

我不能将const CI = props => <ControlledInput binder={this} {...props} />;放在全局范围内,因为那样我就无法访问render

有没有办法解决这个问题?

更新

这是<this.CI>的当前代码(正在进行中):

CI

整个工作的重点是简化创建具有受控组件的表单,避免在每个表单中添加thisControlledInput,我认为这不是DRY。

然后我想通过不将// @flow import React, { Component } from "react"; type Props = { containerStyle?: Object, label: string, propName?: string, binder: Component<Object, Object>, onChange?: Object => void }; class ControlledInput extends Component<Props> { render() { const props = this.props; const propName = props.propName || props.label.toLowerCase(); return ( <div style={props.containerStyle}> <p>{props.label}</p> <input type="text" label={props.label} onChange={ this.props.onChange || (e => { props.binder.setState({ [propName]: e.target.value }); }) } value={props.binder.state[propName]} ></input> </div> ); } } 传递给每个组件来获得更多的DRY,这就是为什么我正在做value={this.state.whatever},这又必须在类内部才能访问{{ 1}},并且在onChange={e=>this.setState({whatever: e})}函数内部被称为binder={this},而不是const CI = props => <ControlledInput binder={this} {...props} />;

第一个解释为什么你需要通过this,尽管我想我也可以有像render这样的道具,在那种情况下,将它们组合成类似的东西确实有意义CI,如@John Ruddell所建议。

请注意,我已经提供了覆盖this.CI的可能性,并计划对大多数或所有其他道具(例如this)进行覆盖。 (尤其是setState={this.setState} parentState={this.state}{...propsToSend}),确实会使组件的可重用性降低,但是主要用例是快速创建没有特殊输入处理的多个输入。

因此,再次,我的理想情况是调用onChange并让组件代码负责正确绑定value={this.props.value || binder.state[propName]}value。如果可能的话。然后,第二种选择是在一个位置上定义一个必要的上下文道具,以便在需要多次实际使用组件时变得很简单,就像这样:

onChange

我听说访问父状态/上下文可能是一种反模式,但是必须有某种方法可以在不使用反模式的情况下做我想做的事情,是吗?

2 个答案:

答案 0 :(得分:1)

如果您想要父级的状态,请在那里处理状态并将值传递给您的输入-ControlledInput除了知道如何处理数据的输入和输出外,什么都不知道。像这样,请注意,我稍微加了些名称,以便您可以看到哪个组件正在处理什么:

import React, { useState } from "react"

const Parent = () => {

  const [title, setTitle] = useState("")

  const handleChangeInParent = (newTitle) => {
    setTitle((oldValue) => newTitle)
  }

  return(<div style={styles.container}>
    <h1>NEW RECIPE</h1>
    <ControlledInput handleChange={handleChangeInParent} label={title} />
  </div>)
}

const ControlledInput = ({handleChange, label}) => {
  return (
    <input onChange={handleChange} type="text" value={label} />
  )
}

如果ControlledComponent需要处理自己的状态,则将其传递为默认值,然后让Parent在保存(或其他任何内容)时读取该值:

import React, { useState } from "react"

const Parent = () => {

  const handleSaveInParent = (newTitle) => {
    console.log("got the new title!")
  }

  return (
    <div style={styles.container}>
      <h1>NEW RECIPE</h1>
      <ControlledInput handleSave={handleSaveInParent} initialLabel="Title" />
    </div>
  )
}

const ControlledInput = ({ handleSave, initialLabel }) => {
  const [title, setTitle] = useState(initialLabel)

  const handleChange = (ev) => {
    const value = ev.target.value
    setTitle((oldValue) => value)
  }

  const handleSubmit = (ev) => {
    ev.preventDefault()
    handleSave(title)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} type="text" value={title} />
    </form>
  )
}

您不应该通过发送this-只需发送值和/或函数来处理值。

答案 1 :(得分:0)

随着更新的实现

(好吧,约翰,你赢了!)

从技术上讲这不是一个肯定的答案,但是我已经重写了该组件,使其采用了state(已更新)一个setterFn的道具:

组件

// @flow
import React, { Component } from "react";
type Props = {
  containerStyle?: Object,
  labelStyle?: Object,
  label: string,
  propName?: string,
  state: Object,
  onChange?: Object => void,
  textArea?: boolean,
  setterFn: (key: string, value: mixed) => void
};

class ControlledInput extends Component<Props> {
  render() {
    const props = this.props;
    const propertyName = props.propName || props.label.toLowerCase();
    const TagType = props.textArea ? "textarea" : "input";
    // only pass valid props to DOM element (remove any problematic custom props)
    const { setterFn, propName, textArea, ...domProps } = props;
    return (
      <div style={props.containerStyle}>
        <p style={props.labelStyle}>{props.label}</p>
        <TagType
          {...domProps}
          label={props.label} // actually could get passed automatically, but it's important so I'm leaving it in the code
          onChange={
            this.props.onChange ||
            (setterFn ? e => setterFn(propertyName, e.target.value) : null)
          }
          value={props.state[propertyName] || ""}
        ></TagType>
      </div>
    );
  }
}

export default ControlledInput;

正在使用(比以前少了一些代码!)

class Wrapper extends Component<Object, Object> {
  state = {};
  render() {
    const setterFn = (k, v) => this.setState({ [k]: v });
    const p = { state: this.state, setterFn: setterFn.bind(this) };
    return <ControlledInput {...p} {...this.props.inputProps} />
  }
}

我想这更合适。它仍然比binder={this}占用更多的空间。

实际上不是以下问题:

  1. 如何从组件访问父级状态。尽管从评论看来,这似乎是一种反模式,但我确实从React的理论中了解了这一点。

  2. 如何在其他地方设置这些重复道具,以便我可以调用`。我猜唯一的解决方案是做这样的事情:

render() {
  const props = {state: this.state, setState: this.setState}
  <ControlledInput {...props} label="Title"/>
} 

这当然不是一个糟糕的解决方案。尤其是如果我将该名称缩写为一个字符。

非常感谢@John Ruddell将我设置在正确的道路上。