0

我正在尝试使用 React JS 开发一个相当简单的电子邮件模板创建器。我使用“react-sortable-hoc”库来处理页面上元素的排序。

目标是允许用户创建“行”,重新排列行,并且在每一行内,有多个“列”,可以包含图像、文本框等组件......

但是我一直遇到与可排序库相同的问题。向上或向下拖动时,表单字段无法保持自己的“状态”。React JS 中组件的状态在可拖动组件中时似乎丢失了。我在使用 JQuery UI 的 Sortable 时遇到过类似的问题,但它需要一个同样荒谬的解决方案。是否经常发现表单字段非常难以重新排列?

作为“概念证明”,我使用了一个复杂的 JSON 对象,该对象将所有信息存储在 Letter.js 组件中,并将其作为 Props 传递,然后传递给每个组件。但正如你所知道的,这变得很麻烦。

下面是一个处理 JSON 对象和行排序的 Letter 组件示例:

import React, {Component} from 'react';
import {render} from 'react-dom';
import {
  SortableContainer, 
  SortableElement, 
  arrayMove
} from 'react-sortable-hoc';
import Row from './Row';

const SortableItem = SortableElement(({row, rowIndex, onChange, addPart}) => {
  return (
    <li>
      <Row 
        row={row} 
        rowIndex={rowIndex} 
        onChange={onChange}
        addPart={addPart} />
    </li>
  )
});

const SortableList = SortableContainer(({rows, onChange, addPart}) => {
  return (
    <ul id="sortableList">
      {rows.map((row, index) => {
        return (
          <SortableItem
            key={`row-${index}`} 
            index={index} 
            row={row}
            rowIndex={index}
            onChange={onChange}
            addPart={addPart}
           /> )
      })}
    </ul>
  );
});

class Letter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      rows: [],
    }

    this.onSortEnd    = this.onSortEnd.bind(this);
    this.onChange     = this.onChange.bind(this);
    this.addRow       = this.addRow.bind(this);
    this.addPart      = this.addPart.bind(this);
  }

  addPart(event, index, value, rowIndex, columnIndex) {
    console.log(value);
    var part = {};
    if(value === 'Text') {
      part = {
        type: 'Text',
        value: ''
      }
    } else if(value === 'Image') {
      part = {
        type: 'Image',
        value: ''
      }
    } else {
      part = {
        type: 'Empty',
      }
    }
    const { rows  } = this.state;
    rows[rowIndex][columnIndex] = part;
    this.setState({ rows: rows })
  }

  onChange(text, rowIndex, columnIndex) {
    const { rows } = this.state;
    const newRows = [...rows];
    newRows[rowIndex][columnIndex].value = text;
    this.setState({ rows: newRows });
  }

  addRow(columnCount) {
    var rows = this.state.rows.slice();
    var row = [];
    for(var i = 0; i < columnCount; i++) {
      var part = {
        type: 'Empty',
      }
      row.push(part);
    }
    rows.push(row);
    this.setState({ rows: rows })
  }

  onSortEnd = ({oldIndex, newIndex}) => {
    this.setState({
      rows: arrayMove(this.state.rows, oldIndex, newIndex),
    });
  };

  render() {
    console.log(JSON.stringify(this.state.rows));
    const SideBar = (
      <div className="sideBar">
        <h3>Add a Row</h3>
        <button className="uw-button" onClick={() => this.addRow(1)}>1 - Column</button><br/><br/>
        <button className="uw-button" onClick={() => this.addRow(2)}>2 - Column</button><br/><br/>
        <button className="uw-button" onClick={() => this.addRow(3)}>3 - Column</button>
        <hr />
      </div>
    );

    if(this.state.rows.length <= 0) {
      return (
        <div className="grid">
          <p>This E-Mail is currently empty! Add some components to make a template.</p>
          {SideBar}
        </div>
      )
    }
    return (
      <div className="grid">
        <SortableList 
          rows={this.state.rows} 
          onChange={this.onChange}
          addPart={this.addPart}
          lockAxis="y"
          useDragHandle={true}
          onSortStart={this.onSortStart} 
          onSortMove={this.onSortMove}
          onSortEnd={this.onSortEnd} 
          shouldCancelStart={this.shouldCancelStart} />
        {SideBar}
      </div>
    );
  }
}

