VUE 总结 (持续刷新。。。)
VUE是渐进式框架
- 可将VUE作为一部分应用嵌入
- 可以使用全家桶构建整个项目
MVVM
View:视图层,DOM层,展示信息
Model: 从服务器请求来的数据
VueModel: 视图模型层,沟通view和model。一方面实现data binding,将model改变反映到view,一方面实现dom listener,当dom发生一些事件,可以被监听,并在需要时改变对应数据。
Vue实例options对象属性
el(string):决定实例会管理哪个DOM
data(Object | Function):子组件中的data必须是函数,并返回对象。数据对象。
methods ({[key: string]: Function}):vue中的方法
computed 计算属性
filters:过滤器 {{ message|capitalize }} v-bind:"message|capitalize" 拿到message的只进行方法过滤
filters: {
captitalize: function(value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
所有组件都继承自vue源型
vue.prototype.test = function(){...("test")}
组件中 this.test()
模板语法
Mustache:{{name}} 标签中使用,也可以是表达式
v-once:数据只渲染一次
v-html:按照string的html解析出来并渲染 v-html="http://www.baidu.com"
v-cloak:避免浏览器出现未来得及编译的mustache标签,直接显示编译后的内容
v-bind:语法糖(:) 将标签属性和vue data进行动态绑定 绑定class 绑定style
:class="{'active': isActive, 'line': isLine}"
:style="{color: currentColor, fontSize:fontSize+'px'}" 对象语法
:class="{'active','line'}"
:style="[baseStyles]" 数组语法
- 计算属性:需要对数据进行进一步转化后才显示。计算属性有getter和setter方法,但set方法不常用,故而不显示。计算属性相比于methods会进行缓存,多次使用只会调用一次。
书籍价值
name=".."
- v-on:语法糖(@) 监听用户点击,拖拽等交互式事件。
在定义事件时写方法忽略了小括号,而方法又需要一个参数,vue会默认将浏览器产生的event事件对象作为参数传入方法
不传参数可以省略() 方法会将event原生事件传进去
拿到事件可以进行一些事件处理(.stop,.prevent,.native,.once)
@click.stop 阻止冒泡 点击子,扶不会被调用
.prevent 阻止事件自己去方法处理数据
keyup.enter="keyup" 监听敲击回车抬起后触发
.native 自定义组件需要加native才会触发
- vue响应式会监听数组改变的方法:push(), pop(), shift()删除第一个元素,unshift()头部添加元素,splice(),sort(),reverse()
arr.splice(2, 0, "wang") index=2的位置插入元素
arr.splice(2, 3, "wang") index=2的位置开始向后删除3个元素
arr.splice(2, 1, "wang") 删除index=2的元素,并用wang代替
arr.splice(2, 3,"wang") 从index=2开始删除三个元素,并插入一个元素wang
arr.slice(6) 提取index=6开始到最后的字符串
arr.slice(6, 10) 提取index从6到10的字符串
- v-model:实现表单元素和数据元素的双向绑定。实质上是语法糖
<==> vbind绑定表单元素 von给数据元素绑定input事件
v-model : radio(单选框) checkbox(复选框) select(复选框)
v-model.lazy(数据失去焦点或回车才会更新) .number(输入内容自动转换为数字类型) .trim(去掉首尾空格)
- input
如果input不加key,当先在账号框输入123时,vue内部会维护一个虚拟dom,当判断下一个input会直接使用vsdom,导致切换到邮箱框仍显示123,所以需要用key来通知vue区新创建一份儿
ES6 语法
- let/var:var只有在函数中才有作用域,在其他地方无作用域。而let具有块级作用域,es5 if,for无块级作用域,es6中if,for有块级作用域
- const:常量,const obj={ name:"why" },obj.name=shuai,const只能保证变量指向内存地址的数据不变,但是对于对象,数组,function等复合变量无法保证指针指向的地址数据结构不变
# 剩余运算符
let [a,...b]=[1,2,3] // a=1,b=[2,3]
let {a,b,...rest}={a:10,b:20,c:20,d:40} // a=10,b=20,rest={c:30,d:40}
function sum(...num){
var sumNum=0;
for (let i=0;i let sum = nums.reduce((x,y)) => {return x+y})
sumNum += parserInt(num[i])
}
console.log(sumNum)
}
# 拓展运算符
let person = {name: "amy", age:15}
let someone = {...person} //拷贝了一份儿
let someone = {...person, name:"mike"} // {name:mike, age:15} 后面会覆盖前面的
let someone = {name:"mike", ...person} // {name: "amy", age:15} 后面会覆盖前面的
object.assign(target,object2,object3) // 对象浅拷贝
assign([2,3],[5]) // [5,3]
[2,3] -> {0:2,1:3}
{0:2,1:3} -> {0:5, 1:3}
-> [5,3]
# MAP object
obj的键只能存字符串或symbols,而map可以存任意值,有序,size获取个数
var myMap = new Map() myMap.set(skyFunc,"123") myMap.get(keyFunc)
迭代
for (var [key, value] of myMap) {
console.log()key+"="+value
}
var key of myMap.keys()
value .values()
myMap.forEach(function(value,key){
key+value
},myMap)
const person = {
seyHi(){
.log("hi") person.hi()
}
}
object.is(value1,value2) === 判断两对象是否严格相同
is(1,1) true is([1],[1]) false is({g:1},{g:1}) false
# set 存储唯一值
let mySet = new set()
mySet.add(1) .add(1,5)
# array
myArray = [...mySet]
去重 var mySet = new set([1,2,3,4,4]) [...mySet] // [1,2,3,4]
并集 new Set([...a, ...b])
交集 ([...a.filter(x=>b.has(x))])
差集 ([...a.filter(x=>!b.has(x))])
# 字符串
.includes .startswith .endswith .indexof .lastIndexOf .repeat(2) // "123,123"
模板字符串 let name="mike" `my name is ${name}` ${f()} 调用函数
# 增强字面量
对象增强写法,增强字面量:let obj = { name, obj } <==> let obj = { name: name, age: age } test: function() {} <==> test () {}
const age=12, name="amy" const person={age, name}
# 函数
function f(...value) {
... value.length f(1,2) f(1,2,3,4)
}
var f = (a,b) => a+b
var f = (id,name) => ({id:id, name:name}) 对象需要加()
# 箭头函数体中this对象,是定义函数时对象而不是使用函数的对象
function fn(){
setTimeout(()=>{
(this.a) // this绑定的是fn中的this对象
}, 0)
}
(param1 ... paramN) => {statements}
=> expression <=> {return expression}
只有一个参数时圆括号可选 singleParam => {statements}
无参数可写成圆括号 () => {statements}
let f = ([a,b]=[1,2],{x,c}={x:a+b}) => a+b+c // f() 6
function(element) {return element.length}
= (element) => {return element.length}
= element => {return element.length}
= element => element.length
var func = () => ({foo:1}) # 对象自必须加()
render:h=>(App) <=> render(h){ return h(App) }
# 导入导出
import {myName,myClass} from "./test.js"
let myName = "Tom"
export {myName as exportName}
import {exportName} from "./test.js"
Promise
- 异步编程的一种解决方案,封装一个网络请求,不能立即拿到结果,往往会传入一个函数,在数据请求成功后,将数据通过传入的函数回调出去,异步操作
链式调用
new Promise((resolve, reject) => {
setTimeout(function() {
resolve('Hello World') // 成功时调用resolve, 后续的then会被回调
}, 1000)
}).then(data => {
console.log(data) // Hello World
return Promise.resolve(data+'111') // 可以简写为 return data+'111'
}).then({
console.log(data) // Hello World111
return Promise.resolve(data+'222')
}).then(error => {
console.log(data) // Hello World111222
return Promise.reject(data+'error') // 失败时调用reject, 后续的catch会被回调
}).then({
console.log(data) // 不会执行
return Promise.resolve(data+'333')
}).catch({
console.log(data) // Hello World111222error
return Promise.resolve(data+'444
}).then({
console.log(data) // Hello World111222error444
Promise的三种状态:
pending:等待 正在进行网络请求时,或者定制器没有到时间
fulfill:满足状态 当我们主动回调了resolve时,处于该状态,并且会回调.then()
reject:拒绝状态 当我们主动调用了reject时,并且会回调.catch()
条件判断与循环遍历
- v-if,v-else-if, v-else: 问题:如果input标签中的元素已经被写入内容,在条件判断切换类型以后内容还在,因为Vue在进行DOM渲染的时候,会尽可能复用已经存在的元素。解决:为input添加key元素,保证key的唯一性,就不在复用。
- v-show 和if区别:if不显示不会渲染DOM,show不显示仅仅是设置样式display:none,当if条件时false时,包含v-if元素根本就不会在DOM中,而v-show只是给元素增加了display:none的行内样式
- v-for 最好加上key。说明:VUE在插入节点时会使用DIFF算法,也就是会将要更新的节点先覆盖插入位置,再往后逐次更新,最后插入最后一个元素,因此需要用key来给每个节点做唯一标识,算法就可以正确识别节点。为了高效的更新虚拟DOM。
{{value}}-{{key}}-{{index}}
info : {
name: 'wang'
}
组件
全局组件注册语法糖
vue.component('cpn',{
template:`
`
})
注册局部组件
const app = new Vue({
..
components:{
'cpn2':{
templates ..
}
}
})
# 模板分离
1. script 标签
通信
父传子
- 父节点通过props属性值向子组件传递数据
子组件中: props: ['message-zi']
父组件中: data: { message-fu: 'hello' }
通过vbind监听父组件message-fu,将数据给message-zi
{{message-zi}} 在子组件模板中使用
- props可以是数组,对象,优先使用对象形式 props: { A: Number, B: [string, Number], C: { type: string, required: true}, D: {type: Number, default: 100}, E : {type: Object, default: function() {return { message: ’hello‘}}}} 对象或数组的默认值必须从一个工厂函数中获取
子传父
- 子组件通过$emit()触发事件,父组件通过v-on监听事件
子组件方法:increment(){
this.counter++;
this.$emit('increment', this.counter) 子组件发出事件
}
子组件模板:
子组件接受父组件参数后就不要对这个属性修改,修改要由父组件完成
子组件
1
cpn:{
template: "#cpn",
props:{number1:number1, number2:number}, 4
data() {
return {
dnumber: this.number1, 3
dnumber2:this.number2
}
},
method: {
num1Input(event) { 2
this.dnumber1=event.target.value 5
this.$emit('num1change', this.dnumber1)
}
},
watch: {
dumber1(newValue, oldValue){
this.dnumber2=newValue*100
this.emit('num1change',newValue)
}
}
}
父组件
..
methods:{
num1change(value){ 3
this.num1=value
}
}
点击1 -> 触发2 -> 3 设置num1 子->父 -> 4-> 5 设置dnumber1 父传子
访问方式
- $children,$ref:父组件可以通过两种方式访问子组件的数据 this.$children 拿到所有子组件的数组对象,推荐使用ref
showRef() {
consloe.log(this.$refs.child1.message) 通过ref指定子组件标签的唯一id,再用refs就可以使用该id进行指定子组件标签的访问
}
- $parent 子组件中使用this.$parent可以拿到父组件的属性,但是不建议这样用,子组件应该保持独立,不要和父组件有耦合事件产生
slot插槽
- 当有一类组件有公共的部分,但在某些方面又各有区别,比如每个页面的导航栏。这时可以使用插槽,抽取公共部分组件,不同部分预留插槽,插槽部分可以让使用者根据自己的需求进行定制化插入操作。
子组件模板:
插槽默认内容
具名插槽 左侧内容
使用子组件:
替换插槽
再次替换插槽
替换左侧 可以使用具名插槽来指定替换的插槽 默认会全部替换
- 作用域插槽:某些场景对同一组数据,需要有不同的显示格式,可以用作用域插槽
子组件模板: pLanguages : ['Java', 'Python', 'C++']
使用: 列表形式展现
- {{info}}
水平展示: {{info}}
模块化
- CommonJS的导入导出:
module.exports = {
flag: true,
test (a, b){
return a+b
}
}
let { test, flag } = require('moduleA')
let _mA = require('moduleA')
let test = _mA.test
- ES6 导入导出 export { name, age, height }
export function test(){} 导出方法
export class Person { 导出类
constructor(name, age) {
this.name = name;
this.age = age;
}
}
引入打包后的文件 或定义出入口:
module.exports = {
entry: './src/main.js'
output: {
path: path.resolve(_dirname, 'dist'),
filename: 'bundle.js',
publicPath: 'dist'
}
}
- loader转换器 css文件处理: css-loader,style-loader,less文件处理:less-loader, 图片文件处理: url-loader, 小于8kb的图片进行base64编码,大于8kb图片,需要file-loader,ES6->ES5: bable-loader
el用于指定vue要管理的DOM,如果vue实例中制定了template, 会替换掉挂载的对应el模板
这样就不用操作index.html,只需更改模板
vue文件三部分分离:template script style
new Vue({
el: '#app',
template: '{{message}}'
data: {
message: 'wang'
}
})
- vue-loader和vue-template-complier可以解析vue格式的文件
webpack.config.js
{
test: /\.vue$/,
use: ['vue-loader']
}
- plugin插件 loader是转换某些类型的模块,plugin是对webpack的扩展,扩展器
bannerPlugin 打包后的bundle添加版权
HtmlWebpackPlugin 将index.html打包到dist文件夹下
uglyfyjs-webpack-plugin 对打包后的js文件进行压缩
VUE CLI
- command line interface 命令行界面 脚手架
- NPM(Node Package Manager) NodeJS包管理和分发工具
- node 为js提供了运行环境
npm install -g @vue/cli 安装vue cli3
npm install -g @vue/cli-init 安装 vue cli2
vue init webpack my-project 初始化 vue cli2项目
vue create my-project 初始化 vue cli3项目
- Runtime-Complier(运行时编译)和Runtime-only(只有运行时)
complier
new Vue({
el: '#app'
components: {App}
template: ' '
})
only
new Vue({
el: '#app'
render: h=>h(App) <=> render(h) { return h(App) } 会把div标签替换掉
})
render函数:
new Vue({
render: (createElement) => {
return createElement('标签', '相关数据对象(选传)', ['内容数组'])
return createElement('div', '{class: 'box'}', ['wang'])
// wang
return createElement('div', '{class: 'box'}', ['wang', createElement('h2', ['标题'])])
// wang标题
})
template -> ast -> render -> vdom -> 真实dom
only 代码中不允许有template,complier可以有template,用于编译,默认使用only
vue运行过程: template被编译成ast(抽象语法数),转换为render函数,返回virtual dom(虚拟dom)
only就是跳过了第一步,直接使用render函数将语法数转换为虚拟dom,比complier少一步,因此更加轻量级,尽量用only进行开发
webpack.base.conf.js起别名
resolve: {
extensions: ['.js', '.vue', '.json'] // 不用在导入时写后缀
alias: {
'@': resolve('src'),
'pages': resolve('src/pages'),
'components': resolve('src/components') // html要加~
}
}
- vue-cli3 大大简化配置,移除文件根目录下的build和config配置文件夹,移除static,新增public文件夹,并将index.html移到public中 使用vue ui可视化配置
cli3起别名
vue.config.js
module.exports = {
configureWebpack: {
resolve: {
alias: {
'components': '@/components',
'pages': '@/pages'
}
}
}
}
VUE ROUTER
路由:路由决定数据包从来源到目的地的路径。转送:将输入端的数据转移到合适的输出端。路由中的路由表(映射表)决定了数据包的指向
后端路由:每个网页有对应的URL,服务器根据URL进行正则匹配,交给相应的controller处理,将生成的HTML数据交给前端,前端模块交给后端人员,前端人员要开发,需要PHP或JAVA,逻辑与HTML混在一起。
前端路由:后端只提供API,前端通过Ajax获取数据,通过JS将数据渲染到页面中。
单页面富应用(SPA):在前后端分离的基础上加了一层前端路由。
history接口
location.href = "http://192.168.1.101:8000/examples/urlChange" 锚点#
history.pushState({}, '', '/foo') // "...8000/foo" 页面可以回退
histtory.replaceState({}, '', '/foo') // "...8000/foo" 页面无法回退
history.go(-1) <=> history.back() // href 后退一级
history.go(1) <=> history.forward() // href 前进一级
- npm install vue-router --save 安装
index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
const Message = () => import('../components/message') // 箭头函数无参数用() 函数体只有一句话可以省略 {} 和 return 路由懒加载
const routes = [
{path: '/',
redirect: '/home'}, // 设置默认路径
{path: '/home',
component: () => import('../components/Home')} // 传统会在构建时将所有页面都打包在一个js文件,导致请求页面耗时,路由的懒加载 将路有对应的组件打包成对应的js代码块,只有在这个路由在被访问到的时候,才去加载对应的组件
{path: '/home',
components: Home,
children: [
{path: '',
component: Message} // 嵌套路由设置默认路径
{path: 'message',
component: Message}, // /home/message
{path: 'news',
component: News} // /home/news
]},
{path: '/about',
components: About},
{path: '/user/:id', // /user/123 动态路由 {{$route.params.id}} 123
components: User}
] // 定义路由
const router = new VueRouter({ // 创建路由实例
routes,
mode: 'history' // 改变路径的显示由hash变为HTML的history模式
})
export default router // 导出路由实例
main.js
import router from './router'
new Vue({
el: '#app'
router, // 挂载到vue实例
render: h => h(App)
})
App.vue
首页 vue-router组件被渲染为a标签 tag: 指定被渲染成什么标签tag='li', replace: 无history记录,无法回退
关于
路由占位标签 会根据当前路径 动态渲染出不同的组件
- 路由代码跳转
this.$router.push('/home') 代码方式跳转到主页
- 参数传递
params类型:
/router/:id 在path后跟对应的值 /router/123, /router/abc
query:
对象中使用query的key作为传递方式 /router?id=123
router方式传参
JS代码方式传参
this.router.push({ path: '/profile' + 123, query: {name: 'wang', age: 18}})
获取参数
$route.params // {"id": 123}
$route.query // {name: 'wang', age: 18}
this.$route 当前活跃的路由 { params:{id:"zhangsan"}, path:"/user/zhangsandf;''"}
$router: VueRouter实例,想要导航道不同URL就用$router.push
$route: 当前活跃的路由信息,可以获取name, path, query, params。。
- 导航守卫:监听路由的进入和离开 beforeEach 路由即将改变前触发 afterEach 路由改变后触发
改变网页的标题
{path: 'home',
component:
meta: {
title: '首页'
}}
// 路由实例 to: 即将要进入的目标路由对象 from: 即将要离开的路有对象 next: 必须调用next方法,才能进入下一个钩子
router.beforeEach((to, from, next) => {
window.document.title = to.meta.title
next()
})
- keep-alive 可以使被包含的组件保留状态,避免重新渲染
Vuex
Vuex是专门为Vue应用程序开发的状态管理模式。提供了在多个组件间共享状态的插件,保证所有的属性做到响应式。
用户的登录状态,用户名称,头像,地理位置,商品的收藏,购物车中的物品可以放在其中。
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
decrement(state) {
state.count--
}
}
})
export default store
main.js
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
使用Vuex
this.$store.state.count
this.$store.commit('increment') // 必须通过commit的方式来修改状态,为了让Vuex可以追踪状态的变化
state:单一状态树 Single Source of Truth 单一数据源
getters: 获取state中变异后的状态
getters: {
greaterAgesStus: state => {
return state.students.filter(s => s.age >= 20)
},
greaterAgesCount: (state, getters) => {
return getters.greaterAgesStus.length
},
stuById: state => { // getters默认不返回参数,如果要传递参数,只能让getters返回另一个函数 function stuById(state) { return function (id) {return ... }}
return id => {
return state.students.find(s=>s.id===id)
}
}
}
- mutation:更新store状态的唯一方式就是提交mutation,同步方法
mutations: {
带参数
decrement(state, n) {
state.count -= n // this.$store.commit('decrement', 2)
}
多个参数
changeCount(state, paylocad) {
state.count = payload.count // this.$store.commit('changeCount', {count: 0})
} // 或者 this.$store.commit({ type: 'changeCount', count: 100})
updateInfo(state, payload) {
state.info['height'] = payload.height // 这样赋值页面不会刷新,不是响应式
Vue.set(state.info, 'height', payload.height) // 响应式
state.info = {...state.info, 'height': payload.height} // 拓展运算符更新属性 响应式
}
}
- mutation常量类型 考虑到mutation的方法会越来越多,可以使用常量来定义mutation里事件的类型
mutation-types.js
export const UPDATA_INFO = 'UPDATE_INFO'
vuex
import * as types from './mutation-types'
mutations: {
[types.UPDATE_INFO](){
}
}
component
import {UPDATE_INFO} from "./store/mutation-types"
this.$store.commit(UPDATE_INFO, {height: 1.88})
- Action 代替mutation进行异步操作 component dispatch => action commit => mutations => state render => component
actions: {
increment(context, payload) { // context 上下文变量
setTimeout(() => {
context.commit('increment', payload)
})
}
}
this.store.dispatch('increment, {cCount, 5}') // 调用action方法时,用dispatch
action返回Promise
action: {
increment(context) {
return new Promise((resolve) => {
...
})
}
}
this.$store.dispatch('increment').then(res => {...}) // 因为返回的是Promise对象,可以使用它的then
- Module 可以将store分割为多个模块,每个模块有自己的state,mutation。。。
const moduleA = { state: {..}, mutations: {..}, ...}
const moduleB = { state: {..}, mutations: {..}, ...}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // moduleA状态
store.state.b // moduleB状态
项目结构
|- index.html
|- main.js
|- api
|- ... # 抽取出API请求
...
|- store
|- index.html # 组装模块并导出 store 的地方
|- actions.js # 根级别的 action
|- mutations
|- modules
|- cart.js # 子模块
|- products.js
vue3
3.0六大亮点
-performance: 性能比vue2.x 快1.2 - 2倍
-Tree shaking support: 按需编译,体积比2.x更小
-Composition API:组合API(类似react hooks)
-Better TypeScript support:更好Ts支持
-Custom Renderer API:暴露了自定义渲染的API
-Fragment,Teleport(Protal),Suspense:更先进的组件
# Vue3.0如何变快的
- diff 算法优化 https://vue-next-template-explorer.netlify.app/
Vue2中的虚拟dom是进行全量的对比
Vue3新增了静态标记(PatchFlag)
在与上次虚拟节点进行对比的时候,只对比带有patch flag的节点,并且可以通过flag信息得知当前节点要对比的具体内容
知播渔
知播渔
知播渔
{{msg}}}
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", null, "知播渔"),
_createVNode("p", null, "知播渔"),
_createVNode("p", null, "知播渔"),
_createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
]))
}
- hotStatic静态提升
vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染
Vue3中对于不参与更新的元素,会做静态提升,只会创建一次,在渲染时直接复用即可
知播渔
知播渔
知播渔
{{msg}}}
静态提升之前:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", null, "知播渔"),
_createVNode("p", null, "知播渔"),
_createVNode("p", null, "知播渔"),
_createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
]))
}
静态提升之后:
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "知播渔", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "知播渔", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "知播渔", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_hoisted_1,
_hoisted_2,
_hoisted_3,
_createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
]))
}
- cacheHandlers事件侦听器缓存
默认情况下onClick会被视为动态绑定,所以每次都会去追踪他的变化,但是是同一个函数,就直接缓存起来复用即可
事件监听缓存
开启事件监听缓存之前:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", { onClick: _ctx.onClick }, "按钮", 8 /* PROPS */, ["onClick"])
]))
}
开启事件监听缓存之后:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
}, "按钮")
]))
}
注意点: 转换之后的代码, 大家可能还看不懂, 但是不要紧
我们只需要观察有没有静态标记即可
因为我们知道在Vue3的diff算法中, 只有有静态标记的才会进行比较, 才会进行追踪
- ssr渲染
+ 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,
即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟dmo来渲染的快上很多很多。
+ 当静态内容大到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node,
这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。
PatchFlags
附录: PatchFlags
export const enum PatchFlags {
TEXT = 1,// 动态文本节点
CLASS = 1 << 1, // 2 // 动态 class
STYLE = 1 << 2, // 4 // 动态 style
PROPS = 1 << 3, // 8 // 动态属性,但不包含类名和样式
FULL_PROPS = 1 << 4, // 16 // 具有动态 key 属性,当 key 改变时,需要进行完整的 diff 比较。
HYDRATE_EVENTS = 1 << 5, // 32 // 带有监听事件的节点
STABLE_FRAGMENT = 1 << 6, // 64 // 一个不会改变子节点顺序的 fragment
KEYED_FRAGMENT = 1 << 7, // 128 // 带有 key 属性的 fragment 或部分子字节有 key
UNKEYED_FRAGMENT = 1 << 8, // 256 // 子节点没有 key 的 fragment
NEED_PATCH = 1 << 9, // 512 // 一个节点只会进行非 props 比较
DYNAMIC_SLOTS = 1 << 10, // 1024 // 动态 slot
HOISTED = -1, // 静态节点
// 指示在 diff 过程应该要退出优化模式
BAIL = -2
}
vite
Vite是作者开发的一款意图取代webpack的工具,实现原理是利用ES6的import会发送请求去加载文件特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间
安装vite:npm install -g create-vite-app
利用vite创建vue3项目:create-vite-app projectName
安装依赖运行项目 cd projectName; npm install; npm run dev
组合API
import { ref } from 'vue'
setup() {
let count = ref(0) // 定义了count变量,初始值是0,发生改变后,Vue会自动更新UI
// 组合API中定义方法直接定义
function myFn() {
count.value += 1
}
// 在组合API中定义的变量/方法,要想在外界使用,必须通过return暴露出去
return { count, myFn }
}
-
// 将方法抽离出去,数据和业务逻辑绑定
function useRemoveStudent() {
// ref函数只能监听简单类型变化,不能监听复杂类型变化(对象/数组)
let state = reactive({
stus: [
{id:1, name:'zs',age:10},
{id:2, name:'ls',age:20},
]
})
function remStu(index) {
state.stus = state.stus.filter((stu, inx) => idx !== index)
}
return {state, remStu}
}
setup {
let { state, remStu } = useRemoveStudent()
return { state, remStu }
}
// setup函数执行时机
beforeCreate: 表示组件刚刚被创建出来,组件的data和methods还没有初始化好
setup
Create: 组件刚刚被创建出来,并且data和methods已经初始化好
注意点:
- 由于在执行setup函数时,还没有create生命周期方法,所以无法使用data和methods
- 所以vue为了避免我们出错,直接将setup函数中的this修改为了undefined
- setup函数只能是同步不能是异步的
// reactive
- reactive是vue3中提供实现响应式数据的方法
- 在vue2中响应式数据是通过defineProperty实现的,而在vue3中响应式数据时通过ES6的proxy实现的
注意点:
- reactive参数必须是对象(json/arr)
- 如果给reactive传递了其他对象,修改对象,界面不会刷新,如果想刷新,可以通过重新赋值的方式
setup() {
let state = reactive({
time: new Date()
})
function myFn() {
// 直接修改以前的不会刷新
state.time.setDate(state.time.getTime())
// 重新赋值
const newTime = new Date(state.time.getTime())
newTime.setDate(state.time.getDate() + 1)
state.time = newTime
}
return {state, myFn}
}
// 什么是ref
- ref和reactive一样,也是用来实现响应式数据的方法
- 由于reactive必须传递一个对象,如果只想让某个变量实现响应式会非常麻烦,vue3就提供了ref方法,实现对简单值的监听
ref本质
- ref底层本质还是reactive,系统会自动根据我们给ref传入的值转换成ref(xx) -> reactive({value: xx})
注意点
- 在vue中使用ref的值不用通过value获取,在js中使用ref值必须通过value获取
{{age}}
setup() {
let age = ref(18)
function myFn(){
age.value = 666
}
return {age, myFn}
}
// ref 和 reactive的区别
如果在template里使用的是ref,vue会自动帮我们添加.value
如果在template使用的是reactive数据,vue不会自动帮我们添加.value
vue如何决定是否需要自动添加.value的
vue在解析数据之前,会自动判断这个数据是否是ref类型的,如果是就自动添加.value,如果不是就不自动添加.value
vue如何判断当前的数据是否是ref类型
通过当前数据的__v_ref来判断
如果有这个私有属性,并且取值为true,就代表一个ref类型的数据
如何判断数据是ref还是reactive -> isRef/isReactive方法
isRef(age) isReactive(age)
递归监听
默认情况下,无论是通过ref还是reactive都是递归监听
递归监听存在数据量较大,非常消耗性能的问题
setup() {
let state = reactive({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
function myFn() {
// state.a = '1';
// state.gf.b = '2';
// state.gf.f.c = '3';
// state.gf.f.s.d = '4';
// state.value.a = '1';
// state.value.gf.b = '2';
// state.value.gf.f.c = '3';
// state.value.gf.f.s.d = '4';
}
}
// 非递归监听
shallowRef / shallowReactive
如何触发非递归监听属性更新界面
shallowRef通过triggerRef触发
一般情况下,使用ref和reactive即可,只有在需要监听的数据量比较大的时候,才使用shallowRef/shallowReactive
ref -> reactive
ref(10) -> reactive({value: 10})
shallowRef -> shallowReactive
shallowRef(10) -> shallowReactive({value: 10})
setup(){
// let state = shallowReactive({
let state = shallowRef({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
});
// state.value = {
// a:'1',
// gf:{
// b:'2',
// f:{
// c:'3',
// s:{
// d:'4'
// }
// }
// }
// }
// state.value.a = '1';
// state.value.gf.b = '2';
// state.value.gf.f.c = '3';
// state.value.gf.f.s.d = '4';
state.value.gf.f.s.d = '4';
注意:vue3只提供了triggerRef方法,没有提供triggerReactive方法,所以如果是reactive类型数据,是无法主动触发界面更新的
triggerRef(state)
注意:如果是shallowRef创建的数据,vue监听的是.value的变化,并不是第一层的变化,因为底层实质上value才是第一层
console.log(state.value.gf.f.s)
}