Vue笔记

包含了Render函数使用,自带插件使用方法,组件及webpack相关内容

安装

使用yarn
$ yarn global add vue-cli
$ vue init webpack vuepro
底下一堆测试的选NO 进目录 yarn安装依赖文件 yarn run dev即可运行

Render函数

前言

Virtual Dom

Object –> render虚拟节点 –> createEle(h)基于虚拟节点创建Dom节点 –> diff状态更新后对比,生成补丁 –> patch遍历补丁,更新Dom节点

创建VNode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var VNode = {
tag:'div'//当前节点的标签名
attributes: {
id: 'main'
} //节点属性
data://当前节点的数据对象(下⼀页)
children://⼦节点,数组,也是 VNode 类型
text://当前节点的⽂本,⼀般⽂本节点或注释节点会有该属性
//一下为不常用
elm://当前虚拟节点对应的真实的 DOM 节点
ns://节点的 namespace
content://编译作⽤域
functionalContext: //函数化组件的作⽤域
key://节点的key属性,⽤于作为节点的标识,有利于 patch 的优化
componentOptions://创建组件实例时会⽤到的选项信息
child://当前节点对应的组件实例
parent://组件的占位节点
raw://原始 html
isStatic://静态节点的标识
isRootInsert://是否作为根节点插⼊,被 <transition> 包裹的节点,该属性的值为false
isComment://当前节点是否是注释节点
isCloned://当前节点是否为克隆节点
isOnce://当前节点是否有 v-once 指令
}

解决场景

代码冗长,大部分代码相同,外城必须包含无用的<div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<h1 v-if="level === 1">
<a :href="#" + title>
<slot></slot>
</a>
</h1>
<h2 v-if="level === 2">
<a :href="#" + title>
<slot></slot>
</a>
</h2>
</div>
</template>

食用

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
render: function (createElement) {
return createElement(
//String|Obj|Function
//HTML标签,组件选项,函数
'h' + this.level,
//obj,对应的属性数据对象,可选
{
class:{'show': this.show},//v-bind:class
style:{},//v-bind:style
attrs:{id: 'ele'},//⼀般的 HTML 属性,⽐如 id
props:{}props,
on:{click: this.handClick},//⾃定义事件,
nativeOn:{},//原⽣事件,⽐如 click,
},
//String|Array 子节点,可选
[
createElement('a',
{
domProps: {
href: '#' + this.title
}
},
this.$slots.default
//或者
'文本内容'
)
]
)
}

约束

