手撸React组件库前必须清楚的9个问题

news2025/2/23 23:37:07

1. 组件库文档问题

以前常用的组件库文档storybook,包括现在也有用dumi、vitepress做组件库文档等。storybook缺点不美观、webpack的热更新太慢,虽然新版本支持了vite提高了速度但还不算稳定。好在各种文档、mdx、测试等组件第三方工具很多集成进去能很方便我们开发。dumi、vitepress虽然颜值高,但是在针对ui组件库层面还有待加强,更偏向于做静态网站。这里讲下我认为文档工具最重要的几个方面,如果以后要自己实现一个文档框架或工具需要考虑的。

如何根据文件层级的依赖关系渲染ui

dumi、storybook等文档都会通过node监听文件是否有改动,一旦有改动那么我们去递归检查所有内部文件的依赖关系然后定义一个对象来描述,这里会涉及exclude、include等配置,无非一些读取配置的操作,在md文档中更可以解析内部的特定格式的字符串来实现配置, 然后通过js来操作网站左侧的sidebar的层级关系,同时配置路由跳转。

如何在写代码的时候自动生成文档

storybook可以通过写注释代码来解析获取配置。其实就是读取文件的字符串通过一些开源库解析成ast树然后判断位置然后用node的fs来写入md文件,然后将md转为html被网站识别展示。那么我们可以用babel写一个插件在运行时解析对应代码生成实时的文档数据。例如以下:

function sayHi (name: string, age: number, a: boolean) {console.log(`hi, ${name}`);return `hi, ${name}`;
} 

转换为 ->

##sayHi
say 你好
name: 名字
>sayHi(name: string, age: number, a: boolean)
#### Parameters:
-name(string)
-age(number)
-a(boolean) 

下面的代码简单看下就行,visitor类似在每个节点套了个壳去访问而不影响原来的节点。里面定义了解析的各个节点,然后通过path的api或者各种terver的包批量修改。你只要知道下面做了两件事情,一个是生成类似vue的描述虚拟节点的集合,通过这个生成md的模板文件。

