Hawei

Say Hi.


  • Home

  • About

  • Tags

  • Categories

  • Archives

proxy.md

Posted on 2020-04-12 | In 算法数据结构

proxy—(基础)

定义: proxy对js中的所有类型, 做一个代理, 所有操作都会先进入代理, 如果我们有设置对应操作的代理,那么优先使用我们自定义的操作(最常见是get/set).如果没有对应代理, 那么进入正常操作.

来看一下语法:

1
2
3
// target: 被代理的目标
// handler: 代理的行为函数
let p = new Proxy(target, handler);

来看一下mdn上面的示例, 我们能更好的理解proxy的行为

示例1: 代理object的get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 // 初始化一个handler, 该handler的内容是设置get方法, 如果我们读取了不在该对象上的属性, 则返回默认值37
let handler = {
get: function(target, name){
return name in target ? target[name] : 37;
}
};
// 初始化一个proxy对象 ,
// 参数1是一个空对象, 意味着我们没有对其他变量进行绑定
// 参数2是一个handler重写了get行为
let p = new Proxy({}, handler);

p.a = 1;
p.b = undefined;

console.log(p.a, p.b); // 1, undefined
// 此时,c不在p内, 所以返回默认值37
// 至此, 我们完成了对对象p, get方法的代理.
console.log('c' in p, p.c); // false, 37

代理转发, 数据绑定

简而言之, proxy构造函数的第一个参数,就是被绑定的元素。当我们修改proxy生成的实例时,修改会传递到被绑定的元素上。达到数据绑定的目的.

来看例子:

1
2
3
4
5
6
let target = {};
let p = new Proxy(target, {});

p.a = 37; // 操作转发到目标

console.log(target.a); // 37. 操作已经被正确地转发

验证对象赋值

我觉得代理有点像钩子的概念, 当我加入代理后, 那么我可以重写原有的默认行为(get/set), 也可以在原有的get/set增加逻辑, 就时钩子的概念啊.

这个例子重写了对象的set方法, 在set方法里验证对于属性’age’的赋值是否合法.并且保留默认的set行为.达到了验证的效果.

我们在vue/react都存在对应的props数据验证.在这个地方是否就是用proxy重写的?

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
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}

// The default behavior to store the value
obj[prop] = value;
}
};

let person = new Proxy({}, validator);

person.age = 100;

console.log(person.age);
// 100

person.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integer

person.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid

高阶应用之: 扩展构造函数

Reflect

这里要引入一个es2015的api.Reflect

Reflect是新增的一个内置对象, 这个对象不是构造函数, 无法使用new操作符,Reflect上所有的方法都是静态的.(类似Math.Refect对象)。

1
2
3
// 例如, get方法
var obj = {a: 1}
Reflect.get(obj, 'a') // ‘a’

所以,其实ReflectAPI实际上是, 把Reflect作为一个静态标识, 类似Math, 然后以第一个参数为this, 去调用this上的get方法.

注意了, 如果Reflect调用时, 对象的默认行为已经被修改, 那么此时, 会触发修改后的行为.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类似这种效果
var likeReflect = {
get (obj, key) {
return obj['a']
}
}

var obj = {
a: 1
}
var prObj = new Proxy(obj, {
get: function(obj, key){
return obj[key] * 3;
}
})
likeReflect.get(prObj, 'a')

到这里, 关于Reflect的基本知识就足够了.

构造函数扩展

平常,我们都是通过以下方式来完成一个构造函数的扩展, 有了proxy我们就可以用一个新姿势来处理这种场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 手动实现构造函数OLO
function Person(name) {
this.name = name
}

function Man(name, sex) {
Person.call(this, name)
this.sex = sex
}
Man.prototype = Person.prototype
Man.prototype.constructor = Man

a = new Person('lili')
b = new Man('xiaoming', 11)

// 使用工具函数实现OLO
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}

扩展构造函数

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
function extend(sup,base) {
// 获取属性的属性(枚举/可读/可写/可配置)
var descriptor = Object.getOwnPropertyDescriptor(
base.prototype,"constructor"
);
// 链接原型链, 子类->父类
base.prototype = Object.create(sup.prototype);
// proxy的处理函数,
// 重写construct: 使用上面的sup的新链, 并且调用原有逻辑(新建一个对象, 执行函数体, 返回obj)
// 重写apply: 保证初始化入参都被调用了 this.key = value
var handler = {
construct: function(target, args) {
var obj = Object.create(base.prototype);
this.apply(target,obj,args);
return obj;
},
apply: function(target, that, args) {
sup.apply(that,args);
base.apply(that,args);
}
};
// 以base为基础新建proxy
var proxy = new Proxy(base,handler);
// 重新设置constructor
descriptor.value = proxy;
Object.defineProperty(base.prototype, "constructor", descriptor);
return proxy;
}

var Person = function(name){
this.name = name
};

// 这样就很清晰了
var Boy = extend(Person, function(name, age) {
this.age = age;
});

Boy.prototype.sex = "M";

var Peter = new Boy("Peter", 13);
console.log(Peter.sex); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13

使用proxy实现双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};

const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
}
});

input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});

Grid布局

Posted on 2019-04-07 | In frontendMaster

Grid布局

为什么是Grid?

  1. 现在grid已经成为css的规范,这是未来的布局方式
  2. 在html中不会再有row(我们从一维布局走到了二维世界)
  3. 建议: flexbox 用在UI元素层面,Grid布局应用在页面布局上

兼容性

IE11以下, Edge15以下, IEmobile10以下 部分支持。

https://caniuse.com/#search=grid

兼容性解决方案:

1. 使用polyfill
2. 使用@supports (but前提是你的浏览器支持@supports)

语法:

  1. 定义grid容器
1
2
3
4
.box {
display: grid;
grid-gap: 20px; /* grid网格之间的间隙 */
}
  1. 给cell布局

    感觉啊, css的发展越来越语义化, 这个cell占的位置都可以直接读出来了。太强大了。

    grid-column: 1/2; /* 我希望这个元素占这个容器的宽度是从第一列到第二列的宽度 = = 还有是这个关系[1, 2) */

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

.col-1 {
grid-column: 1/2;
}

.col-2 {
grid-column: 2/3;
}
.col-3 {
grid-column: 3/4;
}

.col-4 {
grid-column: 4/5;
}

响应式图像

Posted on 2019-04-07 | In frontendMaster

响应式图像

图片大小会影响性能。在不同的设备上显示不同大小的图片就是响应式图片。根据设备的大小来选择要显示哪张图片。

主要注意两个属性: media type

media 属性

这个属性内放媒体查询语句,如果结果为false,那么跳过

1
2
3
4
<picture>
<source srcset="mdn-logo-wide.png" media="(min-width: 600px)">
<img src="mdn-logo-narrow.png" alt="MDN">
</picture>

type属性

指定资源类型,如果支持则用这个类型,如果不支持则跳过

1
2
3
4
<picture>
<source srcset="mdn-logo.svg" type="image/svg+xml">
<img src="mdn-logo.png" alt="MDN">
</picture>

兼容性: 不支持IE

MDN页面

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/picture

Vue响应式原理-get/set

Posted on 2019-04-07 | In frontendMaster

vue响应式原理—–get/set

什么是响应式?

要说什么是响应式, 那么就要知道什么不是响应式,看个例子

1
2
3
4
5
let a = 1;
let b = a * 5;

a = 2;
b; // 5 我们期望, 如果a改变,那么基于a的计算都可以同步, 我们希望b为10

这里就要提出一个属性Object.defineProperty()

使用方法为:

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
Object.defineProperty(obj, 'keyName', { 
// 这里就是配置obj这个对象的keyName这个属性的地方
get () {

},
set() {

}
})

// 首先是四个属性值的属性
// enumerable : 是否可枚举 // [].length
// writeable : 是否可写 // a.prop1 = 1;
// configurable : 是否可配置
// value : 我获取值时返回的value