所有的组件树中,如果 VNode 是组件,或含有组件的 slot,那 VNode 必须唯⼀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var Child = {
render: function(creatElement){
return creatElement('p', 'text)
}
}
Vue.component('ele',{render: function (creatElement){
var ChilNode = creatElement(Child)
rentrn creatElement('div',[ChilNode,ChilNode])
//不允许,因为子组件重复了

//或者

return creatElement('div',
[this.$slots.default,this.$slots.default])
//子组件包含组件且重复
//<ele><div><Child></></></>
//对应HTML,Child为component新创建的子组件,不是上面那个
}})

解决办法

1
2
3
4
5
6
7
//第一个,重复5次,每次返回创建节点
return createElement('div',
Arrat.apply(null,{length:5}).map(function () {
return createElement(Child)
})
//第二个,slot克隆

使用JS

Render中没有指令(v-bind),使用JS原生实现。e

v-for

1
2
3
4
5
6
7
8
9
render: function (h) {
var nodes = [];
for (var i=0; i<this.list.length;i++){
nodes.push(h('p', this.list[i]));
}
return h('div', nodes);
},
props: {list:{type:Array}}
var app = new Vue({data: {list: ['1','2']}})

v-if

1
2
3
4
5
6
7
8
9
render: function (h) {
if (this.show) {
return h('p','show的值为true ')
} else {
return h('p','show的值为false ')
}
},
props: {show:{type:Boolean,default: false}};
var app = new Vue({data: {list: ['1','2']}})

v-model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
render: function (h) {
var _this = this;
return h('div', [
h('input', {
domProps: {
value: this.value
},
on: {
input: function (event) {
_this.value = event.target.value
}
}
}),
h('p', 'value:' + this.value)
])
},
data: function () {
return{value:''}
}

slot默认内容,$slots.default等于undefined说明⽗组件中没有定义slot

1
2
3
if (this.$slots.default === undefined) {
return createElement('div', '没有slot时显示的文本')
}

函数化组件

Vue.js 提供了⼀个 functional 的布尔值选项,设置为 true 可以使组件⽆状态和⽆实例,也就是没有data 和 this 上下⽂。这样⽤ render 函数返回虚拟节点可以更容易渲染,因为函数化组件只是⼀个函数,渲染开销要⼩很多。
使用函数化组件时(export defaule),Render 函数提供了第二个参数 context 来提供临时上下⽂。组件需要的 data、props、slots、children、parent 都是通过这个上下⽂来传递,⽐如 this.level 要改写为context.props.level,this.$slots.default 改写为context.children。

组件

注册

全局

1
2
3
Vue.component: ('my-component', {
template: '<div>全局</div>'
})

局部

1
2
3
4
5
6
7
8
9
10
var Child = {
template: '<div>局部</div>'
}
new Vue({
components: {
'my-component': Child
//当标签名和组件名一样时
Child,
}
})

其他

tbody等标签使用的话使用is="my-compontent"达到效果
模板内使用{{ meesage }}来双向绑定数据,data(){return{message:'1'}}

事件监听&数据传递

自定义事件

.native 修饰符,表示监听的是一个原生事件,监听的是该组件的根元素。
<my-component v-on:click.native="handleClick"></my-component>

$event获取事件值

父组件

1
2
3
4
5
6
7
8
9
// 参数为 传递字符串 事件的值
<div @creat="cpuData('cpu', $event)"></div>
// attr == 'cpu' $event = 事件对象(包含数据)
methods: cpuData(attr, value) {
return this.$store.commit('Shop', {
attr,
value
})
},

子向父

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<my @creat="父组件定义的事件"></my>
//@子组件传递的事件名="父组件定义的事件"
//只能通过事件传递出数据

Vue.component('my', {
template: '<div><button @click="hand"></button></div>'
//data: {a:0}
methods: {
hand () {
this.a ++;
this.$emit('creat', this.a);//传递事件名,数据

}
}
})

父向子

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
<my-component :init-count="count"></my-component>
//:子组件数据名="父组件数据名"
//JS
var Child = {
props: { //接收父组件传过来的数据
initCount: Number
}
template: '<div>局部{{ message }}{{ count }}</div>',
data () {
return{
message: 'hello',
count: this.initCount
}
},
methods: {
//子组件不能直接修改props的数据,由data接收重新定义即可修改
}
}
new Vue({
components: {
'my-component': Child,
//当标签名和组件名一样时
//Child
},
data: {
count : 1
}
})

子组件使用props获取父组件传递的数据,由data定义this.count传递给template,修改时在methods中。由标签的:init-count="count"接受父组件数据。
在 JS 中对象和数组是引用类型,指向同一个内存空间,所以 props 是对象和数组时,在子组件内改变是会影响父组件的。

v-model

接受value属性,在有新value时触发input事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<my v-model="value"></my>
//value为实例(父组件)value

//父向子传递
<button @click="hand"></button>

//父组件
hand () {修改数据}
data {value: 1}

//子组件
props必须是value名数据
this.$emit('input', this.changeValue)传出数据
//监听value值,双向绑定数据
watch: {
value(val){ this.changeValue = val}
}

非父子通信

BUS总线&父链子链方法

使用一个空Vue实例当中间件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const bus = new Vue();

Vue.component('com', {
template: '<button @click="hand">传递事件</button>',
methods: {
hand () {
bus.$emit('message', '来自组件com的内容')
}
}
})

new Vue({
data: {
count : 1
},
mounted () {
bus.$on('message', (msg) => {
this.message = msg;
})
}
})
父链子链(不常用,应急方法)

父组件内

  • 拿到子组件,多个为数组
    this.$children

  • 使用方法
    this.$children.hand ()

  • 访问数据
    this.$children.msg

  • 多个com区分,ref属性
    <com ref="a"></com>
    this.$refs.a

Vuex

跳转到Vuex插件篇

数据验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
props: {
A: Number, // 必须是Number类型
B: [String,Number],
C: { // 默认参数
type: Boolean,
default: true
},
D: { // 必传参数
type: Number,
required: true
},
E: { // 如果是数组或对象,默认值必须是一个函数返回
type: Array,
default function () {
return [];
}
},
F: { // 自定义一个验证函数
validator: function (value) {
return value > 10;
}
}
}

slot内容分发

创建&获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<app>
<div slot="a"></div>
<div slot="b"></div>
</app>

Vue.component('app', {
template: '<div>\
<slot name="a"></slot>\
<slot name="b">可以写入默认内容</slot>\
</div>'
})

new Vue({
mounted () {
this.$slots.a
}
})

递归组件

1
2
3
4
5
6
7
<app :count="1"></app>

Vue.component('app', {
template: '<div>\
<com :count="count + 1" v-if"count < 3">{{ count }}</com>\
</div>'
})

内联模板

子组件上加上inline-template属性,模板由父组件定义。
导致作用域改变,message由子组件定义

1
2
3
4
5
6
7
8
9
10
	<app inline-template>
<div>{{ message }}</div>
</app>

Vue.component('app', {
template: '<div>\
<slot>1</slot>\
</div>',
data() {return{message:'你好'}}
})

效果显示你好

异步组件

1
2
3
Vue.component('app',(resolve, reject) => {
window.setTimeout(() => resolve({template: '<div>异步内容</div>'}), 2000)
})

$nextTick

v-if显示的div在未显示时拿不到内容
可以提前渲染

1
2
3
4
5
new Vue({
el: '#app'
created () {
this.$nextTick(() => {})
}})

x-template

在未使用webpack等工具构建时,写template中写标签很麻烦

1
2
3
4
5
6
7
8
<my-component></my-component>

//在body中直接写入
<script type="text/x-template" id="my-component">
<div></div>
</script>

Vue.component('my-component',{template: '#myComponent'})

手动挂载实例

1
2
3
4
5
const myCom = Vue.extend({
template: '<div>from extend</div>'
})

new myCom().$mount('#app')

插件篇

全局注册组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MyPlugin.install = function (Vue, options) {
// 全局注册组件 (指令等功能)
// 全局使用com-a组件
Vue.component('com-a', {
// 组件内容
})
// 添加实例方法,Vue.$Notice
Vue.prototype.$Notice = function () {
//逻辑
}
// 添加全局方法或属性
Vue.globalMethod = function () {
//逻辑
//this.globalMethod全局访问
}
// 添加全局混合
Vue.mixin({
mounted () {
//逻辑
}
})
}

vue-router

官方文档
以下为安装vue时附带安装vue-router的配置文件,在src/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
30
31
import Vue from 'vue'
import VRouter from 'vue-router'

Vue.use(VRouter)

// 多路由,异步加载
const Routers = [
{
path: '/index',
component: (resolve) => require(['../components/Hello'], resolve)
},
{
path: '/hello',
component: (resolve) => require(['../components/Hello1'], resolve)
},
// 通配符,设置未指定页面的指向页面
{
path: '*',
redirect: '/index'
},
{
path: '/hello/:id',
component: (resolve) => require(['../components/user.vue'], resolve)
},
]

// 开启history模式
export default new VRouter({
mode:'history',
routes: Routers
})

user.vue文件

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
// 获取到id
<div>
{{ $route.params.id}}
</div>

// 页面跳转
// 动态跳转 :to=变量
// 换标签 tag="li"
// 不保存历史记录 replace
// 点击后切换class active-class="calssname"
<router-link to="/hello">跳转到Hello</router-link>

//第二种方法,用js
<button @click="hand">跳转到user </button>


<script>
// 获取到id
this.$route.params.id
methods: {
hand () {
this.$router.push('/user/123')
this.$router.replace('/user/123') // 不写入记录
this.$router.go(-1) // 前进后退
}
}

</script>

改变tittle,切换页面后滚动条位置重置,检测是否登录,
main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 点击切换时触发
// to 即将要进入的路由对象
// from 要离开的路由对象
// next 主动调用,进入下一个生命周期
// next(false) 取消导航

// 改变tittle
router.beforeEach((to, from, next) => {
window.document.title = to.meta.title; // 获取路由中的meta信息
next();

//检测是否登录,未登录跳转到登录页面
if (window.localStorage.getItem('token')) {
next()
} else {
next('/login')
}
})

// 切换之后触发
// 滚动条重置
router.afterEach( (to, from, next) => {
window.scrollTo(0, 0);
})

router配置

1
2
3
4
5
6
7
8
{
path: '/index',
meta: {
title: '首页'
}
component: (resolve) => require(['../components/Hello'], resolve)
}

Vue特殊方法

阻止事件冒泡

@click 改成@click.stop

阻止与默认行为

@contextmenu 改成@contextmenu.prevent

Vuex

官方文档

安装

yarn add vuex,新建 store 目录,index.js中写入

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
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// 所有应用级别的状态都放在这里
const state = {
// 计数器的初始状态为 0
count: 0
}

// 类似于组件中的 computed,可以对 state 里面的数据
// 做处理,然后通过 $store.getters.func 的方式获取
const getters = {

}

// state 里面的状态不能直接改变,想要改变就需要在这里
// 定义函数,函数的第一个参数是 state,通过这个参数改
// 变 state 里面的数据
// 在组件中通过 $store.commit() 触发
// 类似于methods
const mutations = {

}

// 和 mutations 类似,但 actions 触发的是 mutations
// mutations 只能进行同步操作,在 actions 中可以定义
// 异步操作
const actions = {

}

export default new Vuex.Store({
state,
getters,
mutations,
actions
})

然后main.js中写入

1
2
3
4
5
import store from './store'
//添加
new Vue({
store,
})

操作方式

state & mutations

1
2
3
4
5
6
7
8
9
10
// div中
{{ $store.state.count }}
{{ count }}

//script
computed: {
count () {
return this.$store.state.count;
}
}

约定 state 只能读取不能修改,实现 count++ 所以在mutations内添加

1
increment: state => state.count++,// 可以接受多个参数({a},{b})

home.vue中

1
<button type="button" @click="$store.commit('increment')">+</button>

或者定义方法

1
2
3
4
5
6
7
8
<button type="button" @click="hand">+</button>

methods: {
hand () {
return this.$store.commit('increment')
}
}

getters

getters 相当于 vuex 的 computed,可以返回对 state 中数据处理后的结果,定义一个函数返回当前的 count 是基数还是偶数

1
2
// 接收 state 作为参数
evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd'

home.vue中调用

1
2
3
4
Count为 {{ $store.state.count }} count is {{ $store.getters.evenOrOdd }}

// script
this.$store.getters.evenOrOdd

actions

异步操作

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
const actions = {
//多值传入
asyncMore: ({ commit }, { amount }) => {
setTimeout(() => {
commit('incrByAmount', { amount })
}, 500)
},
//单值传入
asyncOne: ({ commit }) => {
setTimeout(() => {
commit('increment')
}, 500)
},
//Promise方法
asyncP(context) {
return new Promise(resolve => {
setTimeout(() => {
context.commit('increment')
resolve()
}, 500)
})
},
//不用Promise
asyncP(context, callback) {
setTimeout(() => {callvack()}, 500)
},
}

home中写入

1
2
3
4
5
6
7
8
9
10
11
12
<button type="button" @click="asyncMore">异步多值</button>
<button type="button" @click="$store.dispatch('asyncOne')">异步</button>
<button type="button" @click="asyncP">异步(Promise)</button>

asyncMore () {
return this.$store.dispatch('asyncMore', { amount: 10 })
},
asyncP () {
return this.$store.dispatch('asyncP').then(() => {
console.log(this.$store.dispatch('asyncP'))
})
}

modules

配置多了后不够优雅,使用 modules 将 store 分发,每个 modules 有自己的配置

1
2
3
4
5
const moduleA = {state:{}}// store的配置 

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

使用

1
this.$store.state.a.xxx

moduleA获取的数据是自身的 state moduleA 访问 store内的数据,avtions 和 getters可用

1
2
3
4
const moduleA = {getters: {
// 自身的state/自身其他的getters/根节点State
a (state, getters, rootState){}
}}

WebPack

基础

  • 安装 yarn webpack --save
  • 添加到:devDependencies
  • cnpm install <package_name> --save
  • cnpm i -D <package_name>
  • 添加到:dependencies
  • cnpm install <package_name> --save-dev

配置

webpack.config.js

出入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//webpack.config.js
const path = require('path');
// from path import path;
const ExtPlugin = require('extract-text-webpack-plugin')

const config = {
// 入口文件
entry: {
main: './main.js'
},
// 出口文件,存放打包目录
output: {
path: path.join(__dirname, './dist'),
publicPath: '/dist/',
filename: 'main.js'
}
}
export default config

导入模块

如果入口导入一个.css文件,就通过style-loadercss-loader转换再打包, use中从后往前编译

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
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.css$/,
use: ExtPlugin.extract({
use: 'css-loader',
fallback: 'style-loader'
})
},
{ //解析vue中的css
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
css: ExtPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader'
})
}
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/ //忽略无需编译的内容
},
{
// loader: 'babel-loader?limit=1024'
//如果文件小于1kb则转为base64加载
}
]
},
plugins: [ //插件
new ExtPlugin('main.css') //导出后的css文件名
]

