├── .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/*'
--------------------------------------------------------------------------------