Angular进阶之八: Angular Animation在项目中的实践经验

news2024/9/21 14:49:01

使用 Angular 进行项目开发的程序员应该都很熟悉 Angular Animation。这是一个 Angular 原生的动画库,它可以替代或者辅助完成原本需要使用 css 的动画功能。

Angular 在国内的运用是很有限的,可借鉴的文档并不很丰富。尤其对于 Angular 动画模块的应用类文档更是少见。我认为原因可能是大家普遍认为动画应当是由 css 去实现的,毕竟它有非常完善并且兼容性极强的动画功能。而且作为前端工程师,精通 css 是基本功,使用 css 完成页面的动画功能成本低、效率高。

既然如此那又为什么需要 Angular Animation 呢?我在实际的项目中体会到,相比 css 动画,Angular Animation 最大的优点是能够提供一系列很准确的关键帧回调函数(callback)。

下面是我模仿项目中的功能写的一个例子。我会罗列出所遇到的问题,并且逐一阐述我的解决方案。

代码环境:

javascript 

{
  "name": "blog-angular-animation",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^17.1.0",
    "@angular/common": "^17.1.0",
    "@angular/compiler": "^17.1.0",
    "@angular/core": "^17.1.0",
    "@angular/forms": "^17.1.0",
    "@angular/platform-browser": "^17.1.0",
    "@angular/platform-browser-dynamic": "^17.1.0",
    "@angular/router": "^17.1.0",
    "lodash": "^4.17.21",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.14.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^17.1.0",
    "@angular/cli": "^17.1.0",
    "@angular/compiler-cli": "^17.1.0",
    "@types/jasmine": "~5.1.0",
    "@types/lodash": "^4.14.202",
    "jasmine-core": "~5.1.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "typescript": "~5.3.2"
  }
}

Demo 效果如下所示,这是一个简单的列表元素添加删除功能:

  • 点击 Add 会在列表末尾添加一个元素
  • 点击 Delete 会从列表中删除当前元素,并且调整后续元素的序号

Animation Demo

现在我们为这两个行为添加一些动画。下面是对动画过程的详细描述:

1. add

添加节点动画分为3个步骤

1.高度从0变为标准节点高度

2.宽度从1%增加到100%

3.渐入式显示内部元素

这三个步骤可以使用一个transition来完成,请看下面的代码:

typescript

trigger('addNode', [
    transition(':enter', [
        style({ height: '0px', width: '1%' }),
        query('.list-item-index', [style({ opacity: 0 })], { optional: true }),
        query('.list-item-value', [style({ opacity: 0 })], { optional: true }),
        query('.list-item-btn', [style({ opacity: 0 })], { optional: true }),
        group([
            style({ height: '0px', width: '1%' }),
            animate('0.2s ease-in-out', style({ height: '50px' }))
        ]),
        group([
            style({ width: '1%' }),
            animate('0.2s 0.1s ease-in-out', style({ width: '100%' }))
        ]),
        group([
            query('.list-item-index', [
                style({ opacity: 0 }),
                animate('0.2s 0.3s ease-in-out', style({ opacity: 1 }))
            ], { optional: true }),
            query('.list-item-value', [
                style({ opacity: 0 }),
                animate('0.2s 0.3s ease-in-out', style({ opacity: 1 }))
            ], { optional: true }),
            query('.list-item-btn', [
                style({ opacity: 0 }),
                animate('0.2s 0.3s ease-in-out', style({ opacity: 1 }))
            ], { optional: true })
        ])
    ])
])

这里有两个问题需要注意:

初始的状态需要被设定

可以看到最上面的四行代码

typescript

style({ height: '0px', width: '1%' }),
query('.list-item-index', [style({ opacity: 0 })], { optional: true }),
query('.list-item-value', [style({ opacity: 0 })], { optional: true }),
query('.list-item-btn', [style({ opacity: 0 })], { optional: true }),

它们的作用就是设定动画开始时的各个元素的初始状态。目的是防止动画开始时的抖动。

因为触发动画的状态是:enter,所以当第一次渲染整个列表时,所有的节点都会触发动画。这可能不是我们需要看到的。此时我们需要其它的参数来标识出不需要动画的节点

如下所示,当我刷新了页面后,所有节点的 add animation 都执行了

