将 class 更改为函数组件 antd

changing class to function component antd

我试图让这段代码作为功能组件工作,但卡住了。

import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Tag, Input, Tooltip } from 'antd';
import { PlusOutlined } from '@ant-design/icons';

class EditableTagGroup extends React.Component {
  state = {
    tags: ['Unremovable', 'Tag 2', 'Tag 3'],
    inputVisible: false,
    inputValue: '',
    editInputIndex: -1,
    editInputValue: '',
  };

  handleClose = removedTag => {
    const tags = this.state.tags.filter(tag => tag !== removedTag);
    console.log(tags);
    this.setState({ tags });
  };

  showInput = () => {
    this.setState({ inputVisible: true }, () => this.input.focus());
  };

  handleInputChange = e => {
    this.setState({ inputValue: e.target.value });
  };

  handleInputConfirm = () => {
    const { inputValue } = this.state;
    let { tags } = this.state;
    if (inputValue && tags.indexOf(inputValue) === -1) {
      tags = [...tags, inputValue];
    }
    console.log(tags);
    this.setState({
      tags,
      inputVisible: false,
      inputValue: '',
    });
  };

  handleEditInputChange = e => {
    this.setState({ editInputValue: e.target.value });
  };

  handleEditInputConfirm = () => {
    this.setState(({ tags, editInputIndex, editInputValue }) => {
      const newTags = [...tags];
      newTags[editInputIndex] = editInputValue;

      return {
        tags: newTags,
        editInputIndex: -1,
        editInputValue: '',
      };
    });
  };

  saveInputRef = input => {
    this.input = input;
  };

  saveEditInputRef = input => {
    this.editInput = input;
  };

  render() {
    const { tags, inputVisible, inputValue, editInputIndex, editInputValue } = this.state;
    return (
      <>
        {tags.map((tag, index) => {
          if (editInputIndex === index) {
            return (
              <Input
                ref={this.saveEditInputRef}
                key={tag}
                size="small"
                className="tag-input"
                value={editInputValue}
                onChange={this.handleEditInputChange}
                onBlur={this.handleEditInputConfirm}
                onPressEnter={this.handleEditInputConfirm}
              />
            );
          }

          const isLongTag = tag.length > 20;

          const tagElem = (
            <Tag
              className="edit-tag"
              key={tag}
              closable={index !== 0}
              onClose={() => this.handleClose(tag)}
            >
              <span
                onDoubleClick={e => {
                  if (index !== 0) {
                    this.setState({ editInputIndex: index, editInputValue: tag }, () => {
                      this.editInput.focus();
                    });
                    e.preventDefault();
                  }
                }}
              >
                {isLongTag ? `${tag.slice(0, 20)}...` : tag}
              </span>
            </Tag>
          );
          return isLongTag ? (
            <Tooltip title={tag} key={tag}>
              {tagElem}
            </Tooltip>
          ) : (
            tagElem
          );
        })}
        {inputVisible && (
          <Input
            ref={this.saveInputRef}
            type="text"
            size="small"
            className="tag-input"
            value={inputValue}
            onChange={this.handleInputChange}
            onBlur={this.handleInputConfirm}
            onPressEnter={this.handleInputConfirm}
          />
        )}
        {!inputVisible && (
          <Tag className="site-tag-plus" onClick={this.showInput}>
            <PlusOutlined /> New Tag
          </Tag>
        )}
      </>
    );
  }
}

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

当我按下“添加标签”按钮时,我的代码抛出一个错误:未处理的运行时错误 类型错误:无法读取未定义的属性(读取 'map')。加上 setState 需要 1 个参数,但得到 2 个(第二个是函数)。我可能对钩子的理解有问题。这是我卡住的地方:

import { PlusOutlined } from '@ant-design/icons';
import { useRef, useState } from 'react';

