React 更新 state 中的数组
更新 state 中的数组
数组是另外一种可以存储在 state 中的 JavaScript 对象,它虽然是可变的,但是却应该被视为不可变。同对象一样,当你想要更新存储于 state 中的数组时,你需要创建一个新的数组(或者创建一份已有数组的拷贝值),并使用新数组设置 state。
开发环境:React+ts+antd
文中例子参考React官方文档教程,不同的是这里使用TypeScript 来写
学习内容
- 如何添加、删除或者修改 React state 中的数组中的元素
- 如何更新数组内部的对象
- 如何通过 Immer 降低数组拷贝的重复度
在没有 mutation 的前提下更新数组
在 JavaScript 中,数组只是另一种对象。同对象一样,你需要将 React state 中的数组视为只读的。这意味着你不应该使用类似于 arr[0] = ‘bird’ 这样的方式来重新分配数组中的元素,也不应该使用会直接修改原始数组的方法,例如 push() 和 pop()。
相反,每次要更新一个数组时,你需要把一个新的数组传入 state 的 setting 方法中。为此,你可以通过使用像 filter() 和 map() 这样不会直接修改原始值的方法,从原始数组生成一个新的数组。然后你就可以将 state 设置为这个新生成的数组。
下面是常见数组操作的参考表。当你操作 React state 中的数组时,你需要避免使用左列的方法,而首选右列的方法:
避免使用 (会改变原始数组) | 推荐使用 (会返回一个新数组) | |
---|---|---|
添加元素 | push,unshift | concat,[…arr] 展开语法 |
删除元素 | pop,shift,splice | filter,slice |
替换元素 | splice,arr[i] = … 赋值 | map |
排序 | reverse,sort | 先将数组复制一份 |
或者,你可以使用 Immer ,这样你便可以使用表格中的所有方法了。
向数组中添加元素
创建一个 新 数组,其包含了原始数组的所有元素 以及 一个在末尾的新元素。这可以通过很多种方法实现,最简单的一种就是使用 … 数组展开 语法:
setArtists( // 替换 state[ // 是通过传入一个新数组实现的...artists, // 新数组包含原数组的所有元素{ id: nextId++, name: name } // 并在末尾添加了一个新的元素]
);
例子
import React from 'react';
import { useState } from 'react';
import { Input, Button } from 'antd';
// 定义 Artist 类型
type Artist = {id: number;name: string;
};
const App: React.FC = () => {let nextId = 0;const [name, setName] = useState<string>('');const [artists, setArtists] = useState<Artist[]>([]);return (<><h1>添加列表:</h1><Inputstyle={{ width: '200px', marginRight: '10px' }}value={name}onChange={e => setName(e.target.value)}/><Buttoncolor="blue"variant="solid"onClick={() => {setArtists([...artists,{id: nextId++, name: name}]);}}>添加</Button><ul>{artists.map(artist => (<li key={artist.id}>{artist.name}</li>))}</ul></>);
};export default App;
数组展开运算符还允许你把新添加的元素放在原始的 …artists 之前:
setArtists([{ id: nextId++, name: name },...artists // 将原数组中的元素放在末尾
]);
这样一来,展开操作就可以完成 push() 和 unshift() 的工作,将新元素添加到数组的末尾和开头。
从数组中删除元素
从数组中删除一个元素最简单的方法就是将它过滤出去。换句话说,你需要生成一个不包含该元素的新数组。这可以通过 filter 方法实现,例如:
import React from 'react';
import { useState } from 'react';
let initialArtists = [{ id: 0, name: 'Marta Colvin Andrade' },{ id: 1, name: 'Lamidi Olonade Fakeye'},{ id: 2, name: 'Louise Nevelson'},
];
const App: React.FC = () => {const [artists, setArtists] = useState(initialArtists);return (<><h1>删除列表:</h1><ul>{artists.map(artist => (<li key={artist.id}>{artist.name}{' '}<button onClick={() => {setArtists(artists.filter(a =>a.id !== artist.id));}}>删除</button></li>))}</ul></>);
};export default App;
点击“删除”按钮几次,并且查看按钮处理点击事件的代码。
setArtists(artists.filter(a => a.id !== artist.id)
);
这里,artists.filter(s => s.id !== artist.id) 表示“创建一个新的数组,该数组由那些 ID 与 artists.id 不同的 artists 组成”。换句话说,每个 artist 的“删除”按钮会把 那一个 artist 从原始数组中过滤掉,并使用过滤后的数组再次进行渲染。
注意,filter 并不会改变原始数组
转换数组
如果你想改变数组中的某些或全部元素,你可以用 map() 创建一个新数组。你传入 map 的函数决定了要根据每个元素的值或索引(或二者都要)对元素做何处理。
在下面的例子中,一个数组记录了两个圆形和一个正方形的坐标。当你点击按钮时,仅有两个圆形会向下移动 50 像素。这是通过使用 map() 生成一个新数组实现的。
import React from 'react';
import { useState } from 'react';
import {Button} from "antd";let initialShapes = [{ id: 0, type: 'circle', x: 0, y: 100 },{ id: 1, type: 'square', x: 100, y: 100 },{ id: 2, type: 'circle', x: 200, y: 100 },
];
const App: React.FC = () => {const [shapes, setShapes] = useState(initialShapes);const handleClick=()=> {const nextShapes = shapes.map(shape => {if (shape.type === 'square') {// 不作改变return shape;} else {// 返回一个新的圆形,位置在下方 50px 处return {...shape,y: shape.y + 50,};}});// 使用新的数组进行重渲染setShapes(nextShapes);}return (<><Button type='primary' onClick={handleClick}>所有圆形向下移动!</Button><div style={{position:"relative"}}>{shapes.map(shape => (<divkey={shape.id}style={{background: 'purple',position: 'absolute',left: shape.x,top: shape.y,borderRadius:shape.type === 'circle'? '50%' : '',width: 20,height: 20,}} />))}</div></>);
};export default App;
点击按钮后:
替换数组中的元素
想要替换数组中一个或多个元素是非常常见的。类似 arr[0] = ‘bird’ 这样的赋值语句会直接修改原始数组,所以在这种情况下,你也应该使用 map。
要替换一个元素,请使用 map 创建一个新数组。在你的 map 回调里,第二个参数是元素的索引。使用索引来判断最终是返回原始的元素(即回调的第一个参数)还是替换成其他值:
import React from 'react';
import { useState } from 'react';
import {Button} from "antd";let initialCounters = [0, 0, 0
];
const App: React.FC = () => {const [counters, setCounters] = useState(initialCounters);const handleIncrementClick=(index:number)=> {const nextCounters = counters.map((c, i) => {if (i === index) {// 递增被点击的计数器数值return c + 1;} else {// 其余部分不发生变化return c;}});setCounters(nextCounters);}return (<><ul>{counters.map((counter, i) => (<li key={i} style={{marginBottom:"20px"}}><span style={{marginRight:"20px"}}>{counter}</span><Button type="primary" onClick={() => {handleIncrementClick(i);}}>+1</Button></li>))}</ul></>);
};export default App;
向数组中插入元素
有时,你也许想向数组特定位置插入一个元素,这个位置既不在数组开头,也不在末尾。为此,你可以将数组展开运算符 … 和 slice() 方法一起使用。slice() 方法让你从数组中切出“一片”。为了将元素插入数组,你需要先展开原数组在插入点之前的切片,然后插入新元素,最后展开原数组中剩下的部分。
下面的例子中,插入按钮总是会将元素插入到数组中索引为 1 的位置。
import React from 'react';
import { useState } from 'react';
import {Button,Input} from "antd";let nextId = 3;
const initialArtists = [{ id: 0, name: '清明上河图密码' },{ id: 1, name: '雪迷宫'},{ id: 2, name: '白夜行'},
];
const App: React.FC = () => {const [name, setName] = useState('');const [artists, setArtists] = useState(initialArtists);const handleClick=()=> {const insertAt = 1; // 可能是任何索引const nextArtists = [// 插入点之前的元素:...artists.slice(0, insertAt),// 新的元素:{ id: nextId++, name: name },// 插入点之后的元素:...artists.slice(insertAt)];setArtists(nextArtists);setName('');}return (<><h1>添加列表:</h1><Inputstyle={{width:"200px",marginRight:"10px"}}value={name}onChange={e => setName(e.target.value)}/><Button type="primary" onClick={handleClick}>插入</Button><ul>{artists.map(artist => (<li key={artist.id}>{artist.name}</li>))}</ul></>);
};export default App;
其他改变数组的情况
总会有一些事,是你仅仅依靠展开运算符和 map() 或者 filter() 等不会直接修改原值的方法所无法做到的。例如,你可能想翻转数组,或是对数组排序。而 JavaScript 中的 reverse() 和 sort() 方法会改变原数组,所以你无法直接使用它们。
然而,你可以先拷贝这个数组,再改变这个拷贝后的值。
例如:
import React from 'react';
import { useState } from 'react';
import {Button} from "antd";const initialList = [{ id: 0, title: 'Big Bellies' },{ id: 1, title: 'Lunar Landscape' },{ id: 2, title: 'Terracotta Army' },
];
const App: React.FC = () => {const [list, setList] = useState(initialList);const handleClick=()=> {const nextList = [...list];nextList.reverse();setList(nextList);}return (<><Button type="primary" onClick={handleClick}>翻转</Button><ul>{list.map(artwork => (<li key={artwork.id}>{artwork.title}</li>))}</ul></>);
};export default App;
点击翻转后
在这段代码中,你先使用 […list] 展开运算符创建了一份数组的拷贝值。当你有了这个拷贝值后,你就可以使用像 nextList.reverse() 或 nextList.sort() 这样直接修改原数组的方法。你甚至可以通过 nextList[0] = “something” 这样的方式对数组中的特定元素进行赋值。
然而,即使你拷贝了数组,你还是不能直接修改其内部的元素。这是因为数组的拷贝是浅拷贝——新的数组中依然保留了与原始数组相同的元素。因此,如果你修改了拷贝数组内部的某个对象,其实你正在直接修改当前的 state。举个例子,像下面的代码就会带来问题。
const nextList = [...list];
nextList[0].seen = true; // 问题:直接修改了 list[0] 的值
setList(nextList);
虽然 nextList 和 list 是两个不同的数组,nextList[0] 和 list[0] 却指向了同一个对象。因此,通过改变 nextList[0].seen,list[0].seen 的值也被改变了。这是一种 state 的 mutation 操作,你应该避免这么做!你可以用类似于 更新嵌套的 JavaScript 对象 的方式解决这个问题——拷贝想要修改的特定元素,而不是直接修改它。下面是具体的操作。
更新数组内部的对象
对象并不是 真的 位于数组“内部”。可能他们在代码中看起来像是在数组“内部”,但其实数组中的每个对象都是这个数组“指向”的一个存储于其它位置的值。这就是当你在处理类似 list[0] 这样的嵌套字段时需要格外小心的原因。其他人的艺术品清单可能指向了数组的同一个元素!
当你更新一个嵌套的 state 时,你需要从想要更新的地方创建拷贝值,一直这样,直到顶层。 让我们看一下这该怎么做。
在下面的例子中,两个不同的艺术品清单有着相同的初始 state。他们本应该互不影响,但是因为一次 mutation,他们的 state 被意外地共享了,勾选一个清单中的事项会影响另外一个清单:
import React, { useState } from 'react';// 定义 Artwork 类型
type Artwork = {id: number;title: string;seen: boolean;
};// 定义初始列表
const initialList: Artwork[] = [{ id: 0, title: 'Big Bellies', seen: false },{ id: 1, title: 'Lunar Landscape', seen: false },{ id: 2, title: 'Terracotta Army', seen: true },
];// 定义 App 组件
const App: React.FC = () => {const [myList, setMyList] = useState<Artwork[]>(initialList);const [yourList, setYourList] = useState<Artwork[]>(initialList);const handleToggleMyList = (artworkId: number, nextSeen: boolean) => {const myNextList = [...myList];const artwork = myNextList.find(a => a.id === artworkId);if (artwork) {artwork.seen = nextSeen;setMyList(myNextList);}};const handleToggleYourList = (artworkId: number, nextSeen: boolean) => {const yourNextList = [...yourList];const artwork = yourNextList.find(a => a.id === artworkId);if (artwork) {artwork.seen = nextSeen;setYourList(yourNextList);}};return (<><h1>艺术愿望清单</h1><h2>我想看的艺术清单:</h2><ItemList artworks={myList} onToggle={handleToggleMyList} /><h2>你想看的艺术清单:</h2><ItemList artworks={yourList} onToggle={handleToggleYourList} /></>);
};// 定义 ItemList 组件的 props 类型
type ItemListProps = {artworks: Artwork[];onToggle: (artworkId: number, nextSeen: boolean) => void;
};// 定义 ItemList 组件
const ItemList: React.FC<ItemListProps> = ({ artworks, onToggle }) => {return (<ul>{artworks.map((artwork) => (<li key={artwork.id}><label><inputtype="checkbox"checked={artwork.seen}onChange={(e) => {onToggle(artwork.id, e.target.checked);}}/>{artwork.title}</label></li>))}</ul>);
};export default App;
问题出在下面这段代码中:
const myNextList = [...myList];const artwork = myNextList.find(a => a.id === artworkId);if (artwork) {artwork.seen = nextSeen;setMyList(myNextList);}
虽然 myNextList 这个数组是新的,但是其内部的元素本身与原数组 myList 是相同的。因此,修改 artwork.seen,其实是在修改原始的 artwork 对象。而这个 artwork 对象也被 yourList 使用,这样就带来了 bug。这样的 bug 可能难以想到,但好在如果你避免直接修改 state,它们就会消失。
你可以使用 map 在没有 mutation 的前提下将一个旧的元素替换成更新的版本。
setMyList(myList.map(artwork => {if (artwork.id === artworkId) {// 创建包含变更的*新*对象return { ...artwork, seen: nextSeen };} else {// 没有变更return artwork;}
}));
此处的 … 是一个对象展开语法,被用来创建一个对象的拷贝.
通过这种方式,没有任何现有的 state 中的元素会被改变,bug 也就被修复了。
import React, { useState } from'react';// 定义 Artwork 类型,描述艺术作品的结构
type Artwork = {id: number;title: string;seen: boolean;
};// 定义初始艺术作品列表
const initialList: Artwork[] = [{ id: 0, title: 'Big Bellies', seen: false },{ id: 1, title: 'Lunar Landscape', seen: false },{ id: 2, title: 'Terracotta Army', seen: true },
];// 定义 BucketList 组件的类型
type BucketListProps = {};// BucketList 组件
const App: React.FC = (props: BucketListProps) => {const [myList, setMyList] = useState<Artwork[]>(initialList);const [yourList, setYourList] = useState<Artwork[]>(initialList);// 处理 myList 中艺术作品状态切换的函数const handleToggleMyList=(artworkId: number, nextSeen: boolean)=> {setMyList(myList.map((artwork: Artwork) => {if (artwork.id === artworkId) {// 创建包含变更的*新*对象return { ...artwork, seen: nextSeen };} else {// 没有变更return artwork;}}));}// 处理 yourList 中艺术作品状态切换的函数const handleToggleYourList=(artworkId: number, nextSeen: boolean)=> {setYourList(yourList.map((artwork: Artwork) => {if (artwork.id === artworkId) {// 创建包含变更的*新*对象return { ...artwork, seen: nextSeen };} else {// 没有变更return artwork;}}));}return (<><h1>艺术愿望清单</h1><h2>我想看的艺术清单:</h2><ItemListartworks={myList}onToggle={handleToggleMyList} /><h2>你想看的艺术清单:</h2><ItemListartworks={yourList}onToggle={handleToggleYourList} /></>);
}
export default App// 定义 ItemList 组件的 props 类型
type ItemListProps = {artworks: Artwork[];onToggle: (artworkId: number, nextSeen: boolean) => void;
};// ItemList 组件
const ItemList=(props: ItemListProps)=>{return (<ul>{props.artworks.map((artwork: Artwork) => (<li key={artwork.id}><label><inputtype="checkbox"checked={artwork.seen}onChange={(e: React.ChangeEvent<HTMLInputElement>) => {props.onToggle(artwork.id,e.target.checked);}}/>{artwork.title}</label></li>))}</ul>);
}
通常来讲,你应该只直接修改你刚刚创建的对象。如果你正在插入一个新的 artwork,你可以修改它,但是如果你想要改变的是 state 中已经存在的东西,你就需要先拷贝一份了。
使用 Immer 编写简洁的更新逻辑
在没有 mutation 的前提下更新嵌套数组可能会变得有点重复。就像对对象一样:
通常情况下,你应该不需要更新处于非常深层级的 state 。如果你有此类需求,你或许需要调整一下数据的结构,让数据变得扁平一些。
如果你不想改变 state 的数据结构,你也许会更喜欢使用 Immer ,它让你可以继续使用方便的,但会直接修改原值的语法,并负责为你生成拷贝值。
下面是我们用 Immer 来重写的艺术愿望清单的例子:
import React from "react";
import { useImmer } from 'use-immer';
// 定义 Artwork 类型
type Artwork = {id: number;title: string;seen: boolean;
};// 定义初始列表
const initialList: Artwork[] = [{ id: 0, title: 'Big Bellies', seen: false },{ id: 1, title: 'Lunar Landscape', seen: false },{ id: 2, title: 'Terracotta Army', seen: true },
];// 定义 BucketList 组件
const BucketList:React.FC=()=> {const [myList, updateMyList] = useImmer<Artwork[]>(initialList);const [yourList, updateYourList] = useImmer<Artwork[]>(initialList);// 处理我的列表中项目的切换const handleToggleMyList=(id: number, nextSeen: boolean)=> {updateMyList((draft) => {const artwork = draft.find((a) => a.id === id);if (artwork) {artwork.seen = nextSeen;}});}// 处理你的列表中项目的切换const handleToggleYourList=(artworkId: number, nextSeen: boolean)=> {updateYourList((draft) => {const artwork = draft.find((a) => a.id === artworkId);if (artwork) {artwork.seen = nextSeen;}});}return (<><h1>艺术愿望清单</h1><h2>我想看的艺术清单:</h2><ItemList artworks={myList} onToggle={handleToggleMyList} /><h2>你想看的艺术清单:</h2><ItemList artworks={yourList} onToggle={handleToggleYourList} /></>);
}export default BucketList// 定义 ItemList 组件的 props 类型
type ItemListProps = {artworks: Artwork[];onToggle: (id: number, nextSeen: boolean) => void;
};// 定义 ItemList 组件
const ItemList=({ artworks, onToggle }: ItemListProps) =>{return (<ul>{artworks.map((artwork: Artwork) => (<li key={artwork.id}><label><inputtype="checkbox"checked={artwork.seen}onChange={(e: React.ChangeEvent<HTMLInputElement>) => {onToggle(artwork.id, e.target.checked);}}/>{artwork.title}</label></li>))}</ul>);
}
请注意当使用 Immer 时,类似 artwork.seen = nextSeen 这种会产生 mutation 的语法不会再有任何问题了:
updateMyTodos(draft => {const artwork = draft.find(a => a.id === artworkId);artwork.seen = nextSeen;
});
这是因为你并不是在直接修改原始的 state,而是在修改 Immer 提供的一个特殊的 draft 对象。同理,你也可以为 draft 的内容使用 push() 和 pop() 这些会直接修改原值的方法。
在幕后,Immer 总是会根据你对 draft 的修改来从头开始构建下一个 state。这使得你的事件处理程序非常的简洁,同时也不会直接修改 state。
摘要
- 你可以把数组放入 state 中,但你不应该直接修改它。
- 不要直接修改数组,而是创建它的一份 新的 拷贝,然后使用新的数组来更新它的状态。
- 你可以使用 […arr, newItem] 这样的数组展开语法来向数组中添加元素。
- 你可以使用 filter() 和 map() 来创建一个经过过滤或者变换的数组。
- 你可以使用 Immer 来保持代码简洁。
相关文章:
React 更新 state 中的数组
更新 state 中的数组 数组是另外一种可以存储在 state 中的 JavaScript 对象,它虽然是可变的,但是却应该被视为不可变。同对象一样,当你想要更新存储于 state 中的数组时,你需要创建一个新的数组(或者创建一份已有数组…...
[250415] OpenAI 推出 GPT-4.1 系列,支持 1M token
目录 OpenAI 推出 GPT-4.1 系列 OpenAI 推出 GPT-4.1 系列 OpenAI 宣布,新一代 GPT-4.1 模型系列正式发布,包括 GPT-4.1, GPT-4.1 mini 和 GPT-4.1 nano 三款模型,该系列模型在各项性能指标上全面超越 GPT-4o 和 GPT-4o mini,尤其…...
分布式锁+秒杀异步优化
文章目录 问题思路setnx实现锁误删问题和解决方案Redis Lua脚本问题引出解决方案 setnx实现的问题Redission快速入门redission可重入锁原理 秒杀优化(异步优化)异步秒杀思路秒杀资格判断Redis消息队列 问题 比如我们两个机器都部署了我们项目,这里nginx使用轮询的方…...
数据服务化 VS 数据中台:战略演进中的价值重构
在企业数据战略的演进历程中,数据中台曾被视为解决数据孤岛的 “万能钥匙”,而数据服务化的兴起则标志着企业从 “数据资源囤积” 向 “数据价值释放” 的深刻转型。两者的核心差异不仅在于技术架构,更在于对数据资产的定位与使用理念的根本分…...
PL/SQL登录慢,程序连接Oracle 提示无法连接或无监听
PL/SQL登录慢,程序连接Oracle 提示无法连接或无监听 错误提示:ORA-12541: TNS: 无监听程序 的解决办法, 现象:PL/SQL登录慢,程序连接Oracle 提示无法连接或无监听 监听已经正常开起,但还是PL/SQL登录慢或…...
【JAVAFX】自定义FXML 文件存放的位置以及使用
情况 1:FXML 文件与调用类在同一个包中(推荐) 假设类 MainApp 的包是 com.example,且 FXML 文件放在 resources/com/example 下: 项目根目录 ├── src │ └── sample │ └── Main.java ├── src/s…...
DDoS(分布式拒绝服务)攻击
DDoS(分布式拒绝服务)攻击 这是一份全面系统的 DDoS(分布式拒绝服务攻击)知识总结,适合用于学习、报告、讲稿或者面试准备。内容涵盖定义、原理、危害、利用、工具、防护策略等。 一、什么是DDoS DDoS(Distributed Denial of Se…...
scikit-learn初探
KFold k交叉验证,k-1个作为训练集,剩下的作为测试集 split split(X, yNone, groupsNone)X: (n_samples, n_features)的矩阵,行数为n_samples,列数为n_features y:(n_samples,)为列向量,表示监…...
深入解析 sklearn 中的多种特征编码方式:功能、适用场景与选择建议
标题:深入解析 sklearn 中的多种特征编码方式:功能、适用场景与选择建议 摘要: 在机器学习中,特征编码是数据预处理的重要环节,直接影响模型的性能和效果。本文详细介绍了 sklearn 及其生态中(含第三方库…...
windows10 wsl2 安装ubuntu和docker
见 弃用Docker Desktop:在WSL2中玩转Docker之Docker Engine 部署与WSL入门-阿里云开发者社区 如果启动docker时报下面这个错, 那是因为systemctl没有启用 sudo systemctl start docker System has not been booted with systemd as init system (PID 1)…...
一文读懂WPF系列之依赖属性与附加属性
依赖属性与附加属性 依赖属性对比C#属性WPF依赖属性(Dependency Properties)优先级计算与值决策回调与验证机制WPF 自带的依赖属性自定义依赖属性 附加属性本质与定义与依赖属性的区别附加属性的典型应用场景自定义附加属性注意事项 属性…...
1×1卷积与GoogleNet
11卷积 卷积核的尺寸等于1的卷积核 11卷积有什么用 1. 通道混合与特征转换 背景:在卷积神经网络中,输入数据通常有多个通道(例如RGB图像有3个通道,经过卷积层后通道数可能会增加)。不同通道的特征图可能包含了不同的…...
Handsontable 表格组件的使用
文章目录 1. 安装 Handsontable2. 创建一个基本表格3. 主要配置3.1、 data 数据3.2、 columns 指定列配置 4. Handsontable 高级功能4.1、 添加排序4.2、 过滤数据4.3、 选中行高亮4.4、 只读单元格4.5、 校验数据 5. Handsontable 与 Vue结合6. 总结 Handsontable 是一个强大的…...
消息中间件面试题
前言 本章内容来自B站黑马程序员java大厂面试题与小林coding 如有侵权立即删除 博主学习笔记,如果有不对的地方,海涵。 如果这篇文章对你有帮助,可以点点关注,点点赞,谢谢你! 1.通用篇 1.1 什么是消息…...
数据结构与算法--1.判断数组中元素是否有重复
在C语言中,我们可以使用类似的方法来实现判断数组中是否有重复值的功能。由于C语言没有内置的哈希集合(如Python的set或C的unordered_set),我们需要自己实现一个简单的哈希表或使用其他方法。 方法一:暴力法ÿ…...
硬件工程师面试常见问题(1)
第一问:单片机上电后没有运转,首先要检查什么? (1)单片机供电是否正常& 电路焊接检查 用万用表测量对应引脚的供电电压,检查对不对。 (2)单片机复位是否释放 用万用表测量复位引…...
测试100问:web测试和APP测试的区别
哈喽,大家好,我是十二,那今天要为大家分享的是高频面试题:web测试和 App测试的区别。 从功能测试方面来讲,web测试和 App测试在测试的流程以及测试用例的设计上是没有区别的,那主要的区别包含以下三个方面&…...
Leetcode 3518. Smallest Palindromic Rearrangement II
Leetcode 3518. Smallest Palindromic Rearrangement II 1. 解题思路2. 代码实现 题目链接:Leetcode 3518. Smallest Palindromic Rearrangement II 1. 解题思路 这一题是题目Leetcode 3517. Smallest Palindromic Rearrangement I的升级版本,其主要的…...
Golang|订单相关
文章目录 秒杀写库策略确保缓存的订单数据不丢失 秒杀写库策略 在我们的抽奖函数中,抽中奖品、减库存成功返回给前端后就应该生成订单写入数据库 但是这里有问题,我们的抽奖函数是支持高并发的,并发量大的情况下mysql无法支持这么大并发量的写…...
Python+Playwright:编写自动化测试的避坑策略
PythonPlaywright:编写自动化测试的避坑策略 前言一、告别 time.sleep(),拥抱 Playwright 的智能等待二、选择健壮、面向用户的选择器,优先使用 data-testid三、严格管理环境与依赖,确保一致性四、分离测试数据与逻辑,…...
P12130 [蓝桥杯 2025 省 B] 移动距离
P12130 [蓝桥杯 2025 省 B] 移动距离 - 洛谷 题目描述 小明初始在二维平面的原点,他想前往坐标 (233, 666)。在移动过程中,他只能采用以下两种移动方式,并且这两种移动方式可以交替、不限次数地使用: 水平向右移动,…...
关于 人工智能(AI)发展简史 的详细梳理,按时间阶段划分,涵盖关键里程碑、技术突破、重要人物及挑战
以下是关于 人工智能(AI)发展简史 的详细梳理,按时间阶段划分,涵盖关键里程碑、技术突破、重要人物及挑战: 字数:约2500字 逻辑结构:时间线清晰,分阶段描述技术突破、关键事件与挑战…...
Formality:Bug记录
相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 本文记录博主在使用Synopsys的形式验证工具Formality中遇到的几个Bug。 Bug复现 情况一 // 例1 module dff (input clk, input d_in, output d_out …...
react-07React提交表单数据调用同一方法(高阶函数,函数柯里化概念)
1.高阶函数与函数柯里化概念 高阶函数:符合其中之一,那该函数为高阶函数 1.A函数,接收的参数是一个函数,那么A就是高阶函数2.A函数,调用的返回值是一个函数,那么A就是高阶函数 常见的高阶函数:…...
js ES6箭头函数的作用
前置知识 1、箭头函数语法简洁,相较于传统的函数表达式,箭头函数的语法更为简洁,尤其适用于简单的函数。 2、解决this取向问题,在传统函数中,this 的值取决于函数的调用方式,这可能会导致一些难以理解和调…...
淘宝商品数据高并发采集方案:API 接口限流机制与分布式调用实战
一、引言 在电商领域,对淘宝商品数据进行采集是一项常见且重要的任务。随着业务规模的扩大,高并发采集需求日益凸显。然而,淘宝 API 接口存在限流机制,为了高效且合规地完成数据采集,需要采用分布式调用的策略。本文将…...
python爬虫 线程,进程,协程
0x00 线程 线程是一个执行单位,是在一个进程里面的,是共享进程里面的提供的内存等资源,使用多个线程时和使用多个进程相比,多个线程使用的内存等资源较少。进程像一座“房子”(独立资源),线程是…...
《忘尘谷》音阶与调性解析
一、音高与音名的对应关系 根据搜索结果及音乐理论,结合《忘尘谷》的曲谱信息,其音阶与调性分析如下: 调性判定 原曲调性为 D调(原曲标注为D调),但曲谱编配时采用 C调指法,通过变调夹夹2品&…...
实验一 HDFS的Shell操作
一、实验目的 熟悉HDFS Shell的语法格式,完成对HDFS上文件和目录的一系列操作 二、实验要求 2.1 练习dfs常用的子命令 ① -ls ② -du ③ -mv ④ -cp ⑤ -rm ⑥ -put ⑦ -cat ⑧ -help ⑨ -mkdir ⑩ -get 2.2通过Shell脚本定时采集数据到HDFS 三、实…...
安装SQLServer管理工具
1.回到安装SQLServer的页面,选择安装SQLServer的管理工具2.跳转官网下载 3.安装SSMS 4.安装中5.安装成功以后打开软件,输入信息连接数据库 也可以在本机通过证书链接选择Windows身份验证,就不需要输入账号密码,但只能在安装sql…...
从JSON到SQL:基于业务场景的SQL生成器实战
引言 在数据驱动的业务场景中,将业务需求快速转化为SQL查询是常见需求。本文将通过一个轻量级的sql_json_to_sql函数,展示如何将JSON格式的查询描述转换为标准SQL语句,并结合实际业务场景验证其功能。 核心代码解析 1. 代码实现 def sql_j…...
记录鸿蒙应用上架应用未配置图标的前景图和后景图标准要求尺寸1024px*1024px和标准要求尺寸1024px*1024px
审核报错【①应用未配置图标的前景图和后景图,标准要求尺寸1024px*1024px且需下载HUAWEI DevEco Studio 5.0.5.315或以上版本进行图标再处理、②应用在展开状态下存在页面左边距过大的问题, 应用在展开状态下存在页面右边距过大的问题, 当前页面左边距: 504 px, 当前页面右边距…...
蓝桥杯嵌入式十六届赛前复习总结与准备
一.软件使用 赛点是没有网络的,要自己下载原件与数据包,这里给大家一个演示 在updater Settings这里设置文件存放位置,为了方便查找和提交文件,建议在桌面建立一个文件夹来存放。 把赛点的芯片包复制到创建的文件夹然后解压缩 之…...
了解一下Unity的RenderQueue
在Unity中,场景里的每个物体都需要通过渲染管线绘制到屏幕上。渲染管线处理光照、材质、纹理等信息,最终决定物体的显示效果。但当场景中有多个物体时,它们的绘制顺序会直接影响画面结果,尤其是在涉及透明物体或特效时。这时&…...
使用CS Roofline Toolkit测量带宽
使用CS Roofline Toolkit测量带宽 工程下载:使用CS Roofline Toolkit测量带宽-案例工程文件,也可以按照下面的说明使用git clone下载 目录 使用CS Roofline Toolkit测量带宽0、Roofline模型理解1、CS Roofline Toolkit下载1.1、设置代理1.2、git clone下…...
第三篇:深入 Framer Motion Variants:掌握组件动画编排的艺术
🎯 前言 在动态交互主导的现代前端开发中,优雅的动画效果已成为提升用户体验的重要元素。Framer Motion 的 Variants(动画变体)功能,通过状态化管理和动画编排能力,让复杂动效的实现变得前所未有的高效。本…...
狂神SQL学习笔记四:基本的命令行操作
注:所有语句用 ; 结尾,– 单行注释,/* 多行注释 */ 连接数据库 mysql -uroot -p123456 --连接数据库查看所有的数据库 切换数据库 查看数据库中所有的表 显示数据库中所有表的信息 创建一个数据库 退出连接...
架构思维:缓存层场景实战_读缓存(下)
文章目录 Pre业务场景缓存存储数据的时机与常见问题解决方案1. 缓存读取与存储逻辑2. 高并发下的缓存问题及解决方案3. 缓存预热(减少冷启动问题) 缓存更新策略(双写问题)1. 先更新缓存,再更新数据库(不推荐…...
软件架构设计:MVC、MVP、MVVM、RIA 四大风格优劣剖析
MVC、MVP、MVVM 和 RIA 都是软件架构中常见的设计风格,以下是对它们的详细介绍: 一、MVC 架构风格(Model - View - Controller) 1.简介:MVC 架构风格将软件应用程序分为三个核心部分,通过这种划分来分离不…...
java基础课程-springmvc课程
一. 回顾MVC: tomcat是servlet容器, servlet实现方式: xml中配置: 二. 回顾servlet: 三. SpringMvc学习 3.1 springMvc搭建和讲解: jar包引入spring-webmvc即可。 核心:DispatcherServlet Spring的web…...
NLP高频面试题(四十二)——RAG系统评估:方法、指标与实践指南
1. 引言:RAG系统概述与评估挑战 检索增强生成(Retrieval-Augmented Generation,简称 RAG)是近年来自然语言处理领域的一个重要进展。RAG系统在大型语言模型生成文本的过程中引入了外部检索模块,从外部知识库获取相关信息,以缓解纯生成模型可能出现的幻觉和知识盲点。通过…...
Flutter学习 滚动组件(1):ListView基本使用
目录 一、ListView构造方法1.1 常规方法1.2 ListView.builder1.3 ListView.separated 二、自定义ListView样式和布局:三、ListView性能优化:总结: 一、ListView构造方法 主要以下几种方法: 常规方法,直接使用默认的构…...
处理 Flutter 没有反应
现象 有以下几种 VS Code 中 Initializing the Flutter SDK. This may take a few minutes. 会一直维持在这个右下角提示窗, 但是无后续动作 Flutter CMD flutter_console.bat 执行 --version 或者 doctor [-v] 没有任何输出, 命令卡住 解决办法 参考官方说明 管理员身份…...
java面向对象06:封装
封装 该露的露,该藏的藏 我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用。 封装(数据的隐藏) 通常&#x…...
测试定时发布
测试定时发布 测试定时发布 测试定时发布 测试定时发布 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 新的改变 我…...
2025年4月15日 百度一面 面经
目录 1. 代理相关 从静态代理到动态代理 2. cglib可以代理被final修饰的类吗,为什么 3. JVM 体系结构 4. 垃圾回收算法 5. 什么是注解 如何使用 底层原理 6. synchronized和reentrantlock 7. 讲一下你项目中 redis的分布式锁 与java自带的锁有啥区别 8. post 请求和 ge…...
抖音卸载了ip属地还会更新吗?深度解析
近期,关于抖音IP属地显示功能的讨论热度持续攀升,许多用户提出疑问:如果卸载抖音APP,账号的IP属地还会继续更新吗?这一问题背后,既涉及平台算法的运作逻辑,也关乎用户对隐私保护的深层需求。本文…...
Spring IoC与DI详解:从Bean概念到手写实现
一、Spring Bean的概念与本质 1.1 什么是Bean? 在Spring框架中,Bean是一个由Spring IoC容器实例化、组装和管理的对象。Bean及其之间的依赖关系通过容器使用的配置元数据来定义。简单来说,Bean就是Spring容器管理的Java对象。简单来说&…...
【Qt】信号和槽
文章目录 信号和槽的概念信号和槽的使用链接信号和槽查看内置信号和槽通过 Qt Creator 生成信号槽代码 自定义信号和槽基本语法带参数的信号和槽 信号与槽的连接方式一对一一对多多对一 信号和槽的其他说明信号与槽的断开 使用 Lambda 表达式定义槽函数 信号和槽的概念 在 Qt 中…...
小事务架构下的业务完整性保障:基于业务处理记录与补偿机制的技术实现
随着微服务架构、事件驱动架构(EDA)和最终一致性理念的普及,传统的大事务管理方式被更细粒度的“小事务”所取代。在这种架构中,全局业务流程被拆解成多个局部事务节点,通过异步消息进行编排。这种解耦提高了可扩展性和…...