REM

REM

1.各种单位

  1. px 像素

  2. em 一个M的宽度,跟自身的 font-size 相同

  3. rem 根元素的 font-size

  4. vh : viewport height 视口高度 100vh === 视口高度

    vm: viewport width 视口宽度 100vw === 视口宽度

2.使用REM

当我们要写一个适配手机的页面的时候,我们就会陷入这样蛋疼的境地。

因为各种品牌,各种型号的手机分辨率是不一样的,这就很蛋疼了。

这时候就使用REM来解决这个问题。

  1. REM始终为根元素(html)的 font-size 的值。

  2. 手机页面的宽度 width 是永远不变的。

知道了上面两个前提,我们就可以做一些事情了。

  1. 通过 width的值设置根元素的 font-size。
  2. 通过 REM 定义各种宽和高,这样就是永远适配当前页面宽度的。

首先,添加 meta:viewport 标签

1
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

第二步,通过 width 计算根元素的font-size

1
2
var $width = window.outerWidth;
document.write(`<style>html{font-size:${$width/10}px}</style>`)

P.S. 虽然这个 fontsize/n 中的 n ,是你可以自己决定的,但不要忘记了

  1. 12px 原则,chrome浏览器会阻止你设置比 12px还小的字。

  2. 假如你换算出来的 px 太小,CSS会认为这是 0 或者 1。

第三步,写CSS

1
2
3
4
5
div{
height:3rem;
widht:3rem;
margin:0 .5rem
}

通过上面的换算,我们可以知道

  1. 根元素的 font-size = $width/10 , 也就是 十分之一个屏幕的宽度。
  2. 所以 3rem === 十分之三个屏幕宽度。
  3. 所以,无论在什么环境下,它都永远等于十分之三个屏幕宽度。
  4. 因此,在每一个设备上的观看效果都相同。

3.使用sass

每次都要计算,非常的麻烦,所以使用 sass 或者 less 帮我们计算。

安装sass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@function px( $px) {
@return $px/$designWidth*10+rem;
//这里为啥✖️10呢,因为在上面我们除以10了,所以要计算出比例值需要✖️10
//假设 屏幕宽度为 320px , 那么 root font-size:32px ,即 1rem === 32px ,把屏幕分成了10等份。
//但是 设计稿宽度为 640px , 里面还有一个高度为 320px 的 div,
//相对于设计稿,div的比例是 320/640=1/2 , 也就是占了5份的屏幕宽度 即为 5rem
//所以 height : 5rem
}
@designWidth:640; //根据设计稿的宽度来写。
body{
font-size: 16px;
}
.child {
width: px2rem(320);;
height: px2rem(160);
margin: px2rem(40) px2rem(40);
border: 1px solid red;
}

LESS

LESS

1.LESS是什么

LESS是一种动态样式语言。

LESS 将 CSS 赋予了动态语言的特性,如 变量, 继承,运算, 函数. LESS 既可以在 客户端 上运行 (支持IE 6+, Webkit, Firefox),也可以借助Node.js或者Rhino在服务端运行。

2.基本用法

1.变量
1
2
@border-color:#fff;
div{border:1px solid @border-color}
2.混合

混合可以将一个定义好的class A轻松的引入到另一个class B中,从而简单实现class B继承class A中的所有属性。我们还可以带参数地调用,就像使用函数一样。

1
2
3
.classA (@radius:5px){border-radius:@radius}
.classB {.classA}
.classC {.classA(10px)}

@arguments变量 像写函数一样写CSS , 理解为JS里的 arguments 即可

1
2
3
4
5
.box-shadow(@x:0 , @y:0 , @blur:1px, @color:#666){
box-shadow:@arguments
}
.box-shadow(1px,2px); // '调用函数'的感觉
.box-shadow(0,0,2px,#888)
3.嵌套

最常用的

我们可以在一个选择器中嵌套另一个选择器来实现继承,这样很大程度减少了代码量,并且代码看起来更加的清晰。(&表示当前元素)

1
2
3
4
5
6
7
8
.Parent{
font-size:12px;
> h1{ font-size : inherit}
> input {
color: black;
&:hover{ color: red}
}
}
4.函数&&运算

运算提供了加,减,乘,除操作;我们可以做属性值和颜色的运算,这样就可以实现属性值之间的复杂关系。LESS中的函数一一映射了JavaScript代码,如果你愿意的话可以操作属性值。

1
2
3
4
5
6
7
8
9
10
11
12
@the-border: 1px;
@base-color: #111;
@red: #842210;
#header {
color: @base-color * 3;
border-left: @the-border;
border-right: @the-border * 2;
}
#footer {
color: @base-color + #003300;
border-color: desaturate(@red, 10%);
}
5.引导

