包含了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} })
|
使用
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>
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-loader
和css-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
- 安装对应的模块
cnpm install node-sass --save-dev
node-sass
cnpm install sass-loader --save-dev
转换为css插件
可选npm install --save-dev extract-text-webpack-plugin
让webpack可以输出css格式的文件
- 打开
webpack.base.config.js
加上1 2 3 4
| { test: /\.scss$/, loaders: ["style", "css", "sass"] },
|
- 用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' }) }
|