2024版,React18+ Nest.js 全栈开发仿问卷星项目
核心代码,注释必读
// download:
3w ukoou com
React生态技术体系介绍 React 是一个由 Facebook 开发的用于构建用户界面的 JavaScript 库。除了 React 本身,React 生态系统还包括许多与 React 配合使用的工具和库,例如 Redux、React Router、Webpack、Babel 等。以下是 React 生态系统的一些关键组成部分:
-
Redux:Redux 是一个用于管理应用程序状态的库,通常与 React 结合使用。它通过单一的不可变状态树来管理应用程序的状态,并借助纯函数来处理状态变更。Redux 提供了一种可预测的状态管理方案,使得复杂应用程序状态的管理变得更加容易。
-
React Router:React Router 是用于处理 React 应用程序中路由的库。它允许开发者在 React 应用中实现路由功能,从而实现不同 URL 对应不同组件的渲染。React Router 提供了一种简单的方式来管理应用的导航状态。
-
Webpack:Webpack 是一个模块打包工具,可将应用程序的所有资源打包成静态文件。在 React 项目中,Webpack 的常见用途包括处理 JavaScript 文件、CSS 文件、图片等资源,并提供一种模块化的开发方式。
-
Babel:Babel 是一个 JavaScript 编译器,可将新版本的 JavaScript 代码转换为旧版本的浏览器可以理解的 JavaScript 代码。在 React 项目中,Babel 常用来将 ES6/ES7 代码转译为 ES5 代码,以确保代码在不同浏览器中的兼容性。
2024React18+ Nest.js 全栈开发仿问卷星项目 - React18 性能优化
-
Concurrent Mode:React 18 引入了 Concurrent Mode(并发模式),它使得 React 能够更好地处理复杂的 UI 更新并发,从而提高应用程序的响应性和流畅性。Concurrent Mode 允许 React 在渲染过程中进行优先级调度,让用户能够更快地与应用进行交互。
-
Server Components:React 18 还引入了 Server Components(服务器组件),这是一种新的组件类型,可以在服务端直接渲染,从而缓解客户端负担,减少客户端渲染的工作量,提高性能。
-
渐进式升级:React 18 支持渐进式升级,这意味着你可以选择性地在现有的 React 项目中引入 React 18 新特性,而无需全面升级整个项目。这样可以降低升级的风险并逐步优化应用性能。
-
自动批量更新:React 18 提供了更好的批量更新机制,避免了不必要的重复渲染,从而减少应用程序的性能开销。
-
新的调度器:React 18 引入了新的调度器,其设计更加灵活、可扩展,并且能够更好地适应不同类型的应用程序和环境,从而提高性能。
2024版,React18+ Nest.js 全栈开发仿问卷星项目 - React18拖拽排序实战
主要实现以下功能:
鼠标hover到【列表项】,显示可【拖动图标】; 抓取【拖动图标】并拖动,【列表项】跟随鼠标; 拖动过程【其他列表项】自行挪动; 拖动到目标位置,释放鼠标,完成排序; 由于项目使用 React,因此用到 React DnD 来实现。
React DnD 是一组 React 高阶组件,使用的时候只需要将对应的 API 将目标组件进行包裹,即可实现拖动或接受拖动元素的功能。
可以在 codesandbox 查看 React DnD 例子的源码,包含ES6、ES7的实现。 components/List.js
import React, { useState } from "react";
import { faTrashAlt, faArrowsAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import classnames from "classnames";
function Item(props) {
const { item, ...restProps } = props;
return (
<div {...restProps}>
<p className="title">{item.title || "标题"}</p>
<ul className="oper-list">
<li className="oper-item icon-move"><FontAwesomeIcon icon={faArrowsAlt} /></li>
<li className="oper-item"><FontAwesomeIcon icon={faTrashAlt} /></li>
</ul>
</div>
);
}
function List(props) {
let { list: propsList, activeItem } = props;
propsList = propsList.map(item => {
const isActive = activeItem.id === item.id;
item = isActive ? activeItem : item;
item.active = isActive;
return item;
});
const [list, setList] = useState(propsList);
const find = id => {
const item = list.find(c => `${c.id}` === id);
return {
item,
index: list.indexOf(item)
};
};
const onClick = event => {
const { id } = event.currentTarget;
const { item } = find(id);
props.onClick(item);
};
return (
<ul className="list">
{list.map((item, index) => (
<li className={classnames("item", { active: item.active })} key={item.id}>
<div className="index">{index + 1}</div>
<Item
className="info"
id={`${item.id}`}
item={item}
onClick={onClick}
/>
</li>
))}
</ul>
);
}
export default List;
App.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import List from "./components/List";
import "./styles.scss";
const defaultList = [
{ id: 1, title: "item1" },
{ id: 2, title: "item2" },
{ id: 3, title: "item3" },
{ id: 4, title: "item4" },
{ id: 5, title: "item5" }
];
function App() {
const [list, setList] = useState(defaultList);
const [activeItem, setActiveItem] = useState(list[0]);
const onClick = item => {
if (item.id !== activeItem.id) {
setActiveItem(item);
}
};
return <List list={list} activeItem={activeItem} onClick={onClick} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
首先简单的实现一个列表,hover 列表项显示操作按钮,点击列表项可以选中。
安装 React DnD
# Using npm
npm i -s react-dnd react-dnd-html5-backend
# Using yarn
yarn add react-dnd react-dnd-html5-backend
复制
这里 react-dnd-html5-backend 是使用 HTML5 的拖放API。也可以选择其他第三方库。
React DnD 核心 API
DragSource:用于包装需要拖动的组件,使组件能够被拖拽(make it draggable)。DropTarget:用于包装接收拖拽元素的组件,使组件能够放置(dropped on it)。DragDropContex:用于包装拖拽根组件,DragSource和DropTarget都需要包裹在DragDropContex内。
详细用法请参考 React DnD 文档 或 react-dnd 用法详解
实现列表拖拽排序
components/DndList.js
import React, { useState } from "react";
import { DragSource, DropTarget, DragDropContext } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";
import { faTrashAlt, faArrowsAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import classnames from "classnames";
function Item(props) {
const {
// 这些 props 由 React DnD注入,参考`collect`函数定义
isDragging, connectDragSource, connectDragPreview, connectDropTarget,
// 这些是组件收到的 props
item, style = {}, find, move, change, remove, ...restProps
} = props;
const opacity = isDragging ? 0.5 : 1;
const onRemove = event => {
event.stopPropagation();
remove(item);
}
return connectDropTarget( // 列表项本身作为 Drop 对象
connectDragPreview( // 整个列表项作为跟随拖动的影像
<div {...restProps} style={Object.assign(style, { opacity })}>
<p className="title">{item.title || "任务标题"}</p>
<ul className="oper-list">
{
connectDragSource(
<li className="oper-item icon-move">
<FontAwesomeIcon icon={faArrowsAlt} />
</li>
) // 拖动图标作为 Drag 对象
}
<li className="oper-item" onClick={onRemove}>
<FontAwesomeIcon icon={faTrashAlt} />
</li>
</ul>
</div>
)
);
}
const type = "item";
const dragSpec = {
// 拖动开始时,返回描述 source 数据。后续通过 monitor.getItem() 获得
beginDrag: props => ({
id: props.id,
originalIndex: props.find(props.id).index
}),
// 拖动停止时,处理 source 数据
endDrag(props, monitor) {
const { id: droppedId, originalIndex } = monitor.getItem();
const didDrop = monitor.didDrop();
// source 是否已经放置在 target
if (!didDrop) {
return props.move(droppedId, originalIndex);
}
return props.change(droppedId, originalIndex);
}
};
const dragCollect = (connect, monitor) => ({
connectDragSource: connect.dragSource(), // 用于包装需要拖动的组件
connectDragPreview: connect.dragPreview(), // 用于包装需要拖动跟随预览的组件
isDragging: monitor.isDragging() // 用于判断是否处于拖动状态
});
const dropSpec = {
canDrop: () => false, // item 不处理 drop
hover(props, monitor) {
const { id: draggedId } = monitor.getItem();
const { id: overId } = props;
// 如果 source item 与 target item 不同,则交换位置并重新排序
if (draggedId !== overId) {
const { index: overIndex } = props.find(overId);
props.move(draggedId, overIndex);
}
}
};
const dropCollect = (connect, monitor) => ({
connectDropTarget: connect.dropTarget() // 用于包装需接收拖拽的组件
});
const DndItem = DropTarget(type, dropSpec, dropCollect)(
DragSource(type, dragSpec, dragCollect)(Item)
);
function List(props) {
let { list: propsList, activeItem, connectDropTarget } = props;
propsList = propsList.map(item => {
const isActive = activeItem.id === item.id;
item = isActive ? activeItem : item;
item.active = isActive;
return item;
});
const [list, setList] = useState(propsList);
const find = id => {
const item = list.find(c => `${c.id}` === id);
return {
item,
index: list.indexOf(item)
};
};
const move = (id, toIndex) => {
const { item, index } = find(id);
list.splice(index, 1);
list.splice(toIndex, 0, item);
setList([...list]);
};
const change = (id, fromIndex) => {
const { index: toIndex } = find(id);
props.onDropEnd(list, fromIndex, toIndex);
};
const remove = item => {
const newList = list.filter(it => it.id !== item.id);
setList(newList);
props.onDelete(newList);
};
const onClick = event => {
const { id } = event.currentTarget;
const { item } = find(id);
props.onClick(item);
};
return connectDropTarget(
<ul className="list">
{list.map((item, index) => (
<li
className={classnames("item", { active: item.active })}
key={item.id}
>
<div className="index">{index + 1}</div>
<DndItem
className="info"
id={`${item.id}`}
item={item}
find={find}
move={move}
change={change}
remove={remove}
onClick={onClick}
/>
</li>
))}
</ul>
);
}
const DndList = DropTarget(type, {}, connect => ({
connectDropTarget: connect.dropTarget()
}))(List);
// 将 HTMLBackend 作为参数传给 DragDropContext
export default DragDropContext(HTML5Backend)(DndList);
App.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import List from "./components/DndList";
import "./styles.scss";
const defaultList = [
{ id: 1, title: "item1" },
{ id: 2, title: "item2" },
{ id: 3, title: "item3" },
{ id: 4, title: "item4" },
{ id: 5, title: "item5" }
];
function App() {
const [list, setList] = useState(defaultList);
const [activeItem, setActiveItem] = useState(list[0]);
const onDropEnd = (list, fromIndex, toIndex) => {
setList([...list]);
};
const onDelete = list => {
setList([...list]);
};
const onClick = item => {
if (item.id !== activeItem.id) {
setActiveItem(item);
}
};
return (
<div className="list-wrap">
<List
list={list}
activeItem={activeItem}
onDropEnd={onDropEnd}
onDelete={onDelete}
onClick={onClick}
/>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));