Reactjs - 将props继承到一级子级和嵌套子级的正确方法

IT技术 reactjs
2021-05-12 11:31:29

就我而言,我尝试创建一个简单的表单组件 - 主要用于“测试” reactjs 并使用它。

为此,我使用 2 个组件。第一个组件是父组件,即“表单”组件。第二个组件是表单的字段 - 例如一个简单的文本字段。这是它看起来像的标记:

<Form
  schema={MyFormSchema}
>
   <Input name="name" />
   <Input name="age" />
</Form>

在 MyFormSchema 中,我拥有每个“输入”类型的孩子所需的所有信息。对于这种情况,我在“表单”组件中完成了此操作:

表单.jsx

Form = React.createClass({
  renderChildren() {
    return React.Children.map(this.props.children, (child)=>{
      if (child.type && child.type.prototype && child.type.prototype.constructor.displayName === 'Input') {
        let additionalProps = {
            fieldSchema: this.props.schema.pick(child.props.name),
            onChange: this.onChange
        };
        return React.cloneElement(child, additionalProps);
      }
      return child;
    });
  },
  render() {
    return(
      <form>
        {this.renderChildren()}
      </form>
    );
  }
});

我在这里做的是“克隆”每个“输入”子项并根据模式添加一些新props。

所以第一个问题是:

这真的是 reactJs 中的正确战争吗?当我没有“克隆”每个元素并添加新属性时,我必须直接在我的视图中添加属性,对吗?类似的东西,但我试图阻止这种情况,因为我需要的所有信息都已经作为表单架构中的props。

在玩弄这个之后我发现, this.props.children 只有第一级的孩子。但是,当我在我的Form组件中嵌套我的孩子时,我的组件将不再工作,因为我的组件正在用Input被操纵的组件替换组件。

例子:

<Form
  schema={MyFormSchema}
>
   <AnotherComponent>
       <Input name="name" />
   </AnotherComponent>
   <Input name="age" />
</Form>

当我像现在一样这样做时,此代码将不再起作用,因为在 this.props.children 我只有 [AnotherComponent, Input[name=age]] 而缺少 Input[name=name]。所以我认为我这样做的方式是错误的。但我不确定。

所以主要问题是:

就像在我的例子中一样:在 ReactJs 中将props(或任何东西)继承给所有孩子(也是嵌套的)的正确方法是什么 - 或者这在“react”方式中是不可能的,我真的必须传递所有必要的props给所有孩子?

编辑:

当我谈论“将所有必要的props传递给所有孩子”时,我的意思是这样的:

<Form
  schema={MyFormSchema}
>
   <AnotherComponent>
       <Input name="name" fieldSchema={this.getFieldSchema('name')} onChange={this.onChange} />
   </AnotherComponent>
   <Input name="age" fieldSchema={this.getFieldSchema('age')} onChange={this.onChange} />
</Form>

在这个例子中,我将传递我想由父级动态添加的所有必要的props。在我上面的示例中,下一个问题是:“this”由于其父 AnotherComponent 而不适用于名称输入。所以我必须参考父母——当然:这是可能的,但我认为这将是一种丑陋的方式。

4个回答

深度传递props的三种正确方式:

1) 实际上只是将它们从每个组件向下传递到下一个组件(这是最易读的(就代码逻辑而言),但是一旦您有太多props要传递并且树中有很多级别,就会变得笨拙。

例子:

import React from 'react';    

var GrandParent = React.createClass({
  render () {
    return (
      <Parent familyName={'componentFamily'} />
    );
  }
});

var Parent = React.createClass({
  render () {
    return (
      <Child familyName={props.familyName} />
    );
  }
});

var Child = React.createClass({
  render () {
    return (
      <p>{'Our family name is ' + props.familyName}</p>
    );
  }
});

2)使用Flux风格的 store(我更喜欢Reflux,虽然Redux现在风靡一时)来保持一个共同的状态。然后所有组件都可以访问该存储。至少对我来说,这是当前的首选方法。很清楚,它将业务逻辑排除在组件之外。

示例(使用回流):

import React from 'react';
import Reflux from 'reflux';

var MyStore = Reflux.createStore({
  // This will be called in every component that connects to the store
  getInitialState () {
    return {
      // Contents of MyFormSchema here
    };
  }
});

var Input = React.createClass({
  propTypes: {
    name: React.PropTypes.string.isRequired
  },
  mixins: [Reflux.connect(MyStore)],
  render () {
    // I don't know what MyFormSchema so I'm generalizing here, but lets pretend its a map that uses the name of each field a key and then has properties of that field within the map stored at the key/value
    return (
      <input type={this.state[name].type} name={this.props.name} value={this.state[name].type} />
    );
  }
});