我们可以利用 Angular animation 的 disabled 属性来禁用非必要动画

html

<div class="my-animation-container">
    <div class="list-container" [@fadeIndexMarke]="fadeIndexMarkeStatus" (@fadeIndexMarke.done)="fadeIndexMarkeDone()">

        <div class="list-item-container"
            *ngFor="let item of list"
            [@.disabled]="animationNodeIndex !== item.index"
            [@addNode]
            [@deleteNode]
            (@addNode.done)="addAnimationDone()"
            (@deleteNode.done)="deleteAnimationDone()">
            <div class="list-item-index" [ngStyle]="{ opacity: animationRunning ? 0 : 1 }">{{item.index}}</div>
            <div class="list-item-value">{{item.value}}</div>
            <div class="list-item-btn" (click)="handleDelete(item.index)">Delete</div>
        </div>

    </div>
    <div class="list-active" (click)="handleAdd()">Add</div>
</div>

typescript

handleAdd() {
  this.animationNodeIndex = this.list?.length || 0;
  this.addNode(); // Push a node in list
}

addAnimationDone() {
    if (this.animationNodeIndex >= 0) {
        this.animationNodeIndex = -1;
    }
}

这样就可以在我们需要动画的时候再执行它

2. delete

这个动画看似与 add animation 相似,但是过程却比它要复杂一些。下面是完整的动画:

这个动画分为3个步骤:

  • 隐藏所有的序号
  • 删除指定节点
  • 显示节点前的序号

这一组动画有很强的顺序性,必须是上一个动画执行完后才能执行下一个动画。特别要注意的是删除节点的操作需要在第二步完成,所以我们需要监听第一个步骤完成时的回调。

这在 css 中很难实现,可能需要借助 setTimeout。在 Angular 中,定时器并非是解决问题的一个好的选择。

Angular Animation 为我们提供了一个更好的方案。我们可以将动画拆分成两部分绑定在不同的元素上

typescript

animations: [
        trigger('fadeIndexMarke', [
            transition('fadeIn => fadeOut', [
                query('.list-item-index', [
                    style({ opacity: 1 }),
                    animate('0.2s ease-in-out', style({ opacity: 0 }))
                ], { optional: true })
            ]),
            transition('fadeOut => fadeIn', [
                query('.list-item-index', [
                    style({ opacity: 0 }),
                    animate('0.2s ease-in-out', style({ opacity: 1 }))
                ], { optional: true })
            ])
        ]),
        trigger('deleteNode', [
            transition(':leave', [
                style({ width: '100%', height: '50px', overflow: 'hidden' }),
                query('.list-item-index', style({ opacity: 0 }), { optional: true }),
                group([
                    query('.list-item-value', [
                        style({ opacity: 1 }),
                        animate('0.2s ease-in-out', style({ opacity: 0 }))
                    ], { optional: true }),
                    query('.list-item-btn', [
                        style({ opacity: 1 }),
                        animate('0.2s ease-in-out', style({ opacity: 0 }))
                    ], { optional: true })
                ]),
                group([
                    animate('0.2s 0.2s ease-in-out', style({ width: '0%' })),
                    animate('0.2s 0.3s ease-in-out', style({ height: '0px' }))
                ])
            ])
        ])
    ]

上面这两个动画分别绑定在最外围的列表元素和每一个节点元素上

html

<div class="list-container" [@fadeIndexMarke]="fadeIndexMarkeStatus"
    (@fadeIndexMarke.done)="fadeIndexMarkeDone()">

    <div class="list-item-container"
        *ngFor="let item of list"
        [@deleteNode]
        (@deleteNode.done)="deleteAnimationDone()">
...

我们可以先执行隐藏索引的动画,然后监听 animation.done,此时再删除指定节点。

typescript

fadeIndexMarkeDone() {
    if (this.fadeIndexMarkeStatus === 'fadeOut') {
        // Step 2
        this.animationRunning = true;
        this.fadeIndexMarkeCallBack();
    }
}

handleDelete(index: number) {
    // Step 1
    this.fadeIndexMarkeCallBack = () => {
        // Step 3
        this.deleteNode(index);
    };
    this.fadeIndexMarkeStatus = 'fadeOut';
    this.animationNodeIndex = index;
}

