学习React(状态管理)

news2024/11/14 13:38:50

        随着你的应用不断变大,更有意识的去关注应用状态如何组织,以及数据如何在组件之间流动会对你很有帮助。冗余或重复的状态往往是缺陷的根源。在本节中,你将学习如何组织好状态,如何保持状态更新逻辑的可维护性,以及如何跨组件共享状态。

本章节

  • 如何将 UI 变更视为状态变更
  • 如何组织好状态
  • 如何使用“状态提升”在组件之间共享状态
  • 如何控制状态的保留或重置
  • 如何在函数中整合复杂的状态逻辑
  • 如何避免数据通过 prop 逐级透传
  • 如何随着应用的增长去扩展状态管理

 

用 State 响应输入

        使用 React,你不用直接从代码层面修改 UI。例如,不用编写诸如“禁用按钮”、“启用按钮”、“显示成功消息”等命令。相反,你只需要描述组件在不同状态(“初始状态”、“输入状态”、“成功状态”)下希望展现的 UI,然后根据用户输入触发状态更改。这和设计师对 UI 的理解很相似。

下面是一个使用 React 编写的反馈表单。请注意看它是如何使用 status 这个状态变量来决定启用或禁用提交按钮,以及是否显示成功消息的。、

import { useState } from 'react';

export default function Form() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing');

  if (status === 'success') {
    return <h1>答对了!</h1>
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await submitForm(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  function handleTextareaChange(e) {
    setAnswer(e.target.value);
  }

  return (
    <>
      <h2>城市测验</h2>
      <p>
        哪个城市有把空气变成饮用水的广告牌?
      </p>
      <form onSubmit={handleSubmit}>
        <textarea
          value={answer}
          onChange={handleTextareaChange}
          disabled={status === 'submitting'}
        />
        <br />
        <button disabled={
          answer.length === 0 ||
          status === 'submitting'
        }>
          提交
        </button>
        {error !== null &&
          <p className="Error">
            {error.message}
          </p>
        }
      </form>
    </>
  );
}

function submitForm(answer) {
  // 模拟接口请求
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let shouldError = answer.toLowerCase() !== '北京'
      if (shouldError) {
        reject(new Error('猜的不错,但答案不对。再试试看吧!'));
      } else {
        resolve();
      }
    }, 1500);
  });
}

选择 State 结构

        良好的状态组织,可以区分开易于修改和调试的组件与频繁出问题的组件。最重要的原则是,状态不应包含冗余或重复的信息。如果包含一些多余的状态,我们会很容易忘记去更新它,从而导致问题产生!

例如,这个表单有一个多余的 fullName 状态变量:

import { useState } from 'react';

export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
    setFullName(e.target.value + ' ' + lastName);
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value);
    setFullName(firstName + ' ' + e.target.value);
  }

  return (
    <>
      <h2>让我们帮你登记</h2>
      <label>
        名:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        姓:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        你的票据将签发给:<b>{fullName}</b>
      </p>
    </>
  );
}

你可以移除它并在组件渲染时通过计算 fullName 来简化代码: 

import { useState } from 'react';

export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');

  const fullName = firstName + ' ' + lastName;

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value);
  }

  return (
    <>
      <h2>让我们帮你登记</h2>
      <label>
        名:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        姓:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        你的票将发给:<b>{fullName}</b>
      </p>
    </>
  );
}

 这看起来似乎只是一个小改动,但却可以避免很多潜在的问题。

在组件间共享状态

        有时候你希望两个组件的状态始终同步更改。要实现这一点,可以将相关状态从这两个组件上移除,并把这些状态移到最近的父级组件,然后通过 props 将状态传递给这两个组件。这被称为“状态提升”,这是编写 React 代码时常做的事。

在以下示例中,要求每次只能激活一个面板。要实现这一点,父组件将管理激活状态并为其子组件指定 prop,而不是将激活状态保留在各自的子组件中。

 

import { useState } from 'react';

export default function Accordion() {
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel
        title="关于"
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}
      >
        阿拉木图人口约200万,是哈萨克斯坦最大的城市。在1929年至1997年之间,它是该国首都。
      </Panel>
      <Panel
        title="词源"
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}
      >
        这个名字源于哈萨克语 <span lang="kk-KZ">алма</span>,是“苹果”的意思,通常被翻译成“满是苹果”。事实上,阿拉木图周围的地区被认为是苹果的祖籍,<i lang="la">Malus sieversii</i> 被认为是目前本土苹果的祖先。
      </Panel>
    </>
  );
}

