从DOM操作看Vue&React的前端组件化,顺带补齐React的demo

简介:
前言

接上文:谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo

上次写完博客后,有朋友反应第一内容有点深,看着迷迷糊糊;第二是感觉没什么使用场景,太过业务化,还不如直接写Vue&react的源码分析,我感觉这里有必要说下我的认识。

首先,要写源码分析很难,第一是他本来就很难,所以一般我们是想了解他实现的思路而不是代码;

第二每个开发者有自己发风格,所以你要彻底读懂一个人的代码不容易,除非你是带着当时作者同样的问题不断的寻找解决方案,不断的重构,才可能理解用户的意图。

我们上一次做的事情其实就是根据自己实际的工作经验做了和外面框架类似的事情,虽然代码的健壮、优雅程度跟不上,但却和其它作者一样为解决同样的问题思考得出的方案,上次做的太晚了,后面就草草结束,事实上在我Demo过程中发现一个事实:业务代码都是差不多的,只是一些细节点不一样,所以决定产品质量的依旧是开发者的业务代码能力,框架只是助力而已。

不能了解作者的意图,不能提高本身的编程水平,就算用上了React&Vue这类组件化的框架,也组织不好代码;事实上这类代码因为是面向大型应用的,反而更考验一个人的架构能力,所以大家要多注重内在修养的提升哦。

下面我们进入今天的正题,这里依旧提供一些帮助理解的资料:

github

代码地址:https://github.com/yexiaochai/module/

演示地址:http://yexiaochai.github.io/module/me/index.html

如果对文中的一些代码比较疑惑,可以对比着看看这些文章:

【一次面试】再谈javascript中的继承

【移动前端开发实践】从无到有(统计、请求、MVC、模块化)H5开发须知

【组件化开发】前端进阶篇之如何编写可维护可升级的代码

预览

使用Vue的思考

因为第一个demo是Vue的,React应该也类似,对比之前的代码发现一个重要差异是:

DOM操作真的完全没有了!!!
对,是完全没有DOM操作了,这个是很牛逼的一件事情,因为我觉得有两个地方要摆脱DOM操作很难:

① 我的组件应该放到哪个容器内,我需要一个定位的元素,比如:

1 this.sortModule = new SortModule({
2     view: this,
3     selector: '.js_sort_wrapper',
4     sortEntity: this.sortEntity
5 });
明确的告诉了组件所属的容器

② 我比较疑惑像这类列表类型的事件该如何处理,因为一些必要参数是根据event获取的,比如:

1 listItemClick: function (e) {
2     var el = $(e.currentTarget);
3     //根据el做一些事情
4 }
关于这个Vue的作者认为应该将事件处理程序内联,做显示声明:

复制代码
你可能注意到这种事件监听的方式违背了传统理念 “separation of concern”。不必担心,
因为所有的 Vue.js 事件处理方法和表达式都严格绑定在当前视图的 ViewModel 上,它不会导致任何维护困难。实际上,使用 v-on 有几个好处:

扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。

因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。
当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何自己清理它们。
复制代码
<button v-on:click="say('hello!', $event)">Submit</button>
复制代码
1 methods: {
2   say: function (msg, event) {
3     // 现在我们可以访问原生事件对象
4     event.preventDefault()
5   }
6 }
复制代码
还有种常用的操作,比如radioList,点击当前选项便选择项目,我们一般的做法是这样的:

1 setIndex: function (i) {
2     this.index = i;
3     this.$('li').removeClass(this.curClass);
4     this.$('li[data-index="' + i + '"]').addClass(this.curClass);
5 }
这样做比较简单,但是会有一个问题,便是数据与dom表现的流程变了,正确的流程是index 变了,dom便根据数据做更新,比如Vue:

复制代码
1 setIndex: function (i) {
2     this.index = i;
3     //这部分逻辑Vue会自动实现
4     //this.$('li').removeClass(this.curClass);
5     //this.$('li[data-index="' + i + '"]').addClass(this.curClass);
6 }
复制代码
之前,不考虑性能,我们会直接根据数据重新渲染整个列表,就为一个简单的选中功能,而Vue&React却做到了局部渲染,这个是否牛逼,我相信这个将会是一个核心算法部分,后面有时间一定要深入了解。

根据以上局部解读,我们得到一个结论,只要达成两个条件,就能摆脱DOM操作:

① 知道组件所处容器

② 根据数据渲染页面

PS:我们这里是很简单的一环,没有考虑组件嵌套,组件通信等过于复杂的问题