deleteAnimationDone() {
    // Step 4
    if (this.animationRunning) {
        this.animationRunning = false;
        this.fadeIndexMarkeStatus = 'fadeIn';
        this.animationNodeIndex = -1;
        this.fadeIndexMarkeCallBack = () => {};
    }
}

这样动画的执行顺序就可以按照我们的需求来规划了。下面是完整的代码:

html

<div class="my-animation-container">

    <div class="list-container" [@fadeIndexMarke]="fadeIndexMarkeStatus" (@fadeIndexMarke.done)="fadeIndexMarkeDone()">

        <div class="list-item-container"
            *ngFor="let item of list"
            [@.disabled]="animationNodeIndex !== item.index"
            [@addNode]
            [@deleteNode]
            (@addNode.done)="addAnimationDone()"
            (@deleteNode.done)="deleteAnimationDone()">
            <div class="list-item-index" [ngStyle]="{ opacity: animationRunning ? 0 : 1 }">{{item.index}}</div>
            <div class="list-item-value">{{item.value}}</div>
            <div class="list-item-btn" (click)="handleDelete(item.index)">Delete</div>
        </div>

    </div>

    <div class="list-active" (click)="handleAdd()">Add</div>

</div>

less

.my-animation-container {
    width: 100%;
    height: 100%;
    overflow: hidden;
    display: flex;
    flex-flow: column;
    justify-content: center;
    align-items: center;
    .list-container {
        width: 400px;
        height: 600px;
        border: 2px solid gray;
        overflow-x: hidden;
        overflow-y: auto;
        padding: 20px;
        .list-item-container {
            width: 100%;
            height: 50px;
            border: 1px solid #CCCCCC;
            display: flex;
            flex-flow: row nowrap;
            justify-content: space-between;
            align-items: center;
            padding: 0 20px;
            .list-item-index {
                font-size: 24px;
                font-weight: 800;
                color: #666666;
                opacity: 1;
                &.hide-index {
                    opacity: 0;
                }
            }
            .list-item-value {
                font-size: 20px;
                font-weight: 500;
                color: #666666;
            }
            .list-item-btn {
                font-size: 14px;
                font-weight: 500;
                color: #666666;
                border: 2px solid skyblue;
                border-radius: 5px;
                padding: 5px;
                cursor: pointer;
                &:hover {
                    background-color: skyblue;
                    color: white;
                }
                &:active {
                    background-color: white;
                    color: skyblue;
                }
            }
        }
    }
    .list-active {
        font-size: 20px;
        font-weight: 500;
        color: #666666;
        border: 2px solid skyblue;
        border-radius: 5px;
        padding: 5px;
        cursor: pointer;
        margin-top: 20px;
        &:hover {
            background-color: skyblue;
            color: white;
        }
        &:active {
            background-color: white;
            color: skyblue;
        }
    }
}

typescript

import { Component, OnInit } from '@angular/core';
import { animate, style, transition, trigger, state, group, query } from '@angular/animations';

import * as _ from 'lodash';

