React:将方法推送到父组件的替代方法

时间:2018-05-22 17:26:26

标签: javascript reactjs state

我正在使用React和Draft.js处理Rich Text Editor组件,该组件将成为我的团队用来构建应用程序UI的库的一部分。我遇到了组织组件以分离状态和功能的问题。我不确定这对我的问题是否重要,但我们在我们的应用中使用Redux,但在我们的库中没有。

这是我简化的代码版本,试图关注我遇到的问题:

class TextInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      editorState: this.getInitialEditorState()
    };
    this.onChange = this.onChange.bind(this);
    this.toggleLinkMenu = this.toggleLinkMenu.bind(this);
    this.toggleInline = this.toggleInline.bind(this);
    this.toggleBlock = this.toggleBlock.bind(this);
  }

  onChange(editorState) {
    this.setState({ editorState });
  }

  toggleLinkMenu() {
    this.setState({ displayLinkTooltip: !this.state.displayLinkTooltip });
  }

  toggleInline(style) {
    this.setState({
      editorState: RichUtils.toggleInlineStyle(this.state.editorState, this.options.style)
    },
      this.editor.focus
    );
  }

  toggleBlock(blockType) {
    this.setState({
      editorState: RichUtils.toggleBlockType(this.state.editorState, this.options.blockType)
    },
      this.editor.focus
    );
  }

  render() {
    // While I'm developing, I have TextInput hardcoded to display every button in TextInputMenu:
    const buttonList = ['bold', 'italic', 'underline', 'hyperlink', 'headline', 'blockquote'];

    return (
      <div className={styles.textInput} >
        <TextInputMenu buttonList={buttonList} toggleInline={this.toggleInline} toggleBlock={this.toggleBlock} />
        <Editor  // This is the actual Draft.js Editor component where the user types their text
          ref={c => (this.editor = c)}
          editorState={this.props.editorState}
          onChange={this.props.onChange}
          placeholder={this.props.placeholder}
        />
      </div>
    );
  }
}

// TextInputMenu */
// Contains a series of buttons that the user can click on to apply an inline
// style, apply a block style, or insert a hyperlink.
//
// The TextInput passes in a buttonList prop containing the names of the buttons
// that it wants to display.
class TextInputMenu extends React.Component {
  constructor(props) {
    super(props);
    this.menuButtons = {
      bold: { name: 'Bold', icon: 'fa-bold', options: /* Options here */ },
      italic: { name: 'Italic', icon: 'fa-italic', options: /* Options here */ },
      underline: { name: 'Underline', icon: 'fa-underline', options: /* Options here */ },
      hyperlink: { name: 'Hyperlink', icon: 'fa-link', options: /* Options here */ },
      headline: { name: 'Headline', icon: 'fa-header', options: /* Options here */ },
      blockquote: { name: 'Block Quote', icon: 'fa-quote-left', options: /* Options here */ }
    };
  }

  getMenuButtons() {
    return _.pick(this.menuButtons, this.props.buttonList);
  }

  render() {
    return (
      <div className={styles.textInputMenu}>
        {_.map(this.getMenuButtons(), ({name, icon, func}) => <TextInputMenuButton key={name} style={name} icon={icon} onClick={func} />)}
        {this.getMenuButtons().find('hyperlink') && <LinkMenu />}
      </div>
    );
  }
}

const TextInputMenuButton = ({style, icon, onClick}) => (
  <div className={styles.textInputButton}>
    <i onClick={() => onClick(style)} className={`fa ${icon}`} />
  </div>
);

const LinkMenu = ({active, insertHyperlink}) => (
  <Tooltip active={active}>
    <form onSubmit={insertHyperlink}>
      <label>URL:</label>
      <input type="text" name="url" />

      <label>Displayed Text:</label>
      <input type="text" name="displayText" />

      <Button />
    </form>
  </Tooltip>
);

export { TextInput };
export default TextInput;

一般来说,我避免使用refs;但是,我需要调用编辑器组件&#34; s#34;焦点&#34;方法。除了使用ref之外,还有更好的方法来调用子组件的方法吗?

我在TextInput中定位editorState,因为它的子组件使用它。但是,这要求我找到在TextInput中改变editorState的方法,即使在子组件中定位它们更有意义。例如,我想将处理TextInputMenu按钮(toggleLinkMenu,toggleInline和toggleBlock)行为的方法移动到菜单本身,因为随着我添加更多按钮,传递配置变得越来越难以处理并处理PropTypes。有一些替代模式吗?我知道React生态系统倾向于支持函数式编程技术,但是从面向对象编程中获取一个页面并将editorState的setter函数传递给Menu似乎更好。

2 个答案:

答案 0 :(得分:0)

Is there a better way to call a child component's methods other than using a ref

我不知道更好,但如果您不想使用ref,那么您可以重新渲染父级(例如,使用setState)将支柱传给孩子 - 例如提升状态 - 然后您可以在孩子的componentDidUpdate方法中使用。将之前的道具与当前道具进行比较,如果您传递的道具不同,则调用该方法。

答案 1 :(得分:0)

Draft.js有一个类似你所描述的setter函数:onChange。您将其传递给Draft.js <Editor>,并且也可以将其传递给<TextInputMenu>。扩展Draft.js编辑器时,这是一种相当常见的模式。

对于您的focus问题,如果您尚未阅读,请查看official documentation,了解为何必须对其进行管理。

查看您的示例,您也可以通过将工具栏按钮与onMouseDown而不是onClick绑定来解决此问题 - 这样,在事件中使用preventDefault()时,就不会有单击按钮时焦点转移,您无需拨打this.editor.focus()。例如,这可以在official rich text example

中完成
class StyleButton extends React.Component {
  constructor() {
    super();
    this.onToggle = (e) => {
      e.preventDefault();
      this.props.onToggle(this.props.style);
    };
  }
  render() {
    let className = 'RichEditor-styleButton';
    if (this.props.active) {
      className += ' RichEditor-activeButton';
    }
    return (
      <span className={className} onMouseDown={this.onToggle}>
        {this.props.label}
      </span>
    );
  }
}