// 两个方法: get/set
// 挑战1: 我需要一个fn,输入obj把里面的每一个属性都转换为可响应式,配上get/set方法
obj = {
a: 1, // get: "a is 1" set: "a is 2"
b: 1 // "b is 1"
}
const transform = (obj, i) => {
var unKnow = obj[i]
Object.defineProperty(obj, i, {
get() {
console.log(`getting key "${i}": ${unKnow}`, unKnow)
return unKnow
},
set(value) {
console.log(`setting key "${i}" to: ${value}`)
unKnow = value
}
})
}

function convert (obj) {
// Implement this!
for (let i in obj) {
transform(obj, i)
}
}

// test
let a = {
a: 1,
b: 2
}

convert(a)

依赖跟踪

data

每个vue的实例都会有一个watcher对象,这个对象中提供提供增加依赖和发布依赖的方法。增加依赖在于getter部分把当数据更新的操作增加, 而在单个属性发生改变,也就是set的时候触发notify, 执行更新操作。在这里用了设计模式中的发布-订阅模式

每个vue实例就是我们的订阅者, 这些订阅者的更新事件都放到watcher对象里区管理。当我们增加一个依赖项的时候把依赖项放入订阅队列, 然后在每次更新的时候区触发对应的事件。完成更新。

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
// 一个依赖项就是一个watch
// watch 中的subscibers是订阅者的一个队列,里面存放每个依赖事件
// 方法:
// depend: 为该属性增加依赖项
// notify: 数据更新时通知依赖项更新
window.Dep = class Dep {
constructor () {
// 订阅队列
this.subscribers = new Set()
// new Set([1, 2, 3, 1])
}

depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}

notify () {
this.subscribers.forEach(sub => sub())
}
}

let activeUpdate = null

function autorun (update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate // 这个就是一个依赖, 注意,这个其实是是外层函数
update() // update里面要执行一个依赖收集
activeUpdate = null
}
wrappedUpdate()
}

结合get/set与依赖跟踪,完成一个小型观察者对象

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class Dep {
constructor () {
this.subscribers = new Set()
}

depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}

notify () {
this.subscribers.forEach(sub => sub())
}
}

// 批量对Obj的属性赋予响应式的能力
function observe (obj) {
// iterate through all properties on the object
// and convert them into getter/setters with
// Object.defineProperty()
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
// each property gets a dependency instance
// 在进行响应式的同时初始化依赖项实例, 之后再对应的getter/setter方法中形成闭包, 把依赖状态持久化
const dep = new Dep()
Object.defineProperty(obj, key, {
// The getter is responsible for registering subscribers
get () {
// 每次进行get方法的时候, 都进行一次依赖收集
dep.depend()
return internalValue
},
// The setter is responsible for notifying change
set (newVal) {
// 检测值是否改变, 如果没有改变, 那么不做处理(为了性能)
const changed = internalValue !== newVal
internalValue = newVal
// triggering re-computation
// 如果发生了改变, 那么在依赖class触发依赖项的更新
if (changed) {
dep.notify()
}
}
})
})
return obj
}

let activeUpdate = null

// 这里要注意一点, 你所有的, 对依赖项相关的操作,都要以匿名函数的方式传入这个函数, 才能完成依赖的收集
function autorun (update) {
// wrap the raw update function into a "job" function that registers and
// unregisters itself as the current active job when invoked
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}

const state = {
count: 0
}



observe(state)

autorun(() => {
// 在state.count就触发了getter操作, 继而触发了依赖收集
a = state.count // 0
})

// 对state.count = 1触发了setter操作, 继而触发了依赖更新, 真他妈天才
state.count = 1

a // 1

2019.08.16
很震撼啊,这东西写得好精巧啊.

重新理一下,如果你要做到响应式.那么你应该有什么?

  1. 你要有监测机制, 因为如果你不知道什么时候改变,那么你就不会知道啥时候响应
  2. 你要有依赖收集, 因为你不能预知依赖到底有多少, 那么你就得管理依赖项
  3. 你要有响应机制, 你检测到了更新, 继而触发依赖收集, 下一步就是在数据更新的时候, 根据收集到的依赖, 去触发响应, 更新依赖项

