如何用嵌套子项填充状态

时间:2019-09-21 17:26:40

标签: reactjs

我想这样填充状态:

state = {
    id: '1',
    parentId: null,
    text: '', // state of the root input
    child: [
        {
            id: '1.1',
            parentId: '1',
            text: '', // State of the first child input
            child: null // First child didn't have any children
        },
        {
            id: '1.2',
            parentId: '1',
            text: '', // State of the second child input
            child: null // Second child didn't have any children
        }],
};

故事:

这是一个递归问题。我正在做的是ul,其中render只有一项,即<Field />组件。

<Field />组件还具有一个状态,即名为id的道具,并呈现input元素和<Quantity />组件。

<Quantity />组件生成嵌套列表项,它们只是<Field />组件。

生成的<Field />组件具有正确的状态结构,例如正确的idparentId,但是主要问题是嵌套的生成的组件具有自己的作用域,并不关心其父{{ 1}}组件状态。

我用错误的模式解决了这个问题吗?

我是否必须从一个有状态的组件<Field />开始,它呈现一个无状态的<List />组件,并将所有父状态作为道具传递给该<Field />组件?

但是如何跟踪状态?

Sandbox link

App.jsx:

<Field />

components / Field.jsx:

import React from 'react';
import {Field} from './components/Field';

    const App = () => {
        return (<ul className="list"><Field id='1'/></ul>);

    };

export default App;

components / Quantity.jsx:

import React, {Component} from 'react';
import {Quantity} from './Quantity';

export class Field extends Component {

    /**
     * @type {{id: string, text: string, parentId: (string|null), child: ([]|null)}}
     */
    state = {
        id: this.props.id,
        parentId: null,
        child: null,
        text: '',
    };

    /**
     * Handle onchange behavior of the input
     * @param target
     */
    onChangeHandler = ({target}) => {
        const {name, value} = target;
        this.setState(prevState => {
            return {[name]: value};
        });
    };


    /**
     * Trigger this function when Button of `Quantity component` is clicked
     * @param {Number} quantity - Generate array of objects with properties
     * and update state
     */
    populateChild = (quantity) => {
        // Construct new array of objects
        const data = [...Array(quantity)].map((_, index) => {
            // id => 1.1 .. 1.2 .. 1.3
            // parentId => 1
            const id = this.state.id + '.' + (index + 1);
            return {...this.state, id: id, parentId: this.state.id}
        });


        this.setState(prevState => {
            return {child: data};
        });
    };


    render() {
        return (<li>
            <div className="wrap">
                <div>
                    <label>Enter Text:</label>
                    <input type="text" name="text" onChange={this.onChangeHandler} value={this.state.text}/>
                </div>
                <div>
                    <Quantity funRef={this.populateChild}/>
                    <br/>
                    <br/>
                </div>
            </div>
            {/* Generate New unordered list only if `child` property of the state is changed.   */}
            {this.state.child !== null &&
            <ul>{this.state.child.map(element => <Field key={element.id} {...element}/>)}</ul>}
        </li>);
    };

}

1 个答案:

答案 0 :(得分:0)

这里是my stackblitz solution,请注意,项目state被管理到顶部App组件中

我决定将项目存储到depth = 1的数组中,更容易处理更新:

items = [
  {
    id: "1",
    parentId: null,
    text: ""
  },
  {
    id: "1.1",
    parentId: "1",
    text: ""
  },
  {
    id: "1.2",
    parentId: "1",
    text: ""
  }
];