当我们想根据表达式进行匹配,而非根据值和参数匹配时,导引就显得非常有用。如果你对函数式编程非常熟悉,那么你很可能已经使用过导引。

为了尽可能地保留CSS的可声明性,LESS通过导引混合而非if/else语句来实现条件判断,因为前者已在@media query特性中被定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//匹配模式和引导表达式
.min(drak,@color){
color: darken(@color,20%);
//将颜色加深20%
}
.min(light,@color){
color: lighten(@color,20%);
//将颜色变浅20%
}
.min(@_,@color){
display: block;
}
@switch1:drak;
@switch2:light;

span{
//.min(@switch1,#ddd);
//.min(drak,#ddd);@color:#ddd
//.min(@switch2,#ddd);
//@_,接受任意值,你传什么值他都会执行;
.min(@word,#ccc);
}

/*---------------------------------*/
.mixin(@a){
color:@a
}
/*@a的color亮度大于50%的话*/
.mixin(@a) when (lightness(@a)>=50%){
background-color:black
}
.mixin(@a) when (lightness(@a)<50%){
background-color:white
}

.classA{.mixin(#ddd)}
.classB{.mixin(#555)}
//注释
/*
注释
* color函数
* 1. lighten(@color,10%):
* 意思:return a color which is 10% lighter than @color
* 2. darken(@color,10%);
* 意思:return a color which is 10% draker than @color
* 3. statuate(@color,10%);//增加饱和度
* 意思:return a color which is 10% statuated than @color
* 4. destatuste(@color,10%);//减少饱和度,褪色
* 5. fadein(@color,10%)
* 6. fadeout(@color,10%)
* 7. fade(@color,50%)
* 8. spin(@color,10)
* 9. spin(@color,-10)
* 10. mix(@color1,@color2);混合
*
*
* 提取颜色信息
* hue(@color):返回当前颜色的色调
* staturation(@color):返回当前颜色的饱和度
* lightness(@color):返回当前颜色的亮度(浅的度数)
* */

得到

1
2
3
4
5
6
7
8
.classA {
background-color: black;
color: #ddd;
}
.classB {
background-color: white;
color: #555;
}
6.计算功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//计算功能:任何颜色,变量都可以参与运算
@base:5%;
@filter:@base*2;//10%
@other:@base*4+@filter;//30%
.box{
color: #FFFF00;
background-color: @filter+#111;
height: 100%/2+@other;
}

@var:10px + 5;
#box1{
width: (@var + 20)*2;
height: (@var - 5)*3;
p{
height: (@var + 5)/2;
background-color: #101010+#000111;
border: @var - 5 solid #0000ff;
}
}

//less也支持部分Math函数
//round(1.67)->2
//ceil(2.1)->3
//floor(2.9)->2
//peercentage(0.5)->50%转为百分数的函数
#s{
width: 100px;
height: 100px;
border-radius: percentage(0.5);
}
7.Import && 字符串插值
1
2
3
4
5
6
7
import 'lib.less'
import 'lib' //可省略后缀名

import 'lib.css' //引入一个CSS,并且不让LESS对它进行处理

@baseURL = 'http://storm4542.github.com'
body{background: url("@{baseURL}/image/bg.jpg")} //类似JS 的 ${xxx}

移动页面开发

移动页面

1.移动页面的区别

  1. 没有 hover

  2. 没有 touch

  3. 没有 resize

  4. 没有 滚动条

  5. 要加一个

    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

  6. 会用 media query

2.媒体查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* wdith>=320px */
@media (min-width : 320px){
body{
background: red
}
}
/* wdith>=375px */
@media (min-width : 375px){
body{
background: orange
}
}
/* wdith>=425px */
@media (min-width : 425px){
body{
background: yellow
}
}
/* wdith>=768px */
@media (min-width : 768px){
body{
background: green
}
}
/* wdith>=769px */
@media (min-width : 769px){
body{
background: blue
}
}

语法 @media(条件)

注意:

  1. 上面的条件使用min-width,假如使用max-width,在width=768之内的时候都是绿色,因为他们都符合条件,根据CSS的语法,谁在后面谁起作用。
  2. 假如非要用max-wdith可以反过来写。大的在前面。
  3. 或者条件复杂一些@media(min-width:375px) and (max-width:424px)

你也可以写多个CSS文件,在 link 标签中限定条件

<link rel="stylesheet" href="./mobile-style.css" media="(max-width:452px)">

一些常用的设计模式

总结一些写轱辘的时候使用的设计模式(套路)

1.发布订阅模式

1
2
3
vm.$emit('update:selected',this.name) //发布
vm.$on('update:selected',(name)=>{this.name = name}) //订阅
vm.$off('update:selected') //取消订阅

2.单项数据流

使用eventBus

1
2
3
4
5
6
7
//父亲
let vm = new Vue({
data(){return { selected: 1 }}
})
vm.eventBus.$emit('update:selected', this.selected) //给儿子说,我给你传个现在最新的selected
vm.eventBus.$on('update:selected',(selected)=>{ this.selected = selected })
//监听儿子有没有让我修改selected
1
2
3
4
5
6
7
//儿子
let vm = new Vue({
data(){return { selected: 2 }}
})
vm.eventBus.$emit('update:selected',this.selected) //给爸爸说一声,我想修改一下 selected的值
vm.eventBus.$on('update:selected',(selected)=>{this.selected = selected})
//监听爸爸有没有 同意 或者 让 我改selected

流程: 爸爸修改儿子数据 —–> 直接告诉儿子

​ 儿子想修改自己的数据 —–> 告诉爸爸想要修改 ——> 爸爸修改数据 —–> 告诉儿子

让爸爸始终掌控大局。

3.正交原则

props 不要违反正交原则

例如,我们设计一个button组件

1
props:['rightIcon','leftIcon']

这两个属性就是不正交的,他们都可以改变icon的位置

修改

1
props:['iconPosition']  // right || left

4.写可测试的代码

Vuex

Vuex

1.Vuex是什么?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

单状态树和模块化并不冲突

2.安装并配置好Vuex

1
2
3
4
5
6
7
8
9
//main.js
import Vue from 'vue'
import stroe from './store' //引用文件夹即可,不用定位到文件

new Vue({
el:'#app',
store
//......
})

将 store 对象提供给store选项,可以把 store 的实例注入到所有组件

我这里没有使用Vuex文档上的方式,我把store放到了一个单独的文件夹里。

1
2
3
4
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

可以看到我们在index.js里使用了Vue.use(Vuex),这表明Vuex本质上来说是一个插件,里面有install方法。

3.State

state 可以理解为组件中的 data , 用于存储各种原始数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const state = {
isNum:true,
BookList:{
book1:{id:1,name:'莎士比亚'},
book2:{id:2,name:'哈姆雷特'}
}
}
export default new Vuex.Store({
state
})

在组件中使用Vuex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// component/book.vue
<template>
<div>
<p v-for="item of $store.state.BookList" :key="item.id">
{{item.name}}
</p>
</div>
</template>
<script>
import store from "../../store/index";
export default {
name:'book'
}
</script>

因为我们注入过 store,所以$store.state.BookList可以访问到state

也可以通过 computed获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<p v-for="item of booklist" :key="item.id">
{{item.name}}
</p>
</div>
</template>

<script>
import store from "../../store/index";
export default {
name:'book',
computed:{
booklist(){
return this.$store.state.BookList
}
}
}
</script>

假如我有很多数据,那么用上面这种方法就会使代码变得很长很难看,所以推荐使用mapstate辅助函数。

1
2
3
4
5
6
7
8
9
10
<script>
import store from "../../store/index";
import {mapState} from 'vuex'
export default {
name:'book',
computed:mapState({
booklist:state=>state.BookList
})
}
</script>

加入你的computed中的属性和state中的名字相同,那就而可以更方便了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 <template>
<div>
<p v-for="item of BookList" :key="item.id">
{{item.name}}
</p>
</div>
</template>

<script>
import store from "../../store/index";
import {mapState} from 'vuex'
export default {
name:'book',
computed:{
...mapState(['BookList'])
}
}
</script>xxxxxxxxxx <script>import store from "../../store/index";import {mapState} from 'vuex'export default {    name:'book',    computed:mapState({        booklist:state=>state.BookList   })}</script>

… :对象展开运算符 — ES6的新语法

4.Getter

有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数:

1
2
3
4
5
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}

如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

Getter 接受 state 作为其第一个参数。

现在我修改一下state,如果我们想找所有还在的书籍,就需要用到getter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const state = {
isNum:true,
BookList:[
{id:1,name:'莎士比亚',ishave:true},
{id:2,name:'哈姆雷特',ishave:true},
{id:3,name:'西游记',ishave:false},
{id:4,name:'红楼梦',ishave:true},
{id:5,name:'三国',ishave:false},
{id:6,name:'射雕',ishave:true}
]
}
const getters = {
findBook:(state)=>(ishave)=>{
return state.BookList.filter(book=>book.ishave===ishave)
},
/*
//上面的写法等价于,就是返回一个函数
findBook:(state)=>{
return (ishave)=>{
return state.BookList.filter(book=>book.ishave===ishave)
}
}
*/
changeNum:(state)=>{
state.isNum = false
},
booksCount:(state)=>{
return state.BookList.length
}

}

组件中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div>
<h3>所有的书</h3>
<p v-for="item of BookList" :key="item.id">
{{item.name}}
</p>
<h3>现在还存在的书</h3>
<p v-for="item of findBook" :key="item.id">
{{item.name}}
</p>
</div>
</template>

<script>
import store from "../../store/index";
import {mapState,mapGetters} from 'vuex'
export default {
name:'book',
computed:{
...mapState(['BookList']),
findBook(){
return this.$store.getters.findBook(true)
}
}
}
</script>

同样 getter 也有简写,上面的因为我们要传参,所以不能简写,getter 的简写形式和 state 差不多

...mapGetters(['xxx'])

1
2
3
4
5
6
7
8
9
10
11
import {mapState,mapGetters} from 'vuex'
export default {
name:'book',
computed:{
...mapState(['BookList','isNum']),
findBook(){
return this.$store.getters.findBook(true)
},
...mapGetters(['booksCount'])
}
}

如果想起一个别的名字

1
2
3
...mapGetters({
bookcount:'booksCount'
})

5.Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数

注意: Mutation必须是同步函数,目的是让 vue的devtool里方便捕捉

简单的来说,这就相当于Vue实例中的methods,修改东西,就要靠他commit.

我现在想要加一个给每个书籍增加数量的功能,就要使用mutations了。

在index.js里的mutations对象里写好逻辑,通过书名增加数量。

1
2
3
4
5
6
7
//index.js
const mutations = {
addBookNum(state,books){ //books = {name:'xxx',addNum:xx}
let thebook = state.BookList.filter(book=>book.name === books.name);
thebook[0].number+= books.addNum
}
}

然后在book.vue里提交,注意,mutations的方法写在methods里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<div>
<h3>所有的书</h3>
<p v-for="item of BookList" :key="item.id">
{{item.name}}, 还有 {{item.number}}本
<button @click="addBook({name:item.name,addNum:1})">增加1本书</button>
</p>
<h3>现在可以借走的书</h3>
<p v-for="item of findBook" :key="item.id">
{{item.name}} , 还有 {{item.number}}本
</p>
</div>
</template>

<script>
import store from "../store/index";
import {mapState,mapGetters} from 'vuex'
export default {
name:'book',
computed:{
...mapState(['BookList']),
findBook(){
return this.$store.getters.findBook(true)
},
...mapGetters(['booksCount'])
},
methods:{
addBook(options){
this.$store.commit('addBookNum', options) //{name:'莎士比亚',addNum:200}
}
}
}
</script>

这样,每一个后面就都加上了一个按钮,并且可以增加书籍的数量。

Vue的作者建议,使用常量代替Mutaions事件类型。具体使用看文档,这里就不写了。

在组件提交的方式

  1. this.$store.commit( 'fn' , playload)

  2. 如果不需要传参数可以使用 mapMutations

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    methods:{
    ...mapMutations([
    'addBook', //把this.addBook()映射为 this.$store.commit('addBook')
    'removeBook'
    ]),
    ...mapMutations({
    add:'addBook' // 把 this.add() 映射为 this.$store.commit('addBook')
    })

    }

6.Action

刚才说了 Mutation 只能为同步函数,那么处理异步的时候就需要 Action了。

  • Action 提交的是 mutation , 而不是更改 state
  • Action 可以包含任何异步操作

Action接收一个context参数,里面包含commit state getters

在actions里面异步提交 addBookNum

1
2
3
4
5
6
const actions = {
addBookNumAsync({commit},options){
setTimeout(()=>{
commit('addBookNum',options)
},3000)
}

在book.vue里通过dispatch触发。

1
2
3
4
5
6
7
8
9
methods:{
addBook(options){
this.$store.commit('addBookNum', options) //{name:'莎士比亚',addNum:200}
},
addBookAsync(options){
console.log(options);
this.$store.dispatch('addBookNumAsync',options)
}
}

上面是最简单的异步处理,

Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise

模拟一下更新书籍的操作

1
2
3
4
5
6
7
8
9
addNewBookAsync({commit}){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
let newBook = [{id:66,name:'水浒', number:12,ishave:true}]
commit('addNewBook',newBook)
resolve()
},3000)
})
}

在book.vue里

1
2
3
4
5
addNewBook(){
this.$store.dispatch('addNewBookAsync').then(()=>{
alert('更新成功')
})
}

在另外一个actions中也可以

1
2
3
4
5
6
7
8
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}

也可以使用 async/await

1
2
3
4
5
6
7
8
9
10
11
// 假设 getData() 和 getOtherData() 返回的是 Promise

actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

7.Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}

const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}

const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

7.总结

使用vuex的流程是这样的

action调用=>mutation修改=>state(发生改变的时候)=>getter