以上这三点, 的实现分别为:

  • 检测机制用 get/set方法进行检测, 作为依赖收集, 触发响应的事件分发点
  • 依赖收集和响应机制我们使用dep这个类来完成, 供检测机制调用
  • 使用autorun包裹存在依赖的操作, 并生成引用, 供dep依赖收集
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
62
63
64
65
66
67
68
69
70
71
72
// 1. get/set批量依赖检测
const scan = (obj) => {
for(let i in obj) {
let prop = obj[i]

Object.defineProperty(obj, i, {
get() {
console.log('get')
return prop
},
set(value) {
console.log('set')
prop = value
}
})
}
}

// 2. 依赖项
class DepObj {
constructor() {
this.taskList = new Set()
}

getDep() {
if (activeUpdate) {
// 为啥这个要使用外部变量不用传参的方式传进来呢?
// 依赖收集的时候,在get方法内部, 在内部我们怎么访问到
this.taskList.add(activeUpdate)
}
}

notify() {
this.taskList.forEach(item => item())
}
}

let activeUpdate = null

function autorun (update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate // 这个就是一个依赖, 注意,这个其实是是外层函数, 在dep类里, 我们会把它存进taskList, 供通知的时候使用
update() // update里面要执行一个依赖收集
activeUpdate = null
}
wrappedUpdate()
}

// 3. 结合数据变化检测 + 依赖项收集
const scan = (obj) => {
for(let i in obj) {
let prop = obj[i]
let dep = new DepObj()
Object.defineProperty(obj, i, {
get() {
console.log('get')
dep.getDep()
return prop
},
set(value) {
console.log('set')
let change = value === prop
prop = value

if(change) {
dep.notify()
}
}
})
}
return obj
}

vue表单验证插件

Posted on 2019-04-07 | In frontendMaster

表单验证

vue插件也可以用来注入全局变量.比如用户基本信息, 一些公共数据, 公告方法.

表单验证的两种方式:

1. 基于标签(html): 把验证逻辑放在html标签内, 优点是更清晰。自定义指令实现。
2. 基于model: 把验证逻辑放在js, 优点是更容易实现, 更易定制化。插件实现。

(挖坑: 用自定义指令实现一个类似的)

实现: 实现一个小型表单验证插件

  1. 先复习插件的关键点: 使用(注入/在实例中使用)/定义(install/mixin/钩子函数)
  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
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<template>
<div id="app">
<form @submit="validate">
<input v-model="text">
<br>
<input v-model="email">

<ul v-if="!$v.valid" style="color:red">
<li v-for="error in $v.errors">
{{ error }}
</li>
</ul>

<input type="submit" :disabled="!$v.valid">
</form>
</div>
</template>
<script>
const validationPlugin = {
// Implement this!
install(Vue) { // 和mixin不一样的是, 这个install的第一个参数是触发vue.use的Vue实例
Vue.mixin({
beforeCreate() { // 我不知道 为什么要在这个钩子函数?Created不行吗
// 获取到实例上的 validations
const rules = this.$options.validations

if(rules) {
// 把验证的对象$v挂载到computed钩子函数上
this.$options.computed =
Object.assign({}, this.$options.computed, {
$v () {
// 初始化验证结果与错误信息
let valid = true
const errors = []

// 遍历rules, 对每个rule进行验证
// 这里要说一点, 我们并没从dom结构上去取值, 我们是直接从state上取, 因为在data里有 text/email
// 所有我们需要的value
Object.keys(rules).map(key => {
const rule = rules[key]
// 注意:我们在data中初始化了这个值,这意味着我们在这里触发了getter, 进行了依赖收集
// 接下来每次触发setter都会执行一次
const value = this[key]

// 把value传入在validations上对应的验证函数
const result = rule.validate(value)
// 如果错误了就触发validations上对应的message函数
if(!result) {
valid = false
errors.push(rule.message(key, value))
}
})

return { // 最后,返回验证结果和队列
valid,
errors
}
}
})
}
},
})
}
}

Vue.use(validationPlugin)

const emailRE = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/

