Elasticsearch在EventHub项目中的实战应用

前言

Event Hub是一个高度可缩放、分布式、基于时间序列的事件中心,能够实时的处理流式事件并进行告警和提醒。

Event Hub作为Newegg事件信息中枢,产品化新蛋各产品资源及平台底层基础设施服务生命周期与运转中的重要事件信息,并构建完善的事件消费渠道与流程,支撑线上监控与运维。

Event Hub产品化提供的事件信息,由Newegg内部各产品模块与底层基础设施服务获取,经过聚合,判定和收敛再最终呈现。信息源来自各模块底层的系统日志与监控项,保障客户透传客户的信息准确性与价值。关于Event Hub更详细介绍,请查看Newegg的事件中心之Event Hub

为了实现事件的多维度查询,事件的追溯性,我们将事件存储在elasticsearch中。我们设计了两个index:event_hub_currentevent_hub_history

事件是具有时间序列特征的,我们会在event_hub_historyindex中写入每条事件信息,通过事件唯一ID,在event_hub_current中更新事件信息,随着时间的流逝,大多数情况我们关注的结果。如果你了解过实时计算,可以参照Stream和Table。一个是流式的,一个是结果。

阅读全文 »

Elasticsearch速览学习笔记

声明:本来是打算春节期间在官网学习,温故一下相关知识,。无意间发现铭毅天下Elasticsearch文章很全,对快速了解一些知识点,很有帮助。尤其是知识星球里的内容,奈何收费。别人辛勤劳动成果,当然无可厚非。我就借鉴了他的知识图谱,确定自己的学习点,再结合官网文档和他的公众号。引用的地方已经标注,特此声明。

如果没钱没时间,收藏我这边篇笔记就好。如果你舍得花钱,有充足的时间,推荐去购买一下他的知识星球。

基本知识点

分词必知

当字段类型为text的时候会进行分词,默认分词器是standard

两个地方会出现分词,一个是indexing,一个是search。文档索引的时候肯定会分词,search时候针对search查询语句内容分析。默认的话是两者保持一致。某些场景下可以在search中设置分词

分词器分为三个部分:Tokenizers (分词)、Token filters(修改分词例如小写,删除分词,增加分词)、Character filters(用在分词前去除字符)

Test分词器

1
2
3
4
5
POST _analyze
{
"analyzer":"standard",
"text": "The quick brown fox. 1"
}

排查当前index分词的结果

1
2
3
4
5
GET kibana_sample_data_logs/_analyze
{
"field": "my_text",
"text": "Is this déjà vu?"
}

