文章目录
2013尤雨溪
在Google基于Angular开发,起初命名为Seed
,同年12月更名为Vue-0.6.0.
3.0
版本基于TypeScript
,框架基于MVVM
模型. SPA(Single Page Application);
一个vue文件就相当一个小组件(里面包含了template+css+js)!data --> Virtual DOM --> Real DOM
install
npm config set registry=http://registry.npm.taobao.org # 全局更改源
npm install -g typescript;
npm install -g @vue/cli # cnpm(国内的npm)有可能会遇到一些诡异问题;
vue upgrade --next; vue -V
# 目录生成
vue create demo; vue ui;
basic
reactive
内部使用window.Proxy
+window.Reflect
来实现响应式ref
内部还是使用Object.defineProperty
来实现响应式vue2
: methods do not use ‘=> function’, because ’this’ will become window instead of vm
project tree
build # webpack
config # 端口等配置
dist # npm run build
node_modules # npm项目依赖模块
public
- index.html # SPA
src
- api
- assets # language
- components # 基础的通常是view依赖的组件
- views # 通过路由来动态切换的组件. 亦可称为'pages'
- router
index.js
- hooks
usePoint.js # setup()中一个独立的单元(数据定义+操作+生命周期)
- store # vuex四元组
index.js # 导出store的地方
actions.js # 根级别的action
mutations.js
- modules
cart.js
- App.vue # 根组件,被main.js引入
- main.js # entry file. render App.vue to public/index.html!
- index.html
- theme.less # 覆盖vant中的主题变量,避免重启
static
js/img/css
.env # 在所有环境中被载入; 假如这里面有变量: XX; 访问: process.env.XX
.env.local/dev/sit/pro # .env.dev.local(不应该加入git) > .env.local > .env.dev > .env
.editorconfig
babel.config.js # ES6 --> ES5
package-lock.json # 包版本控制(锁定版本号)
package.json
webpack.config.js
vue.config.js
component & template
绑定即绑定到Vue中的变量.方便动态变更数据<–>UI!
DOM Template
. Warning: vue2 Only allows one root node!
key | function |
---|---|
v-pre | vue will omit this element. improved efficiency! |
v-once | 仅执行第一次插值,后面数据变化不再变动; <span v-once>{{msg}}</span> |
v-html | raw html; <span v-html="rawHtml"></span> ; for safe reason, do not use this directive in form! |
v-text | 实际上通常使用插值表达式(无法用在属性上) {{}} 来替代该指令! |
v-show | 是否展示(非真移除,通过display属性); <h1 v-show="ok">xx</h1> |
v-cloak | 斗篷/披风/遮盖物; 该属性的元素配合style标签内如下形式可解决闪烁问题: [v-cloak]{display:none} |
demos
vue高亮有bug,以html替代之
<!--
* template: 会隐含的包含一个根标签<fragment>(但是渲染时又去掉了,vue2则必须将多个放在一个div里面)
-->
<template>
<!-- 引用某个组件,在下面的script-component声明过了 -->
<HelloWorld msg="" />
<!-- 注意:插值表达式无法用在属性key(只能用在内容节点)! 可以是一些简单的js表达式! -->
{{5+5}}
<!-- nb些,可计算一个表达式! -->
{{`rgb(${r},${g},${b})`}}
<!-- if var.b undefined but var defined. this will return empty string! -->
{{ ok ? 'yes': 'no' }}
<!-- 注意:if等流控制是不行的 -->
{{ message | msg.split().reverse().join() }}
<!-- 条件(真的添加删除元素),仅能用在1个元素上 -->
<p v-if="show">xx</p>
<!-- ref: 方便vue通过'this.$refs.ref1'成员来引用我(DOM对象)! ref也可用在子组件身上(方便父亲调用子组件中的方法)! -->
<p v-else-if ref="ref1"></p>
<!-- 多个元素需使用template包裹 -->
<template v-else="var == 'A'"><h1></1><p></p></template>
<!-- 也可省略index(再次插入不能打乱原有的顺序); key用来记录每一项的状态(必须为数字或字符串且唯一),放置vue优化乱掉 -->
<template v-for="(site, index) in sites" :key="user.id">
<li>{{site.title}}</li>
</template>
<!-- 迭代对象的属性,也可以添加键名或索引: (v, key, index) in object -->
<li v-for="v in object">{{v}}</li>
<!-- 循环数字 -->
<li v-for="n in 8">{{n}}</li>
<!-- binding - the value is considered as a expression -->
<!-- 给属性key添加绑定"单向数据绑定"! 注意:双引号中的时纯js表达式,9就是数字类型.若想使用字符类型需单独加上单引号!!但不加bind时,他们的属性值都是字符串! -->
<button :status="disabled">xx</button>
<!-- var is a vue variable defined in 'data'; value can also be a function! -->
<div v-bind:title="'box-' + var"></div>
<!-- binding to a css value(must be an object or an array)! supports computed! -->
<!-- css value expression often be defined as a variable in vm(ViewModel) -->
<div :style="{backgroundColor: `rgb(${r},${g},${b})`;}"></div>
<input type.trim="text" v-model="msg.name"> <!-- collects the default attribute 'value' to msg.name -->
<input type="nubmer" v-model.number="msg.age"> <!-- warning: v-model always collect the value as a string by default. type's 'number' limits the input! -->
<input type="radio" v-model="sex" value="male"> <!-- specify an additional value attribute(radio doesn't have value attribute by default) -->
<input type="radio" v-model="sex" value="female">
<input type="checkbox" v-model="hobby" value="eat"> <!-- vue variable 'hobby' would be an array -->
<input type="checkbox" v-model="hobby" value="play">
<input type="checkbox" v-model="agree"> <!-- 'agree' would be a bool. collects the default attribute 'checked' -->
<textarea v-model.lazy="msg.info"></textarea> <!-- only update 'info' when lose the focus! -->
<select v-model="province">
<option value="">请选择</option>
<option :value="1">北京</option> <!-- colon makes the "1" be a number or consider using 'v-model.number' -->
</select>
<!-- events -->
<!-- vue always treat component's event as customed event, so if you wanna use primitive event(vue2 only): '@click.native="cb"' -->
<Son @update:evt1="cb"></Son> <!-- 自定义事件(组件销毁后自动失效). 接收子组件emit传递过来的值; 也可配合once.. -->
<!-- keydown/mousedown/mouseover/submit; delete esc space up down left right -->
<!-- these key should combine with keydown: tab alt ctrl meta shift -->
<a v-on:keyup.enter="keyEvent">
<!--
可不传参,省略小括号; $event是固定写法,表当前触发事件的对象(无自定义参数时可不写)! 可直接对变量进行简单操作(无需methods).
* @click.stop: 默认click会传播到父标签(冒泡,前提时父标签也绑定了),阻止这种传递(==e.stopPropagation())
* @click.self: 不接收冒泡,也不会向外传播. 仅当直接点击(可能要排除子控件范围)了自己(event.target==self),才会触发!
* @click.once: 仅会触发一次(后续点击不会触发了)
* @click.capture: 最外层先捕获(需要外层添加capture,方可优先捕获)
* @click.prevent: 阻止link或submit的跳转(当然也可在响应函数中使用e.preventDefault()来实现)
-->
<a @click="doSth(2, $event)">..</a>
<form @submit.prevent="add"></form> <!-- 防止页面刷新且绑定提交 -->
<!--
* @blur: 失去焦点
* @scroll: rsp cb then scroll the scrollbar. Same as wheel!
* @wheel.passive: // async execute cb then scroll the mouse wheel!
-->
<!-- component: the micro-vm -->
<!-- component is the set of local codes and resources -->
<keep-alive include="comp1,compdo2"> <!-- avoid destroy; cache comp1 and comp2 -->
<component :is="指定哪个组件名称就显示谁"></component>
</keep-alive>
<!-- slot -->
<!-- 定义插槽: 将当前组件中的数据'x'传递给外面的插槽使用者(如组件标签的内容),方便使用者使用本组件中定义好的数据! -->
<slot name='slotName' :param="x" :user="bindingVar">
<p>This is the default content</p>
</slot>
<!-- 使用插槽: warning: v-slot command(#) can only be used in template/component! -->
<Left>
<template #slotName="paramValueobj"> <!-- #slotName='{ msg, user }' '#slotName' '#default':means anonymous slot -->
<!-- obj: { msg: 'x', user: {} }; -->
</template>
</Left>
</template>
<script>
import HelloWorld from './components/hello_world.vue'
export default { // Vue will call Vue.extend({}) automatically
name: 'MyComp1', // the name of the current component. This name will be used in keep-alive and debug
el: '#div1', // this component will replace the div1 element in index.html
components: {
// register another component; can be used in template node by '<HelloWorld>xxx<HelloWorld>' or '<hello-world></hello-world>'
// HelloWorld is an independent instance of the component! when use it in template vue will call 'new VueComponent(options)'
// VueComponent.prototype.__proto__ = Vue.prototype --> supports using Vue members
HelloWorld // vm.children[0]
}
</script>
common scripts
import {ref, reactive, provide, toRef, toRefs, computed, watch, watchEffect, onBeforeMount} from 'vue' // vue3
import HelloWorld from './components/hello-world.vue' // webpack会提升最外层所有的import到上方!
const Child = defineAsyncComponent(() => import('./components/Child')) // 动态引入,不用等待最慢的人(配合Suspence)
import Vue from 'vue'
import TheComponent from './components/my-comp1.vue'
Vue.use(Vant)
Vue.prototype.$http = axios // 很少用
Vue.http.options.root = 'xxx'
Vue.http.options.emulateJSON = true
Vue.config.keyCodes.f2 = 113 // custom global keycode
const app = Vue.createApp({}) // app is the light weight vm
app.directive('focus', {
mounted(el) { // 被绑定的元素挂载到DOM中时
el.focus(); // 聚焦元素
}
})
app.component('my-comp1', TheComponent) // usage: <my-comp1 self-def-prop=""></my-comp1>; consider use 'app.component(TheComponent.name, TheComponent)'
app.mount('#app')
// reactive
const p = new Proxy(obj, { // 拦截对象中任意属性的变化
get(target, propName){ // target:当前被代理的对象obj!
return Reflect.get(target,propName) // 操作任意对象的属性
},
set(target, propName, value){ // 修改或增加属性
Reflect.set(target, propName, value)
}
deleteProperty(target, propName){ // 删除属性
return Reflect.deleteProperty(target, propName) // true/false
}
})
props(vue2+vue3)
// usage: <MyComp1 :prop-one="x" undefinedProp="This prop will be omitted" />
// <==> VueComponent.$attrs.propOne; 类比 $slots 也会去捡漏
props: {
propOne: { // warning: 'propOne' is readonly! 'this.propOne++' will cause a warning! reserved keys: 'key, ref'
default: 2, // 未指定时,初始值; 若是Object则必须通过一个function来返回{};
type: [Number, String], // 限制其接收值的类型. 也可以是一个自定义对象类型Object(可改变对象的成员,但都不建议去修改prop属性)
required: true, // even a default value is specified, missing this prop will still causing a warning in the console!
validator(value) { // validate the prop value
return true
}
},
// usage: <MyComp1 :prop1="var" />
// usage: <MyComp1 v-model:prop1="var" />: bi-directional data binding(always associated with a customized event)
// if you don't declare it in props, then it will appear in $this.attrs.prop2
prop1: var || []
},
emits: [
'evt1' // context.emit('evt1', xx)
],
setup(vue3)
// 于'beforeCreate()'前执行; 这里面不要使用'this'
// setup() includes: beforeCreate() + created()
setup(props, context){
context.attrs // props没有定义但调用时传递了,则出现在此处!
context.slots // slot
context.emit('evt1', xx)
// 这里面有些逻辑可能很复杂,可以单独组合成独立的模块. 然后在这里引入使用! -- hooks
// isRef isReactive isReadonly isProxy(响应式的obj经过readOnly修饰后还是proxy的)
let age = ref(2) // 指定一个响应式变量的初始值. 使用时:'age.value=5'; 如果修饰的是个对象,内部还是会调用reactive! isRef/isReactive/isProxy/isReadonly;
let obj = reactive({name:''}) // 适用于对象或数组(后续动态追加的属性也自动具有响应式,不想?使用:obj.car=markRaw({})); shallowReactive:仅处理对象第1层属性的响应式!
let point = savePoint() // 调用hooks中的某个模块
let x = toRef(obj.jb, 'jb_attr_name') // 使对象的属性具有响应式(参数1必须是个对象,无需.value)! toRefs(obj.jb):把jb第1层的所有属性都toRef!
let y = toRaw(obj) // 去掉reactive修饰的响应式(与UI剥离关系)
obj = readonly(obj) // 用别人的响应式数据,但是保证不修改它; shallowReadonly(obj):使响应式变量的第1层不可修改
/* MVVM */
// warning: attention the recursive call problem! pay attention to the caller's variable and the template's variable!!
function say(n, e) { // <button @click=clickEvent(2, $event)></button>
e.target.style.backgroundColor = 'blue' // .target表触发事件的事件源!如是一个按钮.
e.preventDefault() // 阻止事件的默认行为(如,点击跳转)
obj.name = '1'
debugger // script中代码的方式打断点
},
context.emit('evt1', 2) // evt1在'emits'属性中声明可能会好点
/* computed */
obj.fullName = computed({ // vue3 defines a computed member. defaults readonly: ()=>{}
get(){
return obj.firstName + obj.LastName
},
set(value){
}
})
/* watch */
watch([obj, obj1], (newValue, oldValue)=>{ // newValues:[objNewValue, obj1NewValue...]; 刚触发时,oldValue可能是空数组[]!
// 注意: 仅能监视'ref/reactive/array'; 能够深度监视(且关不掉,vue2则默认关闭深度监视)
// 注意: reactive修饰的对象拿不到oldValue! 若要监视ref修饰的对象,则需要obj.value(非对象的ref无需)!
})
watch(()=>obj.attr1, (newValue, oldValue)=>{
// 仅监视其中的某个属性(此时oldValue好用了); 如果要监视多个属性,则第一个参数写成数组!
// 注意: 如果attr1本身是个obj,则必须加上{deep:true}才能监视到obj.attr1.xx的变化(即又回落到了vue2)!
}, {
immediate: true // 没有修改初始化值的时候自动进来一次
})
watchEffect(()=>{ // 这里面用到的变量都将被监视. 另外,还附加了'immediate=true'
const x = age.value
const y = obj.job.salary // 很聪明:其他层次或属性的变化并不会进来!
})
return {
obj, // {{obj}}
say,
...x // 解包对象x的所有有属性,一个个的共享给template
}
}
hooks & lifecycle
// usePoint.js
import {reactive, onMounted, onBeforeUnmount} from 'vue'
export function savePoint() {
let point = reactive({
x: 0, y: 0
})
function savePoint(event) {
point.x = event.pageX // 鼠标坐标
point.y = event.pageY
}
/* lifecycle */
on[Before]Mount(()=>{ // 都需要import; v-if时就伴随着组件的挂载/卸载
// before时,模板还没有被渲染,对外表现还是{{x}}.故此时不要使用dom元素!
// mounted后,dom已经被挂载到了view,此时可以操作dom元素.此时长会做一些初始化操作:
// setInterval(), network requests, subscribes, binding events
window.addEventListener('click', savePoint)
})
on[Before]Update(()=>{
// The page is old but the data is new; view is not sync with the data yet!
})
on[Before]Unmount(()=>{
// 一般都是在onBeforeUnmount()里面做一些收尾:
// 仍可使用所有的 datas + methods,但修改data并不会影响view了!
// clearInterval() + cancel subscribes + unbind events
this.$bus.$off('update:evt1')
window.removeEventListener('click', savePoint)
})
return point // 一般会在setup中被使用
}
// component lifecycle
activated() {
this.timer = setInterval(()=>{})
}
deactivated() { // 如tab路由切换
clearInterval(this.timer)
}
beforeRouteEnter(to, from, next) { // 通过路由规则(以下不算:直接引入/嵌入组件)进入当前组件时
next()
}
beforeRouteLeave(to, from, next) { // 通过路由规则离开当前组件时
next()
}
}
custom referrence & provide
setup(props, context){
function myRef(value, extraDefParam) { // let x = myRef('value')
let timer
return customRef((track, trigger)=>{
return {
get(){ // 只要有地方使用了myRef修饰的对象,如v-model="x"或{{x}}
track() // 追踪value后续数据的变化(默认仅保留第1次的值); 与set中的trigger对应(不调用则不会响应trigger)!
return value
},
set(newValue){ // 有人通过UI改了{{x}}
clearTimeout(timer)
timer = setTimeout(()=>{
value = newValue
trigger() // 通知vue重新解析模板(再次触发上面的get-track)
})
}
}
})
}
/* provide */
provide( // 给子孙组件传递数据
'car': obj
)
let n = inject('car') // 使用父辈数据.注意:是响应式的数据!
}
style
npm install -D less
npm install animate.css
import Vant from 'vant' // put these import sentences to <script> section
import 'animate.css'
// scoped: this style only applied to the current component(automatically add 'data-v-xxxx' attribute to every element) == Rule 'A'
// App doesn't use scoped tag, use for storing public styles
<style lang="scss" scoped>
:deep(.xxx) { /* break the rule 'A'. apply current css to child's element */
transition: 1s linear; /* 使用下面的transition(A) */
}
.hello-enter, .hello-leave-to {
transform: translateX(-100%);
}
.hello-enter-to, .hello-leave {
transform: translateX(0);
}
.hello-enter-active, .hello-leave-active {
transform: translateX(0);
}
</style>
vue2
data
// data proxier. This would be a heavy procedure!
// data and its operating method should stay in the same place!
data() { // == vm._data;
return {
// warning: Vue does not add reactive getter/setter to array's item!
// Vue agents and listens all the modified members(push,pop,shift,unshift,splice,sort,reverse) of an array
// If a member of an array is an object, it will become the react object!
// member like lists will be proxied by Object.defineProperty(vm, 'lists', {get(){};set(){}}); vue3 use Proxy instead of defineProperty
lists: [ // vm.lists == vm._data.lists
{name:'leiz', age:'26'},
{name:'dabao', age:'5'}
],
styleObject: { // css value expression
fontSize: '2px' // can be used as prop's value--> :prop1="obj.name" :class="obj"
},
myPropOne: this.propOne // prop has the highest priority(occupied the name: 'propOne'), here is the trick to store and change the prop!
}
},
methods
beforeCreate(){
}
xx(n, e){
this.$refs.btn // access the DOM element by its attribute 'ref="btn"'; can also get the component instance(not only the DOM)!
this.$nextTick(()=>{}) // 延迟执行回调(当dom渲染完毕后)
if ('newAttr' in obj) { // obj.hasOwnProperty('newAttr')
obj.newAttr = value // 第1次已经注册过了,故此处无需再向Vue注册
} else {
this.$set(obj, 'newAttr', value) // vue2:动态给obj添加一个响应式的变量(注册给了vue,以便监听该变量,建立其与View的联系); $delete()
this.hobby[0] = '' // vue2也监听不到数组中元素的变更(不仅仅使对象动态新增或删除的属性)
}
e.keycode // enter is 13
e.target.value = ''; // 此处的target一般表示input
// 箭头函数会继承外层函数的this指向;若直接function(){},则这里面的this会被理解成window!
this.intervalId = setInterval(() => {
var start = this.msg.substring(0, 1)
var end = this.msg.substring(1)
// VM实例会监听自己身上 data 中所有数据的改变: 只要数据一发生变化,就会自动把最新的数据从data上同步到页面中去
this.msg = end + start
}, 400)
}
emit1(v) { // child should listening: <Son @update:evt1="onEvt1"></Son>
// this is the parent logic -->
// bind ref1 to an event(not using <Child @evt1=""/>)
this.$refs.ref1.$on/$once('evt1', (){ // this cb use for receiving data from child
// warning: this stands for the exact component that bind to this event. so, normally you should write this cb outside(or use => function)!
})
this.$bus.$on/$once('evt1', (obj){ // global event bus: suitable for communication between a small number of components
this.info = {...this.info, ...obj} // 仅修改info中改变的项(跳过那些undefined项)
}
// this is the child logic -->
// customized events 'evt1' used for notifying msg from child to parent or siblings
// params will pass to parent
this.$emit('update:evt1', {'key': this.prop1})
this.$bus.$emit('update:evt1', this.prop1) // global event bus
// this is the child(which is binded to the specific event) logic, unbind an event
this.$off('evt1') // unbind multiple events: ['evt1', 'evt2']; empty param means unbind all events!
}
computed
// do not embedded async task(setTimeout e.g.)
// usually used in searching filter
computed: {
rgb(r,g,b){ // like macro, usage: {{ rgb }}
return `rgb(${this.r}, ${this.g}, ${this.b})`
},
fullName(){
return this.firstName + this.lastName
},
isAll(){ // combined with 'v-model:checked=isAll'. get/set's logical are different
get(){
return this.doneTotal == this.total && this.total > 0
}
set(value){ // value: bool
// change all the associated datas
}
}
}
watch
watch: { // supports async task(setTimeout e.g.)
name(newName, oldName) { // 若input则敲击一个字变化一次.可使用lazy修饰v-model!
setTimeout(()=>{ // warning: ensure using '=>'(doesn't have this, so use the outer this instead 'window'--the explorer)!
this.getList()
})
},
username: {
// 若input则敲击一个字变化一次.可使用lazy修饰v-model!
handler: function(new, old){
localStorage.setItem('todos', JSON.stringify(new))
},
immediate: true, // 进入时先触发一次
deep: true // watch this object's attributes change also
}
}
directive
// called when this derective's template changed!
// directive must use this form: 'x-xx-xx'
// 'this' inside directive becomes 'window'!
directives: { // usage: <p v-focus="'red'"></p>
v-focus: { // directive name
bind(e, binding) { // 1. the directive is binding to the calling element by vue
e.style.color = binding.value // red
}
mounted/inserted(e, binding) { // 2. inserting to DOM
e.focus() // <input v-focus>
},
update(element, binding) { // 3. when DOM updated; always has the same body as bind, so can be abbreviated: 'v-focus'(){}
element.value = binding.value
element.focus()
}
}
}
misc
transition, teleport …
<template>
<transition-group name="hello" appear> // for single element, just use '<transition>'
<h1 v-show="show" key="1">hello</h1> // key is required
<h1 v-show="show" key="2">hello</h1>
</transition>
<teleport to="body"> // 可以不显示在当前组件里面,而是跑到主页面的body中,越级了
</teleport>
<Suspence> // 配合动态引入,占位; 此时setup就可以返回异步对象了
<template v-slot:default>
</template>
<template v-slot:fallback>
</template>
</Suspence>
</template>
filters
filters: { // vue3被剔除了
capitalize(str, arg1, arg2){ // 自定义参数总是在前一个输入项后面
}
}
vue.config.js
const path = require('path')
const themePath = path.join(__dirname, './src/theme.less')
moduel.exports = {
css: {
loaderOptions: {
less: {
modifyVars: {
hack: `true; @import "@{themePath}"`
}
}
}
},
devServer: {
proxy: {
'/api/get_users': {
target: '<host>', // 转发到哪里
pathRewrite: { // rewrite path
'^/api': ''
}
}
}
}
}