const autoDocsPlugin = declare((api, options, dirname) => {api.assertVersion(7);return {pre (file) {file.set('docs', []);},visitor: {FunctionDeclaration (path, state) {const docs = state.file.get('docs')docs.push({type: 'function',name: path.get('id').toString(),params: path.get('params').map(paramsPath => {return {name: paramsPath.toString(),type: resolveType(paramsPath.getTypeAnnotation()) //get type}}),return: resolveType(path.get('returnType').getTypeAnnotation()),doc: path.node.leadingComments && parseComment(path.node.leadingComments[0].value)})state.file.set('docs', docs)},ClassDeclaration (path, state) {const docs = state.file.get('docs');const classInfo = {type: 'class',name: path.get('id').toString(),constructorInfo: {},methodsInfo: [],propertiesInfo: []};if (path.node.leadingComments) {classInfo.doc = parseComment(path.node.leadingComments[0].value);}path.traverse({ClassProperty (path) {classInfo.propertiesInfo.push({name: path.get('key').toString(),type: resolveType(path.getTypeAnnotation()),doc: [path.node.leadingComments, path.node.trailingComments].filter(Boolean).map(comment => {return parseComment(comment.value);}).filter(Boolean)})},ClassMethod (path) {if (path.node.kind === 'constructor') {classInfo.constructorInfo = {params: path.get('params').map(paramPath => {return {name: paramPath.toString(),type: resolveType(paramPath.getTypeAnnotation()),doc: parseComment(path.node.leadingComments[0].value)}})}} else {classInfo.methodsInfo.push({name: path.get('key').toString(),doc: parseComment(path.node.leadingComments[0].value),params: path.get('params').map(paramPath => {return {name: paramPath.toString(),type: resolveType(paramPath.getTypeAnnotation())}}),return: resolveType(path.getTypeAnnotation())})}}});docs.push(classInfo);state.file.set('docs', docs);}},post (file) {const docs = file.get('docs');const res = generate(docs, options.format);fse.ensureDirSync(options.outputDir);fse.writeFileSync(path.join(options.outputDir, 'docs' + res.ext), res.content);}}
}) 

2. react开发的组件要注意什么

export const verticalMenu: ComponentStory<typeof Menu> = () => (<><PageHeader title="垂直"></PageHeader><Menu mode="vertical" onSelect={(index) => console.log(index)}><MenuItem>标签1</MenuItem><MenuItem>标签2</MenuItem><MenuItem>标签3</MenuItem><SubMenu title="标签4"><MenuItem>标签1</MenuItem><MenuItem>标签2</MenuItem><MenuItem>标签3</MenuItem></SubMenu></Menu></>
); 

在antd中我们经常能看到各种各样的嵌套, 比如这里的SubMenu组件的title属性,很显然Menu父组件要遍历children,找到对应的tag,然后将props传入控制对应的子组件。或者你也可以用Provider和Consumer。这样的好处更多的标签可以帮助我们理解,对props做分层控制。下面举个例子, 这里要注意下Children和cloneElement这两个核心api的使用。

const renderChildren = () => {return React.Children.map(children, (child, index) => {const childElement = child as React.FunctionComponentElement<IMenuItemProps>;const { displayName } = childElement.type;if (displayName === 'MenuItem' || displayName === 'SubMenu') {return React.cloneElement(childElement, {index: index.toString(),});} else {console.error('Warning: Menu has a child which is not a MenuItem component');}});}; 

在一些常见你也可以用类的写法,在antd中通过hoc高阶组件来实现比如Layout组件。

function generator({ suffixCls, tagName, displayName }: GeneratorProps) {return (BasicComponent: any) => {const Adapter = React.forwardRef<HTMLElement, BasicProps>((props, ref) => {return <BasicComponent ref={ref} prefixCls={suffixCls} tagName={tagName} {...props} />;});if (process.env.NODE_ENV !== 'production') {Adapter.displayName = displayName;}return Adapter;};
}
const Header = generator({suffixCls: "speed-header",tagName: "header",displayName: "Header",
})(Basic);

const Footer = generator({suffixCls: "speed-footer",tagName: "footer",displayName: "Footer",
})(Basic); 

在一些弹窗组件你可以看到一个回调就可以执行组件的调用,显示隐藏、控制时间、结束的回调等。在vue也一样通过install函数传入Vue实例,然后挂载对应的sfc文件,$mount挂载对应dom。生成的组件实例赋值给Vue的原型对象,我们可以调用对应的方法来控制全局组件。

function createNotification() {const div = document.createElement('div')document.body.appendChild(div)const notification = ReactDOM.render(<Toast />, div)return {addNotice(notice) {return notification.addNotice(notice)},destroy() {ReactDOM.unmountComponentAtNode(div)document.body.removeChild(div)}}
} 

在一些Table表单组件、日历等组件是需要很大的自定义余地的,不能仅仅靠children解析来实现,那么你要考虑props传入对应的JSX的节点,例如下面:

const customTableTpl: ComponentStory<typeof Table> = args => {const [dataSource, setDataSource] = React.useState(defaultDataSource);const [paginationParams, setPaginationParams] = React.useState(defaultpaginationParams);const [isModalVisible, setIsModalVisible] = React.useState(false);/** input 单元格 */const hanldeBlur = e => {let val = e.target.value;if (val.trim()) {let cloneData = [...dataSource];cloneData.forEach((item, index) => {if (item.key === source.key) {cloneData[index].name = val;}});setDataSource(cloneData);}};/** 编辑操作 单元格 */const showModal = () => {setIsModalVisible(true);};const handleConfirm = () => {setIsModalVisible(false);};const handleCancel = () => {setIsModalVisible(false);};const handleDelete = key => {let result = dataSource.filter(item => item.key !== key);setDataSource(result);setPaginationParams({...paginationParams,total: paginationParams.total - 1,});};const columns = [{title: 'ID',dataIndex: 'key',key: 'key',},{title: '姓名',dataIndex: 'name',key: 'name',render: source => {if (!source) return;return <Input placeholder={source.name} onBlur={hanldeBlur} style={{ width: '200px' }} blurClear></Input>;},},{title: '年龄',dataIndex: 'age',key: 'age',render: source => {if (!source) return;return <Button>{source.age}</Button>;},},{title: '住址',dataIndex: 'address',key: 'address',},{title: '操作',dataIndex: 'edit',key: 'edit',render: source => {return (<><Modal visible={isModalVisible} onConfirm={handleConfirm} onCancel={handleCancel}><h2>我是{source.name}</h2></Modal><Space><Button btnType='primary' onClick={showModal}>编辑</Button><Button btnType='danger' onClick={() => handleDelete(source.key)}>删除</Button></Space></>);},},];return (<><PageHeader title='自定义表格' /><Table dataSource={dataSource} columns={columns} paginationParams={paginationParams}></Table></>);
}; 

在antd使用过程中你可能会遇到<From.FromItem /> 其实也就是导出的模块对象Form内部定义个属性,Form.Item = FormItem;

3. 组件库的主题怎么做、样式用什么方案

antd5以前,原来用了less的方案在网站的加载前置请求link,动态获取css的配置,那么对于设计师修改方案的主题可能是一种折磨,你不能定位到组件级别的ui。less在某种层面导致对样式的可控性有很大的局限。less和scss一样可以通过js控制对应的样式变量,那么这个变量在scss或者less中可以被控制

@mixin theme-aware($key, $color) {
	@each $theme-name, $theme-color in $themes {
		.theme-#{"" + $theme-name} & {
			#{$key}: map-get(map-get($themes, $theme-name), $color);
		}
	}
}

/**
 * 这里定义了map,对应多个主题面板,通过mixin函数获取对应的map的值,在对应的标签内使用@include 使用覆盖函数
 */
$themes: (
	light: (
		global-background: #fff,
		global-color: #37474f,
	),
	dark: (
		global-background: #37474f,
		global-color: #fff,
	),
	blue: (
		global-background: #10618a,
		global-color: #fff,
	),
);
body {@include theme-aware("background", "global-background");@include theme-aware("color", "global-color");
}

button {
	.theme-light & {
		background: #f30000;
	}
	.theme-dark & {
		background: #ee0fd0;
	}
}
//这里通过js控制const changeTheme = (theme: string) => {document.documentElement.className = "";document.documentElement.classList.add(`theme-${theme}`);}; 

这种方案局限在只能控制全局的,无法细粒度的修改,就算修改也是很麻烦的一件事,同时维护也挺累的,不断的@include。

社区有很多方案,比如cssmodule、原子化的css、运行时的cssinjs、编译时的cssinjs。都各有利弊。那么我们先来看看最近antd5做了什么吧,用了自研的运行时的cssinjs方案,好处是可控性更强了,在组件级别的更新相比emotion等方案有更好的性能,token的控制hash可以让组件的样式被缓存。我这里写一写原来的老版本antd的大致的样式思路,在antd的会尽量很少用style挂载样式,尽量通过classNames这个库做的名更新。同时为了隔离,通过全局的变量方法在各个组件内获取类名的前缀来隔离,下面做个简单的演示。

 //....const { getPrefixCls } = useContext(ConfigContext);let prefixCls = getPrefixCls("notification", customizePrefixCls);const cls = classNames(prefixCls, className, {[`${prefixCls}-tc`]: position === 'tc',[`${prefixCls}-tl`]: position === 'tl',[`${prefixCls}-tr`]: position === 'tr',[`${prefixCls}-bc`]: position === 'bc',[`${prefixCls}-bl`]: position === 'bl',[`${prefixCls}-br`]: position === 'br',});return (< >{notices.map((notice, index) => {return (<div className={cls} style={getStyles()} key={index}<div className={`${prefixCls}-card`}>{iconJSX ? iconJSX : <Icon icon={solid('check')} size='2x' color='#18ce55'></Icon>}<div className={`${prefixCls}-warp`}><h4>{notice.title}</h4><p className={`${prefixCls}-content`}>{notice.content}</p></div></div></div>)})}</>); 

如果自己实现,那么个人推荐用cssinjs的社区方案,更好的兼容、变量的共享、隔离化、代码提示、主题定制更灵活可控。缺点就是hash序列化隔离的时候性能微微的损耗,打包的体积大些。

4. 组件库用什么打包

组件库的代码调试的时候,其实可以考虑vite做项目的构建,更快的单模块请求更新开发体验还是很棒的。由于vite需要esm模块,用其他模块会有问题,如果你的项目及其依赖都是esm那么可以考虑vite打包, vite也是可以使用rollup生态的。rollup更适合小库的打包,静态分析很不错,treeshaking给力。打包上手及其简单。gulp相对适合中大项目,出色的串行和并行的工作流,更规范和可控。webpack也可以但是有点笨重了,就是没有gulp轻量和相对简单。

//...
function compileScripts (babelEnv, destDir) {const { scripts } = paths;process.env.BABEL_ENV = babelEnv;return gulp.src(scripts).pipe(babel({"presets": [["@babel/preset-react",{ "runtime": "automatic", "importSource": "@emotion/react" }]],"plugins": ["@emotion/babel-plugin"]})) // 使用gulp-babel处理.pipe(gulp.dest(destDir));
}
/**
 * 编译cjs
 */
function compileCJS () {const { dest } = paths;return compileScripts('cjs', dest.lib);
}

/**
 * 编译esm
 */
function compileESM () {const { dest } = paths;return compileScripts('esm', dest.esm);
}

//...
const buildScripts = gulp.series(compileCJS, compileESM);

const buildSDK = gulp.series(compressionSdkCJS, compressionSdkESM);

const buildStyle = gulp.parallel(buildBasicStylesMin, buildBasicStylesMax, buildSpeedStylesMax, buildSpeedStylesMin, buildComponentStyle);

// 整体并行执行任务
const build = gulp.parallel(buildTypes, buildScripts, buildSDK, buildStyle); 

5. typescript在组件库中如何用

这里简单展示下大致的tsx文件的组件的ts类型定义。还有一些其他要注意的比如类型断言as,在ref获取dom节点的去操作的时候ts可能会提示你可能undefined,这个时候你可以很放心的告诉他是一定存在的。

export interface ResultProps {/** 样式命名隔离 */prefixCls?: string;/** 组件子节点 */children?: ReactNode;/** 容器内联样式 */style?: React.CSSProperties;/** 组件类名 */className?: string;/** 操作区 */extra?: React.ReactNode;/** 自定义icon */icon?: React.ReactNode;/** 改变的回调 */onChange: (e: ChangeEvent<HTMLInputElement>) => void;/** 失去焦点的回调 */onBlur: (e: ChangeEvent<HTMLInputElement>) => void;/** 状态 */status?: StatusType;/** 主标题 */title?: React.ReactNode;/** 副标题 */subTitle?: React.ReactNode;
}
const Result: FC<ResultProps> = props => {const {children,className,prefixCls: customizePrefixCls,style,title,subTitle,icon,extra,status = 'success',} = props;//...return (<div className={cls} style={style}></div>);
}; 

6. 单元测试怎么做

我们会在每个文件夹都定义一个__tests__文件夹,那么你要注意这个文件夹不能被gulp打包进入。 默认我们使用的是@testing-library/react、'@testing-library/jest-dom, 操作跟jest很相似,一个是react的轻量的测试库、一个是jest的dom的一些api集成。当然还有其他库可以使用,下面我简单用了例子

要特别注意组件库可能并不适合TDD开发,测试驱动开发。我们可以在写完组件后,针对关键功能做测试用例,更关注结果,里面繁杂的处理过程一定要忽视。

我们一般都常用的就是快照测试、样式的判断、dom的判断、一些异步延迟的场景判断。

import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { Calendar } from '../../index';

describe('test Calendar component', () => {it('should render base Calendar', () => {const { asFragment } = render(<Calendar></Calendar>);expect(asFragment()).toMatchSnapshot();});it('callback is successfully after click event', () => {const handleSelect = jest.fn();const handlePanelChange = jest.fn();render(<Calendar onSelect={handleSelect} onPanelChange={handlePanelChange}></Calendar>);fireEvent.click(screen.getByText('15'));fireEvent.click(screen.getByText('>'));fireEvent.click(screen.getByText('<'));expect(handlePanelChange).toHaveBeenCalledTimes(2);expect(handleSelect).toHaveBeenCalledTimes(2);});it('should find classnames', () => {const { container } = render(<Calendar></Calendar>);expect(container.querySelector('.speed-calendar')).toBeTruthy();expect(container.querySelector('.speed-calendar-picker')).toBeTruthy();expect(container.querySelector('.speed-calendar-arrow')).toBeTruthy();expect(container.querySelector('.speed-calendar-detail')).toBeTruthy();expect(container.querySelector('.speed-calendar-week')).toBeTruthy();expect(container.querySelector('.speed-calendar-day')).toBeTruthy();});it('custom data in the Calendar component', () => {const Demo = () => {let date = new Date();const customData = [{day: 1,month: date.getMonth() + 1,year: date.getFullYear(),getNode: () => {return (<div style={{ display: 'flex' }}><div>吃饭</div><divstyle={{background: 'red',width: '5px',height: '5px',borderRadius: '50%',position: 'relative',left: '5px',}}></div></div>);},},{day: 2,month: date.getMonth() + 1,year: date.getFullYear(),getNode: () => {return <div>睡觉</div>;},},];return <Calendar customData={customData}></Calendar>;};const { asFragment } = render(<Demo></Demo>);expect(asFragment()).toMatchSnapshot();expect(screen.getByText('吃饭')).toBeInTheDocument();expect(screen.getByText('睡觉')).toBeInTheDocument();});
}); 

7. 组件库如何本地调试

这里要注意你的package的配置

//package.json {"types": "dist/types/index.js","main": "dist/lib/components/index.js","module": "dist/esm/components/index.js","files": ["dist"],} 

1. npm run link

最快速的一般通过 npm run link 可以找到全局的npm文件夹内找到,这个时候npm i xxx -g就可以了。

2. Verdaccio

通过搭建私有仓库npm,需要在自己的服务器上部署Verdaccio, npm生成新的镜像源,然后输入用户名和密码连接成功后,nrm use <自己的镜像地址名称>

3. package配置

在你要引入的项目的package.json中配置加个link配置个相对路径,个人认为这个是最简单的

devDependencies: {"yourPackage": "link:../../dist"//注意这里的link哈!
} 

8. 开发中如何提高效率,减少重复代码

如果你真的写过组件库那么你会感到组件库是一个很繁琐,倒不是说没有技术含量。每次当你创建文件夹,复制粘贴其他组件的重复代码其实是一个很痛苦的问题。所以一定需要这么一个模板,我输入一行命令行直接给我生成组件文件夹、组件tsx、基础样式、文档说明、基础测试用例。我敢说真能减少30%的工作量了。腾出的时间去做些别的有技术含量的东西他不香么?

ejs模板解析

当然我目前实现上还是有问题,我用的ejs模板做解析,ejs模板只是处理字符串而已做个变量的替换只要更换文件后缀名就可以了,注意这里要去掉prettier或者vscode的代码格式约束,通过noode的argv来获取的组件参数名来控制创建的文件夹,那么有了这个组件名,我们可以将字符串解析并替换。其他的无非node的读写到对应文件而已。注意ejs好像对css的语法无法做解析。读者可以考虑用vue cli内部使用的库来实现模板解析。这里暂时用ejs演示具体用法.

/**
 * @description 命令行直接生成组件开发模板
 * 
 * 在命令行输入 node src/generator.ts Test注意要大写
 */

const fs = require('fs')
const path = require('path')
let ejs = require('ejs')
let prettier = require('prettier')

const componentName = process.argv[2] || 'TestComponent'
const lowerName = componentName.toLowerCase()

const templatePath = path.join(__dirname, 'components', 'Template') //模板路径
const toPath = path.join(__dirname, 'components', componentName) //生成路径
const stylePath = path.join(__dirname, 'styles', 'componentStyle') //生成路径

console.log(`当前正在生成${process.argv[2]}组件模板.....`);

function copyDir (srcDir, desDir) {fs.readdir(srcDir, { withFileTypes: true }, (err, files) => {if (fs.existsSync(desDir)) {console.log("无法覆盖原文件, 请删除已有文件夹");return} else {fs.mkdirSync(desDir);}for (const file of files) {//判断是否为文件夹if (file.isDirectory()) {const dirS = path.resolve(srcDir, file.name);const dirD = path.resolve(desDir, file.name);//判断是否存在dirD文件夹if (!fs.existsSync(dirD)) {fs.mkdir(dirD, (err) => {if (err) console.log(err);});}copyDir(dirS, dirD);} else {function handleOutputFilename (name) {if (name === 'template.stories.ejs') {return `${lowerName}.stories.tsx`}if (name === 'Template.ejs') {return `${componentName}.tsx`}if (name === 'index.ejs') {return `index.ts`}if (name === 'style.ejs') {return `${lowerName}.scss`}}const srcFile = path.resolve(srcDir, file.name);let desFile//输出的路径let desName = handleOutputFilename(file.name) //输出的文件名//如果是样式路径if (desName.includes('scss')) {desFile = path.resolve(stylePath, desName);} else {//如果是文件路径desFile = path.resolve(desDir, desName);}fs.copyFileSync(srcFile, desFile);//传入ejs渲染const template = fs.readFileSync(desFile, {encoding: 'utf-8'})const code = ejs.render(template, { name: componentName, lowname: lowerName })let newCode = prettier.format(code, {parser: "babel-ts"}); //格式化fs.writeFileSync(desFile, newCode)}}})
}

copyDir(templatePath, toPath) 

9. 如何实现按需加载的组件库

我们知道在以前按需加载通过babel-import-plugin引入,原理也很简单就是babel解析ast做个模块导入的转换。我去尝试使用感觉有问题,可能跟我组件库的文件目录不是很契合。哈哈,所以自己搞了一个类似的babel插件。

有人可能会说现在esm的tree-shaking不是已经可以了么,但其实在副作用的函数调用,传参是个对象那么你打包后你可以看下还是会被引入。更不用说其他特殊的副作用。当然rollup内部的一些算法能解决这些问题。所以esm你可以不考虑这个问题,那么其他模块比如common、cmd、amd我们还是考虑下吧。同时你要打包出来的文件要拆分好,css、js不同模块文件。下面我做个此插件的简单原理实现:

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

day37 动态规划 | 738、单调递增的数字 714、买卖股票的最佳时机含手续费 968、监控二叉树

题目 738、单调递增的数字 给定一个非负整数 N&#xff0c;找出小于或等于 N 的最大的整数&#xff0c;同时这个整数需要满足其各个位数上的数字是单调递增。 &#xff08;当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。&a…

什么是项目沟通管理? 借助系统软件管理项目沟通

在如今竞争激烈的市场环境中&#xff0c;很多企业内部往往有多个项目同时进行着&#xff0c;不同类别的项目需要项目负责人实时跟进&#xff0c;而成功的项目是离不开项目人员之间的日常互相沟通进行。只有良好的沟通&#xff0c;项目经理才能够获取到足够的信息&#xff0c;可…

国产真无线蓝牙耳机哪个好?国产半入耳蓝牙耳机推荐

近几年&#xff0c;生活中随处可见的有戴蓝牙耳机的人&#xff0c;而蓝牙耳机也因为使用更便捷、功能更先进受到了不少用户的喜爱。蓝牙耳机按照佩戴方式来划分&#xff0c;可以有入耳式、半入耳式、头戴式等。在此&#xff0c;我来给大家推荐几款国产半入耳蓝牙耳机&#xff0…

LeetCode 606.根据二叉树创建字符串,102.二叉树的层序遍历和牛客 二叉搜索树与双向链表

文章目录1. 根据二叉树创建字符串2. 二叉树的层序遍历3. 二叉搜索树与双向链表1. 根据二叉树创建字符串 难度 简单 题目链接 解题思路&#xff1a; 这里的意思就是&#xff1a;用前序遍历遍历这颗树。然后左子树和右子树分别在一个括号里。括号里的规则是&#xff1a; 1.左右都…

W800|WIFI|CDK|W80X SDK v1.00.10|官方demo|学习(2):t-connect

W800 SDK代码及相关文档获取地址&#xff1a; https://www.winnermicro.com/html/1/156/158/558.html 1、W800 SDK v1.00.10更新内容&#xff1a; 1. 驱动更新 1&#xff09;提供模组ADC校准功能接口 2&#xff09;修复PSRAM IO复用不完整问题 3&#xff09;Flash驱动修改不再…

CSDN每日一练非降序数组 C语言/C++

题目名称&#xff1a;非降序数组 时间限制&#xff1a;1000ms内存限制&#xff1a;256M 题目描述 写一个函数&#xff0c;传入两个非降序的整数数组&#xff08;A, B&#xff09;&#xff0c;将 A, B 合并成一个非降序数组 C&#xff0c;返回 C&#xff08;不要使用内置 sort 函…

新项目分析

1&#xff1a;数据类型处理 # sep‘\s‘ 这是正则表达式&#xff0c;通过一定规则的表达式来匹配字符串用的 \s 表示空白字符&#xff0c;包括但不限于空格、回车(\r)、换行(\n)、tab或者叫水平制表符(\t)等&#xff0c;这个根据编码格式不同代表的含义也不一样&#xff0c;感…

一文讲解系统调用与函数调用有什么区别?

作为程序员你肯定写过无数的函数&#xff0c;假设有这样两个函数&#xff1a; void funcB() { }void funcA() {funcB(); } 函数之间是可以相互调用的&#xff0c;这很简单很happy有没有。 要知道是代码、是函数就可以相互调用&#xff0c;不管你用什么语言写的。 假设funcB…

2023/02/21 事件循环-eventloop 宏任务 微任务 讲解

1 JS是单线程 js是单线程的。也就是说&#xff0c;同一个时间只能做一件事。作为浏览器脚本语言&#xff0c;与它的用途有关。JavaScript的主要用途是和用户互动&#xff0c;以及操作DOM&#xff0c;这决定了它只能是单线程。 js是单线程的。也就是说&#xff0c;同一个时间只…

如何使用 API 工具做 Websocket 测试

在 API 测试中&#xff0c;对 Websocket 协议的支持呼声越来越高&#xff0c;今天给大家推荐一款 开源的 API 管理工具——Postcat&#xff0c;以及教教大家&#xff0c;如何利用 API 管理工具做 Websocket 测试。 在线 Demo 链接&#xff1a;Postcat - Open Source API Ecosys…

17 个短代码,检验你 Python 基本功

Python 是一门非常优美的语言&#xff0c;其简洁易用令人不得不感概人生苦短。在本文中&#xff0c;蛋糕将带大家回顾 17个非常有用的 Python 技巧&#xff0c;例如查找、分割和合并列表等。这 17 个技巧都非常简单&#xff0c;但它们都很常用且能激发不一样的思路。 人生苦短&…

来一波骚操作,Java内存模型

文章整理自 博学谷狂野架构师 什么是JMM 并发编程领域的关键问题 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息。在编程中&#xff0c;线程之间的通信机制有两种&#xff0c;共享内存和消息传递。 ​ 在共享内存的并发模型里&#xff0c;线程之间共享程序的公共…

项目管理从需求管理开始--不懂需求管理还敢带项目?

分析报告指出&#xff0c;多达76%的项目失败是因为差劲的需求管理&#xff0c;这个是项目失败的最主要原因&#xff0c;比技术、进度失控或者混乱的变更管理还要关键。很多PMO和PM却没有把需求管理重视起来&#xff0c;甚至认为这只是产品经理的事情&#xff0c;自己只做交付即…

Spark RDD及内存计算

文章目录Spark RDD及内存计算性能调优RDD 的核心特征和属性内存计算Spark RDD及内存计算 性能调优 性能调优的本质&#xff1a; 性能调优不是一锤子买卖&#xff0c;补齐一个短板&#xff0c;其他板子可能会成为新的短板。因此&#xff0c;它是 一个动态、持续不断的过程&…

第51篇-某彩网登录参数分析-webpack【2023-02-21】

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析一、前言 今天我们看一个webpack的网站 aHR0cHM6Ly8xMGNhaTUwMC5jYy9sb2dpbg==二、网站分析 首先…

Springboot 全局异常处理类

全局异常处理 在开发过程中&#xff0c;不管是Dao、Servie、Controller,层都有可能发生异常&#xff0c;对于异常处理&#xff0c;通常是try&#xff0d;catch或者直接throw&#xff0c;这会让try&#xff0d;catch的代码在代码中任意出现&#xff0c;系统的代码耦合度高&…

Elasticsearch7.8.0版本进阶——数据更新流程

目录一、数据更新流程概述二、数据更新流程步骤2.1、数据更新流程图解2.2、部分更新一个文档的步骤2.3、数据更新流程注意事项一、数据更新流程概述 部分更新一个文档需要结合数据读取和写入流程。 二、数据更新流程步骤 2.1、数据更新流程图解 2.2、部分更新一个文档的步骤…

经典文献阅读之--MSC-VO(曼哈顿和结构约束VIO)

0. 简介 对于视觉里程计而言&#xff0c;在面对低纹理场景时&#xff0c;往往会出现退化的问题&#xff0c;究其原因是人造环境往往很难找到足够数量的点特征。而其他的几何视觉线索则是比较容易找到&#xff0c;在城市等场景中&#xff0c;通常表现出结构规律&#xff0c;如平…

程序中的日期使用问题-格式转化:SimpleDateFormat、org.apache.commons.lang3.time.DateUtils

前言 日期使用问题主要是格式转换的问题 场景&#xff1a;通过excel导入数据&#xff0c;其中一个字段为出生日期&#xff0c;需要对字段值进行合法性校验 博客地址&#xff1a;芒果橙的个人博客 【http://mangocheng.com】 一、个人浅谈日期 时间日期作为一个基础的标识和维度…

Windows下使用git提交代码到gitee

Windows下使用git提交代码到gitee 1、下载安装git 2、或者参考Gitee初始仓库里面的配置代码 上传本地代码到gitee上 1、 在本要上传的代码文件夹&#xff0c;右键 选择&#xff1a;Git Bash Here 2、执行&#xff1a;git init 3、然后执行&#xff1a;git add 文件名。&…