Appearance
babel 插件
vIfTransformPlugin
- 让 react 的 v-if 语法支持在 jsx 中使用
实现思路
- 通过
visitor
策略模式遍历 JSXElement 节点 - 判断节点属性是否包含 v-if 属性
- 获取 v-if 的条件表达式
- 移除 v-if 属性
- 替换为条件渲染:{condition && }
插件实现
js
// plugins/vIfTransformPlugin.js
import { types } from "@babel/core";
function vIfTransformPlugin() {
return {
visitor: {
JSXElement(path) {
const { node } = path;
const { openingElement } = node;
const { attributes } = openingElement;
const attrIndex = attributes.findIndex(
(attr) => attr.name?.name === "v-if"
);
if (attrIndex === -1) return;
// 获取条件表达式
const condition = attributes[attrIndex].value?.expression;
// 移除属性
attributes.splice(attrIndex, 1);
// 替换为条件渲染:{condition && <div />}
path.replaceWith(
types.jsxExpressionContainer(
types.logicalExpression("&&", condition, node)
)
);
},
},
};
}
export default vIfTransformPlugin;
引用插件
js
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import vIfElsePlugin from "./plugins/vIfTransformPlugin";
export default defineConfig({
plugins: [
react({
babel: {
plugins: [vIfElsePlugin], // 应用自定义 Babel 插件
},
}),
],
});
使用
jsx
import { useState } from "react";
function App() {
let [num, setNum] = useState(0);
return (
<>
<button onClick={() => setNum((num) => ++num)}>click{num}</button>
<div v-if={num == 0}>aa{num}</div>
<div v-if={num == 1}>bb{num}</div>
<div v-if={num >= 2}>cc{num}</div>
</>
);
}
export default App;
vShowTransformPlugin
- 让 react 的 v-show 语法支持在 jsx 中使用
实现思路
- 通过
visitor
策略模式遍历 JSXElement 节点 - 判断节点属性是否包含 v-show 属性
- 获取 v-show 的条件表达式
- 移除 v-show 属性
- 查找现有的 style 属性
- 如果有,则合并样式,否则创建新样式
- 替换为条件渲染
插件实现
js
// plugins/vShowTransformPlugin.js
import { types } from "@babel/core";
function vShowTransformPlugin() {
return {
visitor: {
JSXElement(path) {
const { node } = path;
const { openingElement } = node;
const { attributes } = openingElement;
// 查找 v-show 属性
const showAttrIndex = attributes.findIndex(
(attr) => attr.name?.name === "v-show"
);
if (showAttrIndex === -1) return;
// 获取条件表达式
const condition = attributes[showAttrIndex].value?.expression;
// 移除 v-show 属性
attributes.splice(showAttrIndex, 1);
// 查找现有的 style 属性
let styleAttrIndex = attributes.findIndex(
(attr) => attr.name?.name === "style"
);
let existingStyle = null;
if (styleAttrIndex !== -1) {
// 提取现有样式表达式
existingStyle = attributes[styleAttrIndex].value;
attributes.splice(styleAttrIndex, 1); // 移除旧样式
}
// 构建新样式表达式
let newStyleExpression;
if (existingStyle) {
// 合并样式:condition ? existingStyle : { ...existingStyle, display: 'none' }
newStyleExpression = types.conditionalExpression(
condition,
existingStyle.expression,
types.objectExpression([
types.spreadElement(existingStyle.expression),
types.objectProperty(
types.identifier("display"),
types.stringLiteral("none")
),
])
);
} else {
// 无现有样式:condition ? undefined : { display: 'none' }
newStyleExpression = types.conditionalExpression(
condition,
types.identifier("undefined"),
types.objectExpression([
types.objectProperty(
types.identifier("display"),
types.stringLiteral("none")
),
])
);
}
// 创建新 style 属性
const newStyleAttr = types.jsxAttribute(
types.jsxIdentifier("style"),
types.jsxExpressionContainer(newStyleExpression)
);
// 添加新样式属性到元素
attributes.push(newStyleAttr);
},
},
};
}
export default vShowTransformPlugin;
引用插件
js
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import vShowTransformPlugin from "./plugins/vShowTransformPlugin";
export default defineConfig({
plugins: [
react({
babel: {
plugins: [vShowTransformPlugin], // 应用自定义 Babel 插件
},
}),
],
});
当元素有 style 属性时:
jsx
// 转换前
<div v-show={isVisible} style={{ color: 'red' }} />
// 转换后
<div style={isVisible
? { color: 'red' }
: { ...{ color: 'red' }, display: 'none' }} />
当元素无 style 属性时:
jsx
// 转换前
<button v-show={isActive}>Submit</button>
// 转换后
<button style={isActive
? undefined
: { display: 'none' }}>Submit</button>