function Panel({
  title,
  children,
  isActive,
  onShow
}) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={onShow}>
          显示
        </button>
      )}
    </section>
  );
}

 

对 state 进行保留和重置

        当你重新渲染一个组件时, React 需要决定组件树中的哪些部分要保留和更新,以及丢弃或重新创建。在大多数情况下, React 的自动处理机制已经做得足够好了。默认情况下,React 会保留树中与先前渲染的组件树“匹配”的部分。

然而,有时这并不是你想要的。例如,在下面这个程序中,输入内容后再切换收件人并不会清空输入框。这可能会导致用户不小心发错消息:

App.js

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat contact={to} />
    </div>
  )
}

const contacts = [
  { name: 'Taylor', email: 'taylor@mail.com' },
  { name: 'Alice', email: 'alice@mail.com' },
  { name: 'Bob', email: 'bob@mail.com' }
];

ContactList.js 

export default function ContactList({
  selectedContact,
  contacts,
  onSelect
}) {
  return (
    <section className="contact-list">
      <ul>
        {contacts.map(contact =>
          <li key={contact.email}>
            <button onClick={() => {
              onSelect(contact);
            }}>
              {contact.name}
            </button>
          </li>
        )}
      </ul>
    </section>
  );
}

 Chat.js

import { useState } from 'react';

export default function Chat({ contact }) {
  const [text, setText] = useState('');
  return (
    <section className="chat">
      <textarea
        value={text}
        placeholder={'Chat to ' + contact.name}
        onChange={e => setText(e.target.value)}
      />
      <br />
      <button>发送给 {contact.email}</button>
    </section>
  );
}

        React 允许你覆盖默认行为,可通过向组件传递一个唯一 key(如 <Chat key={email}/>强制 重置其状态。这会告诉 React ,如果收件人不同,应将其作为一个 不同的 Chat 组件,需要使用新数据和 UI(比如输入框)来重新创建它。现在,在接收者之间切换时就会重置输入框——即使渲染的是同一个组件。 

App.js

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat key={to.email} contact={to} />
    </div>
  )
}

const contacts = [
  { name: 'Taylor', email: 'taylor@mail.com' },
  { name: 'Alice', email: 'alice@mail.com' },
  { name: 'Bob', email: 'bob@mail.com' }
];

ContactList.js 

export default function ContactList({
  selectedContact,
  contacts,
  onSelect
}) {
  return (
    <section className="contact-list">
      <ul>
        {contacts.map(contact =>
          <li key={contact.email}>
            <button onClick={() => {
              onSelect(contact);
            }}>
              {contact.name}
            </button>
          </li>
        )}
      </ul>
    </section>
  );
}

Chat.js 

export default function ContactList({
  selectedContact,
  contacts,
  onSelect
}) {
  return (
    <section className="contact-list">
      <ul>
        {contacts.map(contact =>
          <li key={contact.email}>
            <button onClick={() => {
              onSelect(contact);
            }}>
              {contact.name}
            </button>
          </li>
        )}
      </ul>
    </section>
  );
}

迁移状态逻辑至 Reducer 中

        对于那些需要更新多个状态的组件来说,过于分散的事件处理程序可能会令人不知所措。对于这种情况,你可以在组件外部将所有状态更新逻辑合并到一个称为 “reducer” 的函数中。这样,事件处理程序就会变得简洁,因为它们只需要指定用户的 “actions”。在文件的底部,reducer 函数指定状态应该如何更新以响应每个 action!

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  return (
    <>
      <h1>布拉格行程</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('未知操作:' + action.type);
    }
  }
}

let nextId = 3;
const initialTasks = [
  { id: 0, text: '参观卡夫卡博物馆', done: true },
  { id: 1, text: '看木偶戏', done: false },
  { id: 2, text: '列侬墙图片', done: false }
];

 

使用 Context 深层传递参数

        通常,你会通过 props 将信息从父组件传递给子组件。但是,如果要在组件树中深入传递一些 prop,或者树里的许多组件需要使用相同的 prop,那么传递 prop 可能会变得很麻烦。Context 允许父组件将一些信息提供给它下层的任何组件,不管该组件多深层也无需通过 props 逐层透传。

        这里的 Heading 组件通过“询问”最近的 Section 来确定其标题级别。每个 Section 的级别是通过给父 Section 添加的级别来确定的。每个 Section 都向它下层的所有组件提供信息,不需要逐层传递 props,而是通过 Context 来实现。

