简体   繁体   English

React:是否可以在容器组件内调用更高阶的组件?

[英]React: Is it possible to call a higher-order component within a container component?

In my codebase I have a higher-order component (HOC) I use to add all the input validation functionality to a given component. 在我的代码库中,我有一个高阶组件(HOC),可用于将所有输入验证功能添加到给定组件。 It works great when used on a defined component like so... 在像这样的已定义组件上使用时,它效果很好。

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;

But I now have a need to define a series of inputs based on an array from the server. 但是我现在需要基于服务器上的数组定义一系列输入。 Something like this... 像这样

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 doesn't like it when you try to render the result of calling an HOC from within another component. 当您尝试呈现从另一个组件内部调用HOC的结果时,React不喜欢它。

I assume it has something to do with the fact that my componentToRender isn't really a React component or something. 我认为这与我的componentToRender实际上不是一个React组件无关。

So my questions are... 所以我的问题是

Why can't I call a HOC from within another component? 为什么不能从另一个组件中调用HOC?

Is there a way to call a HOC on each element of an array? 有没有一种方法可以在数组的每个元素上调用HOC?

Here's a jsfiddle that might help: https://jsfiddle.net/zt50r0wu/ 这是一个可能有用的jsfiddle: https ://jsfiddle.net/zt50r0wu/

EDIT TO CLARIFY SOME THINGS: 编辑以澄清某些事情:

The array that I'm mapping over is actually an array of objects that describe the details of the input. 我映射到的数组实际上是描述输入细节的对象数组。 Including the type of input (select, checkbox, text, etc). 包括输入类型(选择,复选框,文本等)。

Also my addInputValidation HOC actually takes more arguments than just the component. 另外,我的addInputValidation HOC实际上接受的参数不仅仅是组件。 It takes an array of store indexes that will be pulled from the Redux store to be used for validation. 它需要一个将从Redux存储拉出的存储索引数组,以用于验证。 These store indexes are derived from information in the array of objects describing the inputs. 这些存储索引来自描述输入的对象数组中的信息。 Having access to this potentially dynamic array is the reason I want to be able to call my HOC within the React life-cycle. 可以访问此潜在动态数组是我希望能够在React生命周期内调用HOC的原因。

So mapping over my array of inputs might look more like this... 因此,在我的输入数组上进行映射可能看起来像这样……

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>
        );
    }
})

Regarding your edit: The problem is still that you are returning a component instead of an element from the .map callback. 关于您的编辑:问题仍然在于,您正在从.map回调中返回组件而不是元素。 But that can be easily solved by changing 但这可以通过更改轻松地解决

return componentToRender;

to

return React.createElement(componentToRender);

The problems in your code are: 您的代码中的问题是:

  • addInputValidation expects to be passed a component but you are passing it an element ( <div /> ). addInputValidation期望传递一个组件,但是您传递给它一个元素<div /> )。
  • JSX expects to be passed an (array of) element(s) but you are passing it an array of components. JSX希望传递一个(数组)元素,但是您正在传递它一个组件数组。

It seems the simplest way to solve your problem would be create a generic Input component that accepts the name and value as prop: 看来解决问题的最简单方法是创建一个通用的Input组件,该组件接受名称和值作为prop:

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);

Which is used as 用作

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

The thing I think you're tripping over is the distinction between a component vs. an element. 我认为您要克服的是组件与元素之间的区别。 I find it helpful to think of a component as a function and an element as the result of that function. 我发现将组件视为功能,并将元素视为该功能的结果很有帮助。 So all you're really trying to do is conditionally choose one of three different functions, pass it some arguments, and the display the results. 因此,您真正要做的就是有条件地选择三个不同功能之一,将其传递一些参数,然后显示结果。 I believe you want something like this: 我相信您想要这样的东西:

(this can be cleaned up, BTW, just tried to preserve your code structure as much as possible) (顺便说一句,可以清理一下,只是尝试尽可能地保留您的代码结构)

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>
        );
    }
})

Yes, there is a way of course. 是的,有一种方法。 But HOC is quite a painful way to handle validation in general. 但是,HOC通常是处理验证的一种痛苦方式。

There is a different approach to validation , based on ValueLink pattern . 基于ValueLink模式有另一种验证方法

Just compare the resulting code to higher order components approach. 只需将结果代码与高阶组件方法进行比较即可。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM