手撸React组件库前必须清楚的9个问题
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的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享
相关文章:

手撸React组件库前必须清楚的9个问题
1. 组件库文档问题 以前常用的组件库文档storybook,包括现在也有用dumi、vitepress做组件库文档等。storybook缺点不美观、webpack的热更新太慢,虽然新版本支持了vite提高了速度但还不算稳定。好在各种文档、mdx、测试等组件第三方工具很多集成进去能很…...

试用国内及国外AI绘图软件后的总结
最近AI很火,所以这几天抱着试试看的角度试用了多款AI绘图软件,大概测试了市面上的3款工具吧,3款国外的,1款国内的。因为有对比,波哥也不是专业的评测机构出身,所以这些比对无论是从角度,还是从对…...

DJI 无人机 Onboard SDK ROS 功能包demo运行
DJI 无人机 Onboard SDK ROS 功能包demo运行demo功能准备测试环境运行 dji sdk 节点运行 demo 节点自动飞行任务航点自动飞行兴趣点环绕自动飞行飞行控制本地坐标位置控制搭建好 Onboard SDK ROS 的开发环境后,功能包自身具备一些写好的demo功能案例 dji sdk 的节点…...

揭开JavaWeb中Cookie与Session的神秘面纱
文章目录1,会话跟踪技术的概述2,Cookie2.1 Cookie的基本使用2.2 Cookie的原理分析2.3 Cookie的使用细节2.3.1 Cookie的存活时间2.3.2 Cookie存储中文3,Session3.1 Session的基本使用3.2 Session的原理分析3.3 Session的使用细节3.3.1 Session…...

2023-02-20 Qt 5.13.1 + OpenCV 4.5.4环境编译
引言 OpenCV图像处理在Qt中编译记录。 之前一直是在Python中使用OpenCV,Python中使用某些模块使用pip工具很容易将对应的模块安装在系统中。根据项目需求项目都要转移在国产化中使用,为了适应国产化需求,将代码转移到Qt开发环境中,…...

波次分拣系统
一、系统架构: v1.2基站软件管理系统仓库标签v1.4仓库标签二、系统简介: 标签系统主要由标签服务器,基站,电子标签前三部分组成,操作界面借助于京东仓库已有的作业电脑来实现,标签服务器与WMS进行数据对接。…...

【Servlet篇】Request请求转发详细解读
文章目录1. 前言2. 实战案例3. 特点1. 前言 请求转发是一种在服务器内部的资源跳转方式,如图: 上图的大致过程为,浏览器发送请求给服务器,服务器中 a 资源接收到请求,资源 a 处理完请求后将请求发送给资源 bÿ…...

vector
目录 vector的成员函数: at: 编辑 size: assign:赋值 insert find? erase swap shrink_to_fit 编辑 vector的模拟实现: vector的框架: 构造函数: size和capacity r…...

LeetCode——104. 二叉树的最大深度
一、题目 给定一个二叉树,找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/maximum…...

漫画 | Python是一门烂语言?
这个电脑的主人是个程序员,他相继学习了C、Java、Python、Go, 但是似乎总是停留在Hello World的水平。 每天晚上,夜深人静的时候,这些Hello World程序都会热火朝天地聊天但是,这一天发生了可怕的事情随着各个Hello wor…...

2023.2 新方案 java代码混淆 java加密 字符串加密
Java字节码可以反编译,特别是创业公司,很好的项目很容易被别人破解反编译,造成很严重的损失,所以本混淆方案能很好的保护源码,而且在不断迭代,增强混淆效果,异常问题处理,达到保护项目的目的: 本次升级包括: 2023年02年19日 : ht-confusion-project-1.8…...

