├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── package.json ├── packages ├── lib │ ├── LICENSE │ ├── README.md │ ├── directive │ │ ├── default.js │ │ ├── index.d.ts │ │ └── index.js │ ├── index.d.ts │ ├── index.js │ ├── jsx-dev-runtime │ │ ├── index.d.ts │ │ └── index.js │ ├── jsx-runtime │ │ ├── index.d.ts │ │ └── index.js │ ├── package.json │ ├── types │ │ ├── jsx-runtime-custom.d.ts │ │ └── jsx-runtime.d.ts │ ├── useModel.d.ts │ ├── useModel.js │ └── utils.js ├── umi-example │ ├── .gitignore │ ├── .umirc.ts │ ├── package.json │ ├── src │ │ ├── assets │ │ │ └── yay.jpg │ │ ├── layouts │ │ │ ├── index.less │ │ │ └── index.tsx │ │ └── pages │ │ │ ├── docs.tsx │ │ │ └── index.tsx │ ├── tsconfig.json │ └── typings.d.ts └── vite-example │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ └── vite.svg │ ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── index.css │ ├── main.tsx │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── pnpm-lock.yaml └── pnpm-workspace.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 dbfu 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @dbfu/react-directive 2 | 3 | 在react项目中使用vue指令,支持自定义指令。 4 | 5 | ## 安装依赖 6 | 7 | ``` 8 | npm i @dbfu/react-directive 9 | ``` 10 | 11 | ## 使用说明 12 | 13 | 如果使用typescript,修改tsconfig.json文件 14 | 15 | ```js 16 | { 17 | "compilerOptions": { 18 | "jsx": "react-jsx", 19 | "jsxImportSource": "@dbfu/react-directive", 20 | } 21 | } 22 | ``` 23 | 24 | 如果使用webpack打包,修改babel.config.json或修改.babelrc 25 | 26 | ```js 27 | // .babelrc / babel.config.json 28 | { 29 | "presets": [ 30 | [ 31 | "@babel/preset-react", 32 | { 33 | "runtime": "automatic", 34 | "importSource": "@dbfu/react-directive" 35 | } 36 | ] 37 | ] 38 | } 39 | 40 | ``` 41 | 42 | 如果使用vite 43 | 44 | ```js 45 | export default defineConfig({ 46 | plugins: [react({ 47 | jsxImportSource: '@dbfu/react-directive' 48 | })], 49 | }) 50 | ``` 51 | 52 | 53 | 如果使用umi,先安装@babel/preset-react依赖,然后修改.umirc配置文件 54 | 55 | ```js 56 | import { defineConfig } from "umi"; 57 | 58 | export default defineConfig({ 59 | extraBabelPresets: [ 60 | [ 61 | "@babel/preset-react", 62 | { 63 | "runtime": "automatic", 64 | "importSource": "@dbfu/react-directive" 65 | } 66 | ] 67 | ], 68 | }); 69 | ``` 70 | 71 | 如果使用react-create-app,先安装@babel/preset-react,然后修改package.json文件,添加babel属性。 72 | 73 | ```js 74 | { 75 | "babel": { 76 | "presets": [ 77 | "react-app", 78 | [ 79 | "@babel/preset-react", 80 | { 81 | "runtime": "automatic", 82 | "importSource": "@dbfu/react-directive" 83 | } 84 | ] 85 | ] 86 | }, 87 | } 88 | ``` 89 | 90 | 然后就可以在项目中,使用框架内置的指令 91 | 92 | ```js 93 | function App() { 94 | 95 | const model = useModel({ name: "jack" }); 96 | 97 | return ( 98 |
99 | ) 100 | } 101 | ``` 102 | 103 | # 内置指令使用文档 104 | 105 | ## v-if 106 | 107 | 和vue中的v-if一样,这个不仅可以对原生dom使用,还能对组件进行使用。 108 | 109 | ## v-show 110 | 111 | 和vue中的v-show一样,这个只能对原生dom使用,因为会修改dom元素的style中display属性,如果组件支持style.display属性的话,也可以使用v-show。 112 | 113 | ## v-model 114 | 115 | 这个指令效果和vue中的v-model差不多,但是用法有点区别,创建的对象必须用useModel这个hooks包装一下,还有v-model指令需要传数组,第一个值是对象,第二个参数是对象里面的key,支持多层级key。看下面的例子: 116 | 117 | ```jsx 118 | import useModel from '@dbfu/react-directive/useModel' 119 | 120 | function App() { 121 | 122 | const model = useModel({ user: { name: 'tom' } }); 123 | 124 | return ( 125 |
126 | 127 |
{model?.user?.name}
128 |
129 | ) 130 | } 131 | 132 | ``` 133 | 134 | ## v-focus 135 | 136 | 表单元素自动获得焦点,和原生autoFocus属性不同的是,这个可以在一开始`display:none`的情况下,后面再显示时也能自动获取焦点,这个autoFocus就实现不了。只要组件实现了focus方法,也可以对组件进行使用。 137 | 138 | ## v-copy 139 | 140 | 点击当前元素时,会把当前指令的值复制到剪切板上,只要支持onClick事件,无论组件或原生dom元素,都可以使用这个指令 141 | 142 | ## v-text 143 | 144 | 和vue实现效果一样,只支持原生dom元素。 145 | 146 | ## v-html 147 | 148 | 和vue实现效果一样,只支持原生dom元素。 149 | 150 | # 自定义指令 151 | 152 | 在项目入口,从`@dbfu/react-directive/directive`引入`directive`,然后就可以自定义指令了。语法如下: 153 | 154 | ```js 155 | import { directive } from "@dbfu/react-directive/directive" 156 | 157 | // name 指令名称 158 | directive("name", { 159 | // 组件渲染之前,在createElement的时候,在这个生命周期可以处理组件props,然后组件渲染的时候,可以拿到处理后的props。注意这个方法只要组件一重新render,就会触发一次。如果返回false这个组件就不渲染了。 160 | // value:当前指令的值 161 | // props:当前组件的props 162 | create: (value, props) => { 163 | // example v-show的实现 164 | if(value === false) { 165 | if(props?.style) { 166 | props.style.display = 'none'; 167 | } else { 168 | props.style = { display: 'none' }; 169 | } 170 | } 171 | }, 172 | // dom元素和组件渲染的时候触发,正常情况只会触发一次。如果组件多次销毁和渲染,每次渲染都会触发这个方法。 173 | // 这个方法里面不能处理props,但是可以拿到组件引用活dom元素引用,可以去调用组件或dom元素的方法 174 | // ref:如果是组件则是组件引用,如果是dom元素就是dom的引用。 175 | // value:当前指令的值 176 | // props:当前组件的props 177 | mounted: (ref, value, props) => { 178 | // example v-text的实现 179 | if(isDOM(ref)) { 180 | ref.innerText = value; 181 | } 182 | 183 | // example v-html的实现 184 | if(isDOM(ref)) { 185 | ref.innerHTML = value; 186 | } 187 | }, 188 | // 这个方法是组件style.display由none转换为block时触发 189 | // 参数和mounted一样 190 | show: (ref, value, props) => { 191 | // example v-focus的实现 192 | ref?.focus?.(); 193 | }, 194 | // 和show相反 195 | hidden: (ref, value, props) => { 196 | // 暂无使用场景 197 | } 198 | }); 199 | ``` 200 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-directive", 3 | "version": "1.0.0", 4 | "description": "在react项目使用指令,并且支持自定义指令。", 5 | "main": "index.js", 6 | "types": "./index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "react": "^18.2.0", 15 | "lodash-es": "^4.17.21" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/lib/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 dbfu 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. -------------------------------------------------------------------------------- /packages/lib/README.md: -------------------------------------------------------------------------------- 1 | # @dbfu/react-directive 2 | 3 | 在react项目中使用vue指令,支持自定义指令。 4 | 5 | ## 安装依赖 6 | 7 | ``` 8 | npm i @dbfu/react-directive 9 | ``` 10 | 11 | ## 使用说明 12 | 13 | 如果使用typescript,修改tsconfig.json文件 14 | 15 | ```js 16 | { 17 | "compilerOptions": { 18 | "jsx": "react-jsx", 19 | "jsxImportSource": "@dbfu/react-directive", 20 | } 21 | } 22 | ``` 23 | 24 | 如果使用webpack打包,修改babel.config.json或修改.babelrc 25 | 26 | ```js 27 | // .babelrc / babel.config.json 28 | { 29 | "presets": [ 30 | [ 31 | "@babel/preset-react", 32 | { 33 | "runtime": "automatic", 34 | "importSource": "@dbfu/react-directive" 35 | } 36 | ] 37 | ] 38 | } 39 | 40 | ``` 41 | 42 | 如果使用vite 43 | 44 | ```js 45 | export default defineConfig({ 46 | plugins: [react({ 47 | jsxImportSource: '@dbfu/react-directive' 48 | })], 49 | }) 50 | ``` 51 | 52 | 53 | 如果使用umi,先安装@babel/preset-react依赖,然后修改.umirc配置文件 54 | 55 | ```js 56 | import { defineConfig } from "umi"; 57 | 58 | export default defineConfig({ 59 | extraBabelPresets: [ 60 | [ 61 | "@babel/preset-react", 62 | { 63 | "runtime": "automatic", 64 | "importSource": "@dbfu/react-directive" 65 | } 66 | ] 67 | ], 68 | }); 69 | ``` 70 | 71 | 如果使用react-create-app,先安装@babel/preset-react,然后修改package.json文件,添加babel属性。 72 | 73 | ```js 74 | { 75 | "babel": { 76 | "presets": [ 77 | "react-app", 78 | [ 79 | "@babel/preset-react", 80 | { 81 | "runtime": "automatic", 82 | "importSource": "@dbfu/react-directive" 83 | } 84 | ] 85 | ] 86 | }, 87 | } 88 | ``` 89 | 90 | 91 | 然后就可以在项目中,使用框架内置的指令 92 | 93 | ```js 94 | import useModel from '@dbfu/react-directive/useModel' 95 | function App() { 96 | 97 | const model = useModel({ name: "jack" }); 98 | 99 | return ( 100 |
101 | ) 102 | } 103 | ``` 104 | 105 | # 内置指令使用文档 106 | 107 | ## v-if 108 | 109 | 和vue中的v-if一样,这个不仅可以对原生dom使用,还能对组件进行使用。 110 | 111 | ## v-show 112 | 113 | 和vue中的v-show一样,这个只能对原生dom使用,因为会修改dom元素的style中display属性,如果组件支持style.display属性的话,也可以使用v-show。 114 | 115 | ## v-model 116 | 117 | 这个指令效果和vue中的v-model差不多,但是用法有点区别,创建的对象必须用useModel这个hooks包装一下,还有v-model指令需要传数组,第一个值是对象,第二个参数是对象里面的key,支持多层级key。看下面的例子: 118 | 119 | ```jsx 120 | import useModel from '@dbfu/react-directive/useModel' 121 | 122 | function App() { 123 | 124 | const model = useModel({ user: { name: 'tom' } }); 125 | 126 | return ( 127 |
128 | 129 |
{model?.user?.name}
130 |
131 | ) 132 | } 133 | 134 | ``` 135 | 136 | ## v-focus 137 | 138 | 表单元素自动获得焦点,和原生autoFocus属性不同的是,这个可以在一开始`display:none`的情况下,后面再显示时也能自动获取焦点,这个autoFocus就实现不了。只要组件实现了focus方法,也可以对组件进行使用。 139 | 140 | ## v-copy 141 | 142 | 点击当前元素时,会把当前指令的值复制到剪切板上,只要支持onClick事件,无论组件或原生dom元素,都可以使用这个指令 143 | 144 | ## v-text 145 | 146 | 和vue实现效果一样,只支持原生dom元素。 147 | 148 | ## v-html 149 | 150 | 和vue实现效果一样,只支持原生dom元素。 151 | 152 | # 自定义指令 153 | 154 | 在项目入口,从`@dbfu/react-directive/directive`引入`directive`,然后就可以自定义指令了。语法如下: 155 | 156 | ```js 157 | import { directive } from "@dbfu/react-directive/directive" 158 | 159 | // name 指令名称 160 | directive("name", { 161 | // 组件渲染之前,在createElement的时候,在这个生命周期可以处理组件props,然后组件渲染的时候,可以拿到处理后的props。注意这个方法只要组件一重新render,就会触发一次。如果返回false这个组件就不渲染了。 162 | // value:当前指令的值 163 | // props:当前组件的props 164 | create: (value, props) => { 165 | // example v-show的实现 166 | if(value === false) { 167 | if(props?.style) { 168 | props.style.display = 'none'; 169 | } else { 170 | props.style = { display: 'none' }; 171 | } 172 | } 173 | }, 174 | // dom元素和组件渲染的时候触发,正常情况只会触发一次。如果组件多次销毁和渲染,每次渲染都会触发这个方法。 175 | // 这个方法里面不能处理props,但是可以拿到组件引用活dom元素引用,可以去调用组件或dom元素的方法 176 | // ref:如果是组件则是组件引用,如果是dom元素就是dom的引用。 177 | // value:当前指令的值 178 | // props:当前组件的props 179 | mounted: (ref, value, props) => { 180 | // example v-text的实现 181 | if(isDOM(ref)) { 182 | ref.innerText = value; 183 | } 184 | 185 | // example v-html的实现 186 | if(isDOM(ref)) { 187 | ref.innerHTML = value; 188 | } 189 | }, 190 | // 这个方法是组件style.display由none转换为block时触发 191 | // 参数和mounted一样 192 | show: (ref, value, props) => { 193 | // example v-focus的实现 194 | ref?.focus?.(); 195 | }, 196 | // 和show相反 197 | hidden: (ref, value, props) => { 198 | // 暂无使用场景 199 | } 200 | }); 201 | ``` 202 | -------------------------------------------------------------------------------- /packages/lib/directive/default.js: -------------------------------------------------------------------------------- 1 | import { directive } from '.'; 2 | import { get, set } from 'lodash-es' 3 | import { isDOM } from '../utils'; 4 | 5 | const hiddenStyle = { 6 | display: 'none', 7 | } 8 | 9 | directive("v-if", { 10 | create: (value) => { 11 | if (!value) return false; 12 | }, 13 | }); 14 | 15 | 16 | directive("v-show", { 17 | create: (value, props) => { 18 | if (!value) { 19 | if (props.style) { 20 | props.style.display = 'none'; 21 | } else { 22 | props.style = hiddenStyle; 23 | } 24 | } 25 | }, 26 | }); 27 | 28 | directive("v-model", { 29 | create: (value, props) => { 30 | if (!value || !Array.isArray(value)) return; 31 | 32 | const [model, field] = value; 33 | props.value = get(model, field); 34 | props.onChange = (e) => { 35 | set(model, field, e.target.value); 36 | } 37 | }, 38 | }) 39 | 40 | directive("v-focus", { 41 | show: (ref) => { 42 | ref?.focus?.(); 43 | }, 44 | }) 45 | 46 | directive("v-copy", { 47 | create: (value, props) => { 48 | function copyText() { 49 | var element = createElement(value); 50 | element.select(); 51 | element.setSelectionRange(0, element.value.length); 52 | document.execCommand("copy"); 53 | element.remove(); 54 | } 55 | 56 | //创建临时的输入框元素 57 | function createElement(text) { 58 | var isRTL = document.documentElement.getAttribute("dir") === "rtl"; 59 | var element = document.createElement("textarea"); 60 | // 防止在ios中产生缩放效果 61 | element.style.fontSize = "12pt"; 62 | // 重置盒模型 63 | element.style.border = "0"; 64 | element.style.padding = "0"; 65 | element.style.margin = "0"; 66 | // 将元素移到屏幕外 67 | element.style.position = "absolute"; 68 | element.style[isRTL ? "right" : "left"] = "-9999px"; 69 | // 移动元素到页面底部 70 | let yPosition = window.pageYOffset || document.documentElement.scrollTop; 71 | element.style.top = `${yPosition}px`; 72 | //设置元素只读 73 | element.setAttribute("readonly", ""); 74 | element.value = text; 75 | document.body.appendChild(element); 76 | return element; 77 | } 78 | 79 | const originOnClick = props.onClick; 80 | 81 | props.onClick = function () { 82 | copyText(); 83 | if (originOnClick) { 84 | originOnClick(...arguments); 85 | } 86 | }; 87 | }, 88 | }); 89 | 90 | directive("v-text", { 91 | create: (value, props) => { 92 | props.children = [value]; 93 | }, 94 | }) 95 | 96 | 97 | directive("v-html", { 98 | create: (value, props) => { 99 | props.dangerouslySetInnerHTML = { 100 | __html: value, 101 | }; 102 | }, 103 | }) 104 | -------------------------------------------------------------------------------- /packages/lib/directive/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface Handle { 2 | create?: (value: any, props: any) => boolean | undefined | void; 3 | mounted?: (ref: any, value: any, props: any) => void; 4 | show?: (ref: any, value: any, props: any) => void; 5 | hidden?: (ref: any, value: any, props: any) => void; 6 | } 7 | export const directiveMap: Map; 8 | export const directive: (name: string, handle: Handle) => void -------------------------------------------------------------------------------- /packages/lib/directive/index.js: -------------------------------------------------------------------------------- 1 | export const directiveMap = new Map(); 2 | 3 | export const directive = (name, hanlde) => { 4 | if (name) { 5 | directiveMap.set(name, hanlde) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { createElement, Fragment } from 'react'; 2 | 3 | export { createElement, Fragment }; -------------------------------------------------------------------------------- /packages/lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * the old `createElement` for build tool or React 16 which does not support automatic jsx-runtime. 3 | */ 4 | 5 | import React from 'react'; 6 | 7 | export function createElement(...args) { 8 | return React.createElement.apply(undefined, args); 9 | } 10 | 11 | export var Fragment = React.Fragment; -------------------------------------------------------------------------------- /packages/lib/jsx-dev-runtime/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../types/jsx-runtime' -------------------------------------------------------------------------------- /packages/lib/jsx-dev-runtime/index.js: -------------------------------------------------------------------------------- 1 | import { Fragment, jsxDEV as jsxDEV_ } from 'react/jsx-dev-runtime'; 2 | import { transformProps } from '../utils'; 3 | 4 | import '../directive/default'; 5 | 6 | function jsxDEV(type, config, maybeKey, source, self) { 7 | 8 | let _type = type; 9 | 10 | const result = transformProps(config, _type); 11 | // 如果返回false,则表示不渲染组件 12 | if (result === false) { 13 | return null; 14 | } 15 | 16 | if (result) { 17 | _type = result; 18 | } 19 | 20 | return jsxDEV_(type, config, maybeKey, source, self); 21 | } 22 | 23 | export { Fragment, jsxDEV }; 24 | -------------------------------------------------------------------------------- /packages/lib/jsx-runtime/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../types/jsx-runtime' -------------------------------------------------------------------------------- /packages/lib/jsx-runtime/index.js: -------------------------------------------------------------------------------- 1 | import { Fragment, jsx as jsx_, jsxs as jsxs_ } from 'react/jsx-runtime'; 2 | import { transformProps } from '../utils'; 3 | 4 | import '../directive/default'; 5 | 6 | function jsx(type, config, maybeKey, source, self) { 7 | 8 | // 如果返回false,则表示不渲染组件 9 | if (transformProps(config, type) === false) { 10 | return null; 11 | } 12 | 13 | return jsx_(type, config, maybeKey, source, self); 14 | } 15 | 16 | 17 | function jsxs(type, config, maybeKey, source, self) { 18 | 19 | // 如果返回false,则表示不渲染组件 20 | if (transformProps(config, type) === false) { 21 | return null; 22 | } 23 | 24 | return jsxs_(type, config, maybeKey, source, self); 25 | } 26 | 27 | export { Fragment, jsx, jsxs }; 28 | -------------------------------------------------------------------------------- /packages/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dbfu/react-directive", 3 | "version": "1.1.4", 4 | "description": "在react项目使用指令,并且支持自定义指令。", 5 | "main": "index.js", 6 | "types": "./index.d.ts", 7 | "repository": "https://github.com/dbfu/react-directive", 8 | "scripts": {}, 9 | "files": [ 10 | "index.js", 11 | "index.d.ts", 12 | "directive", 13 | "jsx-dev-runtime", 14 | "jsx-runtime", 15 | "useModel.d.ts", 16 | "useModel.js", 17 | "utils.js", 18 | "package.json", 19 | "types" 20 | ], 21 | "keywords": [ 22 | "directive" 23 | ], 24 | "author": "876809592@qq.com", 25 | "license": "MIT", 26 | "dependencies": { 27 | "react": "^18.2.0", 28 | "lodash-es": "^4.17.21" 29 | }, 30 | "publishConfig": { 31 | "access": "public" 32 | } 33 | } -------------------------------------------------------------------------------- /packages/lib/types/jsx-runtime-custom.d.ts: -------------------------------------------------------------------------------- 1 | import 'react'; 2 | 3 | type WithIntrinsicAttributesProps = { 4 | "v-if"?: boolean; 5 | "v-model"?: [Record, string]; 6 | "v-show"?: boolean; 7 | "v-focus"?: any; 8 | "v-text"?: string; 9 | "v-html"?: string; 10 | "v-copy"?: string; 11 | }; 12 | 13 | // unpack all here to avoid infinite self-referencing when defining our own JSX namespace 14 | type ReactJSXElement = JSX.Element; 15 | type ReactJSXElementClass = JSX.ElementClass; 16 | type ReactJSXElementAttributesProperty = JSX.ElementAttributesProperty; 17 | type ReactJSXElementChildrenAttribute = JSX.ElementChildrenAttribute; 18 | type ReactJSXLibraryManagedAttributes = JSX.LibraryManagedAttributes< 19 | C, 20 | P 21 | >; 22 | type ReactJSXIntrinsicAttributes = JSX.IntrinsicAttributes; 23 | type ReactJSXIntrinsicClassAttributes = JSX.IntrinsicClassAttributes; 24 | type ReactJSXIntrinsicElements = JSX.IntrinsicElements; 25 | 26 | export namespace CJSX { 27 | interface Element extends ReactJSXElement {} 28 | interface ElementClass extends ReactJSXElementClass {} 29 | interface ElementAttributesProperty 30 | extends ReactJSXElementAttributesProperty {} 31 | interface ElementChildrenAttribute extends ReactJSXElementChildrenAttribute {} 32 | 33 | type LibraryManagedAttributes = WithIntrinsicAttributesProps & ReactJSXLibraryManagedAttributes; 34 | 35 | type IntrinsicAttributes = ReactJSXIntrinsicAttributes & WithIntrinsicAttributesProps; 36 | 37 | interface IntrinsicClassAttributes 38 | extends ReactJSXIntrinsicClassAttributes {} 39 | 40 | type IntrinsicElements = ReactJSXIntrinsicElements & WithIntrinsicAttributesProps; 41 | } 42 | -------------------------------------------------------------------------------- /packages/lib/types/jsx-runtime.d.ts: -------------------------------------------------------------------------------- 1 | export { CJSX as JSX } from './jsx-runtime-custom'; 2 | -------------------------------------------------------------------------------- /packages/lib/useModel.d.ts: -------------------------------------------------------------------------------- 1 | declare function useModel>(initialState: S): S; 2 | export default useModel; -------------------------------------------------------------------------------- /packages/lib/useModel.js: -------------------------------------------------------------------------------- 1 | import { useState, useMemo, useRef } from 'react' 2 | import { isPlainObject } from 'lodash-es' 3 | 4 | // k:v 原对象:代理过的对象 5 | const proxyMap = new WeakMap(); 6 | // k:v 代理过的对象:原对象 7 | const rawMap = new WeakMap(); 8 | 9 | function observer(initialVal, cb) { 10 | 11 | const existingProxy = proxyMap.get(initialVal); 12 | 13 | // 添加缓存 防止重新构建proxy 14 | if (existingProxy) { 15 | return existingProxy; 16 | } 17 | 18 | // 防止代理已经代理过的对象 19 | if (rawMap.has(initialVal)) { 20 | return initialVal; 21 | } 22 | 23 | const proxy = new Proxy(initialVal, { 24 | get(target, key, receiver) { 25 | const res = Reflect.get(target, key, receiver); 26 | return isPlainObject(res) || Array.isArray(res) ? observer(res, cb) : res; 27 | }, 28 | set(target, key, val) { 29 | const ret = Reflect.set(target, key, val); 30 | cb(); 31 | return ret; 32 | }, 33 | deleteProperty(target, key) { 34 | const ret = Reflect.deleteProperty(target, key); 35 | cb(); 36 | return ret; 37 | }, 38 | }); 39 | 40 | proxyMap.set(initialVal, proxy); 41 | rawMap.set(proxy, initialVal); 42 | 43 | return proxy; 44 | } 45 | 46 | function useModel(initialState) { 47 | const [, update] = useState({}); 48 | 49 | const stateRef = useRef(initialState); 50 | 51 | const state = useMemo(() => { 52 | return observer(stateRef.current, () => { 53 | update({}); 54 | }) 55 | }, []); 56 | 57 | 58 | return state; 59 | } 60 | 61 | export default useModel; -------------------------------------------------------------------------------- /packages/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { clone, isFunction } from 'lodash-es'; 2 | 3 | import { directiveMap } from './directive'; 4 | 5 | const refMap = new WeakMap(); 6 | 7 | export const isDOM = (element) => { 8 | return element instanceof HTMLElement; 9 | } 10 | 11 | export const hasOwnProperty = Object.prototype.hasOwnProperty; 12 | 13 | function execHiddenDirective(props, ref) { 14 | directiveMap.forEach((handle, key) => { 15 | if (props && hasOwnProperty.call(props, key)) { 16 | handle?.hidden?.(ref, props[key], props, refMap.get(ref)) 17 | } 18 | }); 19 | } 20 | 21 | function execShowDirective(props, ref) { 22 | directiveMap.forEach((handle, key) => { 23 | if (props && hasOwnProperty.call(props, key)) { 24 | handle?.show?.(ref, props[key], props, refMap.get(ref)) 25 | } 26 | }); 27 | } 28 | 29 | export const transformProps = (props, type) => { 30 | 31 | const originProps = clone(props); 32 | 33 | for (let [key, handle] of directiveMap) { 34 | if (props && hasOwnProperty.call(props, key)) { 35 | const value = props[key]; 36 | if (handle?.create?.(value, props) === false) { 37 | return false; 38 | } 39 | delete props[key]; 40 | } 41 | } 42 | 43 | const originRef = props.ref; 44 | 45 | 46 | if (typeof type === 'string') { 47 | props.ref = (ref) => { 48 | if (!ref) return; 49 | 50 | const oldProps = refMap.get(ref); 51 | if (oldProps) { 52 | // 如果上一次display为none,当前不为none,则说明组件由隐藏变为显示,所以触发show事件 53 | if ((oldProps?.style?.display === 'none' && originProps?.style?.display !== 'none')) { 54 | execShowDirective(originProps, ref); 55 | } 56 | 57 | // 如果上一次display不为none,当前为none,则说明组件由显示变为隐藏,所以触发hidden事件 58 | if ((oldProps?.style?.display !== 'none' && originProps?.style?.display === 'none')) { 59 | execHiddenDirective(originProps, ref); 60 | } 61 | } else { 62 | // 能获取到ref说明组件已经渲染了,并且只需要执行一次 63 | directiveMap.forEach((handle, key) => { 64 | if (originProps && hasOwnProperty.call(originProps, key)) { 65 | handle?.mounted?.(ref, originProps[key], originProps, refMap.get(ref)) 66 | } 67 | }); 68 | 69 | if ((originProps?.style?.display !== 'none')) { 70 | execShowDirective(originProps, ref); 71 | } else { 72 | execHiddenDirective(originProps, ref); 73 | } 74 | } 75 | 76 | if (originRef) { 77 | if (isFunction(originRef)) { 78 | originRef(ref); 79 | } else if (hasOwnProperty.call(originRef, 'current')) { 80 | originRef.current = ref; 81 | } 82 | } 83 | 84 | refMap.set(ref, props); 85 | } 86 | } 87 | 88 | 89 | } -------------------------------------------------------------------------------- /packages/umi-example/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.env.local 3 | /.umirc.local.ts 4 | /config/config.local.ts 5 | /src/.umi 6 | /src/.umi-production 7 | /src/.umi-test 8 | /dist 9 | .swc 10 | -------------------------------------------------------------------------------- /packages/umi-example/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "umi"; 2 | 3 | export default defineConfig({ 4 | routes: [ 5 | { path: "/", component: "index" }, 6 | { path: "/docs", component: "docs" }, 7 | ], 8 | npmClient: 'pnpm', 9 | extraBabelPresets: [ 10 | [ 11 | "@babel/preset-react", 12 | { 13 | "runtime": "automatic", 14 | "importSource": "@dbfu/react-directive" 15 | } 16 | ] 17 | ], 18 | }); 19 | -------------------------------------------------------------------------------- /packages/umi-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dbfu/umi-example", 3 | "private": true, 4 | "author": "fudebao ", 5 | "scripts": { 6 | "dev": "umi dev", 7 | "build": "umi build", 8 | "postinstall": "umi setup", 9 | "setup": "umi setup", 10 | "start": "npm run dev" 11 | }, 12 | "dependencies": { 13 | "@dbfu/react-directive": "workspace:*", 14 | "umi": "^4.0.64" 15 | }, 16 | "devDependencies": { 17 | "@babel/preset-react": "^7.18.6", 18 | "@types/react": "^18.0.0", 19 | "@types/react-dom": "^18.0.0", 20 | "typescript": "^5.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/umi-example/src/assets/yay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbfu/react-directive/2edeb52534fc57ad89f9eb454a7601402f16f6e9/packages/umi-example/src/assets/yay.jpg -------------------------------------------------------------------------------- /packages/umi-example/src/layouts/index.less: -------------------------------------------------------------------------------- 1 | .navs { 2 | ul { 3 | padding: 0; 4 | list-style: none; 5 | display: flex; 6 | } 7 | li { 8 | margin-right: 1em; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/umi-example/src/layouts/index.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Outlet } from 'umi'; 2 | import styles from './index.less'; 3 | 4 | export default function Layout() { 5 | return ( 6 |
7 |
    8 |
  • 9 | Home 10 |
  • 11 |
  • 12 | Docs 13 |
  • 14 |
  • 15 | Github 16 |
  • 17 |