那么如果达成了以上条件,我们能否做到业务逻辑中不包含dom操作呢?我们下面就来试试。

如何摆脱DOM操作

这里真的是demo类尝试,思维验证,便不使用之前过于复杂的业务逻辑了,这里将me目录拷贝一块出来,依旧以原来的代码做底层依赖,只要列表与顶部排序部分功能,这里为了简化实现,保持代码重用,我们这里直接想将entity模块复用,要求data中的对象必须是一个entity实例,这里第一步是抽象出来了list module模块,于是主控制器变成这样了,事实上这个时候已经没dom操作了:

复制代码
 1 initEntity: function () {
 2     //实例化排序的导航栏的实体
 3     this.sortEntity = new SortEntity();
 4     this.sortEntity.subscribe(this.renderList, this);
 5 },
 6 
 7 initModule: function () {
 8     //view为注入给组件的根元素
 9     //selector为组件将要显示的容器
10     //sortEntity为注入给组件的数据实体,做通信用
11     //这个module在数据显示后会自动展示
12     this.sortModule = new SortModule({
13         view: this,
14         selector: '.js_sort_wrapper',
15         sortEntity: this.sortEntity
16     });
17     this.listModule = new ListModule({
18         view: this,
19         selector: '.js_list_wrapper',
20         entity: this.sortEntity
21     });
22 },
23 
24 propertys: function ($super) {
25     $super();
26 
27     this.initEntity();
28     this.initModule();
29     this.viewId = 'list';
30     this.template = layoutHtml;
31     this.events = {};
32 }
复制代码
这里简单看看列表组件的实现,其实就是将原来根View的代码换个位置:

 View Code
就这种简单的改变,貌似便摆脱了DOM操作,页面所有的状态事实上是可以做到由数据控制的,但是这里没有形成“标签化”,似乎不太好,于是我们来试试是否能改造为标签化的代码。

我们这里的业务代码(module与entity)没有什么需要改动的,这里主要在底层做改造,这里在我看来是提供了一种“语法糖”的东西,这里的具体概念后续阅读Vue源码再深入了解,这里先照着做,这里看结果想实现,也是我们常用的一种设计方案,首先我们的index编程了这个样子:

复制代码
1 <article class="cm-page page-list" id="main">
2     <div class="js_sort_wrapper sort-bar-wrapper">
3         <mySortBar :entity="sortEntity"></mySortBar>
4     </div>
5     <myList :entity="listEntity" :sort="sort"></myList>
6 </article>
复制代码
复制代码
 1 (function () {
 2     require.config({
 3         paths: {
 4             'text': 'libs/require.text',
 5 
 6             'AbstractView': 'js/view',
 7             'AbstractEntity': 'js/entity',
 8             'ModuleView': 'js/module'
 9         }
10     });
11 
12     require(['pages/list.label'], function (List) {
13         var list = new List();
14         list.show();
15     });
16 })();
复制代码
PS:里面的js钩子基本无用了

这里标签化带来的好处是,根View中有一段实例代码可以不用与选择器映射了,比如这个:

1 this.sortModule = new SortModule({
2     //view: this,
3     //selector: '.js_sort_wrapper',
4     //sortEntity: this.sortEntity
5 });
因为处于组件中,其中所处位置已经定了,view实例或者entity实例全部是跟View显示注入的,这里根View中参考Vue的使用,新增一个components与components与entities属性,然后增加一$watch对象。

大家写底层框架时,私有属性或者方法使用_method的方式,如果是要释放的可以是$method这种,一定要“特殊化”防止被实例或者继承覆盖
复制代码
 1 define([
 2     'AbstractView', 'pages/en.sort', 'pages/mod.sort', 'pages/mod.list'
 3 ], function (AbstractView, SortEntity, SortModule, ListModule) {
 4     return _.inherit(AbstractView, {
 5         propertys: function ($super) {
 6             $super();
 7             this.$entities = {
 8                 sortEntity: SortEntity
 9             };
10             this.$components = {
11                 mySortBar: SortModule,
12                 listModule: ListModule
13             };
14             this.$watch = {
15 
16             };
17             this.viewId = 'list';
18             this.template = layoutHtml;
19             this.events = {};
20         }
21     });
22 });
复制代码
他这种做法,需要组件在显示后框架底层将刚刚的业务代码实现,使用组件生成的html代码将原来标签的占位符给替换掉。

这里在组件也需要明示根View需要注入什么给自己:

PS:事实上这个可以不写,写了对后续属性的计算有好处

