├── pnpm-workspace.yaml ├── .gitignore ├── packages ├── repeat │ ├── jest.config.js │ ├── tsconfig.json │ ├── src │ │ └── index.ts │ ├── README.md │ ├── rollup.config.ts │ └── package.json ├── sleep │ ├── jest.config.js │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ ├── README.md │ ├── rollup.config.ts │ └── package.json ├── load-image │ ├── jest.config.js │ ├── tsconfig.json │ ├── README.md │ ├── rollup.config.ts │ ├── package.json │ └── src │ │ └── index.ts ├── quick-sort │ ├── jest.config.js │ ├── tsconfig.json │ ├── README.md │ ├── rollup.config.ts │ ├── src │ │ ├── index.test.ts │ │ └── index.ts │ └── package.json ├── array-to-tree │ ├── jest.config.js │ ├── tsconfig.json │ ├── rollup.config.ts │ ├── package.json │ ├── src │ │ ├── index.test.ts │ │ └── index.ts │ └── README.md └── click-outside │ ├── jest.config.js │ ├── tsconfig.json │ ├── README.md │ ├── src │ ├── index.ts │ └── index.test.ts │ ├── rollup.config.ts │ └── package.json ├── lerna.json ├── jest.config.js ├── tsconfig.json ├── LICENSE ├── package.json └── README.md /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | types/ 4 | coverage/ 5 | lerna-debug.log 6 | -------------------------------------------------------------------------------- /packages/repeat/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'jsdom', 4 | collectCoverage: true, 5 | verbose: false, 6 | }; -------------------------------------------------------------------------------- /packages/sleep/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'jsdom', 4 | collectCoverage: true, 5 | verbose: false, 6 | }; -------------------------------------------------------------------------------- /packages/load-image/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'jsdom', 4 | collectCoverage: true, 5 | verbose: false, 6 | }; -------------------------------------------------------------------------------- /packages/quick-sort/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'jsdom', 4 | collectCoverage: true, 5 | verbose: false, 6 | }; -------------------------------------------------------------------------------- /packages/array-to-tree/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'jsdom', 4 | collectCoverage: true, 5 | verbose: false, 6 | }; -------------------------------------------------------------------------------- /packages/click-outside/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'jsdom', 4 | collectCoverage: true, 5 | verbose: false, 6 | }; -------------------------------------------------------------------------------- /packages/sleep/src/index.ts: -------------------------------------------------------------------------------- 1 | export default function (time = 1000) { 2 | return new Promise((resolve) => { 3 | setTimeout(() => { 4 | resolve(); 5 | }, time); 6 | }); 7 | } -------------------------------------------------------------------------------- /packages/array-to-tree/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declarationDir": "dist" 6 | }, 7 | "include": ["src/index.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/click-outside/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declarationDir": "dist" 6 | }, 7 | "include": ["src/index.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/sleep/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declaration": true, 6 | "declarationDir": "dist" 7 | }, 8 | "include": ["src/index.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/load-image/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declaration": true, 6 | "declarationDir": "dist" 7 | }, 8 | "include": ["src/index.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/quick-sort/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declaration": true, 6 | "declarationDir": "dist" 7 | }, 8 | "include": ["src/index.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/repeat/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declaration": true, 6 | "declarationDir": "dist" 7 | }, 8 | "include": ["src/index.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/repeat/src/index.ts: -------------------------------------------------------------------------------- 1 | export default function (times: number, callbcak: (i: number) => void, start: number = 0) { 2 | const array = []; 3 | const l = times + start 4 | for (let j = start; j < l; j++) { 5 | array.push(callbcak(j)); 6 | } 7 | return array; 8 | } -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "pnpm", 3 | "useWorkspaces": true, 4 | "publish": { 5 | "ignoreChanges": [ 6 | "ignored-file" 7 | ], 8 | "message": "chore(release): publish", 9 | "registry": "https://registry.npmjs.org" 10 | }, 11 | "packages": [ 12 | "packages/*" 13 | ], 14 | "version": "independent" 15 | } 16 | -------------------------------------------------------------------------------- /packages/sleep/README.md: -------------------------------------------------------------------------------- 1 | # @any86/sleep 2 | [![NPM Version][npm-image]][npm-url] [![Size][size-image]][size-url] 3 | 4 | [npm-image]: https://badgen.net/npm/v/@any86/sleep 5 | [npm-url]: https://npmjs.org/package/@any86/sleep 6 | [size-image]: https://badgen.net/bundlephobia/minzip/@any86/sleep 7 | [size-url]: https://bundlephobia.com/result?p=@any86/sleep 8 | 9 | 延迟, 模拟PHP的sleep函数 10 | 11 | ## 安装 12 | ```shell 13 | npm i -S @any86/sleep 14 | ``` 15 | 16 | ## 快速开始 17 | ```javascript 18 | import sleep from '@any86/sleep'; 19 | // 延迟2秒 20 | await sleep(2000); 21 | ``` -------------------------------------------------------------------------------- /packages/load-image/README.md: -------------------------------------------------------------------------------- 1 | # @any86/load-image 2 | [![NPM Version][npm-image]][npm-url] [![Size][size-image]][size-url] 3 | 4 | [npm-image]: https://badgen.net/npm/v/@any86/load-image 5 | [npm-url]: https://npmjs.org/package/@any86/load-image 6 | [size-image]: https://badgen.net/bundlephobia/minzip/@any86/load-image 7 | [size-url]: https://bundlephobia.com/result?p=@any86/load-image 8 | 9 | 加载图片 10 | 11 | ## 安装 12 | ```shell 13 | npm i -S @any86/load-image 14 | ``` 15 | 16 | ## 快速开始 17 | 18 | ```javascript 19 | import loadImage from '@any86/load-image'; 20 | 21 | loadImage(url).then(image=>{ 22 | // 加载成功 23 | // 图片对象, 24 | console.log(image.width) // 图片宽度 25 | }).catch(e=>{ 26 | // 加载失败 27 | }) 28 | ``` -------------------------------------------------------------------------------- /packages/click-outside/README.md: -------------------------------------------------------------------------------- 1 | # @any86/click-outside 2 | [![NPM Version][npm-image]][npm-url] [![Size][size-image]][size-url] 3 | 4 | [npm-image]: https://badgen.net/npm/v/@any86/click-outside 5 | [npm-url]: https://npmjs.org/package/@any86/click-outside 6 | [size-image]: https://badgen.net/bundlephobia/minzip/@any86/click-outside 7 | [size-url]: https://bundlephobia.com/result?p=@any86/click-outside 8 | 9 | 点击指点元素外部触发回调, 支持手机/桌面端. 10 | 11 | [返回首页](../../README.md) 12 | ## 安装 13 | ``` 14 | npm i -S @any86/click-outside 15 | ``` 16 | 17 | ## 使用 18 | ```javascript 19 | import clickOutside from '@any86/click-outside'; 20 | 21 | // 开始监听 22 | const cancel = clickOutside(el, e=>{ 23 | // 点击el外部触发 24 | }); 25 | 26 | // 取消监听 27 | cancel(); 28 | ``` 29 | 30 | [返回首页](../../README.md) -------------------------------------------------------------------------------- /packages/click-outside/src/index.ts: -------------------------------------------------------------------------------- 1 | const eventNames = ['click', 'touchend']; 2 | export default function (el: Node, callback: (ev:Event) => void) { 3 | let isTouch = false; 4 | function handler(ev: Event): void { 5 | // touchend 6 | if (eventNames[1] === ev.type) isTouch = true; 7 | // 禁止移动端touchend触发后还触发click 8 | if (eventNames[0] === ev.type && isTouch) return; 9 | 10 | if (!el.contains(ev.target as Node)) { 11 | callback(ev); 12 | } 13 | } 14 | 15 | eventNames.forEach(name => { 16 | document.addEventListener(name, handler); 17 | }); 18 | 19 | return () => { 20 | eventNames.forEach(name => { 21 | document.removeEventListener(name, handler); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/quick-sort/README.md: -------------------------------------------------------------------------------- 1 | # @any86/quick-sort 2 | [![NPM Version][npm-image]][npm-url] [![Size][size-image]][size-url] 3 | 4 | [npm-image]: https://badgen.net/npm/v/@any86/quick-sort 5 | [npm-url]: https://npmjs.org/package/@any86/quick-sort 6 | [size-image]: https://badgen.net/bundlephobia/minzip/@any86/quick-sort 7 | [size-url]: https://bundlephobia.com/result?p=@any86/quick-sort 8 | 9 | 10 | 快速排序, 原地排序, 非递归版本. 11 | 12 | ## 安装 13 | ```shell 14 | npm i -S @any86/quick-sort 15 | ``` 16 | 17 | ## 快速开始 18 | 19 | ```javascript 20 | import qSort from '@any86/quick-sort'; 21 | 22 | // 正序排列 23 | const array1 = [100,1,99]; 24 | const arraySorted = qSort(array); 25 | 26 | // 倒序排列, 指定排序的字段 27 | const array1 = [{a:100},{a:1},{a:99}]; 28 | const array1Sorted = qSort(array1, (a,b)=>b.a-a.a); 29 | ``` -------------------------------------------------------------------------------- /packages/repeat/README.md: -------------------------------------------------------------------------------- 1 | # @any86/repeat 2 | 3 | [![NPM Version][npm-image]][npm-url] [![Size][size-image]][size-url] 4 | 5 | [npm-image]: https://badgen.net/npm/v/@any86/repeat 6 | [npm-url]: https://npmjs.org/package/@any86/repeat 7 | [size-image]: https://badgen.net/bundlephobia/minzip/@any86/repeat 8 | [size-url]: https://bundlephobia.com/result?p=@any86/repeat 9 | 10 | 按次数循环 11 | 12 | ## 安装 13 | 14 | ```shell 15 | npm i -S @any86/repeat 16 | ``` 17 | 18 | ## 快速开始 19 | 20 | ```javascript 21 | import repeat from "@any86/repeat"; 22 | // 运行3次 23 | await repeat(3, (i) => { 24 | // 运行3次, 依次输出: 1,2,3 25 | console.log(i); 26 | }); 27 | 28 | // 运行3次 29 | await repeat( 30 | 3, 31 | (i) => { 32 | // 运行3次, 从5开始, 33 | // 所以依次输出为:5,6,7 34 | console.log(i); 35 | }, 36 | 5 37 | ); 38 | ``` 39 | -------------------------------------------------------------------------------- /packages/repeat/rollup.config.ts: -------------------------------------------------------------------------------- 1 | 2 | const typescript = require('rollup-plugin-typescript2'); 3 | import { defineConfig } from 'rollup'; 4 | import { terser } from "rollup-plugin-terser"; 5 | export default defineConfig({ 6 | input: './src/index.ts', 7 | 8 | plugins: [ 9 | typescript({ 10 | exclude: 'node_modules/**', 11 | typescript: require('typescript'), 12 | }), 13 | terser() 14 | ], 15 | 16 | output: [{ 17 | format: 'es', 18 | file: `./dist/index.es.js`, 19 | sourcemap: true, 20 | }, { 21 | format: 'cjs', 22 | file: `./dist/index.cjs.js`, 23 | sourcemap: true, 24 | exports: 'default' 25 | }, { 26 | format: 'umd', 27 | name: 'loadImage', 28 | file: `./dist/repeat.umd.js`, 29 | sourcemap: true, 30 | }] 31 | }); -------------------------------------------------------------------------------- /packages/sleep/rollup.config.ts: -------------------------------------------------------------------------------- 1 | 2 | const typescript = require('rollup-plugin-typescript2'); 3 | import { defineConfig } from 'rollup'; 4 | import { terser } from "rollup-plugin-terser"; 5 | export default defineConfig({ 6 | input: './src/index.ts', 7 | 8 | plugins: [ 9 | typescript({ 10 | exclude: 'node_modules/**', 11 | typescript: require('typescript'), 12 | }), 13 | terser() 14 | ], 15 | 16 | output: [{ 17 | format: 'es', 18 | file: `./dist/index.es.js`, 19 | sourcemap: true, 20 | }, { 21 | format: 'cjs', 22 | file: `./dist/index.cjs.js`, 23 | sourcemap: true, 24 | exports: 'default' 25 | }, { 26 | format: 'umd', 27 | name: 'loadImage', 28 | file: `./dist/sleep.umd.js`, 29 | sourcemap: true, 30 | }] 31 | }); -------------------------------------------------------------------------------- /packages/click-outside/rollup.config.ts: -------------------------------------------------------------------------------- 1 | 2 | const typescript = require('@rollup/plugin-typescript'); 3 | import { defineConfig } from 'rollup'; 4 | import { terser } from "rollup-plugin-terser"; 5 | export default defineConfig({ 6 | input: './src/index.ts', 7 | 8 | plugins: [ 9 | typescript({ 10 | exclude: 'node_modules/**', 11 | typescript: require('typescript'), 12 | }), terser() 13 | ], 14 | 15 | output: [{ 16 | format: 'es', 17 | file: `./dist/index.es.js`, 18 | sourcemap: true, 19 | }, { 20 | format: 'cjs', 21 | file: `./dist/index.cjs.js`, 22 | sourcemap: true, 23 | exports: 'default' 24 | }, { 25 | format: 'umd', 26 | name: 'clickOutside', 27 | file: `./dist/click-outside.umd.js`, 28 | sourcemap: true, 29 | }] 30 | }); -------------------------------------------------------------------------------- /packages/load-image/rollup.config.ts: -------------------------------------------------------------------------------- 1 | 2 | const typescript = require('rollup-plugin-typescript2'); 3 | import { defineConfig } from 'rollup'; 4 | import { terser } from "rollup-plugin-terser"; 5 | export default defineConfig({ 6 | input: './src/index.ts', 7 | 8 | plugins: [ 9 | typescript({ 10 | exclude: 'node_modules/**', 11 | typescript: require('typescript'), 12 | }), 13 | terser() 14 | ], 15 | 16 | output: [{ 17 | format: 'es', 18 | file: `./dist/index.es.js`, 19 | sourcemap: true, 20 | }, { 21 | format: 'cjs', 22 | file: `./dist/index.cjs.js`, 23 | sourcemap: true, 24 | exports: 'default' 25 | }, { 26 | format: 'umd', 27 | name: 'loadImage', 28 | file: `./dist/load-image.umd.js`, 29 | sourcemap: true, 30 | }] 31 | }); -------------------------------------------------------------------------------- /packages/quick-sort/rollup.config.ts: -------------------------------------------------------------------------------- 1 | 2 | const typescript = require('rollup-plugin-typescript2'); 3 | import { defineConfig } from 'rollup'; 4 | import { terser } from "rollup-plugin-terser"; 5 | export default defineConfig({ 6 | input: './src/index.ts', 7 | 8 | plugins: [ 9 | typescript({ 10 | exclude: 'node_modules/**', 11 | typescript: require('typescript'), 12 | }), 13 | terser() 14 | ], 15 | 16 | output: [{ 17 | format: 'es', 18 | file: `./dist/index.es.js`, 19 | sourcemap: true, 20 | }, { 21 | format: 'cjs', 22 | file: `./dist/index.cjs.js`, 23 | sourcemap: true, 24 | exports: 'default' 25 | }, { 26 | format: 'umd', 27 | name: 'quickSort', 28 | file: `./dist/quick-sort.umd.js`, 29 | sourcemap: true, 30 | }] 31 | }); -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'jsdom', 4 | // roots: ['./src/'], 5 | // testEnvironment: 'jest-environment-jsdom-global', 6 | testEnvironmentOptions: { 7 | userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1' 8 | }, 9 | collectCoverage: true, 10 | coverageDirectory: "./coverage/", 11 | collectCoverageFrom: [ 12 | 'packages/*/src/**/*.ts'], 13 | verbose: false, 14 | globals: { 15 | __TEST__: true, 16 | __VERSION__: require('./package.json').version, 17 | ontouchstart: null 18 | }, 19 | moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], 20 | moduleNameMapper: { 21 | '^@any86/(.*?)$': '/packages/$1/src' 22 | }, 23 | }; -------------------------------------------------------------------------------- /packages/quick-sort/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import qsort from './index'; 2 | 3 | test('空数组', () => { 4 | expect(qsort([])).toMatchObject([]); 5 | }); 6 | 7 | test('是否有返回值', () => { 8 | expect(qsort([1, 2, 3, 4, 5, 6])).toMatchObject([1, 2, 3, 4, 5, 6]); 9 | expect(qsort([6, 5, 4, 3, 2, 1])).toMatchObject([1, 2, 3, 4, 5, 6]); 10 | }); 11 | 12 | test('简单排序数字', () => { 13 | const array = [11, 22, 1]; 14 | qsort(array); 15 | expect(array).toMatchObject([1, 11, 22]); 16 | }); 17 | 18 | test('倒序', () => { 19 | const array = [1999, 21, 11, 89, 0, 1]; 20 | qsort(array, (a, b) => { 21 | return b - a; 22 | }) 23 | expect(array).toEqual([1999, 89, 21, 11, 1, 0]); 24 | }) 25 | 26 | test('根据对象指定键值排序', () => { 27 | const array = [{ x: 99 }, { x: 1999 }, { x: 89 }, { x: 0 }, { x: 1 }]; 28 | qsort(array, (a, b) => { 29 | return b.x - a.x; 30 | }) 31 | expect(array).toEqual([{ x: 1999 }, { x: 99 }, { x: 89 }, { x: 1 }, { x: 0 }]); 32 | }) -------------------------------------------------------------------------------- /packages/sleep/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any86/sleep", 3 | "version": "0.0.1", 4 | "description": "睡眠", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "types": "dist/index.d.ts", 8 | "unpkg": "dist/index.min.js", 9 | "jsdelivr": "dist/index.min.js", 10 | "keywords": [ 11 | "sleep" 12 | ], 13 | "files": [ 14 | "dist" 15 | ], 16 | "author": "any86", 17 | "homepage": "https://github.com/any86/my/packages/sleep#readme", 18 | "license": "ISC", 19 | "directories": { 20 | "test": "__tests__" 21 | }, 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/any86/my.git" 28 | }, 29 | "scripts": { 30 | "test": "jest", 31 | "dev": "rimraf dist && rollup -c --watch --environment NODE_ENV:development", 32 | "build": "rimraf dist && rollup -c" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/any86/my/issues" 36 | } 37 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "resolveJsonModule":true, 4 | // 不报告执行不到的代码错误。 5 | "allowUnreachableCode": true, 6 | "strictNullChecks": true, 7 | // 严格模式, 强烈建议开启 8 | "strict": true, 9 | // 支持别名导入: 10 | // import * as React from "react" 11 | "esModuleInterop": true, 12 | // 目标js的版本 13 | "target": "es6", 14 | // 目标代码的模块结构版本 15 | // "module": "CommonJS", 16 | // 在表达式和声明上有隐含的 any类型时报错。 17 | "noImplicitAny": true, 18 | // 删除注释 19 | "removeComments": true, 20 | // 保留 const和 enum声明 21 | "preserveConstEnums": false, 22 | // 生成sourceMap 23 | "sourceMap": true, 24 | 25 | // 编译过程中需要引入的库文件的列表 26 | "lib": ["dom", "esnext"], 27 | // 额外支持解构/forof等功能 28 | "downlevelIteration": true, 29 | // 是否生成声明文件 30 | "declaration": true, 31 | // 声明文件路径 32 | // "declarationDir": "./types", 33 | // 此处设置为node,才能解析import xx from 'xx' 34 | "moduleResolution": "node", 35 | "baseUrl": ".", 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/load-image/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any86/load-image", 3 | "version": "0.0.1", 4 | "description": "加载图片", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "types": "dist/index.d.ts", 8 | "unpkg": "dist/index.min.js", 9 | "jsdelivr": "dist/index.min.js", 10 | "keywords": [ 11 | "load-image" 12 | ], 13 | "files": [ 14 | "dist" 15 | ], 16 | "author": "any86", 17 | "homepage": "https://github.com/any86/my/packages/loadImage#readme", 18 | "license": "ISC", 19 | "directories": { 20 | "test": "__tests__" 21 | }, 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/any86/my.git" 28 | }, 29 | "scripts": { 30 | "test": "jest", 31 | "dev": "rimraf dist && rollup -c --watch --environment NODE_ENV:development", 32 | "build": "rimraf dist && rollup -c" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/any86/my/issues" 36 | } 37 | } -------------------------------------------------------------------------------- /packages/repeat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any86/repeat", 3 | "version": "0.0.2", 4 | "description": "重复执行, 执行结果队列到数组中", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "types": "dist/index.d.ts", 8 | "unpkg": "dist/index.min.js", 9 | "jsdelivr": "dist/index.min.js", 10 | "keywords": [ 11 | "repeat" 12 | ], 13 | "files": [ 14 | "dist" 15 | ], 16 | "author": "any86", 17 | "homepage": "https://github.com/any86/my/packages/repeat#readme", 18 | "license": "ISC", 19 | "directories": { 20 | "test": "__tests__" 21 | }, 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/any86/my.git" 28 | }, 29 | "scripts": { 30 | "test": "jest", 31 | "dev": "rimraf dist && rollup -c --watch --environment NODE_ENV:development", 32 | "build": "rimraf dist && rollup -c" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/any86/my/issues" 36 | } 37 | } -------------------------------------------------------------------------------- /packages/click-outside/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any86/click-outside", 3 | "version": "0.1.2", 4 | "description": "点击指定元素之外触发", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "unpkg": "dist/index.min.js", 8 | "jsdelivr": "dist/index.min.js", 9 | "keywords": [ 10 | "click", 11 | "clickOutside", 12 | "click-outside" 13 | ], 14 | "files": [ 15 | "dist" 16 | ], 17 | "author": "any86", 18 | "homepage": "https://github.com/any86/my/packages/clickOutside#readme", 19 | "license": "ISC", 20 | "directories": { 21 | "test": "__tests__" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/any86/my.git" 29 | }, 30 | "scripts": { 31 | "test": "jest", 32 | "dev": "rimraf dist && rollup -c --watch --environment NODE_ENV:development", 33 | "build": "rimraf dist && rollup -c" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/any86/my/issues" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/quick-sort/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any86/quick-sort", 3 | "version": "0.0.9", 4 | "description": "快速排序", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "types": "dist/index.d.ts", 8 | "unpkg": "dist/index.min.js", 9 | "jsdelivr": "dist/index.min.js", 10 | "keywords": [ 11 | "sort", 12 | "quick-sort" 13 | ], 14 | "files": [ 15 | "dist" 16 | ], 17 | "author": "any86", 18 | "homepage": "https://github.com/any86/my/packages/clickOutside#readme", 19 | "license": "ISC", 20 | "directories": { 21 | "test": "__tests__" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/any86/my.git" 29 | }, 30 | "scripts": { 31 | "test": "jest", 32 | "dev": "rimraf dist && rollup -c --watch --environment NODE_ENV:development", 33 | "build": "rimraf dist && rollup -c" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/any86/my/issues" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/array-to-tree/rollup.config.ts: -------------------------------------------------------------------------------- 1 | 2 | const typescript = require('rollup-plugin-typescript2'); 3 | import { terser } from "rollup-plugin-terser"; 4 | import { defineConfig } from 'rollup'; 5 | import pkg from './package.json'; 6 | 7 | export default defineConfig({ 8 | input: './src/index.ts', 9 | 10 | plugins: [ 11 | typescript({ 12 | exclude: 'node_modules/**', 13 | typescript: require('typescript'), 14 | }), 15 | terser() 16 | ], 17 | 18 | external: id => Object.keys(pkg.dependencies).includes(id) || /^@any86/.test(id), 19 | output: [{ 20 | format: 'es', 21 | file: `./dist/index.es.js`, 22 | sourcemap: true, 23 | }, { 24 | format: 'cjs', 25 | file: `./dist/index.cjs.js`, 26 | sourcemap: true, 27 | exports: 'default' 28 | }, { 29 | format: 'umd', 30 | name: 'arrayToTree', 31 | file: `./dist/array-to-tree.umd.js`, 32 | sourcemap: true, 33 | globals: { '@any86/quick-sort': 'quickSort' } 34 | }, 35 | ] 36 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Russell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/array-to-tree/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@any86/array-to-tree", 3 | "version": "0.1.1", 4 | "description": "数组转树", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "types": "dist/index.d.ts", 8 | "unpkg": "dist/array-to-tree.min.js", 9 | "jsdelivr": "dist/array-to-tree.min.js", 10 | "keywords": [ 11 | "sort", 12 | "quick-sort" 13 | ], 14 | "files": [ 15 | "dist" 16 | ], 17 | "author": "any86", 18 | "homepage": "https://github.com/any86/my/packages/array-to-tree#readme", 19 | "license": "ISC", 20 | "directories": { 21 | "test": "__tests__" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/any86/my.git" 29 | }, 30 | "scripts": { 31 | "test": "jest", 32 | "dev": "rimraf dist && rollup -c --watch --environment NODE_ENV:development", 33 | "build": "rimraf dist && rollup -c" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/any86/my/issues" 37 | }, 38 | "dependencies": { 39 | "@any86/quick-sort": "^0.0.8" 40 | }, 41 | "gitHead": "b2c16c29a71f24edf6e224c53c737198cad584e6" 42 | } 43 | -------------------------------------------------------------------------------- /packages/click-outside/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import clickOutside from './index'; 2 | test('点击元素外触发回调', done => { 3 | const onClick = jest.fn(); 4 | const el = document.createElement('div'); 5 | const el2 = document.createElement('div'); 6 | const { body } = document; 7 | body.appendChild(el); 8 | body.appendChild(el2); 9 | const remove = clickOutside(el, onClick); 10 | el2.dispatchEvent(new Event('touchend',{bubbles:true})); 11 | el2.dispatchEvent(new Event('click',{bubbles:true})); 12 | 13 | setTimeout(() => { 14 | expect(onClick.mock.calls[0][0].type).toBe('touchend'); 15 | remove(); 16 | done(); 17 | }, 100); 18 | }); 19 | 20 | 21 | test('点击元素内部不触发回调', done => { 22 | const onClick = jest.fn(); 23 | const el = document.createElement('div'); 24 | const el2 = document.createElement('div'); 25 | const { body } = document; 26 | body.appendChild(el); 27 | el.appendChild(el2); 28 | const remove = clickOutside(el, onClick); 29 | el2.dispatchEvent(new Event('touchend',{bubbles:true})); 30 | setTimeout(() => { 31 | expect(onClick).toBeCalledTimes(0); 32 | remove(); 33 | done(); 34 | }, 100); 35 | }); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my", 3 | "version": "0.0.0", 4 | "private": true, 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "description": "常用的代码集合", 12 | "devDependencies": { 13 | "lerna": "^4.0.0", 14 | "rollup-plugin-terser": "^7.0.2", 15 | "@types/jest": "^27.1.4", 16 | "jest": "^27.0.0", 17 | "rimraf": "^3.0.2", 18 | "rollup": "^2.70.1", 19 | "rollup-plugin-typescript2": "^0.31.2", 20 | "ts-jest": "^27.0.0", 21 | "tslib": "^2.3.1", 22 | "typescript": "^4.6.0" 23 | }, 24 | "scripts": { 25 | "rm": "rimraf node_modules && rimraf pacakges/**/dist", 26 | "release:next": "lerna publish --force-publish --dist-tag next", 27 | "release": "lerna publish --force-publish", 28 | "release:p:next": "lerna publish from-package --force-publish --dist-tag next", 29 | "release:p:latest": "lerna publish from-package --force-publish" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/any86/my.git" 34 | }, 35 | "author": "any86", 36 | "license": "ISC", 37 | "bugs": { 38 | "url": "https://github.com/any86/my/issues" 39 | }, 40 | "homepage": "https://github.com/any86/my#readme", 41 | "sideEffects": false 42 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 常用短代码 [![lerna](https://img.shields.io/badge/packages-lerna-cc00ff.svg)]() [![lerna](https://img.shields.io/badge/lang-TypeScript-blue)]() 2 | 3 | 代码都不长, 都用 **typescript** 实现, 方便大家学习 typescript. 4 | 5 | ## 安装 6 | ```shell 7 | npm i @any86/array-to-tree -S 8 | npm i @any86/quick-sort -S 9 | npm i @any86/click-outside -S 10 | ... 11 | ``` 12 | 13 | ## 有什么? 14 | 15 | [@any86/array-to-tree](packages/array-to-tree) 数组变树. 16 | 17 | [@any86/quick-sort](packages/quick-sort) 快速排序. 18 | 19 | [@any86/click-outside](packages/click-outside) 点击元素外部触发回调. 20 | 21 | [@any86/load-image](packages/load-image) 加载图片. 22 | 23 | [@any86/sleep](packages/sleep) 暂停. 24 | 25 | [@any86/repeat](packages/repeat) 按次数循环. 26 | 27 | 28 | 29 | 30 | ## 📚 typescript 基础 31 | 32 | [第一课, 体验typescript](https://juejin.im/post/6844904008583217165) 33 | 34 | [第二课, 基础类型和入门高级类型](https://juejin.im/post/6844904008583233544) 35 | 36 | [第三课, 泛型](https://juejin.im/post/6844904008587411463) 37 | 38 | [第四课, 解读高级类型](https://juejin.im/post/6844903902563794952) 39 | 40 | [第五课, 命名空间(namespace)是什么](https://juejin.im/post/6844903921031479309) 41 | 42 | [特别篇, 在vue3🔥源码中学会typescript🦕 - "is"](https://juejin.im/post/6844903967877513230) 43 | 44 | [第六课, 什么是声明文件(declare)? 🦕 - 全局声明篇](https://juejin.im/post/6844903993727008776) 45 | 46 | [第七课, 通过vue3实例说说declare module语法怎么用🦕模块声明篇](https://juejin.cn/post/7008710181769084964) 47 | 48 | [新开发vscode插件: ⚡any-type, 一键json到ts类型](https://juejin.cn/post/7055097715994132516) 49 | 50 | ## 微信群 51 | 52 | 由于腾讯对微信群的 100 人限制, 超过 100 人后必须由我拉进去. 53 | 54 | ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/41d88adec9564f5aaef95f8bc4b4cdfc~tplv-k3u1fbpfcp-zoom-1.image) 55 | -------------------------------------------------------------------------------- /packages/quick-sort/src/index.ts: -------------------------------------------------------------------------------- 1 | type Compare = (pivotItem: T, currentItem: T) => number; 2 | /** 3 | * 比较函数 4 | * @param pivotItem 待比较值 5 | * @param currentItem 当前值 6 | * @returns 数组,大于0正序,反之倒序 7 | */ 8 | function compareNumber(pivotItem: number, currentItem: number): number { 9 | return pivotItem - currentItem; 10 | } 11 | 12 | /** 13 | * 分区, 14 | * @param array 15 | * @param startIndex 16 | * @param endIndex 17 | * @returns 18 | */ 19 | function partition(array: Item[], startIndex: number, endIndex: number, compareFn: Compare) { 20 | // 参考值 21 | const pivot = array[startIndex]; 22 | // 分割标准值的索引 23 | let divideIndex = startIndex; 24 | for (let i = startIndex + 1; i <= endIndex; i++) { 25 | if (0 > compareFn(array[i], pivot)) { 26 | divideIndex++; 27 | swap(array, divideIndex, i); 28 | } 29 | } 30 | swap(array, divideIndex, startIndex); 31 | return divideIndex; 32 | } 33 | 34 | 35 | function swap(array: unknown[], i: number, j: number) { 36 | const v = array[i]; 37 | array[i] = array[j]; 38 | array[j] = v; 39 | } 40 | 41 | /** 42 | * 快速排序(原地排序) 43 | * 通过参数可以选择被排序的索引范围 44 | * 参考: https://zhuanlan.zhihu.com/p/109971850 45 | * @param array 数组 46 | * @param startIndex 起始索引 47 | * @param endIndex 结束索引 48 | * @returns 49 | */ 50 | export default function quickSort(array: Item[], compareFn: Compare = compareNumber as any): Item[] { 51 | const {length} = array; 52 | if(0 === length) return array; 53 | const startIndex = 0; 54 | const endIndex = length - 1; 55 | 56 | const stack: [number, number][] = []; 57 | stack.push([startIndex, endIndex]); 58 | while (0 !== stack.length) { 59 | const [startIndex, endIndex] = stack.pop()!; 60 | const pivotIndex = partition(array, startIndex, endIndex, compareFn); 61 | if (startIndex < pivotIndex - 1) { 62 | stack.push([startIndex, pivotIndex - 1]); 63 | } 64 | if (pivotIndex + 1 < endIndex) { 65 | stack.push([pivotIndex + 1, endIndex]); 66 | } 67 | } 68 | return array; 69 | } 70 | -------------------------------------------------------------------------------- /packages/load-image/src/index.ts: -------------------------------------------------------------------------------- 1 | interface Options { 2 | isCrossOrigin?: boolean, 3 | beforeLoad?: (data: any) => void, 4 | onSuccess?: (data: any) => void, 5 | onError?: (data: any) => void, 6 | onAbort?: (data: any) => void 7 | }; 8 | 9 | 10 | /** 11 | * 是否是base64字符串 12 | * @param {String} 任意字符串 13 | * @returns {Boolean} 是否base64编码格式 14 | */ 15 | const isBase64 = (string: string): boolean => { 16 | const regex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*?)\s*$/i; 17 | return regex.test(string); 18 | }; 19 | 20 | /** 21 | * 获取图片信息 22 | * @param {String} 图片地址 23 | * @param {Object} 配置是否跨域和各个阶段的钩子 24 | */ 25 | export default function (url: string, { 26 | isCrossOrigin = false, 27 | beforeLoad = () => { }, 28 | onSuccess = () => { }, 29 | onError = () => { }, 30 | onAbort = () => { } 31 | }: Options = {}) { 32 | return new Promise((resolve, reject) => { 33 | const startTime = Date.now(); 34 | const img = new Image(); 35 | // 由于ios下dataURL使用跨入设置会报错, 所以做个判断(在ios9下发现的该问题, 其他版本未尝试) 36 | if (!isBase64(url)) { 37 | if (isCrossOrigin) img.crossOrigin = 'anonymous'; 38 | } 39 | img.onload = event => { 40 | let data = { 41 | img: img, 42 | width: img.width, 43 | height: img.height, 44 | costTime: Date.now() - startTime, 45 | nativeEvent: event, 46 | }; 47 | onSuccess(data); 48 | resolve(data); 49 | }; 50 | 51 | img.onerror = event => { 52 | let data = { 53 | url, 54 | img: img, 55 | costTime: Date.now() - startTime, 56 | nativeEvent: event, 57 | type: 'error' 58 | }; 59 | onError(data); 60 | reject(data); 61 | }; 62 | 63 | img.onabort = event => { 64 | let data = { 65 | img: img, 66 | costTime: Date.now() - startTime, 67 | nativeEvent: event, 68 | }; 69 | onAbort(data); 70 | reject(data); 71 | // img.null; 72 | }; 73 | 74 | img.src = url; 75 | beforeLoad(img); 76 | }); 77 | }; -------------------------------------------------------------------------------- /packages/array-to-tree/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import arrayToTree from './index'; 2 | 3 | test('无参数', () => { 4 | const tree = arrayToTree(createArray()); 5 | const comparedTree = [ 6 | { 7 | id: 1, 8 | name: "蔬菜", 9 | order: 21, 10 | children: [ 11 | { 12 | id: 2, 13 | name: "土豆", 14 | pid: 1, 15 | order: 2, 16 | }, 17 | { 18 | id: 3, 19 | name: "豆角", 20 | pid: 1, 21 | order: 1, 22 | }, 23 | ], 24 | }, 25 | { 26 | id: 4, 27 | name: "水果", 28 | order: 11, 29 | }, 30 | ]; 31 | expect(tree).toMatchObject(comparedTree); 32 | }) 33 | 34 | test('节点键值不标准', () => { 35 | const array = [ 36 | { xxid: 1 }, 37 | { xxid: 2, xxpid: 1, }, 38 | ]; 39 | const array2 = arrayToTree(array, { 40 | KEY_ID: "xxid", 41 | KEY_PID: "xxpid", 42 | }); 43 | expect(array2).toMatchObject([{ xxid: 1, children: [{ xxid: 2, xxpid: 1 }] }]) 44 | }) 45 | 46 | 47 | test('使用transform', () => { 48 | const array = [ 49 | { id: 1, order: 1 }, 50 | { id: 2, pid: 1, order: 2 }, 51 | ]; 52 | function transform(node: any) { 53 | if (2 === node.id) { 54 | node.category = '植物'; 55 | } 56 | return node; 57 | } 58 | 59 | const array2 = arrayToTree(array, { 60 | transform, 61 | }); 62 | 63 | expect(array2).toMatchObject([{ id: 1, order: 1, children: [{ id: 2, pid: 1, order: 2, category: '植物' }] }]) 64 | }) 65 | 66 | 67 | test('自定义排序', () => { 68 | const array = arrayToTree( 69 | [ 70 | { id: 2, x: 2 }, 71 | { id: 1, x: 1 }, 72 | ], 73 | { 74 | compareOrder(a, b) { 75 | return a.x - b.x; 76 | }, 77 | } 78 | ); 79 | expect(array).toMatchObject([{id:1,x:1},{id:2,x:2}]); 80 | }) 81 | 82 | function createArray() { 83 | return [ 84 | { 85 | id: 2, 86 | name: "土豆", 87 | pid: 1, 88 | order: 2, 89 | }, 90 | { 91 | id: 1, 92 | name: "蔬菜", 93 | order: 21, 94 | }, 95 | { 96 | id: 3, 97 | name: "豆角", 98 | pid: 1, 99 | order: 1, 100 | }, 101 | { 102 | id: 4, 103 | name: "水果", 104 | order: 11, 105 | }, 106 | ]; 107 | } 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /packages/array-to-tree/src/index.ts: -------------------------------------------------------------------------------- 1 | import quickSort from '@any86/quick-sort'; 2 | type KV = Record; 3 | type NodeMap = Record; 4 | type CompareOrder = (a: Node, b: Node) => number; 5 | interface Options { 6 | KEY_ID?: string; 7 | KEY_PID?: string; 8 | KEY_CHILDREN?: string; 9 | compareOrder?: CompareOrder; 10 | transform?: (node: Node) => Node | void; 11 | isRoot?: (node: Node) => boolean; 12 | } 13 | 14 | 15 | /** 16 | * 数组转树 17 | * @param 输入数组 18 | * @param options.KEY_ID 表示唯一性键值(id) 19 | * @param options.KEY_PID 对应的父id 20 | * @param options.compareOrder 排序函数, 默认根据order的值倒序 21 | * @param options.isRoot 判断是否根节点, 接收一个参数(当前循环的节点) 22 | * @param options.transform 控制节点的返回格式 23 | * @returns 树结构 24 | */ 25 | 26 | export default function (array: Node[], options: Options = {}): Node[] { 27 | // 默认值 28 | const { KEY_ID, KEY_CHILDREN, KEY_PID, compareOrder, transform } = { 29 | KEY_ID: 'id', 30 | KEY_PID: 'pid', 31 | KEY_CHILDREN: 'children', 32 | compareOrder: ((a: Node, b: Node) => b.order - a.order) as CompareOrder, 33 | transform: (node: Node): Node & KV | void => node, 34 | ...options 35 | }; 36 | 37 | const isRoot = options.isRoot || ((node: Node) => !node[KEY_PID]); 38 | 39 | let tree = []; 40 | let pidAndChildrenMap: NodeMap | null = {}; 41 | 42 | for (const node of array) { 43 | const { [KEY_ID]: id, [KEY_PID]: pid } = node; 44 | const currentNode = transform(node); 45 | if (void 0 === currentNode) continue; 46 | 47 | if (isRoot(node)) { 48 | // 根节点 49 | tree.push(currentNode); 50 | } else { 51 | // 非根节点 52 | if (void 0 === pidAndChildrenMap[pid]) { 53 | pidAndChildrenMap[pid] = [] 54 | } 55 | 56 | // if (void 0 !== currentNode) { 57 | pidAndChildrenMap[pid].push(currentNode); 58 | // } 59 | } 60 | 61 | // 用每个节点的id做map 62 | if (void 0 === pidAndChildrenMap[id]) { 63 | pidAndChildrenMap[id] = []; 64 | } 65 | 66 | // 让每个节点的children指向pidChildrenMap中的值 67 | currentNode[KEY_CHILDREN as any] = pidAndChildrenMap[id]; 68 | } 69 | 70 | // 删除空的children字段 71 | for (const node of array) { 72 | // transform后可能是undefined 73 | if (0 === node[KEY_CHILDREN]?.length) { 74 | delete node[KEY_CHILDREN] 75 | } 76 | } 77 | 78 | // 排序 79 | for (const pid in pidAndChildrenMap) pidAndChildrenMap[pid] = quickSort(pidAndChildrenMap[pid], compareOrder) 80 | tree = quickSort(tree, compareOrder); 81 | 82 | // 有循环引用, 手动销毁 83 | pidAndChildrenMap = null; 84 | return tree; 85 | }; -------------------------------------------------------------------------------- /packages/array-to-tree/README.md: -------------------------------------------------------------------------------- 1 | # @any86/array-to-tree [![NPM Version][npm-image]][npm-url] [![Size][size-image]][size-url] [![Node CI](https://github.com/any86/arr2tree/actions/workflows/nodejs.yml/badge.svg)](https://github.com/any86/arr2tree/actions/workflows/nodejs.yml) 2 | 3 | [npm-image]: https://badgen.net/npm/v/@any86/array-to-tree 4 | [npm-url]: https://npmjs.org/package/@any86/array-to-tree 5 | [size-image]: https://badgen.net/bundlephobia/minzip/@any86/array-to-tree 6 | [size-url]: https://bundlephobia.com/result?p=@any86/array-to-tree 7 | 8 | 🌲 数组转树 9 | 10 | ## 安装 11 | 12 | ```shell 13 | npm i -S @any86/array-to-tree 14 | ``` 15 | 16 | ## 快速开始 17 | 18 | ```javascript 19 | const tree = arr2tree(array); 20 | ``` 21 | 22 | ### 数组(array) 23 | 24 | ```javascript 25 | // ======= 输入数组 🍔:======= 26 | const array = [ 27 | { 28 | id: 1, 29 | name: "蔬菜", 30 | order: 1, 31 | }, 32 | { 33 | id: 2, 34 | name: "土豆", 35 | pid: 1, 36 | order: 2, 37 | }, 38 | { 39 | id: 3, 40 | name: "豆角", 41 | pid: 1, 42 | order: 1, 43 | }, 44 | { 45 | id: 4, 46 | name: "水果", 47 | order: 2, 48 | }, 49 | ]; 50 | ``` 51 | 52 | ### 树(tree) 53 | 54 | ```javascript 55 | // ======= 输出 🌲:======= 56 | [ 57 | { 58 | id: 1, 59 | name: "蔬菜", 60 | order: 1, 61 | children: [ 62 | { 63 | id: 3, 64 | name: "豆角", 65 | pid: 1, 66 | order: 1, 67 | }, 68 | { 69 | id: 2, 70 | name: "土豆", 71 | pid: 1, 72 | order: 2, 73 | }, 74 | ], 75 | }, 76 | { 77 | id: 4, 78 | name: "水果", 79 | order: 2, 80 | }, 81 | ]; 82 | ``` 83 | 84 | ## 进阶 85 | 86 | ### 兼容不同的键值 87 | 88 | 我们可以通过参数来兼容不同业务下的键值: 89 | 90 | ```javascript 91 | const arr = [ 92 | { xxid: 1, xxorder: 1 }, 93 | { xxid: 2, xxpid: 1, xxorder: 2 }, 94 | ]; 95 | 96 | arr2tree(arr, { 97 | KEY_ID: "xxid", 98 | KEY_PID: "xxpid", 99 | }); 100 | ``` 101 | 102 | ### 自定义返回节点结构 103 | 104 | 注意不要删除或者结构 node, 这里只可以对他附加属性, 否则无法形成树. 这里相当于遍历节点, 所以一些需要虚幻的操作可以放在这里. 105 | 106 | ```javascript 107 | const array = [ 108 | { id: 1, order: 1 }, 109 | { id: 2, pid: 1, order: 2 }, 110 | ]; 111 | function transform(node: any) { 112 | if (2 === node.id) { 113 | node.category = "植物"; 114 | } 115 | return node; 116 | } 117 | 118 | // 输出 119 | // [{ id: 1, order: 1, children: [{ id: 2, pid: 1, order: 2, category: '植物' }] }] 120 | ``` 121 | 122 | ### 自定义根节点的判断条件 123 | 124 | 默认情况下**arr2tree**通过判断"节点上的**KEY_PID**对应的属性是否 false"来区分根/子节点. 也就是值为空或者小于 1 的值都会当做父节点. 125 | 126 | 内部逻辑: 127 | 128 | ```javascript 129 | const isRoot = (node) => !node[KEY_PID]; 130 | ``` 131 | 132 | 如果不满足也可通过自定义`isRoot`来设置判断函数: 133 | 134 | ```javascript 135 | arr2tree(arr, { 136 | isRoot: (node) => node.iAmRoot, 137 | }); 138 | ``` 139 | 140 | ### 自定义排序 141 | 142 | 默认根据节点的 order 字段倒序排列,比如: 143 | 144 | ```javascript 145 | arr2tree([ 146 | { id: 1, order: 1 }, 147 | { id: 2, order: 2 }, 148 | ]); // [{id:2,order:2},{id:1,order:1}] 149 | ``` 150 | 151 | 现实中你可能需要正序, 更可能没有 order 字段, 所以通过`compareOrder`函数可以调整. 152 | 153 | ```javascript 154 | // 根据"x"排序, 并且正序. 155 | arr2tree( 156 | [ 157 | { id: 2, x: 2 }, 158 | { id: 1, x: 1 }, 159 | ], 160 | { 161 | compareOrder(a, b) { 162 | return b.x - a.x; 163 | }, 164 | } 165 | ); // [{id:1,x:1},{id:2,x:2}] 166 | ``` 167 | --------------------------------------------------------------------------------