【问题标题】:React: Is it possible to call a higher-order component within a container component?React:是否可以在容器组件中调用高阶组件?
【发布时间】:2017-05-19 07:46:48
【问题描述】:

在我的代码库中,我有一个高阶组件 (HOC),用于将所有输入验证功能添加到给定组件。在像这样定义的组件上使用时效果很好......

let NameInput = React.createClass({
    render() {
        return (
            <div>
                <label htmlFor="name-input">Name</label>
                <input name="name-input" />
            </div>
        );
    }
});

let NameInput = addInputValidation(NameInput);

module.exports = NameInput;

但我现在需要根据来自服务器的数组定义一系列输入。像这样的...

let TestApp = React.createClass({
    render() {
        // Pretend the names array came from the server and is actually an array of objects.
        let names = ['First name', 'Middle name', 'Last name'];

        // Map over our names array in hopes of ending up with an array of input elements
        let nameComponents = names.map((name, index) => {
            let componentToRender = (
                    <div key={index}>
                        <label htmlFor={name}>{name}</label>
                        <input name={name} />
                    </div>
            );

            // Here is where I'd like to be able to use an HOC to wrap my name inputs with validation functions and stuff
            componentToRender = addInputValidation(componentToRender);

            return componentToRender;
        })


        return (
            <div>
                <p>Enter some names:</p>
                {nameComponents}
            </div>
        );
    }
})

let addInputValidation = function(Component) {
    let hoc = React.createClass({
        getInitialState() {
            return {
                isValid: false
            };
        },
        render() {
            return (
                <div>
                    <Component {...this.props} />
                    {this.state.isValid ? null : <p style={{color: "red"}}>Error!!!!</p>}
                </div>
            );
        }
    });

    return hoc;
}

module.exports = TestApp;

React 不喜欢你尝试渲染从另一个组件中调用 HOC 的结果。

我认为这与我的 componentToRender 不是真正的 React 组件或其他东西有关。

所以我的问题是......

为什么我不能从另一个组件中调用 HOC?

有没有办法在数组的每个元素上调用 HOC?

这是一个可能有帮助的 jsfiddle:https://jsfiddle.net/zt50r0wu/

编辑以澄清一些事情:

我映射的数组实际上是一个描述输入细节的对象数组。包括输入的类型(选择、复选框、文本等)。

另外,我的addInputValidation HOC 实际上接受的参数不仅仅是组件。它需要一组存储索引,这些索引将从 Redux 存储中提取以用于验证。这些存储索引源自描述输入的对象数组中的信息。访问这个潜在的动态数组是我希望能够在 React 生命周期内调用我的 HOC 的原因。

所以映射到我的输入数组可能看起来更像这样......

let Select = require('components/presentational-form/select');
let Text = require('components/presentational-form/select');
let CheckboxGroup = require('components/presentational-form/select');
let TestApp = React.createClass({
    render() {
        // Pretend the inputs array came from the server
        let inputs = [{...}, {...}, {...}];
        // Map over our inputs array in hopes of ending up with an array of input objects
        let inputComponents = inputs.map((input, index) => {    
            let componentToRender = '';

            if (input.type === 'select') {
                componentToRender = <Select labelText={input.label} options={input.options} />;
            } else if (input.type === 'text') {
                componentToRender = <Text labelText={input.label} />;
            } else if (input.type === 'checkbox') {
                componentToRender = <CheckboxGroup labelText={input.label} options={input.options} />;
            }

            // Here is where I'd like to be able to use an HOC to wrap my name inputs with validation functions and stuff
            componentToRender = addInputValidation(componentToRender, input.validationIndexes);

            return componentToRender;
        })


        return (
            <div>
                <p>Enter some names:</p>
                {inputComponents}
            </div>
        );
    }
})

【问题讨论】:

  • addInputValidation 期望传递一个组件,但您传递的是一个 元素 (&lt;div /&gt;)。那是行不通的。 “有没有办法在数组的每个元素上调用 HOC?” 当然。 HOC 是一个函数,很容易为数组的每个元素调用一个函数。这是否有效取决于元素是什么以及函数期望什么。如果你有一个 components 数组,它会正常工作。
  • @Kory 你能告诉我们你的 addInputValidation HOC 吗?