.babelrc

js的配置文件

1
2
3
4
5
{
"presets": ["es2015"], //使用es2015
"plugins": ["transform-runtime"],
"comments": false //
}

package.json

包管理

  • devDependencies 是你开发时候用的库, 比如测试库,测试服务器之类的,在真实生产环境是不需要的。
  • dependencies 是你生产环境需要的依赖库。
  • 如果你使用了一些构建工具,比如webpack之类的,打包的时候,是不会把dev库打进去的。

局域网访问监听服务

"scripts" : {"dev": "webpack-dev-server --host 本机IP --port 8888" --open --config webpack.config.js }

main.js

实时修改
document.getElementById('app').innerHTML = 'hello'
页面会被修改为hello,覆盖原页面的字符

index.html

使用sass

  1. 安装对应的模块
    cnpm install node-sass --save-devnode-sass
    cnpm install sass-loader --save-dev转换为css插件 可选npm install --save-dev extract-text-webpack-plugin让webpack可以输出css格式的文件
  2. 打开webpack.base.config.js加上
    1
    2
    3
    4
    {
    test: /\.scss$/,
    loaders: ["style", "css", "sass"]
    },
  3. 用scss的地方写
    1
    2
    3
    <style lang="scss" scoped="" type="text/css">
    @import '../../static/test.css';
    </style>