export default Letter;

这是行的一个例子:

import React, { Component } from 'react';
import { Text, Image } from './components/';

import { SortableHandle } from 'react-sortable-hoc';

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card';
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';


const DragHandle = SortableHandle(() => <span className="dragHandle"></span>);


class Row extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    if(this.props.row !== undefined && this.props.row.length > 0) {
      const row = this.props.row.map((column, columnIndex) => {
        if(column.type === 'Empty') {
          return (
            <MuiThemeProvider key={columnIndex}>
              <div className="emptyColumn">
                <Card>
                  <DragHandle />
                  <CardTitle title="Empty Component"/>
                  <DropDownMenu value={"Empty"} onChange={(event, index, value) => this.props.addPart(event, index, value, this.props.rowIndex, columnIndex)}>
                    <MenuItem value={"Empty"} primaryText="Empty" />
                    <MenuItem value={"Text"} primaryText="Textbox" />
                    <MenuItem value={"Image"} primaryText="Image" />
                  </DropDownMenu>
                </Card>
              </div>
            </MuiThemeProvider>
          )
        } else if(column.type === 'Text') {
           return (
            <MuiThemeProvider key={columnIndex}>
              <div className="textColumn">
                <Card>
                  <DragHandle />
                  <CardTitle title="Textbox"/>
                  <DropDownMenu value={"Text"} onChange={(event, index, value) => this.props.addPart(event, index, value, this.props.rowIndex, columnIndex)}>
                    <MenuItem value={"Empty"} primaryText="Empty" />
                    <MenuItem value={"Text"} primaryText="Textbox" />
                    <MenuItem value={"Image"} primaryText="Image" />
                  </DropDownMenu>
                  <Text 
                    value={this.props.row[columnIndex].value} 
                    onChange={this.props.onChange} 
                    columnIndex={columnIndex}
                    rowIndex={this.props.rowIndex} />
                </Card>
              </div>
            </MuiThemeProvider>
          )         
        } else if(column.type === 'Image') {
           return (
            <MuiThemeProvider key={columnIndex}>
              <div className="textColumn">
                <Card>
                  <DragHandle />
                  <CardTitle title="Image"/>
                  <DropDownMenu value={"Image"} onChange={(event, index, value) => this.props.addPart(event, index, value, this.props.rowIndex, columnIndex)}>
                    <MenuItem value={"Empty"} primaryText="Empty" />
                    <MenuItem value={"Text"} primaryText="Textbox" />
                    <MenuItem value={"Image"} primaryText="Image" />
                  </DropDownMenu>
                  <Image 
                    columnIndex={columnIndex}
                    rowIndex={this.props.rowIndex} />
                </Card>
              </div>
            </MuiThemeProvider>
          )         
        }
      })


      return (
        <div className="row">
          {row}
        </div>
      )
    }

    return <p>No components</p>;

  }
}

export default Row;

最后,这就是 Text.js 的样子

import React, { Component } from 'react';
import ReactQuill from 'react-quill'; 

class Text extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <ReactQuill value={this.props.value}
                  onChange={(text) => this.props.onChange(text, this.props.rowIndex, this.props.columnIndex)} />
    )
  }
}

export default Text;

因此,我一直不得不将荒谬的参数传递给 onChange 函数和其他函数,以确保在排序和编辑时保持状态。那么,我应该如何处理呢?我不希望Letter.js(基本上是 App.js)来处理我的所有数据处理。我希望每个组件都处理它自己的。我希望 Text.js 处理其文本的 onChange 效果。但我只是看不到将所有东西都作为道具传递下去的方法。

4

0 回答 0