18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /packages/umi-example/src/pages/docs.tsx: -------------------------------------------------------------------------------- 1 | const DocsPage = () => { 2 | return ( 3 |
4 |

This is umi docs.

5 |
6 | ); 7 | }; 8 | 9 | export default DocsPage; 10 | -------------------------------------------------------------------------------- /packages/umi-example/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import yayJpg from '../assets/yay.jpg'; 2 | 3 | export default function HomePage() { 4 | return ( 5 |
6 |

Yay! Welcome to umi!

7 |

8 | 9 |

10 |

11 | To get started, edit pages/index.tsx and save to reload. 12 |

13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/umi-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./src/.umi/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/umi-example/typings.d.ts: -------------------------------------------------------------------------------- 1 | import 'umi/typings'; 2 | -------------------------------------------------------------------------------- /packages/vite-example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/vite-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/vite-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dbfu/vite-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@dbfu/react-directive": "workspace:*", 13 | "@types/lodash-es": "^4.17.7", 14 | "ahooks": "^3.7.6", 15 | "lodash-es": "^4.17.21", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^18.0.28", 21 | "@types/react-dom": "^18.0.11", 22 | "@vitejs/plugin-react": "^3.1.0", 23 | "typescript": "^4.9.3", 24 | "vite": "^4.2.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/vite-example/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/vite-example/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /packages/vite-example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, forwardRef, useImperativeHandle } from 'react' 2 | import useModel from '@dbfu/react-directive/useModel' 3 | import reactLogo from './assets/react.svg' 4 | import viteLogo from '/vite.svg' 5 | import './App.css' 6 | 7 | 8 | const Demo = forwardRef((_, ref) => { 9 | 10 | useImperativeHandle(ref, () => { 11 | return { 12 | name: 'demo' 13 | } 14 | }, []); 15 | 16 | 17 | return ( 18 |
demo
19 | ) 20 | }) 21 | 22 | 23 | function App() { 24 | const [count, setCount] = useState(0) 25 | const [show, setShow] = useState(false); 26 | 27 | const ref = React.createRef(); 28 | 29 | const model = useModel({ name: 'tom' }); 30 | 31 | return ( 32 |
33 | 34 | 35 |
copy
36 |
{ 38 | console.log(2222) 39 | console.log(ref.current) 40 | }} 41 | v-if="dddd" 42 | > 43 | {model.name} 44 |
45 | 53 |

{ 55 | setShow(prev => !prev) 56 | }} 57 | > 58 | Vite + React 59 |