配置一个分析器,去掉英文修饰词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
PUT my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"std_english": {
"type": "standard",
"stopwords": "_english_"
}
}
}
},
"mappings": {
"properties": {
"my_text": {
"type": "text",
"analyzer": "standard",
"fields": {
"english": {
"type": "text",
"analyzer": "std_english"
}
}
}
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
POST my-index-000001/_analyze
{
"field": "my_text",
"text": "The old brown cow"
}
// [ the, old, brown, cow ]
POST my-index-000001/_analyze
{
"field": "my_text.english",
"text": "The old brown cow"
}
// [ old, brown, cow ]
阅读全文 »

春雷行动-前端技术之CSS必备知识

前言

这篇文章不是教程,也不是让读者能够学会和掌握CSS,而是我的学习笔记。文章的目的是让我实践,系统的了解一下一些必备知识,仅此而已。

如果你需要这方面的知识详细学习的话,推荐你看参考的链接。内容全部来源于此。

正文

五种经典布局

1.空间居中

1
2
3
4
.parent{
display: grid;
place-items: center;
}

核心代码是place-items属性,那个是它的简写形式place-items: <align-items> <justify-items>; 两者相同的话可以省略。

左上角 place-items: start;

右下角place-items: end;

2.并列式布局

并列式布局就是多个项目并列。容器设置成 Flex 布局,内容居中(justify-content)可换行(flex-wrap

1
2
3
4
5
.container{
display: flex;
justify-content: center;
flex-wrap: wrap;
}

项目上面只用一行flex

1
2
3
.col{
flex: 1 1 300px;
}

flex属性是flex-growflex-shrinkflex-basis这三个属性的简写形式 flex: <flex-grow> <flex-shrink> <flex-basis>;

  • flex-basis:项目的初始宽度。
  • flex-grow:指定如果有多余宽度,项目是否可以扩大。
  • flex-shrink:指定如果宽度不足,项目是否可以缩小。

flex: 0 1 300px; 表示项目初始宽度是300,宽度不够的话可以缩小,但是不可以扩大。flex: 1 1 300px;,表示项目始终会占满所有宽度。

阅读全文 »

Newegg的事件中心之Event Hub

万事皆事件

事件是信息的一种承载媒介,描述特定对象某一瞬间的非持续性变化,与唯一时刻和唯一对象关联。例如:某台计算机从运行状态变更为关机,程序运行开始和结束,办公大楼停电等。事件是对象在两个不同状态中的变更瞬间的记录。

对于事件,我们关注时间点,什么事件,什么状态。在企业中存在大量的事件,系统事件,监控事件,业务事件等,通过对事件的治理和挖掘,能够发现很多价值,解决切实的痛点。基于以上思考,我们构建了Event Hub。

Event Hub简介

Event Hub是一个高度可缩放、分布式、基于时间序列的事件中心,能够实时的处理流式事件并进行告警和提醒。

Event Hub作为Newegg事件信息中枢,产品化新蛋各产品资源及平台底层基础设施服务生命周期与运转中的重要事件信息,并构建完善的事件消费渠道与流程,支撑线上监控与运维。

Event Hub产品化提供的事件信息,由Newegg内部各产品模块与底层基础设施服务获取,经过聚合,判定和收敛再最终呈现。信息源来自各模块底层的系统日志与监控项,保障客户透传客户的信息准确性与价值。

目前应用场景

企业级监控/告警平台

在Event Hub之前公司监控存在一些问题:

  • 告警不可追溯
  • 告警不可指派
  • 状态可变更很弱
  • 监控信息可视化很弱
  • 没有更好的统计报表

为了解决以上问题,治理企业级监控问题,我们在Event Hub中基于现存的问题,构建了企业级监控平台。俗话说,先挑软柿子捏。

作为企业级监控平台,Event Hub 立足于能够助力发现、定位、解决问题,保障系统与服务整体的稳定与性能。引入事件作为监控的信息载体,能更准确与直接描述资源底层基础设施服务的运行状态,助力更高效发现、定位从而解决问题。致力于提交信息描述准确性,减少延迟,传递更多的信息,完善监控信息维度,使用通用事件引擎对告警类信息加工处理,尽而告警。

1611048694361

阅读全文 »

Java NIO扫盲篇

概述

对于java世界,想了解高性能网络编程。那么就必须了解NIO,和常用的网络编程框架,以及高性能的网络编程模式。这里面缺一不可,本篇只是管中窥豹,将自己学习的过程和笔记记录下来,希望给工作中很难接触这方面的同学带来一点点帮助!

Java NIO是什么?

NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 ChannelSelectorBuffer 等抽象。

NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。

NIO 的基本流程

通常来说 NIO 中的所有 IO 都是从 Channel(通道) 开始的。

  • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
  • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。

NIO 核心组件

NIO 包含下面几个核心的组件:

  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(选择器)

通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。

如何使用?

内存映射文件 I/O

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。

下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。

1
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);

这里多说一句,在Kafka源码中索引就大量使用了内存映射文件。

文件NIO

读文件

读取文件涉及三个步骤:(1) 从 FileInputStream 获取 Channel,(2) 创建 Buffer,(3) 将数据从 Channel 读到 Buffer

第一步:获取通道

1
2
FileInputStream fis = new FileInputStream("C:\\demo.txt");
FileChannel fileChannel = fis.getChannel();

然后创建Buffer,这里选择的间接缓冲

1
2
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//ByteBuffer buffer = ByteBuffer.allocateDirect( 1024 ); 直接缓存,速度更快,JVM层面尽肯能避免多次拷贝

最后将数据从 Channel 读到 Buffer

1
fileChannel.read(byteBuffer)

写文件

几乎和读文件一样。

首先从 FileOutputStream 获取一个通道

1
2
FileOutputStream fos = new FileOutputStream("C:\\demo.txt");
FileChannel channel = fos.getChannel();

下一步是创建一个缓冲区并在其中放入一些数据

1
2
3
4
5
6
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for (int i=0; i<byteBuffer.limit(); ++i) {
buffer.put( byteBuffer.get(i));
}

buffer.flip();

最后一步是写入缓冲区中:

1
channel.write(buffer);

套接字NIO

NIO 实现了 IO 多路复用中的 Reactor 模型

  • 一个线程(Thread)使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。
  • 通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。

创建选择器

1
Selector selector = Selector.open();

将通道注册到选择器上

1
2
3
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);

注册的具体事件,主要有以下几类:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

监听事件

1
int num = selector.select();

使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。

处理事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while (true) {
int num = selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// ...
} else if (key.isReadable()) {
// ...
}
keyIterator.remove();
}
}