const Tags = () => {
  const [state, setState] = useState({
    tags: ['Unremovable', 'Tag1', 'Tag2', 'Tag3'],
    inputVisible: false,
    inputValue: '',
    editInputIndex: -1,
    editInputValue: '',
  });
  const handleClose = removedTag => {
    const tags = state.tags.filter(tag => tag !== removedTag);
    console.log(tags);
    setState({ tags });
  };

  const showInput = () => {
    setState({ inputVisible: true }, () => input.focus());
  };

  const handleInputChange = e => {
    setState({ inputValue: e.target.value });
  };

  const handleInputConfirm = () => {
    const { inputValue } = state;
    let { tags } = state;
    if (inputValue && tags.indexOf(inputValue) === -1) {
      tags = [...tags, inputValue];
    }
    console.log(tags);
    setState({
      tags,
      inputVisible: false,
      inputValue: '',
    });
  };

  const handleEditInputChange = e => {
    setState({ editInputValue: e.target.value });
  };

  const handleEditInputConfirm = () => {
    setState(({ tags, editInputIndex, editInputValue }) => {
      const newTags = [...tags];
      newTags[editInputIndex] = editInputValue;

      return {
        tags: newTags,
        editInputIndex: -1,
        editInputValue: '',
      };
    });
  };
const inputRef = useRef('') 
  const saveInputRef = input => {
    inputRef.current = input;
  };
const editInput = useRef('')
  const saveEditInputRef = input => {
    editInput.current = input;
  };
  const { tags, inputVisible, inputValue, editInputIndex, editInputValue } =
      state;

  return (
    <>
    {tags.map((tag, index) => {
      if (editInputIndex === index) {
        return (
          <Input
            ref={saveEditInputRef}
            key={tag}
            size="small"
            className="tag-input"
            value={editInputValue}
            onChange={handleEditInputChange}
            onBlur={handleEditInputConfirm}
            onPressEnter={handleEditInputConfirm}
          />
        );
      }

      const isLongTag = tag.length > 20;

      const tagElem = (
        <Tag
          className="edit-tag"
          key={tag}
          closable={index !== 0}
          onClose={() => handleClose(tag)}
        >
          <span
            onDoubleClick={e => {
              if (index !== 0) {
                setState(
                  { editInputIndex: index, editInputValue: tag },
                  () => {
                    editInput.focus();
                  });
                e.preventDefault();
              }
            }}
          >
            {isLongTag ? `${tag.slice(0, 20)}...` : tag}
          </span>
        </Tag>
      );
      return isLongTag ? (
        <Tooltip title={tag} key={tag}>
          {tagElem}
        </Tooltip>
      ) : (
        tagElem
      );
    })}
    {inputVisible && (
      <Input
        ref={saveInputRef}
        type="text"
        size="small"
        className="tag-input"
        value={inputValue}
        onChange={handleInputChange}
        onBlur={handleInputConfirm}
        onPressEnter={handleInputConfirm}
      />
    )}
    {!inputVisible && (
      <Tag className="site-tag-plus" onClick={showInput}>
        <PlusOutlined /> add tag
      </Tag>
    )}
  </>
  )
};

export default Tags;

问题

您遇到的问题是您错过了通过 useState 挂钩获取功能组件中的状态更新 不是 浅合并到state.

useState

Note

Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:

const [state, setState] = useState({});
setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

因为您没有合并状态更新,所以当您单击添加标签按钮时,处理程序正在从状态中删除 tags 属性。

const showInput = () => {
  setState({ inputVisible: true }); // <-- no tags property!!
};

解决方案

Unhandled Runtime Error TypeError: Cannot read properties of undefined (reading 'map')

您必须自己管理状态合并。使用功能状态更新将前一个状态浅复制到下一个返回的状态对象中。

const Tags = () => {
  const [state, setState] = useState({
    tags: ["руль", "фара", "табло", "дворники"],
    inputVisible: false,
    inputValue: "",
    editInputIndex: -1,
    editInputValue: ""
  });
  const handleClose = (removedTag) => {
    const tags = state.tags.filter((tag) => tag !== removedTag);
    setState((state) => ({ ...state, tags })); // <-- shallow copy previous state

  };

  const showInput = () => {
    setState((state) => ({ ...state, inputVisible: true })); // <-- shallow copy previous state
  };

  const handleInputChange = (e) => {
    setState((state) => ({ ...state, inputValue: e.target.value })); // <-- shallow copy previous state
  };

  const handleInputConfirm = () => {
    const { inputValue } = state;
    let { tags } = state;
    if (inputValue && tags.indexOf(inputValue) === -1) {
      tags = [...tags, inputValue];
    }
    console.log(tags);
    setState((state) => ({
      ...state, // <-- shallow copy previous state
      tags,
      inputVisible: false,
      inputValue: ""
    }));
  };

  const handleEditInputChange = (e) => {
    setState((state) => ({ ...state, editInputValue: e.target.value })); // <-- shallow copy previous state
  };

  const handleEditInputConfirm = () => {
    setState(({ tags, editInputIndex, editInputValue, ...state }) => {
      const newTags = [...tags];
      newTags[editInputIndex] = editInputValue;

      return {
        ...state, // <-- shallow copy previous state
        tags: newTags,
        editInputIndex: -1,
        editInputValue: ""
      };
    });
  };

  ...

  return (
    <>
      ...
    </>
  );
};

setState expects 1 argument, but gets 2(2nd one is a function)

useState 更新函数只接受一个参数。如果您需要 运行 在 状态更新后 效果,请使用具有适当依赖性的 useEffect

示例:

