项目习惯
在开始项目的时候,想想业务逻辑,可以画画图看看各个业务之间的联系。再先划分目录,比如在 vue 项目中,划分 src 的目录,别一开始直接动手。
vue-cli4
(不选择 vue-router 和 vuex 的情况下)中已经默认在 src 目录下建立了 views
、assets
、components
三个文件夹。
大目录划分
这时候,我们需要新建几个目录。
新建
store
目录,用于 vuex新建
router
目录用于 vue-router新建
plugins
用于之后可能会引用的插件库,如element-ui
新建
network
用于网络请求的封装,先建立一个简单封装axios
的 js 文件新建
common
目录,用于存放可能会定义的常量、工具代码、混入代码等,里面可能放置utils.js
、const.js
、mixin.js
这种文件。
大的目录划分好了之后再划分小的目录。
小目录划分
在
components
中新建几个目录- 新建
common
目录,里面存放完全公共的组件,与业务完全无关的组件,可以直接拖到下个项目中复用的组件 - 新建
content
目录,里面存放与业务相关的组件,可能是多个组件复用了这个组件,所以放到这里更加合适,无法被下个项目复用
- 新建
在
assets
中新建几个目录- 新建
css
目录,存放公共样式及初始化样式。在创建项目的时候,一般都会直接去找到normalize.css
这个 css 文件,并引入,github 上直接搜或者npm install normalize.css --save
就行,用于把各种标签让它们样式变得差不多,风格统一。之后你可以自己新建一个base.css
,用于控制项目整体的基本设置。先把这两个 css 文件搞定,样式的初始化就完成了。之后自己如果还有代码,就接着写在base.css
里。 - 新建
img
目录,存放图片,到时候每个页面用到的资源也单独分一个文件夹,如在 img 目录下新建home
、detail
、profile
等文件夹 - 新建
font
目录,存放可能引用的字体图标(阿里矢量图标库)
- 新建
单个文件整理
.editorconfig
在根目录下新建 .editorconfig
,里面配置项目的代码风格,vue-cli4
中已经自动生成了,但你可以修改。
1 | root = true 根据 root 来解析,只有 root 为 true 时才会解析下面的。 |
vue.config.js
在根目录下新建 vue.config.js
,可以自定义 webpack
配置。一般先配置一下目录别名
1 | module.exports = { |
base.css
在 assets/css
目录下新建 base.css 文件夹
1 | @import "./normalize.css"; |
引入 request.js 的网络请求
将每个页面需要请求的接口全部再多封装一层,达到统一控制的效果,只需要控制新的这个 js 文件,就可以改变多个页面的请求配置。举例,比如有个 home 组件需要发送请求,我们就在 network 目录下新建一个 home.js 来统一控制
1 | // network/home.js |
import 的顺序与分组
在组件中引入已封装好的内容时,需要分组,如下,下面的注释内容在真正写的时候应该为空行,用于分组。
1 | <script> |
复杂数据的处理
遇到复杂数据时,先决定一下要使用什么样的数据结构再开始写
1 | goods: { // 举例总共三组数据,每组数据当前 page 都不一样,需要分组记录,各自独立自己的 page |
提取数据的处理
有时候会遇到很分散的数据,他们分布在不同对象中,他们里面有很多属性(可能有的数据对我们来说一点用也没有,因为它们是给 Android 或 iOS 用的,大家用的都是同一个 API 服务器),但我们需要同时使用它们这几个对象里面的某几个属性。这时候就需要把松散的数据整合一下,再进行处理。
1 | // detail.js |
对象的方法的调用(异步)
我们在对对象上的方法进行调用时,需要先做一层判断,如果存在这个对象(或者对象不为空),我们才尝试进行调用方法,否则能回变成调用 null 这个对象上的方法。
1 | let obj = null |
数组长度的获取(异步)
我们在获取数组的长度时,需要先做一层判断,该数组长度是否为 0,之后再获取长度。并且之后如果有某个属性想实时监听数组的长度,可以使用 watch 来监听该数组的变化。
1 | { |
计算属性的返回值
在计算属性中,我们可能要根据请求的结果返回不同的值,我们可以使用 ||
1 | // 某个可复用组件中 |
better-scroll
它是移动端的滚动插件。优化滚动,原生滚动太卡了,并且这个插件有触底回弹效果。它是用 transform: translate
来实现上下滚动的,所以写在 .wrap
里的原生的 position: fixed
会失效,因为并不是由于内容高度撑起导致的滚动条,所以 fixed
无效。在 translate
向上移动的时候, 被 fixed
的元素也被上移了。
注意点
它需要挂载 DOM,所以应该在 mounted 钩子内调用,并且必须给该容器元素一个固定高度,该高度必须小于它的直接子元素高度,并且它的直接子元素只允许有一个,我们可以在它的直接子元素内填写相应的内容,不允许直接把内容直接添加在容器元素内并成为容器元素的直接子元素。
最后记得给容器加上 overflow: hidden
由于它替代了原生的滚动,所以如果需要对滚动进行处理,需要先拿到 new BScroll 这个对象实例,使用它的方法来触发滚动事件。
refresh
注意涉及异步的操作都需要使用 BS 的 refresh()
方法来重新确定 .content
这个 DOM 的高度,如涉及图片加载需要用到 图片的 onload 事件,表示图片加载完成,再调用一次 refresh()
,网络请求成功的 success 也需要 refresh()
。
keep-alive
如果使用 BS 的组件被包裹在 <keep-alive>
内,则每次 activated
都需要先 refresh
,再进行其他的 BS 操作,否则高度获取会出现问题。如果想保存当前 BS 高度,让下次激活页面时仍停留在这里,写法是
1 | { |
关于图片、请求等异步操作
如果需要获取涉及图片的 DOM 元素的高度,需要用到 图片的 onload 事件,表示图片加载完成,此时再获取 DOM 才是真正的高度,否则由于图片异步加载,获取高度时图片可能还未加载,高度为 0,获取到错误高度。应该在 onload 事件的回调函数中获取 DOM 高度。
同理,发送网络请求也应该在 success 的回调函数中获取 DOM 高度。
1 | <!-- 正确写法 --> |
当然,如果用原生的 scroll (overflow: scroll)
,就只需要一层包装即可,不像上面需要两层才能写真正的内容。
插件库的依赖问题
不要直接依赖任何插件库,最好都自己再封装一层,让这些组件依赖于自己封装的 js 文件,让自己封装的 js 文件依赖于插件库,这样如果插件库出了问题,就只需要修改自己封装的 js 文件即可,不需要到每个组件中去替换并引入新的插件。
1 | // 不要这样写 |
防抖节流
虽然我们有时要用到防抖节流,但用户卡了也不知道是网页的问题还是网络的问题。。就这样,卡了等于自己的问题hhh
防抖
当某个事件频繁触发导致某个事件处理函数频繁调用的时候,可以考虑使用防抖来提升性能。将被频繁调用的函数传入 debounce 来生产新的优化过后的函数,并把事件处理函数修改成它。效果是,当连续的几次事件触发事件非常短时,前几次方法都将被取消 (clearTimeout)
,只调用最后一次方法。
1 | function debounce(func, delay) { |
节流
在一定时间内只调用一次真正的处理函数 (外面的 if 将被执行多次)
。不管事件触发多少次。
1 | function throttle(func, delay) { |
z-index
z-index
只对定位元素起效果,所以要用的话,至少加个 position: relative;
如果元素脱离了标准流,如果它和另一个标准流元素在同一个位置展示,它将会覆盖在标准流元素的上面。
数据提交(文件上传)
注意:当一个表单包含<input type="file">
时,表单的 enctype
必须指定为 multipart/form-data
, method
必须指定为 post
,浏览器才能正确编码并以multipart/form-data
格式发送表单的数据。
出于安全考虑,浏览器只允许用户点击<input type="file">
来选择本地文件,用JavaScript对<input type="file">
的 value
赋值是没有任何效果的。当用户选择了上传某个文件后,JavaScript也无法获得该文件的真实路径。
常见的数据提交有两种形式(还可以有 websocket,但这种方法需要服务器单独开发接口,而且它也无法获取上传进度,比较少见,还有 flash,已经淘汰了)。
form
使用 form 进行提交,这是比较古老的提交方式,在以前都是后端老哥干的,那时候没有 ajax,由于这种提交方式是同步的,并且点击之后直接跳转到请求地址,所以一般会在页面内嵌入 iframe。
表单有几个属性用来控制提交表单时的行为,详见 MDN
1 | <form action="url" method="post" enctype="multipart/form-data"> |
AJAX
这种就是我们比较熟悉的写法了,在 Vue 中一般通过 axios 来完成。
FormData 接口提供了一种表示表单数据的键值对的构造方式,经过它的数据可以使用了XMLHttpRequest.send()
方法送出,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data"
,它会使用和表单一样的格式。MDN
1 | <template> |
axios
在 axios 中,发送 post 请求时,它默认会将请求头的 Content-Type
变成 application/json;charset=utf-8
,然后直接发送 JSON 数据,但实际上,我们的后端一般要求的都是 'Content-Type': 'application/x-www-form-urlencoded'
,这时候我们需要先将请求头的 Content-Type
改一下,然后引入 qs,用于修改我们的数据格式,后端需要的数据一般都是键值对,而不是 JSON,因为后端处理起 JSON 格式较为麻烦。
1 | /** |
Vue 的 data 数据重置
this.$options.data
里保存着最开始定义的 data 函数,可以通过 Object.assign(this._data, this.$options.data)
来重置成最开始我们设置的数据,但是它也会把网络请求得到的数据一并消除,因为最开始定义的那些数据都为空值。this.$options.data
实际是 this.$options.__proto__.data
, data
从原型链上继承而来。
日期格式处理
请求时我们经常从后端拿到时间戳,并且页面展示的要求是 yyyy-mm-dd
,所以我们需要对它进行转化,而由于时间戳转日期格式的需求非常常见,所以我们需要把它封装一下以供复用。不要每次都用 + 拼接字符串了。
不过, js 里没有内置这种格式化日期的方法,需要手动实现,其他语言大部分都有。
1 | /* utils.js */ |
关于 this.$nextTick()
的使用
由于 mounted
不会承诺所有的子组件也都一起被挂载,因此可能会遇上这样的情况。
在页面中,如果引入了很多组件,那么显然,当该页面自身的元素渲染完毕时,组件内的元素可能还未渲染完毕,此时获取 DOM 可能并不正确,如果希望在全部组件都渲染完毕时 (图片等资源的内容可能是异步的,但该元素已被加载到页面中,此时可能拿不到正确的图片大小) 获取 DOM,可以在 mounted
钩子中加入 this.$nextTick
,它里面的回调函数将在下一次 DOM 渲染完毕时调用。
1 | { |
for … in …
for ... in ...
是对象遍历使用的,但如果用在数组上,返回的 key 就是下标,而且是字符串类型的下标。注意使用。
判断一个数的区间
需求是给一个数,拿到该数所在的区间下标。
1 | const zo = [0, 2000, 4000, 6000] |
Vuex
每个 mutation 都建议执行单一功能,比如在购物车中,可能会有 addCart (添加购物车) 的 mutation,它里面可能会有 商品数量 + 1 ,和添加新商品两种功能。我们应该把这两种功能写成两个 mutation,而不是被整合到 addCart 中。而判断数量 + 1还是添加新商品的逻辑,建议放到 action 中,它不止可以处理异步,还可以处理复杂的逻辑。
Vuex 的文件管理
Vuex 的各个部分也应该被拆分。以下是简单的购物车例子
1 | // store/index.js |
css 顺序
改变布局 》 改变盒子的 》 颜色之类的普通样式调整。如
1 | .content { |
移动端的点击优化
由于移动端的 click 事件默认有 300ms 的延迟判断时间(用于判断是否为双击放大),但我们基本上不需要这个 300ms 的延迟。所以引入 fastClick 来处理这 300ms 的延迟。
1 | // npm 安装 |
图片懒加载
有时候我们希望当我们当前视口里能看到图片时才加载图片,可以使用 vue-lazyload 插件。
1 | // npm 安装 |
px2rem、px2vm
移动端响应式的解决方案,将 px 转换成 rem 或者 vw。可以下载 px2rem 或 px2vw 这种插件,它们可以帮我们写的 px 在打包时转化为 rem 或 vw,两个选一个即可。
1 | // 安装 px2vm,它的配置项需要单独新建一个 postcss.config.js |
题外
另一种封装及使用组件的方式
我们希望在全局都能使用某个弹窗(toast)时,可以使用这种形式的组件封装。
1 | // 组件内容在该代码框最后。 |
判断 undefined 和 null
判断值是否为 undefined 或 null,可以用 obj == null
来判断,如果 obj 为 undefined,为真,如果为 null,也会真,这是偷懒的写法。正常应该是 obj === undefined || obj === null
行内 background-image 样式
在行内写 backgound-image 的三元表达式的时候,一定要加 ()
!!!!
1 | <!-- 错误写法 --> |