学习React Playground:文件增删改

191 阅读3分钟

该文章是学习react小册的React Playground项目的总结

Vue Playground具有新增、删除文件,修改文件名的功能,接下来我们也实现一下 chrome-capture-2024-8-4.gif

修改文件名称

在FileNameList组件添加一个编辑的状态,来判断展示输入框还是展示名称
在blur事件中同步更新store并展示修改后的名称

const updateFileName = useStore(state => state.updateFileName)

const handleEditComplete = (name: string, prevName: string) => {
    updateFileName(prevName, name);
    setSelectedFileName(name);
  }
<FileNameItem
  key={item + index}
  value={item}
  actived={selectedFileName === item}
  onClick={() => {
    setSelectedFileName(item)
  }}
  index={index}
  swapIndex={swapIndex}
  creating={index === arr.length - 1 && creating}
+  onEditComplete={(name: string) => handleEditComplete(name, item)}
></FileNameItem>

在FileNameItem接收

export interface FileNameItemProps {
  value: string;
  actived: boolean;
  onClick: () => void;
  index: number
  swapIndex: (index1: number, index2: number) => void
  creating: boolean
+  onEditComplete: (name: string) => void
}

+  const { value, actived = false, onClick, index, swapIndex, creating, onEditComplete } = props;

添加双击事件展示输入框修改文件名称

const [editing, setEditing] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)

  const handleDoubleClick = () => {
    setEditing(true)
    setTimeout(() => {
      inputRef?.current?.focus()
    }, 0)
  }



   const hanldeInputBlur = () => {
    setEditing(false);
    onEditComplete(name)
  }
 <div
      ref={ref}
      className={classnames(
        `inline-flex pt-2  pb-2.5  px-1.5 cursor-pointer text-sm	items-center border-b-4 border-solid   ${actived ? 'border-cyan-400 text-cyan-400' : 'border-transparent'
        } ${isOver ? " bg-cyan-100" : ""}`,
      )}
      onClick={onClick}
    >
      {
        editing ? (
          <input
            ref={inputRef}
            className="w-24  py-1 pr-2.5  text-xs	 text-black	bg-slate-200	rounded outline-none	border-solid	border border-slate-200"
            value={name}
            onChange={(e) => setName(e.target.value)}
            onBlur={hanldeInputBlur}
          />
        ) : (
          <span onDoubleClick={handleDoubleClick}>{name}</span>
        )
      }
    </div>

chrome-capture-2024-8-4-2.gif 但是修改后Preview组件显示空白,并且控制台报错了,这是因为什么?
这是因为我们修改了files对象中的文件名称,但是代码中的名称并没有修改 image.png 修改代码中的名称后,可以看到Preview组件显示正常 image.png

新增文件

image.png 在FileNameList组件添加新增按钮

<div className="flex items-center h-12 overflow-x-auto overflow-y-hidden	border-b border-solid  border-gray-400 box-border	text-black	bg-white scroll-bar" ref={containerRef} onWheel={handleWheel}>
      {tabs.map((item, index) => (
        <FileNameItem
          key={item + index}
          value={item}
          actived={selectedFileName === item}
          onClick={() => {
            setSelectedFileName(item)
          }}
          index={index}
          swapIndex={swapIndex}
          onEditComplete={(name: string) => handleEditComplete(name, item)}
        ></FileNameItem>
      ))}
+      <div onClick={addTab}>
+        +
+      </div>
    </div>

使用Comp+随机数作为名称,并同步更新store

const addTab = () => {
    const newFileName = 'Comp' + Math.random().toString().slice(2, 8) + '.tsx';
    addFile(newFileName);
    setSelectedFileName(newFileName);
  }

点击新增按钮后,会显示输入框,新增都是在最后一个,是不是可以判断该FileNameItem组件是最后一个,如果是最后一个就让focus chrome-capture-2024-8-4-3.gif

在FileNameList组件定义创建状态creating,传递给FileNameItem组件,在点击事件中设置creating为true,在blur时设置creating为false

+ const [creating, setCreating] = useState(false);

const addTab = () => {
    const newFileName = 'Comp' + Math.random().toString().slice(2, 8) + '.tsx';
    addFile(newFileName);
    setSelectedFileName(newFileName);
+    setCreating(true)
  }
const handleEditComplete = (name: string, prevName: string) => {
    updateFileName(prevName, name);
    setSelectedFileName(name);
+    setCreating(false);
  }
  
<FileNameItem
  key={item + index}
  value={item}
  actived={selectedFileName === item}
  onClick={() => {
    setSelectedFileName(item)
  }}
  index={index}
  swapIndex={swapIndex}
  onEditComplete={(name: string) => handleEditComplete(name, item)}
+  creating={index === arr.length - 1 && creating}
></FileNameItem>

如果没有定义创建状态creating,只判断index === arr.length - 1的话,在初始化时就显示编辑框 image.png 在FileNameItem组件中接收creating,给editing作为初始值是为了显示编辑框,再判断creating为true时让编辑框focus

export interface FileNameItemProps {
  value: string;
  actived: boolean;
  onClick: () => void;
  index: number
  swapIndex: (index1: number, index2: number) => void
+  creating: boolean
}

- const [editing, setEditing] = useState(false)
+ const [editing, setEditing] = useState(creating)

+ useEffect(() => {
+    if (creating) {
+      inputRef?.current?.focus()
+ 
+    }
+  }, [creating])

新增文件以及语法高亮正常
chrome-capture-2024-8-5.gif

删除文件

新增的文件都有删除按钮,但是特定的一些文件没有删除按钮 image.png 点击删除按钮后还有确认框 image.png 在FileNameItem组件引入main.tsx、App.tsx、import-map.json

const import { ENTRY_FILE_NAME, IMPORT_MAP_FILE_NAME, APP_COMPONENT_FILE_NAME } from '@/files';

readonlyFileNames = [ENTRY_FILE_NAME, IMPORT_MAP_FILE_NAME, APP_COMPONENT_FILE_NAME];

再判断name是否一样,如果一样就不展示删除按钮

{!readonlyFileNames.includes(name) && <span className='flex ml-1' onClick={(e) => {
  e.stopPropagation()
  handleRemove(name)
}}>
  <svg width='12' height='12' viewBox='0 0 24 24'>
    <line stroke='#999' x1='18' y1='6' x2='6' y2='18'></line>
    <line stroke='#999' x1='6' y1='6' x2='18' y2='18'></line>
  </svg>
</span>}

添加删除事件,添加confirm,当点击确认调用removeFile,再把当前激活的文件名称设置成main.tsx

const removeFile = useStore(state => state.removeFile)


const handleRemove = (name: string) => {
    const result = confirm(`是否要删除${name}?`)
    if (result) {
      removeFile(name)
      setSelectedFileName(ENTRY_FILE_NAME)
    }
  }

chrome-capture-2024-8-5-1.gif

image.png

表现

新建一个Alter组件 image.png 在app中引入 image.png 点击alter按钮,显示弹窗,可以说明新增文件成功了 image.png