高性能网络编程

Reactor模式

Reactor模式+任务池模式

多Reactor模式

参考

  1. Java NIO

  2. Scalable IO in Java

  3. 《Scalable IO in Java》笔记

只是记录自己不知道的知识点,或者经常容易忘的内容,想知道更多的信息建议看课程或者找我聊天交换技能。在课程的内容基础之上,补充了一些自己知道的知识点。

Kafka的认知

  • 分布式消息引擎平台
  • 分布式实时流式处理平台

早期Kafka社区对Kafka的定位为⼀个分布式、分区化且带备份功能的提交⽇志(Commit Log)服务,近期在官网彻底更改为分布式实时流式处理平台。

Kafka流式处理框架的优势

  • 更容易实现端到端的正确性(Correctness)
  • 轻量型,嵌入式流式计算的定位

避免不必要的Rebalance

  • session.timeout.ms
  • heartbeat.interval.ms
  • max.poll.interval.ms
  • GC参数

session.timout.ms决定了Consumer存活性的时间间隔

heartbeat.interval.ms决定存活心跳发送间隔。

max.poll.interval.ms 它限定了Consumer端应⽤程序两次调⽤poll⽅法的最⼤时间间隔。

消费者TCP管理

消费者实例在KafkaConsumer.poll建立TCP连接,主要分为3类连接:

  1. 确定协调者和获取集群元数据。
  2. 连接协调者,令其执⾏组成员管理操作。
  3. 执⾏实际的消息获取。

第一类连接仅在开始前建立,稍后(第三类创建成功)就会销毁,consumer实例会长期保留2,3类连接。

Consumer实例会长期建立broker数量(分区所在broker数量)+1个连接。

TCP连接的三个时机:

  1. 发起FindCoordinator请求时
  2. 连接协调者时
  3. 消费数据时

何时关闭TCP连接:

  1. ⼿动调⽤KafkaConsumer.close()⽅法
  2. 执⾏Kill命令
  3. Kafka⾃动关闭(是由消费者端参数connection.max.idle.ms控制的,该参数现在的默认值是9分钟)
阅读全文 »

概要

记录线上Zookeeper集群和Kafka集群部署过程,操作系统配置,以及一些参数的设置。给大家部署提供一些宝贵意见和参考。部署一个集群,按照官方社区的文档,很容易就搭建一个集群,但是为了更好的发挥集群的性能,有很多设置是可以避免产生不必要的问题,都是在惨痛的教训中产生的经验。

本文内容来自NeweggConfluent 产线上Kafka cluster运维经验,仅供参考。

Newegg 产线Kafka版本选择Confluent发行版本,版本对照表:

Confluent Platform Apache Kafka
5.5.x 2.5.x

Docker镜像列表:

Service Info
Zookeeper confluentinc/cp-zookeeper:5.5.1
Kafka confluentinc/cp-kafka:5.5.1

Zookeeper Cluster

硬件

内存至少4GB,Zookeeper对swap敏感,应当避免swap。

集群配置

zookeeper节点数据应该为2n+1,n大于0,保持集群中超过一半节点存活。

Confluent docker 镜像参数设置是在ENV(环境变量)中设置,以ZOOKEEPER开头,以_替换.

参数清单

1
2
3
4
5
6
7
8
9
10

- ZOOKEEPER_SERVER_ID=1
- ZOOKEEPER_TICK_TIME=2000
- ZOOKEEPER_CLIENT_PORT=2181
- ZOOKEEPER_INIT_LIMIT=10
- ZOOKEEPER_SYNC_LIMIT=5
- ZOOKEEPER_SERVERS=192.168.0.1:2888:3888;192.168.0.2:2888:3888;192.168.0.3:2888:3888
- ZOOKEEPER_AUTOPURGE_SNAP_RETAIN_COUNT=5
- ZOOKEEPER_AUTOPURGE_PURGE_INTERVAL=10
- KAFKA_HEAP_OPTS=-Xmx4G -Xms4G

