my-react-admin-fe项目笔记

290 阅读4分钟

my-react-admin-fe

react+antd+mobx+postcss+react-router-dom+webpack

虚拟列表

需求:大数据量渲染。但同时渲染大量DOM会造成页面卡顿。

解决方案:

  • 虚拟列表(也叫按需渲染或可视区域渲染)
    • 监听scroll事件,通过slice 方法对数据进行分割展示
  • 延迟渲染(即懒渲染)
    • 监听scroll事件,当子项的 offsetTop(偏移高度)<innerHeight(视窗高度)+ scrollTop(滚动高度)说明已经滚到下方
    • IntersectionObserver API 是异步的,不随目标元素的滚动同步触发,性能消耗小
    • getBoundingClientRect 方法返回元素的大小机器相对于视窗的位置
  • 时间分片
    • 定时器或requestAnimationFrame
    • DocumentFragment

最优选是虚拟列表,DOM 树上只挂载有限的DOM;懒加载和时间分片的缺点在于插入大量的DOM,占内存运行时会造成卡顿。

虚拟列表通过仅渲染大型数据集的一部分(刚好足以填充视口)来工作。这有助于解决一些常见的性能瓶颈:

  1. 它减少了渲染初始视图和处理更新所需的工作量(和时间)。
  2. 它通过避免过度分配 DOM 节点来减少内存占用。

自动滚动

css3 animation

功能:鼠标移上去停止滚动,会聚焦相应列表项。鼠标移开恢复。

@keyframes rowup {

0% {

-webkit-transform: translate3d(0, 0, 0);

transform: translate3d(0, 0, 0);

}

100% {

-webkit-transform: translate3d(0, -307px, 0);

transform: translate3d(0, -307px, 0);

}

}

.list {

border: 1px solid #999;

margin: 20px auto;

height: 200px;

overflow: hidden;

}

.list :hover {

animation: none;

}

.rowup {

-webkit-animation: 10s rowup linear infinite normal;

animation: 10s rowup linear infinite normal;

}

.rowup :hover {

background-color: red;

}
<div className="list">

<div className="rowup">

{new Array(1000).fill(

<div className="item">

1. 自动滚动⚽️

</div>

)}

</div>

</div>

js控制滚动条

可以实现虚拟列表自动滚动

react

   useEffect(() => {
        // TODO:fix
        const child = document.querySelector('.list') as HTMLElement;
        let timer: string | number | NodeJS.Timeout | undefined;
        if (isScroll&&child) {
            timer = setInterval(() => {
                const isBottom =
                    Math.abs(child.scrollHeight - child.clientHeight - child.scrollTop) < 1;
                isBottom ? (child.scrollTop = 0) : (child.scrollTop += speed);
            }, 1000);
        }
        return () => clearInterval(timer);
    }, [isScroll]);

tailwind干扰antD样式

@tailwind base;
@tailwind components;
@tailwind utilities;

会使Image组件预览图片位于左下角。

解决

如果您想完全禁用 Preflight - 可能是因为您要将 Tailwind 集成到现有项目中,或者是因为您想提供自己的基本样式 - 您所需要做的就是在 tailwind.config.js 文件的 corePlugins 部分,设置 preflight 为 false:

  // tailwind.config.js
  module.exports = {
    corePlugins: {
     preflight: false,
    }
  }

动态换肤

src/index.js

import 'antd/dist/antd.variable.min.css';
import { ConfigProvider } from 'antd';
ConfigProvider.config({
  theme: {
    primaryColor: '#25b864',
  },
});
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <ConfigProvider>
      <App />
    </ConfigProvider>
);

src/components/Dashboard/MyToolbar.tsx

const initColor={
    primaryColor: '#1890ff',
    errorColor: '#ff4d4f',
    warningColor: '#faad14',
    successColor: '#52c41a',
    infoColor: '#1890ff',
}
const ColorChange: FC = () => {
    const [color, setColor] = useState(()=>initColor);
    const [isModalOpen, setIsModalOpen] = useState(false);
    const {globalStore}=useStores()
    // 配置选项卡内容
    const primary = (
        <SketchPicker
            presetColors={['#1890ff', '#25b864', '#ff6f00']}
            color={color.primaryColor}
            onChange={({ hex }) => {
                onColorChange({
                    primaryColor: hex,
                });
            }}
        />
    );
    const error = (
        <SketchPicker
            presetColors={['#ff4d4f']}
            color={color.errorColor}
            onChange={({ hex }) => {
                onColorChange({
                    errorColor: hex,
                });
            }}
        />
    );
    const warning = (
        <SketchPicker
            presetColors={['#faad14']}
            color={color.warningColor}
            onChange={({ hex }) => {
                onColorChange({
                    warningColor: hex,
                });
            }}
        />
    );
    const success = (
        <SketchPicker
            presetColors={['#52c41a']}
            color={color.successColor}
            onChange={({ hex }) => {
                onColorChange({
                    successColor: hex,
                });
            }}
        />
    );
    const info = (
        <SketchPicker
            presetColors={['#1890ff']}
            color={color.infoColor}
            onChange={({ hex }) => {
                onColorChange({
                    infoColor: hex,
                });
            }}
        />
    );
    const items = Object.entries({ primary, success, error, warning, info }).map((item) => {
        const [label, children] = item;
        return {
            label,
            children,
            key: label,
        };
    });
    const onColorChange = (nextColor: Partial<typeof color>) => {
        const mergedNextColor = {
            ...color,
            ...nextColor,
        };
        setColor(mergedNextColor);
        // 组件共享theme
        globalStore.setTheme(mergedNextColor);
        ConfigProvider.config({
            theme: mergedNextColor,
        });
    };

    return (
        <Tabs animated={true} items={items} />
    );
};