@Component({
    selector: 'my-animation',
    templateUrl: './animation.component.html',
    styleUrls: ['./animation.component.less'],
    animations: [
        trigger('fadeIndexMarke', [
            transition('fadeIn => fadeOut', [
                query('.list-item-index', [
                    style({ opacity: 1 }),
                    animate('0.2s ease-in-out', style({ opacity: 0 }))
                ], { optional: true })
            ]),
            transition('fadeOut => fadeIn', [
                query('.list-item-index', [
                    style({ opacity: 0 }),
                    animate('0.2s ease-in-out', style({ opacity: 1 }))
                ], { optional: true })
            ])
        ]),
        trigger('addNode', [
            transition(':enter', [
                style({ height: '0px', width: '1%' }),
                query('.list-item-index', [style({ opacity: 0 })], { optional: true }),
                query('.list-item-value', [style({ opacity: 0 })], { optional: true }),
                query('.list-item-btn', [style({ opacity: 0 })], { optional: true }),
                group([
                    style({ height: '0px', width: '1%' }),
                    animate('0.2s ease-in-out', style({ height: '50px' }))
                ]),
                group([
                    style({ width: '1%' }),
                    animate('0.2s 0.1s ease-in-out', style({ width: '100%' }))
                ]),
                group([
                    query('.list-item-index', [
                        style({ opacity: 0 }),
                        animate('0.2s 0.3s ease-in-out', style({ opacity: 1 }))
                    ], { optional: true }),
                    query('.list-item-value', [
                        style({ opacity: 0 }),
                        animate('0.2s 0.3s ease-in-out', style({ opacity: 1 }))
                    ], { optional: true }),
                    query('.list-item-btn', [
                        style({ opacity: 0 }),
                        animate('0.2s 0.3s ease-in-out', style({ opacity: 1 }))
                    ], { optional: true })
                ])
            ])
        ]),
        trigger('deleteNode', [
            transition(':leave', [
                style({ width: '100%', height: '50px', overflow: 'hidden' }),
                query('.list-item-index', style({ opacity: 0 }), { optional: true }),
                group([
                    query('.list-item-value', [
                        style({ opacity: 1 }),
                        animate('0.2s ease-in-out', style({ opacity: 0 }))
                    ], { optional: true }),
                    query('.list-item-btn', [
                        style({ opacity: 1 }),
                        animate('0.2s ease-in-out', style({ opacity: 0 }))
                    ], { optional: true })
                ]),
                group([
                    animate('0.2s 0.2s ease-in-out', style({ width: '0%' })),
                    animate('0.2s 0.3s ease-in-out', style({ height: '0px' }))
                ])
            ])
        ])
    ]
})

export class MyAnimationComponent implements OnInit {

    list: { index: number; value: string; }[] = [];
    animationRunning = false;
    animationNodeIndex: number = -1;
    fadeIndexMarkeStatus = 'fadeIn';

    fadeIndexMarkeCallBack = () => {};

    ngOnInit() {
        this.list = _.chain(3).range().map((num) => ({ index: num, value: `This is the ${num + 1}'s item` })).value();
    }

    fadeIndexMarkeDone() {
        if (this.fadeIndexMarkeStatus === 'fadeOut') {
            // Step 2
            this.animationRunning = true;
            this.fadeIndexMarkeCallBack();
        }
    }

    handleAdd() {
        this.animationNodeIndex = this.list?.length || 0;
        this.addNode();
    }

    handleDelete(index: number) {
        // Step 1
        this.fadeIndexMarkeCallBack = () => {
            // Step 3
            this.deleteNode(index);
        };
        this.fadeIndexMarkeStatus = 'fadeOut';
        this.animationNodeIndex = index;
    }

    addAnimationDone() {
        if (this.animationNodeIndex >= 0) {
            this.animationNodeIndex = -1;
        }
    }

    deleteAnimationDone() {
        // Step 4
        if (this.animationRunning) {
            this.animationRunning = false;
            this.fadeIndexMarkeStatus = 'fadeIn';
            this.animationNodeIndex = -1;
            this.fadeIndexMarkeCallBack = () => {};
        }
    }

    private addNode() {
        const targetIndex = (this.list?.length || 0);
        this.list = _.concat(this.list, [{ index: targetIndex, value: `This is the ${targetIndex + 1}'s item` }]);
    }

    private deleteNode(index: number) {
        this.list = _.reduce(this.list, (result: { index: number; value: string; }[], curr, currIndex) => {
            if (currIndex > index) {
                curr.index -= 1;
                curr.value = `This is the ${curr.index + 1}'s item`;
                result.push(curr);
            } else if (currIndex < index) {
                result.push(curr);
            } else {
                // currIndex === index, exclude node
            }
            return result;
        }, []);
    }
}

以上所谈到的是我在项目中遇到的主要问题以及解决的方案。

下面还有一些在后期优化时所遇到的问题:

动画的回调函数的时机

Angular Animation 的 done 可以监听动画完成时的回调。这是官方文档的说法,但实际上它监听的是 animation state 的改变。组件在初始化后动画的状态就会改变,如下所示:

我没有执行任何操作但是 done 就被调用了,所以在监听这个回调的时候我们需要额外的参数来进行判断。

过多的 DOM 元素导致过多的渲染

列表中的节点越多,重新渲染的性能就越低。甚至当组件过于复杂或者嵌套的子组件过多的时候,动画会出现卡顿。