ZOOKEEPER_AUTOPURGE_SNAP_RETAIN_COUNT控制快照的数量,ZOOKEEPER_AUTOPURGE_PURGE_INTERVAL控制清除快照的时间间隔,默认不清除,这个很重要。

文件映射

Host volume Container volume
/opt/app/cp-zookeeper-5.5.1/data /var/lib/zookeeper/data
/opt/app/cp-zookeeper-5.5.1/log /var/lib/zookeeper/log
/etc/localtime /etc/localtime
阅读全文 »

前言

本篇文章介绍生成一个自签名SSL证书以及使用Nginx docker 代理一个https服务。

SSL证书验证安全连接,有两种验证模式:

  1. 仅客户端验证服务器的证书,客户端自己不提供证书;
  2. 客户端和服务器都互相验证对方的证书。

显然第二种更安全,一般web采用第一种,比较简单。

创建自签名证书

创建步骤

  1. 创建Key;
  2. 创建签名请求;
  3. 将Key的口令移除;
  4. 用Key签名证书。

创建脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/sh

# create self-signed server certificate:

read -p "Enter your domain [www.example.com]: " DOMAIN

echo "Create server key..."

openssl genrsa -des3 -out $DOMAIN.key 2048

echo "Create server certificate signing request..."

SUBJECT="/C=US/ST=Mars/L=iTranswarp/O=iTranswarp/OU=iTranswarp/CN=$DOMAIN"

openssl req -new -subj $SUBJECT -key $DOMAIN.key -out $DOMAIN.csr

echo "Remove password..."

mv $DOMAIN.key $DOMAIN.origin.key
openssl rsa -in $DOMAIN.origin.key -out $DOMAIN.key

echo "Sign SSL certificate..."

openssl x509 -req -days 3650 -in $DOMAIN.csr -signkey $DOMAIN.key -out $DOMAIN.crt

echo "TODO:"
echo "Copy $DOMAIN.crt to /etc/nginx/ssl/$DOMAIN.crt"
echo "Copy $DOMAIN.key to /etc/nginx/ssl/$DOMAIN.key"
echo "Add configuration in nginx:"
echo "server {"
echo " ..."
echo " listen 443 ssl;"
echo " ssl_certificate /etc/nginx/ssl/$DOMAIN.crt;"
echo " ssl_certificate_key /etc/nginx/ssl/$DOMAIN.key;"
echo "}"

执行以上脚本会生成4个文件:

核心配置

1
2
3
4
5
6
7
8
9
10
11
 server {
listen 443 ssl;
server_name localhost;

ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;

location / {
proxy_pass http://localhost:8866/;
}
}

完整配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;

sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;
index index.html index.htm;

