该文章是学习react小册的React Playground项目的总结
Vue Playground具有新增、删除文件,修改文件名的功能,接下来我们也实现一下
修改文件名称
在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>
但是修改后Preview组件显示空白,并且控制台报错了,这是因为什么?
这是因为我们修改了files对象中的文件名称,但是代码中的名称并没有修改
修改代码中的名称后,可以看到Preview组件显示正常
新增文件
在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
在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的话,在初始化时就显示编辑框
在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])
新增文件以及语法高亮正常
删除文件
新增的文件都有删除按钮,但是特定的一些文件没有删除按钮
点击删除按钮后还有确认框
在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)
}
}
表现
新建一个Alter组件
在app中引入
点击alter按钮,显示弹窗,可以说明新增文件成功了