前端最佳实践(以 Vue.js 为特色)
前端 Web 开发的最佳实践
前端 Web 开发的最佳实践
CSS
边界元法
块元素修饰符是编写可维护 CSS 的概念。
编写 CSS 对某些开发人员来说可能是一种负担。复杂的设计需要大量且易于维护的代码,并且易于修改。
这就是 BEM 的用途。BEM 代表块、元素、修饰符,其理念是始终以块为单位来编写样式。
例如
<button class='button'>
<span class='button__text'>Submit<span>
<img class='button__icon' src='icon.svg' alt='' />
</button>
.button {
display: flex;
align-items: center;
background: lightgreen;
padding: 0 1.6rem;
}
.button__text {
font-size: 2rem;
}
.button__icon {
width: 1.4rem;
height: 1.4rem;
}
块是具有一个或多个子元素的有意义的标记块(在上面的例子中,span 和 img 元素都是按钮的子元素)。
元素是每个块的子元素。
那么修饰符呢?
如果上面的提交按钮还需要一个disabled
将按钮背景颜色更改为灰色的类怎么办?
例如 Vuejs
<button :class="{
'button': true,
'button--disabled': disabled
}">
<span class='button__text'>Submit<span>
<img class='button__icon' src='icon.svg' alt='' />
</button>
.button--disabled {
background: lightgrey;
}
萨斯
使用颜色、字体、大小和断点。
设计系统是确保一致性的统一概念。
前端开发人员在编写任何代码之前必须努力理解这些设计系统并寻找重复模式。
地图获取
实现一致性的最佳方法是拥有单一事实来源。在 Sass 中,我们有一个名为 的实用程序map-get
,可以非常轻松地实现这一点。
例如variables.scss
$colors: (
blue: #4286f4;
lightblue: #b8d1f9;
);
$font: (
main-family: sans-serif,
weight-thin: 300,
weight-medium: 500,
weight-fat: 700,
base-size: 62.5%,
smaller-size: 50%
);
$breakpoints: (
small: 50em,
medium: 76.8em,
large: 102.4em,
larger: 128em,
extra-large: 144em
);
$sizes: (
header-height: 6rem
);
例如App.vue
<template>
<div class='app'>
<div class='app__header'>
<span class='header__text'>Hello World</span>
</div>
</div>
</template>
// use the scoped atrr in components
<style lang="scss">
@import "./variables";
html {
font-size: map-get($font, base-size);
font-family: map-get($font, main-family);
font-weight: map-get($font, weight-thin);
box-sizing: border-box;
@media screen and (max-width: map-get($breakpoints, small)) {
font-size: map-get($font, smaller-size);
}
}
.app {
display: flex;
flex-direction: column;
background: map-get($colors, blue);
&__header {
width: 100%;
height: map-get($sizes, header-height);
background: transparent;
display: flex;
justify-content: center;
align-items: center;
.header__text {
color: map-get($colors, lightblue);
}
}
}
</style>
颜色、字体、大小和断点必须在 中定义variables.scss
,并在需要时使用。应避免使用 中尚未定义的值(例如颜色、字体、大小和断点)variables.scss
。如果需要创建新值(例如设计师添加了新颜色),请将其添加到 中variables.scss
,然后与 一起使用map-get
。
响应式设计
关于像素完美
设计师和开发人员拥有共同的目标,那就是让产品栩栩如生,但他们使用不同的工具来实现这一目标。
当设计师交付原型时,他/她期望开发人员能够负责将其转换为实时的网页版本。然而,他/她常常忘记,网页是一个动态平台,其宽度/高度会根据用户的设备而变化,因此设计必须适应这一现实。
像素完美网页设计的实用观点
手机、平板电脑和台式电脑。这些是网站设计中最常见的屏幕分辨率。
- 开发人员应努力使模型尽可能接近这些分辨率,并确保它在不常见的分辨率下仍然看起来不错(水平和垂直调整浏览器的大小,一切都应该看起来一致且到位)。
- 当不常见的解决方案破坏设计时,设计师应该帮助开发人员解决这些情况。
停止使用px
,rem
改用
px
是固定的计量单位。“固定”听起来对你来说合适吗?
不合适?为什么你还在用它?
rem
是相对计量单位,这意味着它的值直接取决于一个相对(根)值(通常通过font-size
设置目标html
标签来设置)。如果该根值发生变化,则 中表示的值rem
也会随之变化。
将 html 设置font-size
为是一个好习惯10px
。这样,你就能更轻松地编写 css 代码px
(就像你习惯的那样),但rem
其他所有代码都可以用它。
例如
html {
font-size: 10px;
}
button {
font-size: 1rem; // equals 10px
}
span {
font-size: 1.6rem; // equals 16px
width: 20rem; // equals 200px
height: 14rem; // equals 140px
}
有什么好处?
如果我们现在将 html 更改font-size
为8px
,则设置的所有内容rem
现在将重新评估为20%
less ,因此看起来会更小。
例如
html {
font-size: 8px;
}
button {
font-size: 1rem; // now equals 8px
}
span {
font-size: 1.6rem; // now equals 12.8px
width: 20rem; // now equals 160px
height: 14rem; // now equals 112px
}
何时使用 %
当你需要设置元素子元素的尺寸(宽度/高度)时,百分比单位会非常方便。
你经常会发现,flexbox
每次使用百分比设置尺寸时,你都会用到它。
例如Navbar.vue
<template>
<nav class='navbar'>
<a class='navbar__link' href="#pricing">Pricing</a>
<a class='navbar__link' href="#ourwork">Ourwork</a>
<a class='navbar__link' href="#about">About</a>
<a class='navbar__link' href="#legal">Legal</a>
</nav>
</template>
<style lang="scss" scoped>
// Whe may want to give the first 3 links more importance/space
.navbar {
display: flex;
// Setting the width with percentage will keep the links space distribution as we
intended even when the screen width changes
&__link {
width: 30%;
&:last-child {
width: 10%;
}
}
}
</style>
编写媒体查询
em
在媒体查询中使用
在链接https://zellwk.com/blog/media-query-units/px
中,你会看到一个使用、rem
和编写媒体查询的实验em
。唯一实现跨浏览器一致性的单位是em
单位。快速阅读该链接,然后再回来。
根据需要编写尽可能多的媒体查询
我们通常有 3 个主要断点(移动设备、平板电脑和桌面设备),但这并不意味着您应该只使用这些断点。先从这些主要断点开始,然后调整屏幕大小,并注意是否存在损坏/不一致的元素。我保证,您会发现在很多分辨率下,您之前所做的调整看起来都很丑陋。
将媒体查询写在规则内部,不要为所有内容都编写一个媒体查询。这样,您就无需维护两个代码结构,并且能够快速了解元素在分辨率变化时会如何变化。
例如
代码如下
.container {
display: flex;
&__block {
width: 80%;
margin: 0 auto;
padding: 0 2.4rem;
@media screen and (max-width: 40em) {
width: 100%;
margin: unset;
}
@media screen and (max-width: 30em) {
padding: 0 1.6rem;
}
.block__text {
font-size: 1.6rem;
@media screen and (max-width: 40em) {
font-size: 1.4rem;
}
@media screen and (max-width: 30em) {
font-size: 1.2rem;
}
}
}
}
不是这个
.container {
display: flex;
&__block {
width: 80%;
margin: 0 auto;
padding: 0 2.4rem;
.block__text {
font-size: 1.6rem;
}
}
}
@media screen and (max-width: 40em) {
.container {
&__block {
width: 100%;
margin: unset;
.block__text {
font-size: 1.4rem;
}
}
}
}
@media screen and (max-width: 30em) {
.container {
&__block {
padding: 0 1.6rem;
.block__text {
font-size: 1.2rem;
}
}
}
}
JS
推荐阅读
你不懂 js
https://github.com/getify/You-Dont-Know-JS
这是一系列书籍,将向您展示JS的来龙去脉javascript
。如果您认真对待JS开发,则必须阅读所有这些书。
干净的代码(针对 js)
https://github.com/ryanmcdermott/clean-code-javascript
摘自鲍勃叔叔的原始清洁代码书,我向您介绍了一些概念/规则,可以让您的代码更具可读性(对于您将来的自己或您的同事而言)。
工具
现代前端开发使用现代工具,使开发体验更加流畅。
如今,几乎每个前端项目都会使用linter、formatter/format-rules和bundler。
截至 2019 年的行业标准如下:
- Linter:Eslint
- 格式化程序:Prettier(尽管我更喜欢标准版)
- 打包工具:Webpack
如果您正在使用,vue-cli
则无需担心配置这些,只需确保在创建项目时手动选择这些选项:
- 巴别塔
- CSS 预处理器 --> Sass/SCSS (使用 node-sass)
- Linter / Formatter --> ESLint + 标准配置 --> Lint 并提交时修复
Vue.js
风格指南
https://vuejs.org/v2/style-guide/请务必阅读官方的 Vuejs 样式指南。遵循那里给出的建议可以确保代码库的一致性,并且使用格式化程序可以使代码看起来更加标准化。
状态管理
当应用程序不断增长并开始变得复杂时,我们会发现我们需要谨慎组织代码。
状态管理模式有助于我们清晰地了解数据流。当你的应用包含多个组件可能共享的全局数据时,可以使用此模式。
不要将此模式用于由 UI 生成的数据(组件内部的状态),而是使用它来管理来自服务器的数据。
Vuex
https://vuex.vuejs.org/ Vuex 文档非常清晰,你应该花时间阅读并了解如何正确使用它。
以下是一些整理store
文件夹的建议。
为您处理的数据的每个有意义的概念构建一个模块(与 API 服务紧密耦合)
假设我们正在store
为一个电商应用构建文件夹。
我们需要以下模块:
- auth(用于处理用户身份验证)
- 产品(用于处理电子商务产品)
- 购物车(用于结账)
笔记:
-
假设每个 api 调用都会返回
[ res ]
“成功”或[ null, error ]
“失败”*
我将在本节中对此进行详细说明App architecture
。 -
操作根据需要执行 API 调用并提交变异。
-
操作返回
{ status }
其在调用该操作的组件中的使用情况(这对于在成功/错误时切换样式很有用)。
auth.js
import POSTauth from 'apiCalls/POSTauth'
export default {
state: {
token: '',
userName: ''
},
mutations: {
setToken(state, token) {
state.token = token
},
setUserName(state, userName) {
state.userName = userName
}
},
actions: {
async loginUser({ commit }, formData) {
const [res, error] = await POSTauth(formData)
if (error) {
return { status: 'error' }
} else {
commit('setToken', res.token)
commit('setUserName', res.userName)
return { status: 'success' }
}
},
},
getters: {
isAuth(state) {
return Boolean(state.token)
}
}
}
products.js
import GETproducts from 'apiCalls/GETproducts'
export default {
state: {
products: []
},
mutations: {
setProducts(state, products) {
state.products = products
}
},
actions: {
async getProducts({ commit }) {
const [res, error] = await GETproducts()
if (error) {
return { status: 'error' }
} else {
commit('setProducts', res.products)
return { status: 'success' }
}
},
}
}
cart.js
import POSTprocess_payment from 'apiCalls/POSTprocess_payment'
export default {
state: {
productsInBasket: []
},
mutations: {
handleProduct(state, { action, selectedProduct }) {
const addProduct = () =>
[...state.productsInBasket, selectedProduct]
const deleteProduct = () =>
state.productsInBasket.filter(prd => prd.id !== selectedProduct.id)
state.productsInBasket = action === 'add' ? addProduct() : deleteProduct()
}
},
actions: {
async processPayment({ state }) {
const [res, error] = await POSTprocess_payment(state.productsInBasket)
if (error) {
return { status: 'error' }
} else {
return { status: 'success' }
}
},
},
getters: {
totalPayment(state) {
return state.productsInBasket.reduce((a, b) => a.price + b.price)
}
}
}
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './auth'
import products from './products'
import cart from './cart'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
auth,
products,
cart
}
})
export default store
复杂组件状态
在某些情况下,使用 props 或 bus 事件处理状态很快就会变得繁琐。我指的是那些设计/本质就很复杂的组件,因此在构建时需要仔细考虑。
如果你仍然不明白,让我问你这个问题:
你是否曾经编写过一个组件,将许多 props 传递给中间组件,而该中间组件也传递这些 props?(这被称为 prop 钻探)
如果您的回答是,请继续阅读,否则您可能不需要我将要描述的模式。
提供/注入
https://vuejs.org/v2/api/#provide-inject此功能类似于 React 的 Context。它允许您在父组件中定义数据,并将其提供给其所有子组件,无论组件层次结构有多深,而无需手动向下传递。
通过工具带中的此功能,我们现在可以为复杂组件复制状态管理模式(从而更清晰地了解数据流)。
让我们构建一个包含两个输入(电子邮件和密码)和一个提交按钮的注册表单。
- 每个输入都应该有验证
- 如果输入无效,则应应用错误样式并显示错误消息
- 如果表单无效,则应禁用提交按钮
应用程序架构
编写可维护应用程序的一个关键方面是将其概念化为多个层,每个层都有自己的职责,它们共同构成应用程序的主体。
有些人称之为“关注点分离” ,但这个术语尚未得到足够清晰的解释,以至于开发人员误以为,为各个部分创建单独的文件html
就等于“关注点分离css
” js
。不,事实并非如此,这只是文件分离而已。
关注点分离是关于定义责任。
页面 - 组件 - networkLayer
我想到在我的项目中拥有以下文件夹的模式:
-
页面:这些文件负责将整个页面渲染到屏幕上(也可以称为视图)。这些文件会调用 store。
-
组件:这些文件代表页面内要使用的单元。
-
- apiCalls:这里列出了应用程序可以发出的所有 apiCall。这些 apiCall 在 store 模块操作中调用。它们返回 或
[res]
。[null, error]
这里的想法是将发出服务器请求和管理请求数据的概念区分开来。 - store:这是要使用的状态管理设置。一个配置文件和一个
modules/
包含 store 模块的文件夹。
- apiCalls:这里列出了应用程序可以发出的所有 apiCall。这些 apiCall 在 store 模块操作中调用。它们返回 或
例如GETproducts.js
(api调用)
import axios from 'axios'
export const GETproducts = () =>
axios
.get(
'/products/'
)
.then((res) => [ res.data ])
.catch((error) => [ null, error ])