60 |
61 | 64 |

65 | Edit src/App.tsx and save to test HMR 66 |

67 |
68 |

69 | Click on the Vite and React logos to learn more 70 |

71 |
72 | ) 73 | } 74 | 75 | export default App 76 | -------------------------------------------------------------------------------- /packages/vite-example/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/vite-example/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | a { 18 | font-weight: 500; 19 | color: #646cff; 20 | text-decoration: inherit; 21 | } 22 | a:hover { 23 | color: #535bf2; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | display: flex; 29 | place-items: center; 30 | min-width: 320px; 31 | min-height: 100vh; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.6em 1.2em; 43 | font-size: 1em; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #1a1a1a; 47 | cursor: pointer; 48 | transition: border-color 0.25s; 49 | } 50 | button:hover { 51 | border-color: #646cff; 52 | } 53 | button:focus, 54 | button:focus-visible { 55 | outline: 4px auto -webkit-focus-ring-color; 56 | } 57 | 58 | @media (prefers-color-scheme: light) { 59 | :root { 60 | color: #213547; 61 | background-color: #ffffff; 62 | } 63 | a:hover { 64 | color: #747bff; 65 | } 66 | button { 67 | background-color: #f9f9f9; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/vite-example/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import { get, set } from 'lodash-es' 4 | import App from './App' 5 | import './index.css' 6 | 7 | 8 | import { directive } from '@dbfu/react-directive/directive' 9 | 10 | 11 | 12 | 13 | 14 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 15 | 16 | 17 | , 18 | ) 19 | -------------------------------------------------------------------------------- /packages/vite-example/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/vite-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "jsxImportSource": "@dbfu/react-directive" 19 | }, 20 | "include": ["src"], 21 | "references": [{ "path": "./tsconfig.node.json" }] 22 | } 23 | -------------------------------------------------------------------------------- /packages/vite-example/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/vite-example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react({ 7 | jsxImportSource: '@dbfu/react-directive' 8 | })], 9 | }) 10 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' --------------------------------------------------------------------------------