├── .travis.yml ├── src ├── hooks │ ├── useBoolean │ │ ├── doc.md │ │ └── index.ts │ ├── useToggle │ │ ├── doc.md │ │ └── index.ts │ ├── useUpdateEffect │ │ ├── doc.md │ │ └── index.ts │ ├── useLayoutUpdateEffect │ │ ├── doc.md │ │ └── index.ts │ ├── useSetState │ │ ├── doc.md │ │ └── index.ts │ ├── usePersistFn │ │ ├── doc.md │ │ └── index.ts │ ├── doc.md │ └── useWhyDidYouUpdate │ │ ├── index.ts │ │ ├── index.zh-CN.md │ │ ├── index.en-US.md │ │ └── demo │ │ └── demo1.tsx ├── index.d.ts ├── ToolBar │ ├── context.ts │ ├── type.ts │ ├── index.less │ └── index.tsx ├── Note │ ├── index.less │ ├── context.ts │ ├── type.ts │ └── index.tsx ├── insideElement.ts ├── customAttrValue.ts ├── index.tsx ├── constants.ts ├── getLevelList.ts ├── getStartNode.ts ├── tool.ts ├── resolveIntersection.ts ├── getCustomSplitNodeInfo.ts ├── setSelectRange.ts ├── Parse │ └── index.ts ├── getSelectedInfo.ts └── getJSON.ts ├── example ├── .npmignore ├── htmlString.ts ├── asset │ └── font │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ ├── iconfont.woff2 │ │ ├── iconfont.json │ │ ├── iconfont.css │ │ ├── iconfont.svg │ │ └── iconfont.js ├── note │ ├── 2.ts │ └── 1.ts ├── index.html ├── tsconfig.json ├── tsdx.config.js ├── package.json ├── util.ts ├── index.less └── index.tsx ├── ignoreJestStyle.js ├── .gitignore ├── jest.setup.js ├── .github └── workflows │ ├── size.yml │ └── main.yml ├── jest.config.js ├── tsdx.config.js ├── todo.md ├── LICENSE ├── tsconfig.json ├── package.json ├── test ├── note.test.tsx └── __snapshots__ │ └── note.test.tsx.snap └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hooks/useBoolean/doc.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.less'; 2 | -------------------------------------------------------------------------------- /example/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | dist -------------------------------------------------------------------------------- /src/hooks/useToggle/doc.md: -------------------------------------------------------------------------------- 1 | ## 自我描述 2 | 3 | 1. 用于两个值之间的切换 4 | -------------------------------------------------------------------------------- /example/htmlString.ts: -------------------------------------------------------------------------------- 1 | const a = ''; 2 | 3 | export default a; 4 | -------------------------------------------------------------------------------- /src/hooks/useUpdateEffect/doc.md: -------------------------------------------------------------------------------- 1 | ## 自我描述 2 | 3 | 1. 初始化不调用,后续依赖更新时调用 -------------------------------------------------------------------------------- /ignoreJestStyle.js: -------------------------------------------------------------------------------- 1 | exports.process = () => { 2 | return ''; 3 | }; 4 | -------------------------------------------------------------------------------- /src/hooks/useLayoutUpdateEffect/doc.md: -------------------------------------------------------------------------------- 1 | ## 自我描述 2 | 3 | 1. 初始化不调用,后续依赖更新时调用 -------------------------------------------------------------------------------- /src/hooks/useSetState/doc.md: -------------------------------------------------------------------------------- 1 | ## 自我描述 2 | 3 | 1. 使用方式同 class组件的 setState 4 | -------------------------------------------------------------------------------- /src/hooks/usePersistFn/doc.md: -------------------------------------------------------------------------------- 1 | ## 自我描述 2 | 3 | 1. 固定返回值,即在函数组件中使用,返回值是固定的(刷新了我对useRef的使用) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | coverage 7 | yarn.lock -------------------------------------------------------------------------------- /example/asset/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iweijie/react-web-highlight/HEAD/example/asset/font/iconfont.eot -------------------------------------------------------------------------------- /example/asset/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iweijie/react-web-highlight/HEAD/example/asset/font/iconfont.ttf -------------------------------------------------------------------------------- /example/asset/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iweijie/react-web-highlight/HEAD/example/asset/font/iconfont.woff -------------------------------------------------------------------------------- /src/hooks/doc.md: -------------------------------------------------------------------------------- 1 | ## 关于Hooks 2 | 3 | 当前使用的 hooks 来至于 ahooks,当然自己有写过一遍(有写一丢丢 ^_^),[github](https://github.com/weijie9520/ahooks) -------------------------------------------------------------------------------- /example/asset/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iweijie/react-web-highlight/HEAD/example/asset/font/iconfont.woff2 -------------------------------------------------------------------------------- /src/ToolBar/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export default createContext(null); 4 | -------------------------------------------------------------------------------- /src/Note/index.less: -------------------------------------------------------------------------------- 1 | [data-wj-custom-split] { 2 | cursor: pointer; 3 | } 4 | 5 | .text-highlight-wrap { 6 | position: relative; 7 | } 8 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | const { configure } = require('enzyme'); 2 | const Adapter = require('@wojtekmaj/enzyme-adapter-react-17'); 3 | 4 | configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /src/ToolBar/type.ts: -------------------------------------------------------------------------------- 1 | export interface IToolBarProps { 2 | // 模仿 antd 弹窗 3 | mask?: boolean; 4 | visible?: boolean; 5 | autoClosable?: boolean; 6 | wrapClassName?: string; 7 | onCancel?: () => void; 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/useBoolean/index.ts: -------------------------------------------------------------------------------- 1 | import useToggle, { IAction } from '../useToggle'; 2 | 3 | function useBoolean( 4 | defaultValue: boolean = false 5 | ): [boolean, IAction] { 6 | return useToggle(!!defaultValue, !defaultValue); 7 | } 8 | 9 | export default useBoolean; 10 | -------------------------------------------------------------------------------- /src/insideElement.ts: -------------------------------------------------------------------------------- 1 | const insideElement = (target: Element, wrap: Element): boolean => { 2 | let node = target; 3 | while (node) { 4 | if (node === wrap) return true; 5 | node = node.parentElement as Element; 6 | } 7 | return false; 8 | }; 9 | 10 | export default insideElement; 11 | -------------------------------------------------------------------------------- /src/customAttrValue.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | let customValue: any = {}; 4 | 5 | export const getCustomValue = (key?: string): any => { 6 | return key ? customValue[key] : customValue; 7 | }; 8 | export const setCustomValue = (value: object): void => { 9 | customValue = { 10 | ...customValue, 11 | ...value, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import Note from './Note/index'; 2 | import ToolBar from './ToolBar/index'; 3 | import setSelectRange from './setSelectRange'; 4 | 5 | export { IModeProps, INote, INoteTextHighlightInfo } from './Note/type'; 6 | export { IToolBarProps } from './ToolBar/type'; 7 | 8 | export { ToolBar, setSelectRange }; 9 | export default Note; 10 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | // 自定义属性,用于标识当前文本被分割 2 | export const customAttr = 'data-wj-custom-text'; 3 | 4 | export const customTag = 'span'; 5 | 6 | export const customSplitAttr = 'data-wj-custom-split'; 7 | 8 | export const customRowKey = 'id'; 9 | 10 | export const customSelectedAttr = 'data-wj-custom-id'; 11 | 12 | export const marginVertical = 15; 13 | -------------------------------------------------------------------------------- /example/note/2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 内容选至 极客时间的重学前端,如有侵犯权益,请联系我这边删除 3 | */ 4 | 5 | export default '
<p class="a">text text text</p>
' 6 | 7 | // export default `