这里是App组件:

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
     items: defaultItems
    };
  }

  handleChangeText = (id, text) => {
    this.setState(prevState => ({
      items: prevState.items.map(
        item => item.id === id ? {...item, text} : item 
      )
    }));
  };

  handleAddChildren = (parentId, quantity) => {
    this.setState(prevState => {
      const childrenItems = prevState.items
        .filter(item => item.parentId === parentId);

      const otherItems = prevState.items
        .filter(item => item.parentId !== parentId);

      const childrenText = childrenItems.map(item => item.text);

      const newChildrenItems = Array.from(Array(quantity))
        .map((_, index) => ({
          parentId,
          id: `${parentId}.${index + 1}`,
          text: childrenText[index] || ""
      }));

      return {
        items: [...otherItems, ...newChildrenItems]
      };
    });
  };

  getChildren = parentId => this.state.items.filter(
      item => item.parentId === parentId
  );

  render() {
    const {items} = this.state;
    return (
      <ul>
        {
          this.getChildren(null).map(item => 
            <Field
              key={item.id}
              item={item}
              getChildren={this.getChildren}
              onChangeText={this.handleChangeText}
              onAddChildren={this.handleAddChildren}
             /> 
          )
        }
      </ul>
    );
  }
}

这是Field组件:

export class Field extends Component {

  handleChangeText = e => {
    const {onChangeText, item} = this.props;
    onChangeText(item.id, e.target.value);
  };

  handleGenerate = quantity => {
    const {onAddChildren, item} = this.props;
    onAddChildren(item.id, quantity);
  };

  render() {
    const {item, getChildren, onChangeText, onAddChildren} = this.props;
    const children = getChildren(item.id);

    return (
      <li>
        <div className="wrap">
          <div>
            <label>Enter Text:</label>
            <input
              type="text"
              name="text"
              onChange={this.handleChangeText}
              value={item.text}
            />
          </div>
          <div>
            <Quantity onGenerate={this.handleGenerate} />
            <br />
            <br />
          </div>
        </div>
        {children.length > 0 && (
          <ul>
            {children.map(item => (
              <Field
                key={item.id}
                item={item}
                getChildren={getChildren}
                onChangeText={onChangeText}
                onAddChildren={onAddChildren}
              />
            ))}
          </ul>
        )}
      </li>
    );
  }
}

请注意,我们可以创建一个React context以避免将属性getChildrenonChangeTextonAddChildren传递给Field组件。 这些属性实际上引用了顶部App组件的功能,并向下传递给嵌套的Field组件

此处the solution使用React.context

您只需要创建一个上下文:

const AppContext = React.createContext();

然后,您需要将上下文值存储到状态中,并用上下文包装您的App。现在,您只需要将道具keyìtem传递到Field组件。

constructor(props) {
  super(props);

  this.state = {
    items: defaultItems,
    contextValue: {
      onChangeText: this.onChangeText,
      onAddChildren: this.onAddChildren,
      getChildren: this.getChildren
    }
  };

}

...

render() {
  const {items, contextValue} = this.state;
  const {Provider} = AppContext;
  return (
    <Provider value={contextValue}>
      <ul>
        {
          this.getChildren(null).map(
            item => <Field key={item.id} item={item}/> 
          )
        }
      </ul>
    </Provider>
  );

}

Field组件中,您可以使用static contextType = AppContext;检索上下文,现在可以从上下文中检索函数onChangeTextonAddChildrengetChildren < / p>

export class Field extends Component {
 static contextType = AppContext;

 handleChangeText = e => {
   const {item} = this.props;
   const {onChangeText} = this.context;

   onChangeText(item.id, e.target.value);
 };

 handleGenerate = quantity => {
   const {item} = this.props;
   const {onAddChildren} = this.context;

   onAddChildren(item.id, quantity);
 };

 render() {
   const {item} = this.props;
   const children = this.context.getChildren(item.id);

   return (
     <li>
       <div className="wrap">
         <div>
           <label>Enter Text:</label>
           <input
             type="text"
             name="text"
             onChange={this.handleChangeText}
             value={item.text}
           />
         </div>
         <div>
           <Quantity onGenerate={this.handleGenerate} />
           <br />
           <br />
          </div>
       </div>
       {children.length > 0 &&
         <ul>{children.map(item => <Field key={item.id} item={item}/>)}</ul>
       }
     </li>
   );
 }
}