new Vue({
el: '#app',
data: {
text: 'foo',
email: ''
},
validations: {
text: {
validate: value => value.length >= 5,
message: (key, value) => `${key} should have a min length of 5, but got ${value.length}`
},
email: {
validate: value => emailRE.test(value),
message: key => `${key} must be a valid email`
}
},
methods: {
validate (e) {
if (!this.$v.valid) {
e.preventDefault()
alert('not valid!')
}
}
}
})
</script>

vue插件

Posted on 2019-04-07 | In frontendMaster

vue插件

我们日常使用插件的方式是: Vue.use(plugin),但是插件是如何写出来的?

关键概念: install -> mixin -> $options.rules -> $watch

  1. install

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // vue 中的插件固定写法
    const RulesPlugin = {
    // Implement this!
    install(Vue) {
    Vue.mixins({
    created() {
    if (this.$options.rules) {
    // do some thing


    })
    }
    }
    })
    }
    }

    Vue.use(RulesPlugin)

    解释一下, vue中插件都会提供一个install , 本质上是往vue上挂载相应的函数,函数内部是用全局的mixin来往根实例添加功能

  2. mixin

    mixin, 一种代码复用的语法糖, 插件的挂载都是用全局mixin来完成的.

  3. $options.rules

    该对象中承载了我们的在组件实例中的自定义插件, 在本例中就是实例中的rules。

  4. $watch

    watch的js语法, 两个参数, 1 观测的属性名 2 .回调函数

完整实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const RulesPlugin = {
// Implement this!
install(Vue) {
Vue.mixins({
created() {
if (this.$options.rules) {
// solution
Object.keys(this.$options.rules).map(key => {
const rule = this.$options.rules[key]
this.$watch(key, (newValue) => {
const result = rule.validate(newValue)
if(!result) {
console.log(rule.message)
}
})
})
}
}
})
}
}

Vue.use(RulesPlugin)

Render Function

Posted on 2019-04-07 | In vue

Render Function

渲染顺序

1. Template->render function   (compiled into)    在这一步, 相当于第一章里说的autorun, 当数据变化的时候我们会重新执行这的函数
- new virtual dom(return)  数据更新后返回新的虚拟dom树
- dom updates(diff against old virtual dom)   旧树和新树进行比较, patch最小差异部分得到即将渲染的虚拟dom树
- actual dom(applied to) 
2. virtual dom   (returns)
3. actual dom (generates)

解释:

​ 我们首先编译temlate转换为render函数

什么是虚拟dom树

先做个类比
浏览器的domAPI:

​ - 语法: document.createElement(‘div’)

​ - 返回: “[object HTMLDivElement]”

​ 虚拟dom: 本质上是一个轻量级的描述dom的一个对象.

​ 语法: vm.$createElement(‘div’)

​ 返回: { tag: ‘div’, data: { attrs: {}…..}, child: []}

dom是一个很大的对象, 所以在vue中(平常也是),我们只要操作了dom那么性能就会存在开销。

but, 和浏览器的dom对象比起来,我们创建多个虚拟dom对象, 很简单,而创建多个浏览器dom实体性能损耗更大.

我们平常使用dom的API对页面进行更新,实际操作是扔掉所有的旧节点,再次生成新dom节点.

虚拟dom最棒的一点是, 解耦了渲染步骤。我们以前渲染是跟着页面的逻辑去一部分一部分的渲染, 在这个过程中很可能出现重复和不必要的渲染。这影响了性能。但是虚拟dom, 是这样做的, 生成最初的虚拟dom树,此时并没有开始渲染,而是先进行逻辑运算,在运算完成得到最后的dom树,完成渲染(渲染过程在最后一步)。

render在响应式做了什么工作

data

我们的每一个组件都有自己的render/Data/watcher

render 往data里面取数据,此时发生了两件事:

1. 触发getter, 由于我们在getter里做了dependency collect,依赖被保存在watcher.当setter数据时,从watcher获取依赖,重新进行render(因为render在getter的时候,把依赖传入了watcher.)
2. 得到数据,进行render

JSX与模板比较

jsx与模板语法本质上都是为了描述我们的state与dom之间的关系.

1
2
3
4
5
export default {
render (h) {
return h('h', {}, [...])
}
}