Swift 周报 第二十三期
前言 本期是 Swift 编辑组自主整理周报的第十四期,每个模块已初步成型。各位读者如果有好的提议,欢迎在文末留言。 欢迎投稿或推荐内容。目前计划每两周周一发布,欢迎志同道合的朋友一起加入周报整理。 勇敢是即便知道好结局不会每每降临在…...
android系统屏幕旋转角度,应用界面横竖屏,设备旋转角度,三者的区别以及使用。
注意区分以下三种概念的区别!!!。以及使用这三种方式判断横竖屏的方式。系统屏幕旋转角度fun getSystemRotation(): Int {val angle (getSystemService(WINDOW_SERVICE) as WindowManager).defaultDisplay.rotation//系统屏幕旋转的角度值re…...

【华为云-开发者专属集市】DevCloud+ECS、MySQL搭建WordPress
文章目录AppBazaar官网选择与购买项目项目概况操作过程购买DevCloud服务创建项目添加制品库应用部署购买ECS添加部署模板并执行任务故障排除安装及访问WordPress登录网站管理后台访问网站完善部署模板资源释放使用总结AppBazaar官网 首先,我们来到AppBazaar的官网&…...

Milvus 群星闪耀时|又一个小目标达成 :社区正式突破 15,000 星!
如果把 Milvus 看作开源世界中的一束微光,那用户便是无垠宇宙中点点闪烁的星光。用户每一次点亮 star 之时,Milvus 就会迸发出更加耀眼的光芒。不知不觉,已有数以万计的 star 为 Milvus 而亮。2022 年 4 月,Milvus 在 GitHub 的 …...

Qt信号与槽使用方法总结
前言 在图形界面编程中QT是为首选,组件之间如何实现通信是核心的技术内容。Qt 使用了信号与槽的机制,非常的高效、简单、易学,方便开发者的使用。本文详细的介绍了Qt 当中信号与槽的概念,并演示了各种信号与槽的连接方式。 什么…...

SpringCloud alibaba-Sentinel服务降级策略
文章目录RT:异常比例:异常数:RT: 平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位…...
python常用函数——random()函数
random() 返回随机生成的一个实数,范围在[0,1)之间 语法如下: import random random.random() # 注意:random()是不能直接访问的,需要导入random包,然后通过random静态对象调用 # 参数: 无 # 返回值 返回随…...

PX4之启动脚本
PX4通过rcS脚本来设定需要启动的程序,比如设备驱动、控制模块、数据通信等。rcS脚本在项目中的文件位置 ROMFS/px4fmu_common/rcS 对应硬件平台固件上的位置 /etc/init.d/rcS 启动脚本流程如下 #!/bin/sh # PX4FMU startup script. # # 一些注释 ## 设置默认参…...

Java零基础入门到精通(持续更新中)
打开CMD命令窗口 WINR输入cmd 常用cmd命令代码 切换磁盘 E: 回车即可切换到e盘查看当前路径下的所有内容 dir进入目录 cd test回退到上一级目录 cd..进入多级目录 cd test\index\aaa回退到磁盘目录 cd \清屏 cls关闭命令行窗口 exit小例子:使用命令行窗口…...

6月1日星期日今日早报简报微语报早读
6月1日星期日,农历五月初六,早报#微语早读。 1、10个省份城镇化率超70%,广东城镇人口超9700万; 2、长沙居民起诉太平财险不赔“新冠险”,立案878天后获胜判; 3、海口:全市范围内禁止投放互联…...

本地部署消息代理软件 RabbitMQ 并实现外部访问( Windows 版本 )
RabbitMQ 是由 Erlang 语言开发的 消息中间件,是一种应用程序之间的通信方法。支持多种编程和语言和协议发展,用于实现分布式系统的可靠消息传递和异步通信等方面。 本文将详细介绍如何在 Windows 系统本地部署 RabbitMQ 并结合路由侠实现外网访问本…...

OpenHarmony定制系统组合按键(一)
一、开发环境 系统版本:OpenHarmony 4.0.10.13 设备平台:rk3568 SDK版本:fullSDK 4.0.10.13 DevEco Studio版本:4.1.0.400 二、需求背景 定制OpenHarmony 系统组合按键功能,例如仿Android Power VOL_Up组合键实现截…...
Hive SQL 中 BY 系列关键字全解析:从排序、分发到分组的核心用法
一、排序与分发相关 BY 关键字 1. ORDER BY:全局统一排序 作用:对查询结果进行全局排序,确保最终结果集完全有序(仅允许单个 Reducer 处理数据)。 语法: SELECT * FROM table_name ORDER BY column1 [A…...
day43 python Grad-CAM
目录 一、为什么需要 Grad-CAM? 二、Grad-CAM 的原理 三、Grad-CAM 的实现 1. 模块钩子(Module Hooks) 2. Grad-CAM 的实现代码 四、学习总结 在深度学习领域,神经网络模型常常被视为“黑盒”,因为其复杂的内部结…...

谷歌工作自动化——仙盟大衍灵机——仙盟创梦IDE
下载地址 https://chromewebstore.google.com/detail/selenium-ide/mooikfkahbdckldjjndioackbalphokd https://chrome.zzzmh.cn/info/mooikfkahbdckldjjndioackbalphokd...

(增强)基于sqlite、mysql、redis的消息存储
原文链接:(增强)基于sqlite、mysql、redis的消息存储 教程说明 说明:本教程将采用2025年5月20日正式的GA版,给出如下内容 核心功能模块的快速上手教程核心功能模块的源码级解读Spring ai alibaba增强的快速上手教程…...
面试题——计算机网络:HTTP和HTTPS的区别?
HTTP(HyperText Transfer Protocol):作为互联网上应用最广泛的网络通信协议,HTTP是基于TCP/IP协议族的应用层协议。它采用标准的请求-响应模式进行通信,通过简洁的报文格式(包含请求行、请求头、请求体等&a…...

Redis最佳实践——安全与稳定性保障之访问控制详解
Redis 在电商应用的安全与稳定性保障之访问控制全面详解 一、安全访问控制体系架构 1. 多层级防护体系 #mermaid-svg-jpkDj2nKxCq9AXIW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-jpkDj2nKxCq9AXIW .error-ico…...

IEEE P370:用于高达 50 GHz 互连的夹具设计和数据质量公制标准
大多数高频仪器,如矢量网络分析仪 (VNA) 和时域反射仪 (TDR),都可以在同轴接口的末端进行非常好的测量。然而,复杂系统中使用的互连很少具有同轴接口。用于表征这些设备的夹具的设计和实施会对测…...