解决的方法是对组件进行优化,尽量减少 DOM 元素。或者降低子组件数量和嵌套层数。Angular 在渲染时会解析组件中所有的子组件,这在性能上会造成极大的损耗,所以应当尽量减少动画所影响到的组件。

节点宽高不定时,如何设定动画宽高的变化值

如果节点的宽高是自适应的,那么我们动画关键帧的 style 就最好使用百分比来表示。或者使用 transform: scale 来进行缩放。

简单的动画细节使用 animation 过于繁琐

定义一个动画需要 trigger, state, style 等一系列属性,即便完成一个很细节的动画也需要写很多代码。这时可以使用 transition 来替代动画,减少代码量。

元素的定位问题

这是一个很容易被忽略的问题。当我们的元素中包含绝对定位时,不同的定位方向可能导致动画的错乱。有些元素可能在动画中被截断,也有一些会发生意想不到的偏移。所以如果绑定动画的组件中存在不同的定位,最好是都统一成一个方向的绝对定位。

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

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

相关文章

[数据结构]堆

一、堆是什么&#xff1f; 堆是一种完全二叉树 完全二叉树的定义&#xff1a;完全二叉树的形式是指除了最后一层之外&#xff0c;其他所有层的结点都是满的&#xff0c;而最后一层的所有结点都靠左边。​​​​​​&#xff0c;从左到右连续。 教材上定义如下: 若设二叉树的…

纹波和噪声有啥区别(一)

首先要知道的是他们都是在电源输出中出现的信号波动&#xff0c;但两者存在明显的区别。 一&#xff0c;纹波的产生 电源纹波是指电源输出时&#xff0c;叠加在稳定的直流电源上的交流成分。 这种波动主要是由于电源自身的开关、PWM 调节等因素引起的&#xff0c;其频率一般…

Javaweb的学习21_CSS_属性

CSS的属性 (常用)属性&#xff1a; 1. 字体、文本 font-size&#xff1a;字体大小 color&#xff1a;文本颜色 text-align&#xff1a;文本的对齐方式 line-height&#xff1a;行高 2. 背景 background&#xff1a;是个复合属性 3. 边框 border&#xff1a;设置边框&#xff0c…

1升级powershell后才能安装WSL2--最后安装linux--Ubuntu 22.04.3 LTS

视频 https://www.bilibili.com/video/BV1uH4y1W7UX查看电脑本版 步骤1:使用 Winget 方式安装 PowerShell 查看是否能更新PowerShell– winget search Microsoft.PowerShell查看结果为 名称 ID 版本 源 ----------------------------…

HTML小游戏27 - Chuck Chicken 魔法蛋网页游戏(附完整源码)

&#x1f482; 网站推荐:【神级源码资源网】【摸鱼小游戏】 【工具大全】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;【轻量化工具创作平台】&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【学习交流群】 本节教程我会带大家使用 HTML 、…

【Kafka系列】Kafka事务一般在什么场景下使用呢

面试官&#xff1a;听说你精通Kafka&#xff0c;那我就考考你吧 面试官&#xff1a;不用慌尽管说&#xff0c;错了也没关系&#x1f60a;。。。 以【面试官面试】的形式来分享技术&#xff0c;本期是《Kafka系列》&#xff0c;感兴趣就关注我吧❤️ 面试官&#xff1a;生产者重…

G1和ZGC垃圾回收器学习

前言 ​ 随着JDK17的占有率不断升高和SpringBoot3最低支持JDk17&#xff0c;JDK17很大概率会成为大家后续升级的一个选择&#xff0c;而JDK17上最重要的垃圾回收器G1和ZGC&#xff0c;也就显得格外重要。大家提前了解或者学习一下肯定是有用的。 ​ 本篇文章也默认大家了解一…

【linux】进程地址空间(进程三)

目录 快速了解&#xff1a;引入最基本的理解&#xff1a;细节&#xff1a;如何理解地址空间&#xff1a;a.什么是划分区域&#xff1a;b.地址空间的理解&#xff1a; 为什么要有进程空间&#xff1f;进一步理解页表与写时拷贝&#xff1a; 快速了解&#xff1a; 先来看这样一段…

docker入门(九)—— docker网络详细介绍