App.js

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
  return (
    <Section>
      <Heading>大标题</Heading>
      <Section>
        <Heading>一级标题</Heading>
        <Heading>一级标题</Heading>
        <Heading>一级标题</Heading>
        <Section>
          <Heading>二级标题</Heading>
          <Heading>二级标题</Heading>
          <Heading>二级标题</Heading>
          <Section>
            <Heading>三级标题</Heading>
            <Heading>三级标题</Heading>
            <Heading>三级标题</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

Section. js

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

export default function Section({ children }) {
  const level = useContext(LevelContext);
  return (
    <section className="section">
      <LevelContext.Provider value={level + 1}>
        {children}
      </LevelContext.Provider>
    </section>
  );
}

Heading.js

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

export default function Heading({ children }) {
  const level = useContext(LevelContext);
  switch (level) {
    case 0:
      throw Error('标题必须在 Section 内!');
    case 1:
      return <h1>{children}</h1>;
    case 2:
      return <h2>{children}</h2>;
    case 3:
      return <h3>{children}</h3>;
    case 4:
      return <h4>{children}</h4>;
    case 5:
      return <h5>{children}</h5>;
    case 6:
      return <h6>{children}</h6>;
    default:
      throw Error('未知级别:' + level);
  }
}

LevelConText.js

import { createContext } from 'react';

export const LevelContext = createContext(0);

使用 Reducer 和 Context 拓展你的应用

        Reducer 帮助你合并组件的状态更新逻辑。Context 帮助你将信息深入传递给其他组件。你可以将 reducers 和 context 组合在一起使用,以管理复杂应用的状态。

        基于这种想法,使用 reducer 来管理一个具有复杂状态的父组件。组件树中任何深度的其他组件都可以通过 context 读取其状态。还可以 dispatch 一些 action 来更新状态。

App.js

import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';

export default function TaskApp() {
  return (
    <TasksProvider>
      <h1>在京都休息一天</h1>
      <AddTask />
      <TaskList />
    </TasksProvider>
  );
}

AddTask.js

import { useState, useContext } from 'react';
import { useTasksDispatch } from './TasksContext.js';

export default function AddTask({ onAddTask }) {
  const [text, setText] = useState('');
  const dispatch = useTasksDispatch();
  return (
    <>
      <input
        placeholder="添加任务"
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={() => {
        setText('');
        dispatch({
          type: 'added',
          id: nextId++,
          text: text,
        });
      }}>添加</button>
    </>
  );
}

let nextId = 3;

TaskList.js

import { useState, useContext } from 'react';
import { useTasks, useTasksDispatch } from './TasksContext.js';

export default function TaskList() {
  const tasks = useTasks();
  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>
          <Task task={task} />
        </li>
      ))}
    </ul>
  );
}

function Task({ task }) {
  const [isEditing, setIsEditing] = useState(false);
  const dispatch = useTasksDispatch();
  let taskContent;
  if (isEditing) {
    taskContent = (
      <>
        <input
          value={task.text}
          onChange={e => {
            dispatch({
              type: 'changed',
              task: {
                ...task,
                text: e.target.value
              }
            });
          }} />
        <button onClick={() => setIsEditing(false)}>
          保存
        </button>
      </>
    );
  } else {
    taskContent = (
      <>
        {task.text}
        <button onClick={() => setIsEditing(true)}>
          编辑
        </button>
      </>
    );
  }
  return (
    <label>
      <input
        type="checkbox"
        checked={task.done}
        onChange={e => {
          dispatch({
            type: 'changed',
            task: {
              ...task,
              done: e.target.checked
            }
          });
        }}
      />
      {taskContent}
      <button onClick={() => {
        dispatch({
          type: 'deleted',
          id: task.id
        });
      }}>
        删除
      </button>
    </label>
  );
}

TasksContext.js

import { createContext, useContext, useReducer } from 'react';

const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);

export function TasksProvider({ children }) {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider
        value={dispatch}
      >
        {children}
      </TasksDispatchContext.Provider>
    </TasksContext.Provider>
  );
}

export function useTasks() {
  return useContext(TasksContext);
}