const showInput = () => {
  setState({ inputVisible: true });
};

React.useEffect(() => {
  if (state.inputVisible) {
    input.focus(); // * NOTE
  }
}, [state, input]);

* 注意: 在您的代码中,我没有看到任何 input 变量的声明位置。

所以,问题出现在这段代码中。

  showInput = () => {
this.setState({ inputVisible: true }, () => this.input.focus());

};

问题一:无法读取undefined的属性。发生这种情况是因为当您设置新状态时,整个状态值仅由 inputVisible 属性 替换。标签 属性 和其他标签已从变量中完全删除。因此,为了克服此错误,您需要使用传播运算符来传播状态变量的其他值。 Spread Operator。 所以新的代码是

const showInput = () => {
setState( prevState =>
  ({ ...prevState , inputVisible: true })
  //  () => input.focus()
);

};

这里的prevState是状态变量之前的值。

问题2:默认setState只能接收一个值,即更新state变量。它无法接收 2 个参数。如果您想根据状态变化执行任何任务,您可以使用 UseEffect Hooks. Where you just have to add the state variable inside the Dependency Array 的 useEffect 挂钩

这是为任何需要的人准备的工作代码!

import { PlusOutlined } from '@ant-design/icons';
import { useEffect, useRef, useState } from 'react';

const Tags = () => {
  const [state, setState] = useState({
    tags: ['Unremovable', 'Tag 1', 'Tag 2', 'Tag 3'],
    inputVisible: false,
    inputValue: '',
    editInputIndex: -1,
    editInputValue: '',
  });

  const handleClose = removedTag => {
    const tags = state.tags.filter(tag => tag !== removedTag);
    console.log(tags);
    setState(state => ({ ...state, tags }));
  };

  const showInput = () => {
    setState(state => ({ ...state, inputVisible: true }));
  };

  const handleInputChange = e => {
    setState({ ...state, inputValue: e.target.value });
  };

  const handleInputConfirm = () => {
    const { inputValue } = state;
    let { tags } = state;
    if (inputValue && tags.indexOf(inputValue) === -1) {
      tags = [...tags, inputValue];
    }
    console.log(tags);
    setState({
      ...state,
      tags,
      inputVisible: false,
      inputValue: '',
    });
  };

  const handleEditInputChange = e => {
    setState(state => ({ ...state, editInputValue: e.target.value }));
  };

  const handleEditInputConfirm = () => {
    setState(({ tags, editInputIndex, editInputValue, ...state }) => {
      const newTags = [...tags];
      newTags[editInputIndex] = editInputValue;

      return {
        ...state,
        tags: newTags,
        editInputIndex: -1,
        editInputValue: '',
      };
    });
  };

  const inputRef = useRef(null);
  const saveInputRef = input => {
    inputRef.current = input;
  };
  const editInput = useRef(null);
  const saveEditInputRef = input => {
    editInput.current = input;
  };
  const { tags, inputVisible, inputValue, editInputIndex, editInputValue } =
    state;

  useEffect(() => {
    if (state.inputVisible) {
      inputRef.current.focus();
    }
  }, [state, saveInputRef]);

  return (
    <>
      {tags.map((tag, index) => {
        if (editInputIndex === index) {
          return (
            <Input
              ref={saveEditInputRef}
              key={tag}
              size="small"
              className="tag-input"
              value={editInputValue}
              onChange={handleEditInputChange}
              onBlur={handleEditInputConfirm}
              onPressEnter={handleEditInputConfirm}
            />
          );
        }

        const isLongTag = tag.length > 20;

        const tagElem = (
          <Tag
            className="edit-tag"
            key={tag}
            closable={index !== 0}
            onClose={() => handleClose(tag)}
          >
            <span
              onDoubleClick={e => {
                if (index !== 0) {
                  setState(state => ({
                    ...state,
                    editInputIndex: index,
                    editInputValue: tag,
                  }));
                  e.preventDefault();
                }
              }}
            >
              {isLongTag ? `${tag.slice(0, 20)}...` : tag}
            </span>
          </Tag>
        );
        return isLongTag ? (
          <Tooltip title={tag} key={tag}>
            {tagElem}
          </Tooltip>
        ) : (
          tagElem
        );
      })}
      {inputVisible && (
        <Input
          ref={saveInputRef}
          type="text"
          size="small"
          className="tag-input"
          value={inputValue}
          onChange={handleInputChange}
          onBlur={handleInputConfirm}
          onPressEnter={handleInputConfirm}
        />
      )}
      {!inputVisible && (
        <Tag className="site-tag-plus" onClick={showInput}>
          <PlusOutlined /> add tag
        </Tag>
      )}
    </>
  );
};

export default Tags;