区别

  • 模板: 模板语法相对于jsx更加的静态, 更多的约束的表达, 但是模板语法更易迁移。模板语法因为存在限制,我们可以对这些限制进行更深一层的优化。
  • jsx: 更加动态,更加灵活, 可以完全体现出fp编程的优势。

render function API

1
2
3
4
5
6
export default {
render (h) {
// 为什么是h, 首先解释一下h的意思, 就是createElement, 为啥不是c是因为设计的时候参照了hyperscript的API, 表示是通过js写html的方法
return h('h', {}, [...]) // arg1: 标签类型 arg2: 数据对象 arg3:
}
}
1
2
3
4
5
6
7
// exmple
h('div', 'some text')
h('div', { class: 'foo' }, 'some text')
h('div', {}, [
'some text',
h('span', 'bar')
])

使用render函数可以直接渲染组件(在router中直接使用render语法渲染组件)

1
2
3
4
5
import component from ''

h(component, {
props: {... }
})
1
2
3
4
5
6
练习1: 使用render语法渲染以下html
<div>
<h1>0</h1>
<h2>1</h2>
<h3>2</h3>
</div>

函数式render

函数式组件是指, 无状态组件,相同输入,固定输出, 不依赖外部变量。像一个处理管道。

组件需要的一切都是通过context传递

1
2
3
4
5
6
7
8
9
10
11
12
Vue.component('my-component', {
functional: true,
// Props 可选
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})

高阶组件

记忆: 高阶组件就像是高阶函数, 都是通过原子组合复用得到更抽象,功能更强的东西。

smartAvatar, 基于Avatar组件组合的高阶组件。

功能, 根据smartAvatar的props属性来决定Avatar的渲染结果

  1. 原子: Avatar低阶组件
  2. 在app这一层准备数据, props/data.url/created()
  3. 在render进行组件的渲染(计算出最后的组件应该是啥样)
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
function fetchURL (username, cb) {
setTimeout(() => {
// hard coded, bonus: exercise: make it fetch from gravatar!
cb('https://avatars3.githubusercontent.com/u/6128107?v=4&s=200')
}, 500)
}

const Avatar = {
props: ['src'],
template: `<img :src="src">`
}

function withAvatarURL (InnerComponent) {
// Implement this!

return {
props: ['username'],
data(){
return { // 配置默认值
url: 'https://avatars3.githubusercontent.com/u/6128107?v=4&s=200'
}
},
created () {
// 在created阶段拿到数据, 为渲染做准备
fetchURL(this.username, url => {
this.url = url
})
},
render(h) {
// render Avatar
return h(InnerComponent, {
props: { // 父级组件向子级组件传递props
src: this.url
}
})
},
}
}

const SmartAvatar = withAvatarURL(Avatar)

new Vue({
el: '#app',
components: { SmartAvatar }
})

总结一下render函数的用法啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
render (h) {
// render标签
return h('div', {
id: '',
class: ''
}, 'contentText')

// render复合标签
return h('div', {}, ['someText', h('div', {}, 'someText')])

// render组件
return h(component, {
props: {
name: 'name'
}
})
}

// 所以常见的main.js, 路由中常见的语法
render: h => h(app)

VueRouter实现

Posted on 2019-04-07 | In frontendMaster

VueRouter实现

我们知道,路由控制有两种方式: hashURL和HTML5的historyAPI.(这部分看文档mdn就成)

我们当前的实现使用的是hash路由

挑战1: 使用vue对router功能基本实现

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
<template>
<div id="app">
<!-- render main view here -->
<a href="#bar" v-if = "display == '#foo'">foo</a>
<a href="#foo" v-if = "display == '#bar'">bar</a>
</div>
</template>
<script>
window.addEventListener('hashchange', () => {
// Implement this!
let router = location.hash
app.changeRouter(router)
})

const app = new Vue({
el: '#app',
// Implement this!
data() {
return {
display: '#foo'
}
},
methods: {
changeRouter (router) {
this.display = router
}
},
})
</script>

在大多数spa页面中,每一个页面就是一个组件,这就是为什么组件的语法是is

