Why React.Children.only?

34

针对React专家的一个快速问题 ;)

React.Children.only是React的顶级API之一,被React-Redux(<Provider />)和React Router(<Router />)广泛使用,用于将store/router作为上下文注入。这样做的原因是什么?为什么不简单地return props.children呢?似乎与JSX有关?

编辑:请不要解释什么是React.Children.only,我想知道为什么要使用它而不是props.children,后者似乎更强大/灵活。


文档似乎很自我解释 - 确保只有一个子项的好方法。 - Andy Ray
为什么要定义一个只有一个子元素的接口呢?从应用程序的角度来看,将props.children作为元素数组返回是很常见的。 - Allen
3个回答

26

正如docs所指出的

验证children只有一个子元素(React元素),并返回它。否则,此方法将抛出错误。

那么,为什么使用它比仅使用props.children更有帮助呢?

主要原因是它会抛出一个错误,从而停止整个开发流程,因此你不能跳过它。

这是一个方便的工具,可以强制规定只有一个子元素。

当然,你也可以使用propTypes,但这只会在控制台中发出警告,你可能会错过它。

React.Children.only的一个用例是强制执行特定的声明式接口,该接口应由一个逻辑Child组件组成:

class GraphEditorEditor extends React.Component {
  componentDidMount() {
    this.props.editor.makeEditable();
    // and all other editor specific logic
  }

  render() {
    return null;
  }
}

class GraphEditorPreview extends React.Component {
  componentDidMount() {
    this.props.editor.makePreviewable();
    // and all other preview specific logic
  }

  render() {
    return null;
  }
}

class GraphEditor extends React.Component {
  static Editor = GraphEditorEditor;
  static Preview = GraphEditorPreview;
  wrapperRef = React.createRef();

  state = {
    editorInitialized: false
  }

  componentDidMount() {
    // instantiate base graph to work with in logical children components
    this.editor = SomeService.createEditorInstance(this.props.config);
    this.editor.insertSelfInto(this.wrapperRef.current);

    this.setState({ editorInitialized: true });
  }

  render() {
    return (
      <div ref={this.wrapperRef}>
        {this.editorInitialized ?
          React.Children.only(
            React.cloneElement(
              this.props.children, 
              { editor: this.editor }
            )
          ) : null
        }
      </div>
    );
  }
}

可以像这样使用:

class ParentContainer extends React.Component {
  render() {
    return (
      <GraphEditor config={{some: "config"}}>
        <GraphEditor.Editor> //<-- EDITOR mode
      </GraphEditor>
    )
  }
}

// OR

class ParentContainer extends React.Component {
  render() {
    return (
      <GraphEditor config={{some: "config"}}>
        <GraphEditor.Preview> //<-- Preview mode
      </GraphEditor>
    )
  }
}

希望这对你有所帮助。

感谢您的回答,我认为您指出的限制只能处理一个子元素的原因可以通过 PropTypes.element 来处理。虽然 propTypes 不强制执行运行时安全,但这是 TypeScript/Flow 的另一个主题。我不认为它与渲染属性有任何关系,因为 Redux Provider 和 React-Router 的 Router 都没有使用它,但它们确实使用了 React.Children.only。无论如何,还是非常感谢! - Allen
@Xlee 我并不是在指出这些项目特别使用了渲染属性,但很多确实使用了。例如,这里有一个项目 https://github.com/chenglou/react-radio-group/blob/9a992f3bbc1bffeb1dc993e42b0f4842ab299f42/index.jsx - Karen Grigoryan
@Xlee 关于强制执行的问题,这里是React-Router的提交记录,明确概述了其目的 https://github.com/ReactTraining/react-router/commit/5a74d46a9fcf1b9663ba42b238ba6dd4584983ab - Karen Grigoryan
2
@Xlee,我的意思是,他们使用React.Children.only是因为他们想处理单个的React.Element而不是一个数组。这是一个简单的保障措施,他们显然不想处理当你传递数组时的逻辑。这是从他们的代码和React.Children.only本身的定义中得出的唯一合乎逻辑的解释。如果你在寻找任何隐藏的含义,我认为你最好直接问创作者Dan,并让我们都知道 :) 祝你好运。 - Karen Grigoryan
像你的建议一样,伙计。 - Allen
显示剩余3条评论

0

在 React 16 之前, 组件的 render 方法不能返回一个元素数组。当实现一个没有引入任何自己标记的组件时,你只能接受一个单一的子元素:

// We have to ensure that there's only one child, because returning an array
// from a component is prohibited.
const DisplayIf = ({children, condition}, {uniforms}) =>
    condition(uniforms) ? Children.only(children) : nothing;
DisplayIf.contextTypes = BaseField.contextTypes;

现在,您可以简单地使用return props.children,要求调用者在数组元素上包含keys,或者使用Fragment来避免返回数组:return <>{props.children}</>
const DisplayIf = ({children, condition}, {uniforms}) =>
    condition(uniforms) ? <>{children}</> : nothing;
DisplayIf.contextTypes = BaseField.contextTypes;

请注意,这个<DisplayIf><SomeComponent /></DisplayIf>的写法是不好的 - SomeComponent总是会被渲染,如果条件不满足,它就会被DisplayIf抛弃。因此,如果SomeComponent的渲染成本很高,或者在不应该出现时可能会产生错误,这可能会导致问题。 - amik

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接