server {
listen 80;
server_name localhost;

location / {
proxy_pass http://localhost:8866/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

server {
listen 443 ssl;
server_name localhost;

ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;

location / {
proxy_pass http://localhost:8866/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_cookie_path / "/; httponly; secure; SameSite=NONE";
}
}
}

启动nginx容器

1
docker run --name my-custom-nginx-container -p 443:443 -v "F:\docker-workspace\nginx-demo\nginx.conf:/etc/nginx/nginx.conf:ro" -v "F:\docker-workspace\nginx-demo\www.example.com.crt:/etc/nginx/www.example.com.crt" -v "F:\docker-workspace\nginx-demo\www.example.com.key:/etc/nginx/www.example.com.key" -d nginx

参考

  1. 给Nginx配置一个自签名的SSL证书

Kafka Summit 2020 -Apache Kafka - The Next 10 Years

前言

Kafka在Confulent成立后发展很快,很明显的变化是发布了很多重大的版本。了解未来的规划,对于我们学习和使用kafka有很大的意义。

Kafka Summit 2020 已经在8-24召开,最近抽出时间看了一些视频,由于自己英语是二把刀,因此本文是自己对该主题的自己理解,尽可能还原Gwen Shapira 的分享(Apache Kafka - The Next 10 Years),中文全网唯一

Kafka 设计原则

High Performance from First Principles

Principles in Action: Elasticity

Principles in Action: Scalability

Principles in Action: Operationally Friend

Design Considerations in Action

KIP-405: Kafka Tiered Storage

Kafka数据在流式中通常使用尾部读取。尾部读取利用操作系统的页面缓存来提供数据,而不是磁盘读取。旧数据和故障恢复通常会从磁盘读取,这些通常很少见。

在分层存储方法中,Kafka集群配置了两层存储-本地和远程。本地层与当前的Kafka相同,使用Kafka broker上的本地磁盘存储日志段。新的远程层使用HDFS或S3等系统来存储完整的日志段。对应于每个层定义了两个单独的保留期。启用远程层后,可以将本地层的保留期从几天显着减少到几个小时。远程层的保留期可能会更长,几天甚至几个月。当日志段在本地层上滚动时,它将与相应的偏移量索引一起复制到远程层。延迟敏感的应用程序执行尾部读取,并利用现有的Kafka机制从本地层提供服务,该机制有效地使用页面缓存来提供数据。回填和应用程序从故障层恢复,需要比本地层中的数据更旧的数据从远程层提供服务。

该解决方案允许扩展存储独立于Kafka集群中的内存和CPU的存储,从而使Kafka成为长期存储解决方案。这也减少了在Kafka代理上本地存储的数据量,因此减少了在恢复和重新平衡期间需要复制的数据量。远程层中可用的日志段无需在代理上还原或延迟还原,而是从远程层提供。这样,增加保留期不再需要扩展Kafka群集存储和添加新节点。同时,总体数据保留时间仍然可以更长,从而无需使用单独的数据管道来将数据从Kafka复制到外部存储,就像目前在许多部署中所做的那样。

KIP-500: Replace ZooKeeper with a Self-Managed Metadata Quorum

主要目的是让部署更简单,配置更简单,用log存储元数据。

主要解决的两三个问题:The Controller Quorum和Broker Metadata Management,以及The Broker State Machine

下一代Kafka架构

更插件化,更弹性,更像云服务一样

Elastic

增加,移除brokers更弹性

Integrated

像使用log一样使用kafka,可以很好的和其他系统集成:像S3、HDFS 等所有的存储系统

Infinite

无限存储,你可以增加更多的broker,没有任何限制,并且不会影响性能。

参考

  1. Keynote Day 1 Morning | Kafka Summit 2020

  2. kafka-summit.org/2020-schedule

  3. 分布式系统理论之Quorum机制

  4. Kafka Improvement Proposals

ice-scripts到ice.js实战迁移之路

TrumanDu github stats

为什么要升级?

纬度\版本 icejs 1.x ice-scripts 2.x ice-scripts 1.x
定位 研发框架 构建工具 构建工具
配置文件 build.json ice.config.js package.json(buildConfig)
文档地址 访问 访问 访问
发布时间 2020.02 2019.06 2018.02
可渐进升级性 不好 不好
插件能力 工程+运行时 工程
工程配置
运行时配置 默认支持 默认不支持 默认不支持
SSR 支持 不支持 不支持

如果你看了这个对比还无法决定,那么说一说我迁移的原因:

  1. ice-scripts官方不维护,查找文档较难
  2. 解决技术债
  3. 我想使用一些新的功能,例如:Hooks and Function Components(当然并不是说不升级就不能用)
  4. 新的前端工程方式,我之所以这么命名,因为我不是一个专业的前端开发,无法将自己的注意力集中在前端领域,只好跟着大厂,这样就不会迷路。这次新版本配置的eslint prettier挺有用。
  5. 前端权限的简洁化(之前推荐的是ant deisgn auth真心不好用)
  6. 布局的简介化

Hooks and Function Components扫盲

快速一览

props非必须,两种方式:

1
2
3
4
const Example = (props) => {
// You can use Hooks here!
return <div />;
}
1
2
3
4
function Example(props) {
// You can use Hooks here!
return <div />;
}

使用useState处理函数组件的状态,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { useState } from 'react';

function Example() {
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

这里要注意的是setCount异步函数,它不会改变count,而是创建一个新的count。而且在接受同一个 state 对象时,即使其对象里的属性变了,但对象地址没变,是不会更新视图的。

函数中的 setXXX 注意事项:

1. 不可局部更新视图

2. 地址一定要变

阅读全文 »
0%