docker 网络 准备工作&#xff1a;清空所有镜像和容器 docker rm -f $(docker ps -aq) docker rmi -f $(docker images -aq)docker0 网络 查看本地网络 ip addr[rootiZbp15293q8kgzhur7n6kvZ /]# ip addr# 本地回环网络 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc…

jmeter接口导入方式

curl直接导入 1、操作页面后&#xff0c;F12查看接口&#xff0c;右击接口-copy-copy as cURL 2、jmeter 工具-import from cURL&#xff0c;粘贴上面复制的curl 根据接口文档导入 1、接口文档示例如下&#xff1a; Path&#xff1a; /api/jobs/xps/exec Method&#xf…

3d画线生成模型之后最大化找不到---模大狮模型网

当你在3D建模软件中画线生成模型后&#xff0c;如果最大化找不到该模型&#xff0c;可能是因为以下几种情况导致&#xff1a; 模型位置偏移&#xff1a; 可能你在绘制线条时&#xff0c;将模型画在了视图界面之外&#xff0c;导致最大化时无法看到。尝试平移或旋转模型&#x…

东方博宜 1449. 求满足条件的数的和

东方博宜 1449. 求满足条件的数的和 这道题我苦想了很久&#xff0c;觉得2个及2个以上很难解决&#xff0c;但是后面发现&#xff0c;可以用一个变量记录次数&#xff0c;次数大于等于2就好了。 #include<iostream> using namespace std; int main() {int n ;cin >…

企业用大模型如何更具「效价比」?百度智能云发布5款大模型新品

服务8万企业用户&#xff0c;累计帮助用户精调1.3万个大模型&#xff0c;帮助用户开发出16万个大模型应用&#xff0c;自2023年12月以来百度智能云千帆大模型平台API日调用量环比增长97%...从一年前国内大模型平台的“开路先锋”到如今的大模型“超级工厂”&#xff0c;百度智能…

革新水库大坝监测:传统软件与云平台之比较

在水库大坝的监测管理领域&#xff0c;传统监测软件虽然曾发挥了重要作用&#xff0c;但在多方面显示出了其局限性。传统解决方案通常伴随着高昂的运维成本&#xff0c;需要大量的硬件支持和人员维护&#xff0c;且软件整合和升级困难&#xff0c;限制了其灵活性和扩展性。 点击…

类对象的初始化过程与方法

类初始化过程与方法 一、类对象的初始化过程 1.初始化的过程 &#xff08;1&#xff09;对象在实例化的时候需要调用构造函数&#xff0c;如果对应的构造函数调用不了&#xff0c;这个对象是没有办法实例化的。 &#xff08;2&#xff09;构造函数的执行&#xff0c;是在内…

好看的表情壁纸

不定时更新表情壁纸&#xff0c;后期支持头像&#xff0c;wx背景等&#xff0c;个人开发&#xff0c;觉得不错&#xff0c;可前往小程序或者公众号查看

深入理解模板进阶:掌握C++模板的高级技巧

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…

基于springboot的员工绩效考核管理系统(含源文件)

&#xff08;源码附文章底部&#xff09; 摘 要 科学时代的发展改变了人类的生活&#xff0c;促使网络与计算机技术深入人类的各个角落&#xff0c;得以普及到人类的具体生活中&#xff0c;为人类的时代文明掀开新的篇章。本系统为月度员工绩效考核管理系统&#xff0c;是专为…

JAVA入门第一步2.0

一、JAVA中的关键字 Java中的关键字是Java编程语言中预先定义并保留的单词&#xff0c;它们具有特殊的含义&#xff0c;不能用作变量名、方法名或类名等标识符。以下是我查到的Java中的一些主要关键字&#xff1a; 由于我还在入门&#xff0c;所以所接触的关键字不多&#xf…

从0到1实现RPC | 02 RpcConsumer的远程调用

一、RPC的简化版原理如下图&#xff08;核心是代理机制&#xff09;。 1.本地代理存根: Stub 2.本地序列化反序列化 3.网络通信 4.远程序列化反序列化 5.远程服务存根: Skeleton 6.调用实际业务服务 7.原路返回服务结果 8.返回给本地调用方 二、新建一个模块rpc-demo-c…