3) 使用 React 的上下文特性。正如您从文档中立即看到的那样,此功能仍在开发中,可能会在 React 的未来版本中进行更改甚至删除。因此,虽然这可能是将 props 传递给组件树的最简单方法,但我个人会远离它,直到它成为更多的最终功能。

我不打算为此写一个例子,因为文档说得很清楚。但是,请确保向下滚动文档页面并查看Parent-child Coupled,这就是您现在正在做的事情。

另一个解决方案是,与其使用一个单独的组件来渲染Form及其Inputs,不如像目前那样将 prop 传递给 Form,然后简单地Input使用Form's渲染个体render()

你可以这样使用 react-addons-clone-with-props 包:

import React, { Component } from 'react';
import cloneWithProps from 'react-addons-clone-with-props';

// ...

class Form extends Component {
  recursivelyMapChildren(children) {
    return React.Children.map(children, child => {
      if (!React.isValidElement(child)) {
        return child;
      }

      return React.cloneElement(child, {
        ...child.props,
        children: this.recursiveCloneChildren(child.props.children)
      });
    })
  }

  render() {
    return (
      <form>{this.recursivelyMapChildren(this.props.children)}</form>
    );
  }
}

代码的作用:

  1. 通过预定义的childrenprop获取所有子组件(请参阅文档)。
  2. 使用React.Children.map方法递归映射子集合,将 lambda 函数应用于每个元素。
  3. 将映射的(即更新的,但未突变的!)子元素保存为mappedChildren常量。
  4. 将它们放在formDOM 元素中。

看起来很简单,应该如此。

但是你必须记住,当你的代码保持干净和透明时,React 很棒。当你明确地传递props时

<Form
  schema={MyFormSchema}
>
   <Input
     name="name"
     schema={MyFormSchema} />
   <Input
     name="age"
     schema={MyFormSchema} />
</Form>

当您不小心更改了底层逻辑时,破坏的事情就会少很多。

谢谢你。学分@Rishat Muhametshin

我已经使用上述方法创建了一个可重用的方法。这很好用:

实用程序/递归MapChildren.jsx

const recursivelyMapChildren = (children, addedProperties) => {

        return React.Children.map(children, child => {
            if (!React.isValidElement(child)) {
                return child;
            }

            return React.cloneElement(child, {
                ...child.props,
                ...addedProperties,
                children: this.recursivelyMapChildren(child.props.children, addedProperties)
            });
        })
 };
export default recursivelyMapChildren;

用例:

表单.jsx

import recursivelyMapChildren from 'utils/recursivelyMapChildren';
class Form extends Component {

    handleValidation(evt, name, strValidationType){
/* pass this method down to any nested level input field */
    }

    render(){

        return (
            <form>
                {recursivelyMapChildren(this.props.children, {
                    handleValidation: this.handleValidation.bind(this)
                })}

                <input type="submit" value="submit" className="validation__submit"/>
            </form>
        )
    }
}
export default Form

SomeExample.jsx

const SomeExample = () => {
    return (
        <Form>
            <input type="hidden" name="_method" value="PUT"/>
            <fieldset>
                <legend>Personal details</legend>
                <div className="formRow">
                    <InputText/>    {/* This component will receive the method - handleValidation, so it is possible to update the state on the nested grand parent - form */}      
                </div>
                <div className="formRow">
                    <InputText/>{/* This component will receive the method - handleValidation, so it is possible to update the state on the nested grand parent - form */}  
                </div>  
            </fieldset>
        </Form>
    )
}

export default SomeExample;

我有一个替代解决方案将props传递给嵌套的孩子。函数 createFormComponents 接受模式并生成一个组件对象,该对象将像往常一样接收props,但已经提供了模式。您可以将我的示例中的 FormContainer 链接到一个商店或使用 setState 来处理随着时间的推移对架构的更改,并且子项将正确更新。

该示例的输出是到控制台以证明 props 已按预期接收。

function Form_(props) {
    console.log('Form props', props)
    return <div>{props.children}</div>
}

function Input_(props) {
    console.log('Input props', props)
    return <div />
}

function createFormComponents(schema) {
    return {
        Form: props => {
            return Form_({ ...props, schema })
        },

        Input: props => {
            return Input_({ ...props, schema })
        },
    }
}

const FormContainer = React.createClass({
    render: function() {
        const myFormSchema = { x: 0, y: 1, z: 2 }
        const {
            Form,
            Input,
        } = createFormComponents(myFormSchema)

        return (
            <Form>
                <Input name="name" />
                <Input name="age" />
            </Form>
        )
    }
})

ReactDOM.render(
    <FormContainer />,
    document.getElementById('container')
)

小提琴:props示例