# 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(); #### 实现效果 ![](./效果.gif) #### 完整代码 `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操作的强大能力。 你会考虑在项目中使用这种方案吗?欢迎在评论区分享你的看法!