1
<component :is='url'></component>

挑战2: 使用标签,在url进行转换的时候, 渲染不同的组件

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
<template>
<div id="app">
<!-- render main view here -->
<componet :is = 'display'></componet>
</div>
</template>
<script>
window.addEventListener('hashchange', () => {
// Implement this!
let router = location.hash
app.changeRouter(router)
})

const app = new Vue({
el: '#app',
// Implement this!
components: {
foo: {template: `<div>foo</div>`},
bar: {template: `<div>bar</div>`}
},
data() {
return {
display: 'foo'
}
},
methods: {
changeRouter (router) {
this.display = router
}
},
})
</script>

挑战3: 使用制作路由表(挖个坑, 这里要重新用render函数实现一次)

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
<template>
<div id="app">
<!-- render main view here -->
<a href="#foo">foo</a>
<a href="#bar">bar</a>
<component :is="routerType"></component>
</div>
</template>
<script>
const Foo = { template: `<div>foo</div>` }
const Bar = { template: `<div>bar</div>` }
const NotFound = { template: `<div>not found!</div>` }

const routeTable = {
// Implement this!
foo: "Foo",
bar: "Bar"
}

window.addEventListener('hashchange', () => {
// Implement this!
app.url = location.hash.slice(1)
})

const app = new Vue({
el: '#app',
// Implement this!,
components: {
Foo,
Bar,
NotFound
},
data() {
return {
url: location.hash.slice(1)
}
},
computed: {
routerType () {
return routeTable.hasOwnProperty(this.url) ? routeTable[this.url] : "NotFound"
}
}
})
</script>

正则路由与动态路由

动态路由: 动态路由指的是路由中包含变量。

例如: /user/:username

这里的:username就是一个变量, 指的是我们在router 中放入的string,例如: /user/paserdark

想象一下,动态路由和静态路由之间的区别是什么?就是url不一样,仅此而已。相比与之前的静态路由,动态路由的url包含了更多信息而已。我们要做的只是从url拿出信息,然后就是遵循静态路由的操作步骤。

对url的处理, 在node.js中, 有个工具模块就是专门处理query字符串的。功能是解析url, 拿出url中的信息。

解析url的库:path-to-regexp

使用方法:

  1. 先定义url的基本模式

  2. 处理输入url

  3. 获取格式化对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const pathToRegexp = require('./path-to-regexp')


    const path = '/bar/123'

    const keys = []

    const regx = pathToRegexp('/:type/:id?', keys)

    const result = regx.exec(path)// ['/bar/123', 'bar', '123'....]

挑战: 编写动态路由解析

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<template>
<div id="app"></div>
</template>
<script>
const Foo = {
props: ['id'],
template: `<div>foo with id: {{ id }}</div>`
}
const Bar = { template: `<div>bar</div>` }
const NotFound = { template: `<div>not found!</div>` }

const routeTable = {
'/foo/:id': Foo,
'/bar': Bar
}

// pre-compile patterns into regex
// 编译路由表
const compiledRouteTable = {}
// 遍历routerTable, 提取router, 制作routerMap,
// 1. 这个map包含regex解析url方法(因为每个url的解析reg都不一样)
// 2. 包含router对应组件
// 3. dynamicSegments,承载keys,该keys为路由动态部分的信息,
// 如该例子为id, 我们在后面遍历这个keys拿到动态参数: {id: '123'}
Object.keys(routeTable).forEach(pattern => {
const dynamicSegments = []
compiledRouteTable[pattern] = {
component: routeTable[pattern],
regex: pathToRegexp(pattern, dynamicSegments),
dynamicSegments
}
})

// '#/foo/123' -> foo with id: 123
// '#/bar' -> Bar
// '#/404' -> NotFound

window.addEventListener('hashchange', () => {
app.url = window.location.hash.slice(1)
})

// path-to-regexp usage:
// const regex = pathToRegexp(pattern)
// const match = regex.exec(path)
// const params = regex.keys.reduce((params, key, index) => {
// params[key] = match[index + 1]
// }, {})