//记录需要根View注入的属性
props:[sortEntity],
PS:底层什么时候执行替换这个是有一定时机的,我们这里暂时放到根View展示后,这里更好的实现,后续我们在Vue与React中去找寻

因为我们这里是demo类实现,为降低难度,我们为每一个组件动态增加一个div包裹层,于是,我们在跟View中,在View展示后,我们另外多加一段逻辑:

1 //实例化实体,后面要用
2 this._initEntity();
3 //新增标签逻辑
4 this._initComponent();
然后将实体与组件的实例化放到框架底层,这里实体的实例化比较简单(如果有特殊数据需求再说,这里只考虑最简单情况):

复制代码
1 _initEntity: function() {
2     var key, entities = this.$entities;
3     //这里没有做特殊化,需要注意
4     for(key in entities) {
5         this[key] = new entities[key]();
6     }
7 },
复制代码
而实例化组件的工作复杂许多,因为他需要将页面中的自定义标签替换掉,还需要完成很多属性注入操作:

复制代码
1 _initComponent: function() {
2     var key, components = this.$components;
3     for(key in components) {
4         //这里实例化的过程有点复杂,首先将页面的标签做一个替换
5         var s = ''
6     }
7 },
复制代码
复制代码
 1 _initComponent: function() {
 2     var key, components = this.$components;
 3     var el, attributes, attr, param, clazz, i, len, tmp, id, name;
 4 
 5     //这里实例化的过程有点复杂,首先将页面的标签做一个替换
 6     for(key in components) {
 7         param = {};
 8         clazz = components[key];
 9         //由原型链上获取根元素要注入给子组件的属性(这个实现好像不太好)
10         attributes = clazz.prototype.props;
11 
12         //首先获取标签dom元素,因为html是不区分大小写的,这里将标签小写
13         el = this.$(key.toLowerCase());
14         if(!el[0]) continue;
15 
16         if(attributes) {
17             for (i = 0, len = attributes.length; i < len; i++) {
18                 attr = attributes[i];
19                 name = el.attr(':' + attr);
20                 param[attr] = this[name] || name;
21             }
22         }
23 
24         //创建一个空div去替换原来的标签
25         id = _.uniqueId('componenent-id-');
26         tmp = $('<div component-name="' + key + '" id="' + id + '"></div>');
27         tmp.insertBefore(el);
28         el.remove();
29         param.selector = '#' + id;
30         param.view = this;
31         this[key] = new components[key](param);
32     }
33 
34 },
复制代码
于是这个标签便能正常展示了:

复制代码
1 <article class="cm-page page-list" id="main">
2     <div class="js_sort_wrapper sort-bar-wrapper">
3         <mySortBar :entity="sortEntity" :myname="111"></mySortBar>
4     </div>
5     <myList :entity="sortEntity" :sort="sort"></myList>
6 </article>
复制代码
后面想要把这段代码去掉也十分轻易,我这里就不进行了:

1 'click .js_sort_item li ': function (e) {
2     var el = $(e.currentTarget);
3     var sort = el.attr('data-sort');
4     this.entity['set' + sort]();
5 }
总结

这里首先根据上次Vue的demo产生了一些思考,并且以简单的demo验证了这些思考,楼主在使用过程中发现Vue很多好的点子,后续应该会深入研究,并且以实际项目入手,这里回到今天的正题,我们使用React实现上次遗留的demo。

React的实现

在我最初接触React的时候,React Native还没出现,所以很多人对React的关注不高,当时做移动端直接放弃了angular,以体量来说,React也不在我们的考虑范围内,谁知道野心勃勃的Facebook搞出了React Native,让React彻底的跟着火了一把,事实上只要有能力以JavaScript统一Native UI的公司,这个实现就算换个框架依旧会火,虽然都已经这么火了,但是React的文档却不怎样,我后续也有试水React Native的兴趣,届时再与各位分享。

PS:根据之前的反馈,这次demo稍微做简单点,也只包含顶部导航和列表即可:

 View Code
他这个语法据说是让开发变得更简单了,我反正是不喜欢,这里有个不好的地方,之前数据实体全部是在根View上实例化的,然后注入给子View,React这里属性完全独享了,现在我触发了状态的改变,如何通知到list组件重新渲染排序呢?

React 组件通信

这里React子组件之间如何通信暂没有研究出来,所以将需要通信的数据做到了父组件中
 View Code
总结