使用stylus

1
2
"stylus-loader": "^2.5.0",
"stylus": "0.52.4"

不多说什么,依赖两个,只用嵌套层的话比sass那个诡异下载好得多

json-server

执行cnpm install json-server --save

  • 打开dev-server,新增jsonServer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const jsonServer = require('json-server')
    const apiserver = jsonServer.create()
    const apirouter = jsonServer.router('db.json')
    const middlewares = jsonServer.defaults()

    apiserver.use(middlewares)
    apiserver.use('/api', apirouter) //第一个参数改变内置路由
    apiserver.listen(3000, () => {
    console.log('JSON Server is running')
    }) //3000为端口号
  • 设置数据文件
    根目录创建db.json格式如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "getboardList": [
    {
    "title": "开放产品",
    "description": "开放产品是一款开放产品",
    "id": "car",
    "toKey": "analysis",
    "saleout": false
    },
  • 设置代理 打开config/index.js
    1
    2
    3
    proxyTable: {
    '/api': 'http://localhost:3000/'
    },
    之后可以开始访问/api来访问jsonServer

一些思路清奇的用法

vuex导入数据,可以直接使用

1
2
3
4
import { mapGetters } from 'vuex'
computed: {
...mapGetters({ dictionary: 'dictionary' }),
}

神一样的解构赋值

1
2
3
4
5
6
let {
width,
height,
ratio,
sourceImgContainer
} = this,

直接将某些插件挂载到prototype上使用

1
2
3
4
5
6
7
Vue.prototype.$error = function(msg) {
Message({
showClose: true,
message: errinfo[msg]||msg,
type: 'error'
})
}