我是很长很长很长很长很长很长很长很长很长很长很长很长很长很长的文本

8 | //

下面是 encode 转换的文本

9 | //
<p class="a">text text text</p>
`; 10 | -------------------------------------------------------------------------------- /src/Note/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { INoteTextHighlightInfo } from './type'; 3 | export interface INoteContextProps { 4 | wrapContainer: React.RefObject; 5 | selectedValue: React.RefObject; 6 | action: React.RefObject; 7 | } 8 | 9 | export default createContext(null); 10 | -------------------------------------------------------------------------------- /src/hooks/useUpdateEffect/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | const useUpdateEffect = (effect: Function, deps: any[]) => { 4 | const isMounted = useRef(false); 5 | useEffect(() => { 6 | if (!isMounted.current) { 7 | isMounted.current = true; 8 | } else { 9 | return effect(); 10 | } 11 | }, deps); 12 | }; 13 | 14 | export default useUpdateEffect; 15 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Playground 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/hooks/useLayoutUpdateEffect/index.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect, useRef } from 'react'; 2 | 3 | const useLayoutUpdateEffect = (effect: Function, deps: any[]) => { 4 | const isMounted = useRef(false); 5 | useLayoutEffect(() => { 6 | if (!isMounted.current) { 7 | isMounted.current = true; 8 | } else { 9 | return effect(); 10 | } 11 | }, deps); 12 | }; 13 | 14 | export default useLayoutUpdateEffect; 15 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // jest.config.js 2 | const { defaults } = require('jest-config'); 3 | 4 | module.exports = { 5 | roots: [ 6 | './test', // 测试目录 7 | ], 8 | transform: { 9 | '^.+\\.tsx?$': 'ts-jest', 10 | '^.+\\.less?$': '/ignoreJestStyle.js', 11 | }, 12 | 13 | setupFilesAfterEnv: ['./jest.setup.js'], 14 | collectCoverage: true, // 统计覆盖率 15 | moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'], 16 | testEnvironment: 'jsdom', 17 | }; 18 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": false, 4 | "target": "es5", 5 | "module": "commonjs", 6 | "jsx": "react", 7 | "moduleResolution": "node", 8 | "noImplicitAny": false, 9 | "noUnusedLocals": false, 10 | "noUnusedParameters": false, 11 | "removeComments": true, 12 | "strictNullChecks": true, 13 | "preserveConstEnums": true, 14 | "sourceMap": true, 15 | "lib": ["es2015", "es2016", "dom"], 16 | "types": ["node"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsdx.config.js: -------------------------------------------------------------------------------- 1 | const postcss = require('rollup-plugin-postcss'); 2 | const cssnano = require('cssnano'); 3 | module.exports = { 4 | rollup(config, options) { 5 | config.plugins.push( 6 | postcss({ 7 | plugins: [ 8 | cssnano({ 9 | preset: 'default', 10 | }), 11 | ], 12 | inject: true, 13 | // only write out CSS for the first bundle (avoids pointless extra files): 14 | extract: false, 15 | less: true, 16 | }) 17 | ); 18 | 19 | return config; 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /example/tsdx.config.js: -------------------------------------------------------------------------------- 1 | const postcss = require('rollup-plugin-postcss'); 2 | const cssnano = require('cssnano'); 3 | module.exports = { 4 | rollup(config, options) { 5 | config.plugins.push( 6 | postcss({ 7 | plugins: [ 8 | cssnano({ 9 | preset: 'default', 10 | }), 11 | ], 12 | inject: true, 13 | // only write out CSS for the first bundle (avoids pointless extra files): 14 | extract: !!options.writeMeta, 15 | less: true, 16 | }) 17 | ); 18 | return config; 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/getLevelList.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取层级 3 | * @param node 4 | * @param TopNode 5 | */ 6 | 7 | const getLevelList = (node: Element, TopNode: Element) => { 8 | const list = []; 9 | while (node !== TopNode) { 10 | const parent = node.parentNode as Element; 11 | const children = parent.childNodes; 12 | for (let i = 0; i < children.length; i++) { 13 | if (node === children[i]) { 14 | list.push(i); 15 | break; 16 | } 17 | } 18 | node = parent; 19 | } 20 | return list.reverse(); 21 | }; 22 | 23 | export default getLevelList; 24 | -------------------------------------------------------------------------------- /src/hooks/usePersistFn/index.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useCallback } from 'react'; 2 | import isFunction from 'lodash/isFunction'; 3 | 4 | export type noop = (...args: any[]) => any; 5 | 6 | const usePersistFn = (fn: T) => { 7 | const fnRef = useRef(fn); 8 | fnRef.current = fn; 9 | 10 | const persistFn = useCallback( 11 | (...arg) => { 12 | if (isFunction(fnRef.current)) { 13 | return fnRef.current(...arg); 14 | } 15 | return undefined; 16 | }, 17 | [fnRef] 18 | ); 19 | 20 | return persistFn; 21 | }; 22 | 23 | export default usePersistFn; 24 | -------------------------------------------------------------------------------- /src/Note/type.ts: -------------------------------------------------------------------------------- 1 | import { INoteTextHighlightInfoItem } from '../getSelectedInfo'; 2 | 3 | export interface IModeProps { 4 | mode: string; 5 | className?: string; 6 | } 7 | 8 | export interface INoteTextHighlightInfo { 9 | list: INoteTextHighlightInfoItem[]; 10 | text: string; 11 | mode?: string; 12 | [x: string]: any; 13 | } 14 | 15 | export interface INote { 16 | template: string; 17 | value?: INoteTextHighlightInfo[]; 18 | tagName?: string; 19 | // attrName?: string; 20 | // splitAttrName?: string; 21 | onAdd?: (props: INoteTextHighlightInfo) => void; 22 | onUpdate?: (props: INoteTextHighlightInfo[]) => void; 23 | rowKey?: string; 24 | modes?: IModeProps[]; 25 | } 26 | -------------------------------------------------------------------------------- /src/hooks/useSetState/index.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | import isFunction from 'lodash/isFunction'; 3 | 4 | const useSetState = ( 5 | defaultValue: T | (() => T) 6 | ): [T, (patch: Partial | ((prevState: T) => Partial)) => void] => { 7 | const [state, setValues] = useState(defaultValue); 8 | const setState = useCallback( 9 | patch => { 10 | setValues(values => { 11 | return Object.assign( 12 | {}, 13 | values, 14 | isFunction(patch) ? patch(values) : patch 15 | ); 16 | }); 17 | }, 18 | [setValues] 19 | ); 20 | return [state, setState]; 21 | }; 22 | 23 | export default useSetState; 24 | -------------------------------------------------------------------------------- /src/getStartNode.ts: -------------------------------------------------------------------------------- 1 | import { customAttr } from './constants'; 2 | 3 | /** 4 | * @description 如果外层有 customAttr 属性, 表示当前节点为自定义分割节点,取最近的一层, 如果没有就取当前节点 5 | * @param startContainer 6 | * @param noteContainer 7 | */ 8 | const getStartNode = ( 9 | startContainer: Element, 10 | noteContainer: Element 11 | ): Element => { 12 | let node: Element = startContainer; 13 | let lastNode = startContainer; 14 | 15 | while (node && node !== noteContainer) { 16 | const isCustom = node.getAttribute && node.getAttribute(customAttr); 17 | if (isCustom) { 18 | lastNode = node; 19 | break; 20 | } 21 | node = node.parentNode as Element; 22 | } 23 | 24 | return lastNode; 25 | }; 26 | 27 | export default getStartNode; 28 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | # 关于 react-web-highlighter 2 | 3 | ## 基本交互逻辑简介(v1) 4 | 5 | 1. 使用的是 react 的 dangerouslySetInnerHTML 接口,注入不同的字符串; 6 | 2. 在初始化时候,parse(HTMLString) 将 HTMLString 转化为虚拟节点 7 | 3. 选中和反选都会基于初始的虚拟节点,去修改 构建不同的虚拟节点 8 | 4. 虚拟节点可以转化为 HTMLString; 9 | 10 | 以上就是完整的逻辑; 11 | 12 | ## 当前可以完善的 13 | 14 | ### v2: 15 | 16 | > V1本采用的是 dangerouslySetInnerHTML 接口,会让整个组件树全部销毁,需要修改 17 | 18 | TODO: 19 | 20 | 1. 解析HTMLString 生成 JSON,依据当前JSON去更新 (已完成) 21 | 2. parse 接口,基于现有的解析没有问题,但测试不全,可以改写为 基于 DOM 树去解析;(已完成) 22 | 3. 如果要修改 生成 HTMLString 的逻辑, 需要修改对应的更新逻辑;(已完成) 23 | 24 | ### V3 25 | 26 | > V2 版本只是考虑了客户端渲染,未考虑服务端渲染,所以需要调整 27 | 28 | TODO : 29 | 30 | 1. 修改DOM解析方式,支持客户端与服务端 31 | 2. 支持移动端 32 | 33 | ## 对于修改文本 34 | 35 | 1. 现有的功能对于修改文本后批注是无法对应上的,有大佬提出的 可以使用 Diff + 决策树 去实现,可以考虑下 36 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['10.x', '12.x', '14.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /example/asset/font/iconfont.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "", 3 | "name": "", 4 | "font_family": "iconfont", 5 | "css_prefix_text": "icon-", 6 | "description": "", 7 | "glyphs": [ 8 | { 9 | "icon_id": "954218", 10 | "name": "取消", 11 | "font_class": "quxiao", 12 | "unicode": "e625", 13 | "unicode_decimal": 58917 14 | }, 15 | { 16 | "icon_id": "5589026", 17 | "name": "edit", 18 | "font_class": "edit", 19 | "unicode": "e62b", 20 | "unicode_decimal": 58923 21 | }, 22 | { 23 | "icon_id": "9545594", 24 | "name": "复制", 25 | "font_class": "fuzhi", 26 | "unicode": "e598", 27 | "unicode_decimal": 58776 28 | }, 29 | { 30 | "icon_id": "14129046", 31 | "name": "划线", 32 | "font_class": "huaxian", 33 | "unicode": "ea0d", 34 | "unicode_decimal": 59917 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "parcel index.html", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": { 11 | "ahooks": "^2.9.4", 12 | "react-app-polyfill": "^1.0.0" 13 | }, 14 | "alias": { 15 | "react": "../node_modules/react", 16 | "react-dom": "../node_modules/react-dom/profiling", 17 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^16.9.11", 21 | "@types/react-dom": "^16.8.4", 22 | "parcel": "^1.12.3", 23 | "typescript": "^3.4.5" 24 | }, 25 | "browserslist": [ 26 | "last 2 versions", 27 | "Android >= 4.4", 28 | "iOS >= 9" 29 | ] 30 | } -------------------------------------------------------------------------------- /src/hooks/useWhyDidYouUpdate/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export type IProps = { 4 | [key: string]: any; 5 | }; 6 | 7 | export default function useWhyDidYouUpdate( 8 | componentName: string, 9 | props: IProps 10 | ) { 11 | const prevProps = useRef({}); 12 | 13 | useEffect(() => { 14 | if (prevProps.current) { 15 | const allKeys = Object.keys({ ...prevProps.current, ...props }); 16 | const changedProps: IProps = {}; 17 | 18 | allKeys.forEach(key => { 19 | if (prevProps.current![key] !== props[key]) { 20 | changedProps[key] = { 21 | from: prevProps.current![key], 22 | to: props[key], 23 | }; 24 | } 25 | }); 26 | 27 | if (Object.keys(changedProps).length) { 28 | console.log('[why-did-you-update]', componentName, changedProps); 29 | } 30 | } 31 | 32 | prevProps.current = props; 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/hooks/useWhyDidYouUpdate/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useWhyDidYouUpdate 3 | nav: 4 | title: Hooks 5 | path: /hooks 6 | group: 7 | title: State 8 | path: /state 9 | --- 10 | 11 | # useWhyDidYouUpdate 12 | 13 | 帮助开发者排查是什么改变导致了组件的 rerender。 14 | 15 | ## 代码演示 16 | 17 | ### 基础用法 18 | 19 | 20 | 21 | ## API 22 | 23 | ```typescript 24 | type IProps = { 25 | [key: string]: any; 26 | } 27 | useWhyDidYouUpdate(componentName: string, props: IProps): void; 28 | ``` 29 | 30 | ### Params 31 | 32 | | 参数 | 说明 | 类型 | 默认值 | 33 | |---------------|----------------------------------------------------------------------------------------|--------|--------| 34 | | componentName | 必填,观测组件的名称 | `string` | - | 35 | | props | 必填,需要观测的数据(当前组件 `state` 或者传入的 `props` 等可能导致 rerender 的数据) | `object` | - | 36 | 37 | 38 | ### Result 39 | 40 | 打开控制台,可以看到改变的 被观测的 `state` 或者 `props` 等输出。 41 | -------------------------------------------------------------------------------- /src/ToolBar/index.less: -------------------------------------------------------------------------------- 1 | .note-none { 2 | display: none; 3 | } 4 | .note-tool-mask { 5 | display: block; 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | width: 100%; 10 | height: 100%; 11 | } 12 | 13 | .note-tool { 14 | display: none; 15 | margin: 0; 16 | padding: 0; 17 | list-style: none; 18 | position: absolute; 19 | font-weight: 300; 20 | // height: 64px; 21 | background: #484848; 22 | color: #fff; 23 | align-items: center; 24 | justify-content: space-around; 25 | // display: flex; 26 | border-radius: 8px; 27 | 28 | &.up::before { 29 | content: ''; 30 | position: absolute; 31 | border-top: 6px solid #484848; 32 | border-right: 8px solid rgba(0, 0, 0, 0); 33 | border-left: 8px solid rgba(0, 0, 0, 0); 34 | width: 0px; 35 | height: 0px; 36 | bottom: -6px; 37 | } 38 | &.down::before { 39 | content: ''; 40 | position: absolute; 41 | border-bottom: 6px solid #484848; 42 | border-right: 8px solid rgba(0, 0, 0, 0); 43 | border-left: 8px solid rgba(0, 0, 0, 0); 44 | width: 0px; 45 | height: 0px; 46 | top: -6px; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 iweijie 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. -------------------------------------------------------------------------------- /example/util.ts: -------------------------------------------------------------------------------- 1 | /** UUID 为零表示当前标记 */ 2 | export const getUUID = (() => { 3 | let uuid = 0; 4 | return () => { 5 | ++uuid; 6 | return ( 7 | Math.random() 8 | .toString(16) 9 | .slice(2) + uuid 10 | ); 11 | }; 12 | })(); 13 | 14 | /** 15 | * 文本复制 16 | */ 17 | 18 | export function copyToShearPlate(str: string): void { 19 | var input = document.createElement('input'); 20 | input.type = 'text'; 21 | input.value = str; 22 | document.body.appendChild(input); 23 | // HTMLInputElement.select() 方法选中一个 334 | 335 | {selectedUpdateNoteID ? ( 336 | 337 | ) : null} 338 | 339 | 340 | 341 | 342 | ); 343 | }; 344 | 345 | ReactDOM.render(, document.getElementById('root')); 346 | --------------------------------------------------------------------------------