标签: javascript reactjs higher-order-functions higher-order-components


【解决方案1】:

关于您的编辑:问题仍然是您从.map 回调返回一个组件而不是一个元素。但这很容易通过改变来解决

return componentToRender;

return React.createElement(componentToRender);

您的代码中的问题是:

  • addInputValidation 期望传递一个 component 但你传递给它一个 element (&lt;div /&gt;)。
  • JSX 期望传递一个(数组)元素,但您传递给它的是一个组件数组。

似乎解决您的问题的最简单方法是创建一个通用的Input 组件,该组件接受名称和值作为道具:

let Input = React.createClass({
    render() {
        return (
            <div>
                <label htmlFor={this.props.name}>{this.props.name}</label>
                <input name={this.props.name} />
            </div>
        );
    }
});

module.exports = addInputValidation(Input);

用作

let nameComponents = names.map((name, index) => <Input key={index} name={name} />);

【讨论】:

  • 感谢您的快速回复!它清除了一些东西。如果一个函数期望返回一个基于给定它的组件的组件,那么在给定一个元素时会中断,这是有道理的。我的根本问题仍未解决。不是你的错。我想我把一些事情简单化了。我将添加一个编辑来澄清其中的一些内容。理想的是能够在另一个组件中定义一个 React 组件。我有一种感觉,要么是不可能的,要么是反模式。
  • 关于您的编辑,返回元素而不是组件所需要做的就是return React.createElement(componentToRender);
【解决方案2】:

我认为您遇到的问题是组件与元素之间的区别。我发现将组件视为函数并将元素视为该函数的结果很有帮助。所以你真正想做的就是有条件地选择三个不同的函数之一,传递一些参数,然后显示结果。我相信你想要这样的东西:

(这个可以清理,顺便说一句,只是尽量保留你的代码结构)

let Select = require('components/presentational-form/select');
let Text = require('components/presentational-form/select');
let CheckboxGroup = require('components/presentational-form/select');
let TestApp = React.createClass({
    render() {
        // Pretend the inputs array came from the server
        let inputs = [{...}, {...}, {...}];
        // Map over our inputs array in hopes of ending up with an array of input objects
        let inputComponents = inputs.map((input, index) => {    
            let ComponentToRender;
            let props;            

            if (input.type === 'select') {
                ComponentToRender = addInputValidation(Select);
                props = { labelText: input.label, options: input.options };
            } else if (input.type === 'text') {
                ComponentToRender = addInputValidation(Text);
                props = { labelText: input.label };
            } else if (input.type === 'checkbox') {
                ComponentToRender = addInputValidation(CheckboxGroup);
                props = { labelText: input.label, options: input.options };
            }

            let element = <ComponentToRender {...props} />;

            return element;
        })


        return (
            <div>
                <p>Enter some names:</p>
                {inputComponents}
            </div>
        );
    }
})

【讨论】:

    【解决方案3】:

    是的,当然有办法。但是 HOC 通常是一种非常痛苦的处理验证方式。

    There is a different approach to validation,基于ValueLink pattern

    只需将生成的代码与高阶组件方法进行比较。

    【讨论】:

    • 我喜欢这个想法,但目前可能不会实施。你的 Medium 文章很好地分解了它。你在你的 React 应用中使用 Redux/Flux 吗?如果是这样,您是否发现 Redux 和 ValueLink 的功能完全重叠或冲突?
    • 我根本不使用 Redux/Flux。简单的案例(实际上可能是非常复杂的页面)可以完全使用链接来处理,如此处所示,并且看起来要好得多。 medium.com/@gaperton/… 对于大型 SPA,我们使用 github.com/Volicon/NestedReact,这是与 Links 集成的通用状态管理解决方案。里面有一些例子。我必须补充一点,我是所有这些东西的作者。
    • 是的,远离 Flux 而有点接近 NestedReact 的流行状态管理解决方案是 mobx。但是,它没有双向数据绑定和序列化功能。但是很受欢迎。
    猜你喜欢
    • 2022-01-19
    • 1970-01-01
    • 2017-07-20
    • 2016-10-09
    • 2018-12-31
    • 1970-01-01
    • 1970-01-01
    • 2020-03-08
    • 2020-02-20
    相关资源
    最近更新 更多