# react-direactive
**Repository Path**: hsxgit/react-direactive
## Basic Information
- **Project Name**: react-direactive
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-04-09
- **Last Updated**: 2025-04-09
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Vue 开发者狂喜!我在 React 中完美复刻了 v-if/v-for 指令
## 前言
> 作为一名同时使用 Vue 和 React 的开发者,我深深被 Vue 的指令系统所吸引。 v-if 、 v-for 、 v-show 等指令让模板开发变得异常简洁高效。但在 React 中,我们却不得不使用略显冗长的三元表达式和 map 方法。这让我萌生了一个想法:能否在 React 中实现类似 Vue 的指令系统?
> 经过多次尝试,我找到了三种实现方案,其中 Babel 插件方案最为完美。下面我将详细介绍这些方案的实现思路和优劣对比。
## 为什么需要 React 指令?
- 传统 React 条件渲染
```jsx
// 条件渲染
{
isShow &&
显示内容
;
}
// 列表渲染
{
items.map((item) => {item.name}
);
}
```
- 理想中的写法
```html
显示内容
{item.name}
```
给人感觉就是很简洁 完美!
## 实现方案对比
### 方案一:高阶组件(不完美)
```jsx
const If = ({ condition, children }) => condition ? children : null;
const For = ({ list, children }) => list.map((item, index) => children(item, index));
显示内容
{(item, index) => {item}
}
```
**优缺点分析:**
- ✅ 优点:遵循 React 设计理念,无需额外工具
- ❌ 缺点:
- 语法不够直观
- 嵌套层级增加
- 无法实现真正的指令效果
### 方案二:Babel 插件
#### 实现思路
因为 React 的 JSX 本质上是 JavaScript 的语法糖,无法直接扩展类似 Vue 的模板指令系统,但是我们可以通过自定义 Babel 插件,在代码编译阶段将类似 r-if 的属性转换为 React 代码
- 核心原理:
通过 Babel AST 转换,将:
```jsx
4}>我大于4才能显示
```
转换为:
```jsx
{
count > 4 && 我大于4才能显示
;
}
```
我们都知道 React 代码的转换主要是通过 Babel 来完成的。
在之前 webpack 项目中 我们还得下载 babel-loader,用于转换 JSX 和 ES6+ 代码
我们现在用 vite 来做项目,@vitejs/plugin-react 插件已经内置了 Babel 配置,我们只需要进行相关配置即可。
接下来核心就是如何实现这个插件了
如果还不熟悉如何编写 bable 插件,可以先看一下下面的文档学习一下
**扩展阅读**:
🔗 [Babel 官方插件指南](https://babeljs.io/docs/plugins)
🔗 [实战:如何编写 Babel 插件](https://juejin.cn/...) *(掘金)*
🔗 [掘金文章](https://juejin.cn/post/6902665277104717838) *(掘金)*
🔗 [掘金文章](https://juejin.cn/post/6918555538628280333?searchId=20250408204452C45E3A2C153C7C64FA47) *(掘金)*
如果觉得内容太多学习太烦,你可以直接看我的代码,其实思路很简单
1. 首先 bable 插件一定是一个函数,函数的参数对象 这个对象有一个 types 属性,这个属性是一个对象,这个对象有很多方法,我们可以通过这些方法来操作 ast 树
```js
export default function (babel) {
//babel 中 types 属性是一个对象,这个对象有很多方法,我们可以通过这些方法来操作 ast 树
const { types: t } = babel;
...
...
}
```
2. 这个函数必须返回一个具有 visitor 属性的对象,具体原因你可以看一下文档,你也可以就当做就是格式如此
```js
export default function (babel) {
//babel 中 types 属性是一个对象,这个对象有很多方法,我们可以通过这些方法来操作 ast 树
const { types: t } = babel;
return {
name: "react-directives",
visitor: {
// 在这里编写你的访问者函数
},
};
}
```
3. 该对象内部是对各种类型的标签(比如 JSXElement)的处理逻辑,是一个个的函数,然后我们编写函数 JSXAttribute ,目的就是转换 jsx 的 Attribute 为我们需要的语法,这个函数名称无所谓
```js
export default function (babel) {
//babel 中 types 属性是一个对象,这个对象有很多方法,我们可以通过这些方法来操作 ast 树
const { types: t } = babel;
return {
name: "react-directives",
visitor: {
JSXElement(path) {},
},
};
}
```
4. visitor 的每个方法都接收两个参数:path 和 state。我们这次只关注 path,path 是一个对象,它包含了当前节点的信息,比如节点的类型、属性、子节点等。
5. 我们可以通过 path 来操作当前节点。具体 path 中有哪些值 不用过多关注,我们要获取的就是两个东西,一个是属性的名称 一个是属性的值,
- 属性的名称 path.node.name.name ===> 'r-if'
- 属性的值 path.node.value.expression ===> {count > 4}
6. 查找当前 JSX 属性节点的最近的 JSX 元素父节点并且将其替换为一个新的 JSX 表达式容器节点,该节点包含一个逻辑表达式,该表达式使用逻辑与运算符将条件和原始的 JSX 元素连接起来。
```js
jsxElement.replaceWith(
t.jSXExpressionContainer(
t.logicalExpression("&&", condition, jsxElement.node)
)
);
```
7. 最后移除原来的属性 path.remove();
#### 实现效果

#### 完整代码
`vite.config.js`
```js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [
react({
babel: {
plugins: ["./babel-plugin-react-directives.js"],
},
}),
],
});
```
插件实现
`babel-plugin-react-directives.js`
```js
export default function (babel) {
const { types: t } = babel;
return {
name: "react-directives",
visitor: {
JSXAttribute(path) {
if (path.node.name.name?.startsWith("r-")) {
const directive = path.node.name.name;
const condition = path.node.value?.expression;
if (directive === "r-if" && condition) {
const jsxElement = path.findParent((p) => p.isJSXElement());
jsxElement.replaceWith(
t.jSXExpressionContainer(
t.logicalExpression("&&", condition, jsxElement.node)
)
);
// 移除原来的属性
path.remove();
}
}
},
},
};
}
```
### 方案三:覆写createElement
我的思路就是在运行时重写createElement,然后在createElement中处理r-if指令,但是我没有成功,有懂的大佬可以留言交流一下,不知道是不是我的方式有问题
```js
import React from "react";
const originalCreateElement = React.createElement;
const customCreateElement = function (type, props, ...children) {
// 处理 r-if 指令
if (props && props["r-if"] === false) {
return null;
}
if (props && typeof props["r-if"] !== "undefined") {
return props["r-if"]
? originalCreateElement(
type,
{ ...props, "r-if": undefined },
...children
)
: null;
}
return originalCreateElement(type, props, ...children);
};
export const applyDirectives = () => {
// 确保只应用一次
if (!React.__directivesApplied) {
React.__directivesApplied = true;
React.createElement = customCreateElement; // 实际应用覆写
}
};
```
然后在main.jsx 引入
```js
import { applyDirectives } from "./directives";
applyDirectives();
```
## 结语
通过Babel插件,我们成功在React中实现了类似Vue的指令系统。这不仅让代码更加简洁,也为React开发者提供了一种新的开发体验。虽然这只是一个开始,但它展示了AST操作的强大能力。
你会考虑在项目中使用这种方案吗?欢迎在评论区分享你的看法!