const app = new Vue({
el: '#app',
data: {
url: window.location.hash.slice(1)
},
render (h) {
const path = '/' + this.url

let componentToRender
let props = {}

// iterate through our compiled route table
// and check if a route matches the current path
// if it matches, extract dynamic segments (params) and use it as props
// for the matched component
Object.keys(compiledRouteTable).some(pattern => { // 为什么使用some,只匹配其中一条路由即可
const { component, regex, dynamicSegments } = compiledRouteTable[pattern]
const match = regex.exec(path)

if (match) {
// we have a match!
componentToRender = component
dynamicSegments.forEach(({ name }, index) => {
props[name] = match[index + 1]
})
return true
}
})

return h('div', { attrs: { id: 'app'} }, [
h(componentToRender || NotFound, {
props
}),
h('a', { attrs: { href: '#foo/123' }}, 'foo/123'),
' | ',
h('a', { attrs: { href: '#bar' }}, 'bar')
])
}
})
</script>

Vuex基础概念

Posted on 2019-04-07 | In frontendMaster

vuex 基础概念

认知:
vuex是为vue专门定制的状态管理工具。我认为主要是把数据和视图完全分开管理,并且所有的改动都从vuex触发,统一管理。解决了跨组件,跨页面的通信方式。

关键概念:store/getter/mutations/actions/modules

  1. store: 一个sore就是一个数据集, 我理解为是一个数据对象,在这个对象中暴露出我们获取数据, 修改数据的方法
    在一个store里包含state/getters/mutations/actions/modules

  2. getter: 在vue中,我们有computed函数, 这个computed本质上是给这个元素设置了get方法,在我们获取这个数据的时候,进行计算, return出值.而在vuex,我们也可以使用类似的功能, 就是getter方法。

  3. mutations: 我们每次对state里的数据进行改动都需要通过一个事件来进行提交, 类似git的commit的概念。类比git的提交过程, 这个就是git commit 命令。每次提交可附带荷载, 并且支持传参和对象的方式提交。

  4. actions: git 每次进行提交之前都需要add所更改的文件,类比过来也是一样的。要提交的mutation就要先走action。我能理解有时不需要使用actions就可以提交, 但是,我们使用action最重要的一点是, actions支持异步, 而mutations只支持同步修改。

  5. modules: 我们使用的状态管理在大型的页面中很容易产生一个巨大的store.js的文件, 那么vuex为我们提供了模块化的语法。

vue-i18n国际化插件

Posted on 2019-04-07 | In frontendMaster

vue-i18n国际化插件

原理:

​ vue-loader内添加了编译插件, vue-loader允许注入自定义模板编译, 转换编译时vue的template.基于这个原理, 这个国际化插件是在编译的时候分析了template,把$t('text')用本地语言文本替换它。然后生成三个不同版本的应用文件, 并部署。因此国际化并不是动态的,而是在我们切换环境的时候,切换对应语言的应用文件。它有三个静态版本文件,它的应用程序都是预编译好的。这就省掉了我们重新编译, 分析,寻找本地化文件并替换的时间。

vue-i18n微型实现

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
<template>
<div id="app">
<h1>{{ $t('welcome-message') }}</h1>
<button @click="changeLang('en')">English</button>
<button @click="changeLang('zh')">中文</button>
<button @click="changeLang('nl')">Dutch</button>
</div>
</template>

<script>
const i18nPlugin = {
// Implement this!
install(Vue, localData) {
Vue.mixin({
methods: {
$t (textFlag) {
return localData[this.lang][textFlag]
}
}
})
}
}

Vue.use(i18nPlugin, /* option */ {
en: { 'welcome-message': 'hello' },
zh: { 'welcome-message': '你好' },
nl: { 'welcome-message': 'Hallo' }
})

new Vue({
el: '#app',
data: {
lang: 'en'
},
methods: {
changeLang (lang) {
this.lang = lang
}
}
})
</script>
<1…567>

Hawei

Fullstack Engineer

61 posts
16 categories
3 tags
RSS
© 2022 Hawei
Powered by Hexo
|
Theme — NexT.Muse v5.1.4