非antd组件的动态换肤 src/hooks/useTheme.ts

import {useStores} from './useStores'
const useTheme=()=>{
    const {globalStore} =useStores()
    const [theme,setTheme]=useState(globalStore.theme)
    const themeRef=useRef(theme)
    themeRef.current=theme
    useEffect(()=>{
        setTheme(globalStore.theme)
    })
    return {theme:themeRef.current}
}
export {useTheme}

富文本编辑器

import { Editor, Toolbar } from '@wangeditor/editor-for-react';
export const MyEditor = () => {
    const [editor, setEditor] = (useState < IDomEditor) | (null > null);
    const toolbarConfig: Partial<IToolbarConfig> = {};
    const editorConfig: Partial<IEditorConfig> = {
        placeholder: '请输入内容。。。',
    };
    // 及时销毁editor
    useEffect(() => {
        return () => {
            if (editor == null) return;
            editor.destroy();
            setEditor(null);
        };
    }, [editor]);
    return (
        <>
            <Toolbar
                editor={editor}
                defaultConfig={toolbarConfig}
                mode="default"
                style={{ borderBottom: '1px solid #ccc' }}
                data-w-e-toolbar={true}
            />
            <Editor
                defaultConfig={editorConfig}
                value={html.val}
                // onCreated属性有问题会使toolbar变成一条线
                onCreated={setEditor}
                onChange={(editor) => {
                    setHTML({ val: editor.getHtml() });
                    setTEXT({ val: editor.getText() });
                    setJSON({ val: editor.children });
                }}
                style={{ minHeight: '300px' }}
                mode="default"
            />
        </>
    );
};

前端水印

原理:水印是一个或多个元素,通过z-index将其设置在上层覆盖所有元素;pointer-events设置为none使元素永远不会成为鼠标事件的target。但是,当其后代元素的pointer-events属性指定其他值时,鼠标事件可以指向后代元素,在这种情况下,鼠标事件将在捕获或冒泡阶段触发父元素的事件侦听器。

  • dom方案需要生成多个dom元素,不优雅也影响性能;
  • svg方案可以隐藏文字样式去掉文字;
  • background方案(使用canvas或svg生成base64url做背景图片)

前端生成dom元素覆盖到页面上的,对于有些前端知识的人来说,可以在开发者工具中找到水印所在的元素,将元素整个删掉,以达到删除页面上的水印的目的。 - 用定时器或MutationObserver观测水印,但可以通过禁用js跳过。

多页签

src/components/Dashboard/MyMenu.tsx

点击 MenuItem时将当前菜单子项的标签和key传给状态管理库

import { useStores } from '@/hooks';
const MyMenu = () => {
    const navigate = useNavigate();
     const { globalStore } = useStores();
    const onHandleClick = (e: any) => {
        let path = e.keyPath.reverse().join('/');
        navigate(path);
        globalStore.setTab({ label: e.key, key: path });
    };
    return <Menu onClick={onHandleClick} items={dragItems} mode="inline" theme="dark" />;
};

src/stores/global.ts

const globalStore = makeAutoObservable({
    tab: { label: '', key: '' },
    setTab(tab: { label: string; key: string }) {
        this.tab = tab;
    },
});

src/components/Dashboard/MyTabs.tsx

状态库的当前标签信息改变时,添加标签页;


export const MyTabs: React.FC = () => {

    const { globalStore } = useStores();
    const navigate = useNavigate();
    useEffect(() => {
        if (globalStore.tab.label !== '' && globalStore.tab.key !== '') {
            add(globalStore.tab);
        }
    }, [globalStore.tab]);
      const onTabClick = (key: string) => {
        const regexp = /.+(?=\*\*)/;
        const res = regexp.exec(key) || [];
        navigate(res[0] || '');
    };
    return (
        <>
            <Tabs
                type="editable-card"
                onChange={onChange}
                activeKey={activeKey}
                onEdit={onEdit}
                onTabClick={onTabClick}
                items={items}
            />
        </>
    );
};

参考资料