欢迎关注微信公众号:我是王多余
大家好,我是Tony。在这篇文章中,我将通过一个实际的TODO List开发案例,展示如何使用dagger.js、React 和 Vue 3 构建功能完全相同的单页应用,深入对比这三种框架在设计和使用上的细节差异。
本示例涵盖了现代前端项目中常见的功能模块,包括模板和指令语法、路由配置、数据状态管理、双向数据绑定、事件处理以及组件设计等。
为了便于横向对比,我们将项目代码拆解为若干步骤,每个步骤依次展示三种框架的实现方式并做代码讲解。无论你是否熟悉这些框架,都可以按照本文的步骤轻松上手。
闲言少叙。接下来,我们将直接进入代码部分(完整的示例代码放在这里,供参考)。
步骤0:开发环境准备
1. 全局安装 Node.js。
本示例项目使用的Node.js 版本为 20.14.0。
需要注意的是,React 和 Vue 项目的构建依赖于Node.js。而dagger.js是一个纯运行时框架,没有构建或打包过程,因此 dagger.js 的开发不需要依赖Node.js。
2. 创建一个名为TODO-List(或你喜欢的任何名字)的文件夹作为项目根目录。
步骤1:项目初始化
为便于横向对比,我们统一使用Vite来初始化项目并启动dev server。
react
在根目录下打开命令行工具,运行npm create vite,根据命令行提示进行项目配置:
-
项目名称:react
-
框架选择:React
-
语言选择:JavaScript
vue
在根目录下打开命令行工具,运行npm create vite,根据命令行提示进行项目配置:
-
项目名称:vue
-
框架选择:Vue
-
语言选择:JavaScript
dagger
在根目录下打开命令行工具,运行npm create vite,根据命令行提示进行项目配置:
-
项目名称:dagger
-
框架选择:Vanilla(原生 JavaScript)
-
语言选择:JavaScript
我们也可以选择不依赖Vite来初始化dagger.js项目。
只需在项目根目录下创建一个名为dagger的子文件夹,并将一个常规的index.html页面模板复制到该文件夹中即可。
如果选择这种初始化方式,你需要通过其他的静态服务器(例如 Nginx 或 Live Server等)来启动dev server。
步骤2:启动项目
react vue dagger
从本步骤开始,命令行工具需要切换到各版本项目子文件夹下。
1. 运行npm install安装依赖模块。
2. 运行npm run dev启动dev server。
步骤3:调整代码结构
react
进入src文件夹。
1. 清空index.css和App.css的内容。
2. 将App.jsx中的代码改为如下图所示,只返回一组空的div标签:
vue
进入src文件夹。
1. 删除components文件夹下的预置组件。
2. 清空style.css的内容。
3. 将App.vue改为如下的空白Vue页面模板:
dagger
1. 删除src文件夹。
2. 将index.html内容修改如下:
红框中的代码是项目的入口模块配置项。我们在根名空间下,声明了一个名为app的模块,其模块代码来自modules/APP.html文件。需要注意的是,dagger.js 框架要求模块名称必须以下划线或小写字母开头。
黄框中通过标签创建了一个app组件实例。
绿框内是dagger.js框架源码的引入方式,注意需要把script标签的类型设置为module,并加上defer声明。你可以通过以下cdn链接获取源码:
debug版本 | https://assets.codepen.io/5782383/dagger-1.0.0-RC.js |
release版本 | https://assets.codepen.io/5782383/dagger.release-1.0.0-RC.js |
在实际项目开发过程中,推荐使用debug版本,它会在控制台输出程序运行日志和详细的报错信息。上线时,建议切换到release版本,它的代码更加精简,执行效率也更高。
4. 创建一个名为modules的文件夹,在该文件夹下创建一个名为APP.html的视图模块文件,内容如下:
刷新浏览器,页面显示“Hello dagger!”:
需要说明的是,dagger.js是一个基于 HTML 的描述式框架,框架会根据模块和路由配置动态触发页面渲染。因此不像React或Vue应用那样需要依赖main脚本模块作为程序的执行入口点。
代码已经清理完成,接下来我们来添加路由。
步骤4:安装路由
react
1. 命令行运行npm install -S react-router-dom安装路由模块。
2. 重新运行npm run dev启动dev server。
vue
1. 命令行运行npm install -S vue-router@4安装路由模块。
2. 重新运行npm run dev启动dev server。
dagger
dagger.js内置了路由管理功能,不需要额外安装插件。
步骤5:添加路由
react
1. 在src下新建router.jsx文件,内容如下:
2. 修改App.jsx,通过RouterProvider标签加载路由组件实例:
vue
1. 在src下新建router.js文件,内容如下:
2. 修改main.js,启用路由功能:
3. 修改App.vue,通过RouterView标签加载路由组件实例:
dagger
1. 在项目文件夹下新建router.json,内容如下:
routing下是路由项和关联模块设置,我们为根路由添加一个viewName常量,并将app和post_list设置为根路由的关联模块名称。
dagger.js默认使用hash路由模式,这里我们将其设置为history模式。
在routing配置下设置路由项和关联模块。我们为根路由添加了一个viewName常量,并将app和post_list设置为根路由的关联模块名称。
2. 修改index.html文件,添加红框内代码引入路由:
注意路由配置项标签的类型为dagger/routers。
3. 修改APP.html文件,删除“Hello dagger!”,并将其替换为一个声明了*html指令的模板标签:
上图中的代码是dagger.js路由的典型使用方式:
框架解析router.json配置项,创建顶层对象$route,并将*html指令表达式设置为预定义常量$route.constants.viewName,这相当于在当前位置插入了<post_list></post_list>标签。
*html指令监听表达式的计算结果,当路由变化时,指令会重新计算并更新路由关联视图。
刷新浏览器,额,控制台报错了:
别着急,接下来我们将添加post_list模块。
步骤6:添加PostList页面
react
1. 在src下新建pages文件夹,并在pages文件夹中新增PostList.jsx文件。文件内容如下:
我们使用useState创建了响应变量postList,并在useEffect钩子中发送 AJAX请求以更新postList数据。onDelete方法用于从postList列表中删除指定的post数据,严格来说,是“将postList变量重置为不包含指定post数据的新列表”。
2. 在router.jsx文件中引入PostList组件:
vue
1. 在src下新建pages文件夹,pages下新增PostList.vue文件:
我们在生命周期回调onMounted中获取并更新postList数据。需要注意的是,这里的postList是一个Vue的ref对象,其value字段指向实际的post数组。Vue框架采用了类似于.NET中“装箱”(boxing)的方法将数据包裹,以实现数据的可观测性。
2. 在router.js文件中引入PostList组件:
dagger
1. 在modules文件夹下新建PostList.html文件,内容如下:
我们在生命周期指令+load中定义了postList数组,并在+loaded指令中发送AJAX请求来更新postList的值。
需要注意的是dagger指令表达式中$scope参数的用法。 $scope代表了当前上下文下可访问的作用域对象,我们在控制台打印出$scope来查看其内容:
可以看到,$scope是一个包含了postList数组的派生对象,$scope的prototype指向根作用域。根作用域下包含前文中提到的顶层路由对象$route。
dagger.js和Vue都通过将数据转化为代理(Proxy)对象来实现可观测性。不同的是,Vue需要将对象进一步包装为一个自定义的Ref对象,而dagger.js则将数据与视图的关联信息存储在对象的元数据当中,这样用户可以使用原生 JavaScript 语法直接访问和修改数据。
2. 在index.html的模块配置项中添加如下代码以创建post_list模块:
需要注意的是dagger.js与其他两个框架在引入组件方式上的区别。
在dagger.js版本中,整个应用程序是由框架进行管理的。开发者需要编写的代码包括两部分:一是配置项(如路由和模块声明等),二是模块本身(如视图、脚本模块等)。dagger.js会根据配置项和当前页面的路由状态,决定加载和执行哪些模块。简而言之,开发者提供的所有业务代码实际上是dagger.js的回调参数。
下一步我们继续添加PostItem组件。
步骤7:添加PostItem组件
react
1. 在src下新建components文件夹,components下新增PostItem.jsx文件:
PostItem仍然是一个常规的函数组件。
红框内的onDelete是在父组件PostList中定义的删除post方法,作为prop参数传递给子组件PostItem。当用户点击“Delete”按钮时,会触发该方法(见绿框代码)。
黄框内使用Link标签定义了一个跳转到编辑页面的链接。
2. 在PostList.jsx中引入PostItem组件并渲染:
黄框内代码用于循环渲染PostItem列表,其中post数据和onDelete方法作为props属性传递给子组件。
vue
1. 在components下新增PostItem.vue文件:
红框内代码用RouterLink标签定义了一个跳转到编辑页面的链接。
黄框内代码通过emit方式将待删除的数据传递回父组件。Vue提供了令人目不暇接的多种组件通信方式,但这不是本文的重点,打算考研的同学请自行参考相关文档进行深入学习。
2. 在PostList.vue中引入PostItem组件并渲染:
Vue通过v-for指令实现数组的循环渲染。这里需要注意@delete事件和PostItem.vue中emit调用的对应关系。
dagger
1. 在modules下新增PostItem.html文件:
如红框中代码所示,dagger.js通过在原生标签a上添加*href指令的方式来创建路由链接。
黄框中是点击删除post事件代码。这里的postList是由父级作用域创建的数组对象,由于作用域存在继承关系,子组件中可以直接访问和修改父组件中定义的数据。当然,我们也可以将删除方法定义在父作用域的脚本模块当中。和作用域一样,脚本模块同样具有继承关系,子组件可以直接调用在父级组件内引入的方法。
2. 在PostList.html中引入PostItem组件并渲染:
这里我们新增了一个类型为dagger/modules的脚本标签,作为当前名空间的模块配置项。实际上,每个名空间模块都有一个缺省的模块配置项:
开发者可以自定义一个完整的模块配置项,也可以通过extends属性声明当前的模块配置项是从缺省模块配置项扩展而来的。
红框内的代码等价于:
黄框里是循环渲染PostItem列表的代码,dagger.js使用*each指令进行循环渲染。我们在*each指令上添加了修饰符#item:post,这相当于在子级作用域中创建了一个名为post的对象。
接下来,我们将添加列表项的创建和编辑页面组件。
步骤8:添加PostForm页面
react
1. 在pages文件夹下新增PostForm.jsx文件。由于文件内容较长,我们将其分成三部分来逐一查看:
通过useState hook创建了model对象来存放post数据。
通过useParams hook获取路由参数id。
在useEffect hook中,使用setModel方法加载指定id的post数据。
接下来是定义用于提交表单数据的onSubmit方法。需要加入红框代码来阻止默认的表单提交行为。另外,在AJAX请求调用完成后,通过路由器对象的router.navigate方法来进行路由跳转(见黄框代码)。
最后一部分是视图模板。在React框架中,并没有内置的双向数据绑定功能,因此开发者需要自行实现input标签的onInput事件来更新数据。
2. router.jsx文件引入PostForm组件并设置新的路由:
vue
1. pages文件夹下新增PostForm.vue文件:
先来看视图模板部分,Vue 提供了内置的双向数据绑定功能,通过v-model指令,可以将数据和input标签的输入内容动态同步。
.prevent指令修饰符将阻止submit事件的默认行为,这相当于React代码中的event.preventDefault()。
通过useRoute获取路由对象。
通过useRouter获取路由器对象。
在AJAX请求调用完成后,通过路由器对象的router.push方法实现路由跳转。
2. router.js文件引入PostForm组件并设置新的路由:
dagger
1. 在modules文件夹下新增PostForm文件夹,在PostForm文件夹下新增namespace.html和script.js两个文件。
我们这里将PostForm名空间下的脚本模块放到单独的js文件中。相比直接在html文件中嵌入script标签来说,使用单独js脚本的资源定位方案实现了关注点分离,代码更加清晰易读,同时可以获得更好的编辑器语法高亮和自动补全支持。
namespace.html:
这里值得注意的内容包括:
#prevent指令修饰符会阻止submit事件的默认行为,相当于React代码中的event.preventDefault()或Vue代码中的.prevent修饰符。
input标签的*value指令提供了双向数据绑定能力,类似于Vue的v-model指令。
接下来看script.js:
参数id通过顶层的路由对象$route中获取。
命令式路由跳转使用原生JavaScript内置的history.pushState或者history.replaceState方法来实现。
2. router.json文件引入PostForm组件并设置新的路由:
dagger.js的路由配置项是一个树型结构对象。
我们在根路由下新增加两个子路由节点,分别与路径new和edit/:id匹配,这两个子路由的viewName常量字段和modules都设置为post_form。
缺省情况下,只有叶路由节点才能作为路由匹配的终点。红框中新增的tailable: true允许当前的路由节点(根路由)作为路由匹配的终止结点。如果去掉这个配置,直接访问http://localhost:5176将匹配失败,控制台报错:
3. 回到index.html页面,在模块配置项中增加post_form模块声明:
我们刷新页面看下:
内容已经渲染出来了,但是看起来非常简陋。现在我们在三个版本的index.html中分别增加如下代码,引入Bootstrap样式库:
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
再次刷新页面,页面现在看起来美观多了:
点击按钮和链接,测试前面加入的各项功能是否正常。到此,我们的演示实例已经基本开发完成了。
关于数据状态管理
接下来聊最后一个话题:数据状态管理。
以React和Vue为代表的现代前端框架,普遍遵循单向数据流(瀑布模型)的设计思想。在单向数据流模型中,应用程序的数据流动是有明确方向的(父级流向子级)。子级组件可以消费父级数据,但原则上不允许直接修改父级数据,数据的维护应由其创建者负责。这一设计思想进一步催生了数据中心存储(dataStore)和不可变性(immutable)等开发模式。
与此不同,dagger.js 更倾向于将整个应用程序视为一块四通八达的电路板(我称之为集成电路模型)。自数据对象创建起,电路(即作用域链)上的每个“电器”(指令)都可以作为数据的消费者和修改者。简单来说,数据即状态,任何组件或指令都可以对其可访问的数据进行操作。同时,任何对数据的原子修改(mutation)都会被其他依赖该数据的指令感知,进而触发页面更新(全域响应性)。
实际上,dagger.js的设计思路更加贴近原生IavaScript语法。我们可以通过一组原生IavaScript嵌套函数调用,来对比瀑布模型与集成电路模型之间的差别。
瀑布模型:
集成电路模型:
孰优孰劣,见仁见智。
本文内容就到这里,后续文章将为大家带来更多案例和讲解。
如果对dagger.js感兴趣的话,请您点赞收藏、分享本系列文章,也欢迎留言或者私信作者提出问题和建议,您的关注是对我最大的支持和鼓励。感谢您的阅读,祝工作学习顺利!
附:本文示例中涉及到的各框架主要差异列表:
框架 | react (18.3.1) | vue (3.5.13) | dagger (1.0.0-RC) |
开发环境 | nodejs | nodejs | 可选安装node.js |
初始化 | Vite | Vite | Vite或手动创建 |
启动开发服务 | npm | npm | npm或其他静态服务器 |
代码入口 | main.jsx | main.js | dagger.js |
路由模块 | react-router插件 | vue-router插件 | 内置 |
路由视图渲染 | RouterProvider | RouterView | *html指令 |
路由链接 | Link | RouterLink | a标签 |
路由对象获取 | useParams | useRoute | $route |
命令式路由跳转 | router.navigate | router.push | history.pushState |
创建可观测数据 | useState | ref | +load指令 |
组件加载完成回调 | useEffect | onMounted | +loaded指令 |
循环语法 | Array.map | v-for指令 | *each指令 |
双向数据绑定 | onInput事件 | v-model指令 | *value指令 |
数据状态管理 | Context API | vuex | 数据即状态 |
构建发布 | npm | npm | 无需构建 |
按:本文中采用的示例取材于油管博主@TheCodeholic的教学视频,作者通过一个TODO List案例展示了react和vue两个框架的用法差异。示例很经典,讲解也十分细致清晰,是非常好的前端单页应用开发入门教程。推荐大家观看原视频。
标签:vue,dagger,马拉,模块,组件,js,路由 From: https://blog.csdn.net/2404_89388058/article/details/145065949