手撸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小例子:使用命令行窗口…...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一:HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二:Floyd 快慢指针法(…...