export function useTasksDispatch() {
  return useContext(TasksDispatchContext);
}

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('未知操作:' + action.type);
    }
  }
}

const initialTasks = [
  { id: 0, text: '哲学家之路', done: true },
  { id: 1, text: '参观寺庙', done: false },
  { id: 2, text: '喝抹茶', done: false }
];

 

 希望这篇文章对你有所帮助,并能在实际工作中为你提供参考。如果你有任何问题或建议,欢迎在评论区留言。请记得一键三连(点赞、收藏、分享)哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1943884.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpingCloud】客户端与服务端负载均衡机制,微服务负载均衡NacosLoadBalancer, 拓展:OSI七层网络模型

客户端与服务端负载均衡机制 可能有第一次听说集群和负载均衡&#xff0c;所以呢&#xff0c;我们先来做一个介绍&#xff0c;然后再聊服务端与客户端的负载均衡区别。 集群与负载均衡 负载均衡是基于集群的&#xff0c;如果没有集群&#xff0c;则没有负载均衡这一个说法。 …

CSS:顶部导航栏固定位置、分类标题栏、底部提示文案固定位置

一、效果图 页面body的css代码 body {position: absolute;width: 100%;height: 100vh;padding: 0;margin: 0;top: 0;left: 0;bottom: 0;background-color #eee;/* overflow: auto;overflow-y: scroll; *//* ::-webkit-scrollbar {display: none;} */ }.content-root {width: 1…

python多进程加速函数运行

python多进程运行可以使函数运行在程序主进程以外&#xff0c;减少主进程的调用&#xff0c;并且可以加速子进程的运行速度   为了测试多进程的加速效果&#xff0c;我们可以通过创建一个包含计算密集型任务的函数&#xff0c;并使用多进程来并行执行这些任务。我们将对比单进…

Telegram曝零日漏洞,可伪装成视频攻击安卓用户

ESET Research在一个地下论坛上发现了一个针对Android Telegram的零日漏洞广告。 ESET将该漏洞命名为“EvilVideo”&#xff0c;并将其报告给Telegram&#xff0c;Telegram于7月11日更新了该应用程序。 EvilVideo允许攻击者发送恶意的有效载荷&#xff0c;这些载荷以视频文件…

《白话机器学习的数学》第2章——学习回归

2.1设置问题 1.机器学习所做的事情正是从数据中进行学习&#xff0c;然后给出预测值。 2.2定义模型 1.一次函数的表达式&#xff1a; 其中θ叫做参数。 在统计学领域&#xff0c;人们常常使用 θ 来表示未知数和推测值。采用 θ加数字下标的形式&#xff0c;是为了防止当未知数…

熟悉set/map了解KV模型和pair结构

set基本介绍 set是key模型,本质是确定一个 元素在不在此容器中,也就是说 set中存储的是一个单一数据 1. set是按照一定次序存储元素的容器 2. 在set中&#xff0c;元素的value也标识它(value就是key&#xff0c;类型为T)&#xff0c; 并且每个value必须是唯一的。set中的元素不…

PHP教程001:PHP介绍和环境配置

文章目录 1、php是什么2、php能做什么3、php程序执行流程4、需要什么基础5、环境介绍5.1、WEB环境5.2、环境集成包3、phpStudio软件下载 1、php是什么 通用&#xff1a;跨平台&#xff0c;如windows、Linux、MacOS开源免费服务器端脚本语言 2、php能做什么 可以快速动态的生…

群管机器人官网源码

一款非常好看的群管机器人html官网源码 搭建教程&#xff1a; 域名解析绑定 源码文件上传解压 访问域名即可 演示图片&#xff1a; 群管机器人官网源码下载&#xff1a;客户端下载 - 红客网络编程与渗透技术 原文链接&#xff1a; 群管机器人官网源码

很酷的仿真翻页书HTML源码,书本页面是加载的图片,基于JQuery实现的翻页特效,结合一些js插件,看起来很酷,在实现在线翻书项目。

仿真翻页书HTML源码https://www.bootstrapmb.com/item/14742 创建一个仿真的翻页书效果在HTML和CSS中可以通过多种方式实现&#xff0c;但通常这也会涉及到JavaScript&#xff08;或jQuery&#xff09;来处理交互和动画。以下是一个简单的示例&#xff0c;展示如何使用HTML、…

openssl 加密

使用tar命令在Linux中加密文件可以通过两种方式实现&#xff1a;使用gzip压缩的同时加密&#xff0c;或者使用加密选项。 1. 使用gzip压缩的同时加密&#xff1a; “ tar cz file1 file2 | openssl enc -e -aes256 -out archive.tar.gz.enc “ – cz&#xff1a;创建tar压缩文…

【数学建模】基于贪心算法的电力市场的输电阻塞管理(附论文及matlab、lingo代码)

适合数学建模新手研究的题目&#xff0c;备战国赛的同学可以拿这道题目练手&#xff0c;本文含论文代码&#xff0c;帮助解题理解思路。 题目&#xff1a; &#xff08;1&#xff09;题目信息&#xff1a; 某电网有若干台发电机组和若干条主要线路&#xff0c;每条线路上的有…

k8s中部署nacos

1 部署nfs # 在k8s的主节点上执行 mkdir -p /appdata/download cd /appdata/download git clone https://github.com/nacos-group/nacos-k8s.git 将nacos部署到middleware的命名空间中 kubectl create namespace middleware cd /appdata/download/nacos-k8s # 创建角色 kub…

鸿蒙界面开发

界面开发 //构建 → 界面 build() {//行Row(){//列Column(){//文本 函数名(参数) 对象.方法名&#xff08;参数&#xff09; 枚举名.变量名Text(this.message).fontSize(40)//设置文本大小.fontWeight(FontWeight.Bold)//设置文本粗细.fontColor(#ff2152)//设置文本颜色}.widt…

乐鑫ACK方案低成本设备开发,智能家居无线技术应用,启明云端乐鑫代理商

随着智能家居行业的蓬勃发展&#xff0c;用户对于智能设备的需求日益增长。乐鑫以其创新的Alexa Connect Kit (ACK) 方案&#xff0c;开启了智能家居设备开发的新篇章。 Alexa Connect Kit&#xff08;ACK&#xff09;方案&#xff0c;不仅提供了一个集成Alexa语音服务的高效开…

Redis八股文(一)

目录 1.什么是Redis&#xff1f; 2.Redis和Memcached有什么区别&#xff1f; 3.为什么Redis作为MySQL的缓存&#xff1f; 4.Redis数据类型及其使用场景分别是什么&#xff1f; 5.五种常见数据类型是怎么实现的&#xff1f; 6.Redis是单线程吗&#xff1f; 7.Redis单线程…

iterm2工具的使用|MAC电脑终端实现分屏|iterm2开启滚动操作

iterm2 工具概括 iTerm2 是一款非常强大的终端工具。 iTerm2 最初是为 macOS 开发的,但也有 Windows 、Linux 发行版&#xff08;Ubuntu、centos…&#xff09;可用。 应用场景 Mac操作系统中想实现终端分屏 iterm2 工具特点 多标签和分屏: 可以在同一个窗口中打开多个标签…

【css】实现扫光特效

对于要重点突出的元素&#xff0c;我们经常可以看到它上面打了一个从左到右的斜向扫光&#xff0c;显得元素亮闪闪的&#xff01;类似于下图的亮光动效 关键步骤 伪元素设置position :absolute【也可以不用伪元素&#xff0c;直接创建一个absolute元素盖在上面】设置渐变line…

基于jeecgboot-vue3的Flowable流程仿钉钉流程设计器-抄送服务处理

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、因为仿钉钉设计器里抄送人是一个服务任务&#xff0c;所以要根据这个服务任务进行处理 2、前端就是一个抄送&#xff0c;选择人 3、这里用了jeecg的选择人组件 <el-form-item prop…

Java开发之Redis

1、非关系型数据库、快、高并发、功能强大 2、为什么快&#xff1f;内存单线程 非阻塞的IO多路复用有效的数据类型/结构 3、应用&#xff1a;支持缓存、支持事务、持久化、发布订阅模型、Lua脚本 4、数据类型&#xff1a; 5 种基础数据类型&#xff1a;String&#xff08;字…

【深度学习】LDA线性判别分析

date:2024/07/23 author:sion tag:Deeping Learn LDA(线性判别分析) 文章目录 LDA(线性判别分析)1.LDA是什么LDA是一种解决二分类问题的线性方法。它描述&#xff0c;对于给定样例集&#xff0c;将样例点投影到一条直线上&#xff0c;这条直线能使异样的样例相距远&#xff0c;…