先废话一下,本人做前端3年到4年,在很小的几家小公司待过,一直一个人做前端,前端水平自认为应该算基础入门了。这是第一篇文章,分享给有相似需求的伙伴。
本文使用场景:
1.新入职一家国企,要熟悉的一个项目是用原生手撸不带框架的,并且也没做过模块化和抽象,改样式都是用过js写 style.display === "none" 这样做的。
2.领导要求让代码能模块化,提高可读性,至少知道哪里错了可以快速找到(纯原生dom获取,改值,每个元素起个id,用监听实现事件处理,阅读代码找bug真的很蛋疼)。
3.本项目是移动端用的(原生android套壳混合开发),重要的事情说三遍,本次改造不适用IE,不适用IE,不适用IE。
4.本人就前端刚入门的水准,大神有更好的方法请指导。
5.这点很重要,这个例子不是说之前的项目作者写的不好,至少他在原生js代码基础上就比我好很多,这篇文章只为小部分朋友最小代价快速改造传统dom操作项目之用
处理方式简述:
1.提高可读性,首先代码不能插入内容,操作样式都用原生js写吧,于是引入了Vue。
2.由于在使用Vue动态组件的时候,发现渲染切换内容区时在移动端达到了2~3秒之多才出现内容,想了想,大概是由于组件不是异步加载导致的。(本项目是改造,不是推翻重来,所以不可以通过Vue脚手架+webpack打包这种形式做)所以引入requirejs进行模块化异步加载。
3.小结,基本就是vue做模板渲染,requirejs进行模块化异步加载,这样改动是最小最快的,并且代码可读性大大提高。
看一下原来的项目写法:
其中一个页面是这样的(这里只是一部分截图,总的model大概有32个,太长了截不了),据说一个model就是一个流程操作页面,为什么这么做,因为流程中不做数据提交,如果多页面很难传递上一步页面的数据。所以用js操作dom显隐来控制数据不丢失,最后从页面元素拿数据。html模板长度723行,这里相当于把32个页面的流程做在一个页面上。
对应的js有2805行。我觉得除了用vue组件,模块化拆分这些步骤外,js的写法也有很大问题,举个例子,由于没有多页面,也就没有路由的概念。页面的前进和返回是这样的,一个物理返回监听,3个if语句里套了一个28个case的的switch,并且物理监听返回写过的东西,再在返回按钮的监听上再写一遍,头部也没有公用。
我改造的写法:
目录结构是这样的,vue基础库require基础库就不截图了,此外还包含主页面一个html,一个主入口js文件
目录结构requirejs配置文件
require.config({
baseUrl:'../js',
paths: {
"vue": "vue",
"components":"../Scene/js/components"
},
shim: {
}
});
主html只放了一个父组件标签,就不截图了
主入口js(我觉得注释很明白了,不解释了)
require(['require_config'],function(config){
require([
'vue',
'components/HeaderBar',
'components/ChooseType',
'components/NewScene',
'components/RuleType',
],
function(Vue,headerBar,chooseType,newScene,ruleType){
var rootPage = Vue.extend({
name: 'rootPage',
template:
`<div>
<!--以下为公共头部-->
<header-bar :title="currentTitle" @goBack="goBack">
<p v-if="currentScene==='newScene'" class="header_more_text" id="icon_confirm">保存</p>
<p v-if="currentScene==='ruleType'" class="header_more_text" id="rule_save">下一步</p>
</header-bar>
<!--以下为页面模块-->
<component v-model="datas" :is="currentScene" @changePage="changePage"></component>
</div>`,
components: {
headerBar,
chooseType,
newScene,
ruleType
},
data: function () {
return {
datas:{
temp_rule:{
type:1
}
},
components:[
{title:'选择场景类型',type:'chooseType'}
]
}
},
computed: {
//动态计算当前标题
currentTitle() {
return this.components[this.components.length-1].title;
},
//动态计算当前显示的页面模块
currentScene() {
return this.components[this.components.length-1].type;
}
},
methods: {
/*
* 此处的changePage,和goBack分表实现页面模板的来回切换方法
* 通过data中的components数组,
* 模拟浏览历史栈数据结构的先进后出的特点,
* 从而实现记录历史步骤
*/
//返回
goBack(){
if(this.components.length>1){
this.components.splice(this.components.length-1,1);
this.$set(this,'components',this.components);
}
},
//点击切换页面
changePage(type,title) {
var obj = {
title:title,
type:type
}
this.$set(this,'components',[...this.components,obj]);
console.log(this.components)
}
},
created(){
var self = this;
//监听物理返回
history.pushState(null, null, location.href);
window.addEventListener('popstate', function() {
self.goBack();
})
}
});
new Vue({
el: '#scene_wapper',
components: {
rootPage
}
})
})
})
公共头组件
define(['vue'],function(Vue){
return Vue.extend({
name: 'headerBar',
props: ['title'],
template:
`<div class="header bottom_border">
<div class="header_back" id="choose_back" @click="goBack">
<img src="../image/Header_Back.png" alt="Arrow">
</div>
<div class="header_content">
<p class="header_content_text">{{title}}</p>
</div>
<div class="header_more">
<slot></slot>
</div>
</div>`,
methods: {
goBack(){
this.$emit('goBack');
}
}
});
})
带双向绑定的ruleType组件示例(其他都是类似模块,不展示了)
define(['vue'], function(Vue) {
return Vue.extend({
name: 'ruleType',
model:{
prop: 'datas',
event: 'change'
},
props: ['datas'],
template:
`<div class="scene_content">
<div class="scene_margin"></div>
<div class="scene_list bottom_border" @click="changeType(1)" :class="datas.temp_rule.type==1?'active':''">
<div class="scene_list_name" style="width: 160px" id="local_rule_name">
本地<span class="scene_tips">(无网络时可用)</span>
</div>
<img class="scene_list_confirm" src="../image/Scene_confirm.png" alt="check">
</div>
<div class="scene_list bottom_border" @click="changeType(2)" :class="datas.temp_rule.type==2?'active':''">
<div class="scene_list_name" id="cloud_rule_name">云端</div>
<img class="scene_list_confirm" src="../image/Scene_confirm.png" alt="check">
</div>
</div>`,
methods: {
changePage(type, title) {
this.$emit('changePage', type, title);
},
changeType(type){
this.datas.temp_rule.type = type;
this.$emit('change',this.datas);
}
},
mounted() {
}
});
})
这样只要在最后一步的组件里直接提交父组件的datas属性就好了,每个子组件只需要关心自己步骤的逻辑,改造示例暂时就是这样,有指导意见请各位看官慷慨赐教,不好之处请提意见我会虚心改正。
我始终是前端界的一个小菜鸟。
开源demo