react的中文文档整理较差,很多资料找不到,jsx语法比较怪异,不是所有人能接受,我去找模板循环时候压根就没找到,所以jsx有个特点,他让你不得不去拆分你的组件,在我写React代码中,感觉React代码控制力度要重一点,但是如果没有良好的架构能力,我可以毫不夸张的说,你依旧写不好业务代码。

至于React与Vue的优劣,这个方面见仁见智吧,好了今天的文章到此为止。

后续我们可能会深入分析下Vue的实现,在React Native上做深入,有兴趣的同学可以持续关注。

文章有任何不足错误,请您提出,因为小钗也是第二次使用React写demo,如果使用不当请多包涵





本文转自叶小钗博客园博客,原文链接:http://www.cnblogs.com/yexiaochai/p/5515264.html,如需转载请自行联系原作者

相关文章
|
11天前
|
前端开发 JavaScript Java
SpringBoot项目部署打包好的React、Vue项目刷新报错404
本文讨论了在SpringBoot项目中部署React或Vue打包好的前端项目时,刷新页面导致404错误的问题,并提供了两种解决方案:一是在SpringBoot启动类中配置错误页面重定向到index.html,二是将前端路由改为hash模式以避免刷新问题。
54 1
|
7天前
|
JavaScript 前端开发 小程序
一小时入门Vue.js前端开发
本文是作者关于Vue.js前端开发的快速入门教程,包括结果展示、参考链接、注意事项以及常见问题的解决方法。文章提供了Vue.js的基础使用介绍,如何安装和使用cnpm,以及如何解决命令行中遇到的一些常见问题。
一小时入门Vue.js前端开发
|
11天前
|
前端开发 JavaScript UED
react或者vue更改用户所属组,将页面所有数据进行替换(解决问题思路)____一个按钮使得页面所有接口重新请求
在React或Vue中,若需在更改用户所属组后更新页面所有数据但不刷新整个页面,可以通过改变路由出口的key值来实现。在用户切换组成功后,更新key值,这会触发React或Vue重新渲染路由出口下的所有组件,从而请求新的数据。这种方法避免了使用`window.location.reload()`导致的页面闪烁,提供了更流畅的用户体验。
22 1
react或者vue更改用户所属组,将页面所有数据进行替换(解决问题思路)____一个按钮使得页面所有接口重新请求
|
11天前
|
JavaScript 前端开发 应用服务中间件
本地运行打包好的React、Vue项目
本文讨论了如何本地运行打包好的React和Vue项目,并解决了使用React-Router时Tomcat部署刷新页面导致404的问题,提出了将请求转回index.html的解决方案。
13 1
本地运行打包好的React、Vue项目
|
10天前
|
前端开发 JavaScript API
React、Vue.js 和 Angular前端三大框架对比与选择
前端框架是用于构建用户界面的工具和库,它提供组件化结构、数据绑定、路由管理和状态管理等功能,帮助开发者高效地创建和维护 web 应用的前端部分。常见的前端框架如 React、Vue.js 和 Angular,能够提高开发效率并促进团队协作。
26 4
|
14天前
|
JavaScript 前端开发 算法
vue和react的区别是什么?
vue和react的区别是什么?
|
11天前
|
JavaScript 前端开发
react字符串转为dom标签,类似于Vue中的v-html
本文介绍了在React中将字符串转换为DOM标签的方法,类似于Vue中的`v-html`指令,通过使用`dangerouslySetInnerHTML`属性实现。
25 0
react字符串转为dom标签,类似于Vue中的v-html
|
9天前
|
JavaScript 前端开发 开发者
深入浅出 Vue.js:构建响应式前端应用
Vue.js 是一个流行的前端框架,以其简洁、高效和易学著称。它采用响应式和组件化设计,简化了交互式用户界面的构建。本文详细介绍 Vue.js 的核心概念、基本用法及如何构建响应式前端应用,包括实例、模板、响应式数据和组件等关键要素,并介绍了项目结构、Vue CLI、路由管理和状态管理等内容,帮助开发者高效地开发现代化前端应用。
|
12天前
|
JavaScript 前端开发
react Or vue中引入animate.css
本文介绍了如何在React或Vue项目中引入animate.css库来使用动画效果,包括通过npm安装或在线链接引入,并展示了如何在React组件和Vue路由过渡中使用动画类。
28 0
|
8天前
|
JavaScript
vue组件中的插槽
本文介绍了Vue中组件的插槽使用,包括单个插槽和多个具名插槽的定义及在父组件中的使用方法,展示了如何通过插槽将父组件的内容插入到子组件的指定位置。
下一篇
无影云桌面