基础------>高级特性+原理(框架)------->设计+工作经验
Vue
v-show和v-if的区别
为什么v-for会用key
描述vue组件生命周期(有父子组件的情况下)
Vue组件如何通信
描述组件渲染和更新的过程
双向数据绑定和v-model的实现原理
React
框架综合应用
基于React设计一个todolist(组件结果,redux state数据结构)
基于Vue设计一个购物车(组件结果,vuex State数据结构)
先学vue2再学vue3
vue和react越来越接近
{{message}}
、表达式{{flage?'yes':'no'}}(只能是表达式,不能是js语句)
:id="myid"
使用动态属性、使用驼峰式写法
<p :class="{ black: isBlack, yellow: isYellow }">使用 class</p>
<p :class="[black, yellow]">使用 class (数组)</p>
<p :style="styleData">使用 style</p>
data() {
return {
isBlack: true,
isYellow: true,
black: 'black',
yellow: 'yellow',
styleData: {
fontSize: '40px', // 转换为驼峰式
color: 'red',
backgroundColor: '#ccc' // 转换为驼峰式
}
}
}
event 参数,自定义参数。1、event是原生对象;2、事件被挂载到当前元素。
<button @click="increment1">+1button>
<button @click="increment2(2, $event)">+2button>
事件修饰符,按键修饰符
<a v-on:click.stop="">a>
<from v-on:submit.prevent="">from>
<a v-on:click.stop.prevent="">a>
<from v-on:click.submit.prevent>from>
<div v-on:click.capture="">div>
<div v-on:click.self="">div>
<button @click.ctrl="">button>
<button @click.ctrl.exact="">button>
<button @click.exact="">button>
【观察】事件被绑定到哪里?
修饰符 lazy number trim
<input type="text" v-model.trim="name"/>
<input type="text" v-model.lazy="name"/>
<input type="text" v-model.number="age"/>
常见表单项 textarea checkbox radio select
<p>多个复选框 {{checkedNames}}p>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jacklabel>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">Johnlabel>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mikelabel>
<p>单选 {{gender}}p>
<input type="radio" id="male" value="male" v-model="gender"/>
<label for="male">男label>
<input type="radio" id="female" value="female" v-model="gender"/>
<label for="female">女label>
父到子,通过props传属性。子到父,通过$emit触发父的方法。
<List :list="list" @delete="deleteHandler"/>
props: {
// prop 类型和默认值
list: {
type: Array,
default() {
return []
}
}
},
methods: {
deleteItem(id) {
this.$emit('delete', id)
}
},
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler)
// 调用自定义事件
event.$emit('onAddTitle', this.title)
// 及时销毁,否则可能造成内存泄露
event.$off('onAddTitle', this.addTitleHandler)
父组件:
{{name}}
子组件:
基本使用
{{website.title}}
默认内容,即父组件没设置内容时,这里显示
作用域插槽
{{website.subTitle}}
{{slotProps.slotData.title}}
缓存组件
频繁切换,不需要重复渲染
Vue常见性能优化
带有层级的,或者tab,用v-show不方便
mixin.js:
export default {
data() {
return {
city: '北京'
}
},
methods: {
showName() {
// eslint-disable-next-line
console.log(this.name)
}
},
mounted() {
// eslint-disable-next-line
console.log('mixin mounted', this.name)
}
}
组件:
{{name}} {{major}} {{city}}
所有的异步操作都在Actions里完成
hash模式(默认),如http://abc.com/#/user/10
H5 hostorty模式,如http://abc/user/20
后者需要server端支持,因此无特殊需求可选择前者
component:()=>import("./a")
总结:vueX和vue-router懂怎么配置就行,重点在vue
v-if会频繁的操作dom,v-show只会频繁控制样式的显示隐藏,如果组件用于频繁切换的场景那么就用v-show。
key不能用index,只能用业务中不能重复的值。
1,单个组件:挂载(beforeCreate,created,beforeMount,mounted),更新(beforeUpdate,updated),销毁(beforeDestroy,destroyed)。
2,父子组件:
创建是从外到内的,渲染是从内到外的。
暂时不会,接下来可以会
暂时不会,接下来可以会
面试为何会考察原理?
面试中如何考察?以何种方式考察?
vue原理包括那些?
“很久以前”就有组件化
数据驱动视图(MVVM,setState)
传统组件,只是静态渲染,更新还要依赖操作DOM
数据驱动视图 - vue MVVM(model-view-viewModel)
数据驱动视图 - React setState
const data = {};
const name = "zhangsan";
Object.defineProperty(data,"name",{
get: function() {
console.log("get")
return name;
},
set: function(newVal) {
console.log("set");
name = newVal;
}
})
console.log(data.name)
// get
// zhangsan
data.name = "lisi";
//set
// 触发更新视图
function updateView() {
console.log('视图更新')
}
// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function () {
updateView() // 触发视图更新
oldArrayProperty[methodName].call(this, ...arguments)
// Array.prototype.push.call(this, ...arguments)
}
})
// 重新定义属性,监听起来
function defineReactive(target, key, value) {
// 深度监听
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
// 深度监听
observer(newValue)
// 设置新值
// 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue
// 触发更新视图
updateView()
}
}
})
}
// 监听对象属性
function observer(target) {
if (typeof target !== 'object' || target === null) {
// 不是对象或数组
return target
}
// 污染全局的 Array 原型
// Array.prototype.push = function () {
// updateView()
// ...
// }
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 重新定义各个属性(for in 也可以遍历数组)
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 准备数据
const data = {
name: 'zhangsan',
age: 20,
info: {
address: '北京' // 需要深度监听
},
nums: [10, 20, 30]
}
// 监听数据
observer(data)
// 测试
// data.name = 'lisi'
// data.age = 21
// // console.log('age', data.age)
data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组
1、基础
2、背景
3、解决方案 - vdom
有了一定复杂度,想要减少计算次数比较困难
能不能把计算放到js中计算?因为JS执行速度很快
vdom - 用js模拟DOM结构,计算出最小变更,操作DOM
<div id="div1" class="container">
<p>vdom p>
<ul style="font-size: 20px">
<li>ali>
ul>
div>
{
tag: "div",
props: {
className: "container",
id: "div1"
},
children: [
{
tag: "p",
children: []
},
{
tag: "ul",
props: {style: "font-size:20px;"},
children: [
{
tag: "li",
children: "a"
}
]
}
]
}
4、通过snabbdom学习vdom
简介强大的dom库,易学易用
Vue参考它实现的vdom和diff
https://github.com/snabbdom/snabbdom
Vue3.0重写了vdom的代码,优化了性能
但vdom的基本理念不变,面试考点也不变
React和vdom的具体实现也不相同,但不妨统一学习
5、snabbdom重点总结
6、vdom总结
1、树的diff时间复杂度O(n^3)
2、优化时间复杂度到O(n)
1、示例:
import { init, classModule, propsModule, styleModule, eventListenersModule, h,} from "snabbdom";
const patch = init([
// 通过传入模块初始化 patch 函数
classModule, // 开启 classes 功能
propsModule, // 支持传入 props
styleModule, // 支持内联样式同时支持动画
eventListenersModule, // 添加事件监听
]);
const container = document.getElementById("container");
const vnode = h("div#container.two.classes", { on: { click: someFn } }, [
h("span", { style: { fontWeight: "bold" } }, "This is bold"),
" and this is just normal text",
h("a", { props: { href: "/foo" } }, "I'll take you places!"),
]);
// 传入一个空的元素节点 - 将产生副作用(修改该节点)
patch(container, vnode);
const newVnode = h(
"div#container.two.classes",
{ on: { click: anotherEventHandler } },
[
h(
"span",
{ style: { fontWeight: "normal", fontStyle: "italic" } },
"This is now italic type"
),
" and this is still just normal text",
h("a", { props: { href: "/bar" } }, "I'll take you places!"),
]
);
// 再次调用 `patch`
patch(vnode, newVnode); // 将旧节点更新为新节点
2、要素:
3、源码:
h函数
return vnode(sel, data, children, text, undefind)
。vnode 导出一个函数
return (sel, data, children, text, elm, key)
。patch函数
来源:var patch = snabbdom.init([相关参数])
。
init函数返回了一个patch函数。
patch函数参数
function patch(oldVnode: VNode|Element, vnode: VNode):VNode {
/** 略 */
// cbs就是callbacks,执行pre hooks。pre属于生命周期第一个周期
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
// 第一个参数不是vnode
if(!isVnode(oldVnode)){
// 创建一个空的vnode,关联到这个DOM元素
oldVnode = emptyNodeAt(oldVnode);
}
// 相同的 vnode,sameVnode返回的是:vnode1.key===vnode2.key && vnode1.sel===vnode2.sel,key和sel都相同,如果没有传key,则都是undefind,undefind===undefind是true。
if(sameVnode(oldVnode, vnode)) {
// 进行vnode对比
patchVnode(oldVnode, vnode, insertedVnodeQueue)
}else{
// 不相同的 vnode,直接删掉重建
/** 略 */
// 重建
createdElm(vnode, insertedVnodeQueue)
}
}
patchVnode
function patchVnode(oldValue: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
// 执行prepatch hook
const hook = vnode.data?.hook;
hook?.prepatch?.(oldVnode, vnode);
// 设置vnode.elem, 把新的vnode上挂上dom
const elm = (vnode.elm = oldVnode.elm)!;
// 旧的children
const oldCh = oldVnode.children as VNode[];
// 新的children
const ch = vnode.children as VNode[];
if(oldVnode === vnode) return;
/** hook相关,略 */
// vnode.text === undefind (vnode.children一般有值)
if(isUndef(vnode.text)){
if (isDef(oldCh) && isDef(ch)) { // 新旧都有 children
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
} else if (isDef(ch)) { // 新children有
if (isDef(oldVnode.text)) api.setTextContent(elm, "");
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) { // 没有新的,有旧的
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) {
api.setTextContent(elm, "");
}
} else if(oldValue.text !== vnode.text) {// vnode.text !== undefind (vnode.children无值)
// 移除旧children
if(isdef(oldCh)) {
// 移除旧children
removeVnodes(elm, oldCh, 0, oldCh.length-1);
}
// 设置新 text
api.setTextContent(elm, vnode.text);
}
}
updateChildren
这个只是snabbdom的实现方式,vue和react可能对比方式有所不同。
function updateChildren (parentElm: Node, oldCh: VNode[], newCh: VNode[], insertedVnodeQueue: VNodeQueue) {
let oldStartIdx = 0;
let newStartIdx = 0;
let oldEndIdx = oldCh.length - 1;
let oldStartVnode = oldCh[0];
let oldEndVnode = oldCh[oldEndIdx];
let newEndIdx = newCh.length - 1;
let newStartVnode = newCh[0];
let newEndVnode = newCh[newEndIdx];
let oldKeyToIdx: KeyToIndexMap | undefined;
let idxInOld: number;
let elmToMove: VNode;
let before: any;
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode == null) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
} else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx];
} else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx];
} else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx];
// 开始和开始对比
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
// 结束和结束对比
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
// 开始和结束对比
} else if (sameVnode(oldStartVnode, newEndVnode)) {
// Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
api.insertBefore(
parentElm,
oldStartVnode.elm!,
api.nextSibling(oldEndVnode.elm!)
);
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
// 结束和开始对比
} else if (sameVnode(oldEndVnode, newStartVnode)) {
// Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldEndVnode.elm!, oldStartVnode.elm!);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
// 没对比上
} else {
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
}
// 拿新节点的key,能否对应上 oldCh 中某个节点的key
idxInOld = oldKeyToIdx[newStartVnode.key as string];
// 如果没有对应上
if (isUndef(idxInOld)) {
// New element
// 新的,没有对应,直接插入
api.insertBefore(
parentElm,
createElm(newStartVnode, insertedVnodeQueue),
oldStartVnode.elm!
);
// 对应上了
} else {
elmToMove = oldCh[idxInOld];
// sel 是否相等(sameVnode的条件)
if (elmToMove.sel !== newStartVnode.sel) {
// New element
// 新的,没有对应,直接插入
api.insertBefore(
parentElm,
createElm(newStartVnode, insertedVnodeQueue),
oldStartVnode.elm!
);
// select 相等,key相等
} else {
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
oldCh[idxInOld] = undefined as any;
api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!);
}
}
newStartVnode = newCh[++newStartIdx];
}
}
if (newStartIdx <= newEndIdx) {
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
addVnodes(
parentElm,
before,
newCh,
newStartIdx,
newEndIdx,
insertedVnodeQueue
);
}
if (oldStartIdx <= oldEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
}
const obj = {a:100, b:200}
console.log(obj.a)
console.log(obj.b)
console.log(obj.c) // undefined
// 使用with,能改变{}内自由变量的查找方式
// 将{}内的自由变量,当做obj的属性来找
with(obj) {
console.log(a)
console.log(b)
console.log(c) // 会报错!!
}
const compiler = require('vue-template-compiler')
// 插值
// const template = `{{message}}
`
// with(this){return createElement('p',[createTextVNode(toString(message))])}
// h -> vnode
// createElement -> vnode
// // 表达式
// const template = `{{flag ? message : 'no message found'}}
`
// // with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}
// // 属性和动态属性
// const template = `
//
//
//
// `
// with(this){return _c('div',
// {staticClass:"container",attrs:{"id":"div1"}},
// [
// _c('img',{attrs:{"src":imgUrl}})])}
// // 条件
// const template = `
//
// A
// B
//
// `
// with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}
// 循环
// const template = `
//
// {{item.title}}
//
// `
// with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}
// 事件
// const template = `
//
// `
// with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}
// v-model
const template = ``
// 主要看 input 事件
// with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}
// render 函数
// 返回 vnode
// patch
// 编译
const res = compiler.compile(template)
console.log(res.render)
// ---------------分割线--------------
// // 从 vue 源码中找到缩写函数的含义
// function installRenderHelpers (target) {
// target._o = markOnce;
// target._n = toNumber;
// target._s = toString;
// target._l = renderList;
// target._t = renderSlot;
// target._q = looseEqual;
// target._i = looseIndexOf;
// target._m = renderStatic;
// target._f = resolveFilter;
// target._k = checkKeyCodes;
// target._b = bindObjectProps;
// target._v = createTextVNode;
// target._e = createEmptyVNode;
// target._u = resolveScopedSlots;
// target._g = bindObjectListeners;
// target._d = bindDynamicKeys;
// target._p = prependModifier;
// }
Vue.component("heading", {
// template: "xxx",
render: function (createElement) {
return createElement('h' + this.level,[ // h1,h2
createElement('a',{
attr: {
name: "headerId",
href: "#" + "headerId"
}
}, "this is a tag")
])
}
})
// http:127.0.0.1:8881/01-hash.html?a=100&b=20#/aaa/bbb
location.protocol // http
location.hostname // 127.0.0.1
location.host // 127.0.0.1:8881
location.pathname // /01-hash.html
location.search // ?a=100&b=20
location.hash // #/aaa/bbb
// hash 变化,包括:
// a. JS 修改 url
// b. 手动修改 url 的 hash
// c. 浏览器前进、后退
window.onhashchange = (event) => {
console.log('old url', event.oldURL)
console.log('new url', event.newURL)
console.log('hash:', location.hash)
}
// 页面初次加载,获取 hash
document.addEventListener('DOMContentLoaded', () => {
console.log('hash:', location.hash)
})
// JS 修改 url
document.getElementById('btn1').addEventListener('click', () => {
location.href = '#/user'
})
// 页面初次加载,获取 path
document.addEventListener('DOMContentLoaded', () => {
console.log('load', location.pathname)
})
// 打开一个新的路由
// 【注意】用 pushState 方式,浏览器不会刷新页面
document.getElementById('btn1').addEventListener('click', () => {
const state = { name: 'page1' }
console.log('切换路由到', 'page1')
history.pushState(state, '', 'page1') // 重要!!
})
// 监听浏览器前进、后退
window.onpopstate = (event) => { // 重要!!
console.log('onpopstate', event.state, location.pathname)
}
// 需要 server 端配合,可参考
// https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
单组件生命周期图(官网)
父子组件生命周期的关系
1,单个组件:挂载(beforeCreate,created,beforeMount,mounted),更新(beforeUpdate,updated),销毁(beforeDestroy,destroyed)。
2,父子组件:
创建是从外到内的,渲染是从内到外的。
props
和this.$emit
event.$on
、event.$off
、event.$emit
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-upWPvX1k-1681782975724)(image/20221122.PNG)]
value = this.name
this.name = $event.target.value
export default {
name: "app"
data() {
return {
name: "vue"
}
}
}
$props
父组件绑定值:
子组件接收:
props接收: props:{ name: String }
model里绑定: model:{ prop: "name", event: "change" }
补充:其实主要是通过$emit
触发去更新父组件的值,而model里绑定的event值就是$emit
的事件名称。
其他:
关于学习的时候发现双向更新数据的好用方法:.sync
修饰符。官网:https://v2.cn.vuejs.org/v2/guide/components-custom-events.html#sync-%E4%BF%AE%E9%A5%B0%E7%AC%A6
父组件绑定:
// 方法一:统一绑定
// 方法二:分开绑定
子组件的接收:
{{ path }}
{{ idList }}
event.$off
// 父组件
{{website.subTitle}}
// 子组件
{{slotProps.website.title}}
export default new VueRouter{
routes: [
path: "/",
component: () => import(
'./../components/Navigater'
)
]
}
<div id="div1" class="container">
<p>vdomp>
<ul style="font-size: 20px;">
<li>ali>
ul>
div>
{
tag: "div",
props: {
id: "div1",
className: "container"
}
children: [
{
tag: "p",
children: "vdom"
},
{
tag: "ul",
props: {style: "font-size: 20px;"},
children: [
{
tag: "li",
children: "a"
}
]
}
]
}
对vue3进行全面讲解,包括vue3升级变动,Vue3核心知识点,compositionAPI如何使用,以及vue3的一些基础原理
(思维导图)
生命周期 {{msg}}
每种颜色是一种逻辑,compositionAPI在代码多、应用复杂的时候更好用
更好的逻辑复用(有一道专门的面试题)
更好的类型推导
{
data() {
return {
a:10
}
},
methods: {
fn1() {
const a = this.a
}
},
mounted() {
this.fn1();
}
}
ref
生成值类型的响应式数据
可用于模板和reactive
通过.value
修改值
代码示例
ref demo {{ageRef}} {{state.name}}
我是一行文字
toRef
针对一个响应式对象(reactive封装)的prop
创建一个ref,具有响应式
两者保持引用关系
代码演示
toRef demo - {{ageRef}} - {{state.name}} {{state.age}}
普通对象实现响应式用reactive;一个响应式对象里一个属性要单独实现响应式用toRef
toRefs
将响应式对象(reactive封装)转化为普通对象
对象的每个prop都是对应的ref
两者保持引用关系
代码演示
toRefs demo {{age}} {{name}}
合成函数返回响应式对象
function useFeatureX() {
const state = reactive({
x: 1,
y:2
})
// 逻辑运行忽略N行
// 返回时转化为ref
return toRefs(state)
}
export default {
setup() {
// 可以在不失去响应性的情况下破坏结构
const {x,y} = useFeatureX()
return {
x,
y
}
}
}
最佳使用方式
为何需要ref?
返回值类型,会丢失响应式
如在setup、computed、合成函数,都有可能返回值类型
如果vue不定义ref,用户将自造ref,反而混乱
why ref demo {{state.age}} - {{age1}}
为何需要.value?
ref是一个对象(不丢失响应式),value存储值
通过.value属性的get和set 实现响应式
用于模板,reactive时,不需要.value,其他情况都需要
// computed 返回的是一个类似于 ref 的对象,也有 .value
const age1 = computed(() => {
return state.age + 1
})
// 错误
function computed(getter) {
let value
watchEffect(()=>{ // 测试的时候可以把watchEffect代替到setTimeout
value = getter()
})
return value
}
// let a = 100
// b = a;
// a = 200; // 等价于 a = computed(()=>100)
// b // 100
// 正确
function computed(getter) {
let ref = {
value: null
}
watchEffect(()=>{ // 测试的时候可以把watchEffect代替到setTimeout
ref.value = getter()
})
return ref
}
// const obj1 = {x:100}
// const obj2 = obj1
// obj1.x = 200; // 等价于 obj1.x = computed(()=>200)
// boj2.x // 200
为何需要toRef toRefs
// vue2.x
const app = new Vue({/* 选项 */})
// vue3.x
const app = Vue.createApp({/* 选项 */})
// vue2.x
Vue.use(/*...*/)
Vue.mixin(/*...*/)
Vue.component(/*...*/)
Vue.directive(/*...*/)
// vue3.x
app.use(/*...*/)
app.mixin(/*...*/)
app.component(/*...*/)
app.directive(/*...*/)
export default {
name: "helloWorld",
props: {
msg: String
},
emits: ["onSayHello"], // 把sayHello改成onSayHello
setup(props,{emit}) {
emit("onSayHello", "vue3")
}
}
{{ title }}
{{ title }}
// vue2.x
new Vue({
components: {
"mycomponent": ()=>import("./my-async-component.vue")
}
})
// vue3.x
import {createApp, defineAsyncComponent} from 'vue'
createApp({
components: {
AsyncComponent: defineAsyncComponent(()=>import("./my-async-component.vue"))
}
})
{{ message | capitalize}}
teleport 弹窗 (父元素是 body)
Loading...
抽离逻辑代码到一个函数
函数命名约定为 useXxxx格式(React Hook也是)
在setup中引用useXxxx函数
mouse position {{x}} {{y}}
import { reactive, ref, onMounted, onUnmounted } from 'vue'
function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
console.log('useMousePosition mounted')
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
console.log('useMousePosition unMounted')
window.removeEventListener('mousemove', update)
})
return {
x,
y
}
}
// function useMousePosition2() {
// const state = reactive({
// x: 0,
// y: 0
// })
// function update(e) {
// state.x = e.pageX
// state.y = e.pageY
// }
// onMounted(() => {
// console.log('useMousePosition mounted')
// window.addEventListener('mousemove', update)
// })
// onUnmounted(() => {
// console.log('useMousePosition unMounted')
// window.removeEventListener('mousemove', update)
// })
// return state
// }
export default useMousePosition
// export default useMousePosition2
// 触发更新视图
function updateView() {
console.log('视图更新')
}
// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function () {
updateView() // 触发视图更新
oldArrayProperty[methodName].call(this, ...arguments)
// Array.prototype.push.call(this, ...arguments)
}
})
// 重新定义属性,监听起来
function defineReactive(target, key, value) {
// 深度监听
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
// 深度监听
observer(newValue)
// 设置新值
// 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue
// 触发更新视图
updateView()
}
}
})
}
// 监听对象属性
function observer(target) {
if (typeof target !== 'object' || target === null) {
// 不是对象或数组
return target
}
// 污染全局的 Array 原型
// Array.prototype.push = function () {
// updateView()
// ...
// }
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 重新定义各个属性(for in 也可以遍历数组)
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 准备数据
const data = {
name: 'zhangsan',
age: 20,
info: {
address: '北京' // 需要深度监听
},
nums: [10, 20, 30]
}
// 监听数据
observer(data)
// 测试
// data.name = 'lisi'
// data.age = 21
// // console.log('age', data.age)
data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组
基本使用
const data = {
name: "zhangsan",
age: "20"
}
//const data = ['a', 'b', 'c'];
//data.push("d");
// 依次打印:"get push" "get length" "set 3 d" "result true" "set length 4" "result true"
const ProxyData = new Proxy(data, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
console.log("get", key)
return result; // 返回结果
},
set(target, key, val, receiver) {
const result = Reflect.get(target, key, val, receiver)
console.log("set", key, val)
console.log("result", result)
return result; // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.get(target, key)
console.log("delete Property", key)
console.log("result", result)
return result; // 是否删除成功
},
})
// const data = {
// name: 'zhangsan',
// age: 20,
// }
const data = ['a', 'b', 'c']
const proxyData = new Proxy(data, {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) // 监听
}
const result = Reflect.get(target, key, receiver)
return result // 返回结果
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
// console.log('result', result) // true
return result // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
// console.log('result', result) // true
return result // 是否删除成功
}
})
Reflect
和Proxy能力一一对应
规范化、标准化、函数式
const obj = {a:100, b:200}
"a" in obj
Reflect.has(obj, "a")
delete obj.a
Reflect.deleteProperty(obj, "a")
代替掉Object上的工具函数
const obj = {a:100, b:200}
Obejct.getOwnPropertyNames(obj) // [a, b]
Reflect.ownKeys(obj)
实现响应式
// 创建响应式
function reactive(target = {}) {
if (typeof target !== 'object' || target == null) {
// 不是对象或数组,则返回
return target
}
// 代理配置
const proxyConf = {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) // 监听
}
const result = Reflect.get(target, key, receiver)
// 深度监听
// 性能如何提升的?
return reactive(result)
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true
}
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的 key', key)
} else {
console.log('新增的 key', key)
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
// console.log('result', result) // true
return result // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
// console.log('result', result) // true
return result // 是否删除成功
}
}
// 生成代理对象
const observed = new Proxy(target, proxyConf)
return observed
}
// 测试数据
const data = {
name: 'zhangsan',
age: 20,
info: {
city: 'beijing',
a: {
b: {
c: {
d: {
e: 100
}
}
}
}
}
}
const proxyData = reactive(data)
{{name}} {{age}}
watch vs watchEffect
{{numberRef}}
{{name}} {{age}}
get instance
编译模板是,动态节点做标记
标记,分为不同的类型,如TEXT PROPS
代码演示:https://vue-next-template-explorer.netlify.app/#eyJzcmMiOiI8ZGl2PlxyXG4gIDxzcGFuPkhlbGxvIFdvcmxkPC9zcGFuPlxyXG4gIDxzcGFuPnt7IG1zZyB9fTwvc3Bhbj5cclxuICA8c3BhbiA6Y2xhc3M9XCJuYW1lXCI+5ZCN56ewPC9zcGFuPlxyXG4gIDxzcGFuIDppZD1cIm5hbWVcIj7lkI3np7A8L3NwYW4+XHJcbiAgPHNwYW4gOmlkPVwibmFtZVwiPnt7IG1zZyB9fTwvc3Bhbj5cclxuICA8c3BhbiA6aWQ9XCJuYW1lXCIgOm1zZz1cIm1zZ1wiPuWQjeensDwvc3Bhbj5cclxuPC9kaXY+Iiwib3B0aW9ucyI6e319
Hello World
{{ msg }}
名称
名称
{{ msg }}
名称
编译后:
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, normalizeClass as _normalizeClass, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("span", null, "Hello World"),
_createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
_createElementVNode("span", {
class: _normalizeClass(_ctx.name)
}, "名称", 2 /* CLASS */),
_createElementVNode("span", { id: _ctx.name }, "名称", 8 /* PROPS */, ["id"]),
_createElementVNode("span", { id: _ctx.name }, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["id"]),
_createElementVNode("span", {
id: _ctx.name,
msg: _ctx.msg
}, "名称", 8 /* PROPS */, ["id", "msg"])
]))
}
// Check the console for the AST
diff算法时,可以区分静态节点,以及不同的类型的动态节点
将静态节点的定义,提升到父作用域,缓存起来(典型的拿空间换时间的优化策略)
代码演示:https://vue-next-template-explorer.netlify.app/#eyJzcmMiOiI8ZGl2PlxyXG4gIDxzcGFuPkhlbGxvIFZ1ZTM8L3NwYW4+XHJcbiAgPHNwYW4+SGVsbG8gVnVlMzwvc3Bhbj5cclxuICA8c3Bhbj5IZWxsbyBWdWUzPC9zcGFuPlxyXG4gIDxzcGFuPnt7IG1zZyB9fTwvc3Bhbj5cclxuPC9kaXY+Iiwib3B0aW9ucyI6eyJob2lzdFN0YXRpYyI6dHJ1ZX19
Hello Vue3
Hello Vue3
Hello Vue3
{{ msg }}
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "Hello Vue3", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, "Hello Vue3", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createElementVNode("span", null, "Hello Vue3", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_hoisted_1,
_hoisted_2,
_hoisted_3,
_createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
// Check the console for the AST
多个相邻静态节点,会被合并起来
代码演示:https://vue-next-template-explorer.netlify.app/#eyJzcmMiOiI8ZGl2PlxyXG4gIDxzcGFuPkhlbGxvIFZ1ZTM8L3NwYW4+XHJcbiAgPHNwYW4+SGVsbG8gVnVlMzwvc3Bhbj5cclxuICA8c3Bhbj5IZWxsbyBWdWUzPC9zcGFuPlxyXG4gIDxzcGFuPkhlbGxvIFZ1ZTM8L3NwYW4+XHJcbiAgPHNwYW4+SGVsbG8gVnVlMzwvc3Bhbj5cclxuICA8c3Bhbj5IZWxsbyBWdWUzPC9zcGFuPlxyXG4gIDxzcGFuPkhlbGxvIFZ1ZTM8L3NwYW4+XHJcbiAgPHNwYW4+SGVsbG8gVnVlMzwvc3Bhbj5cclxuICA8c3Bhbj5IZWxsbyBWdWUzPC9zcGFuPlxyXG4gIDxzcGFuPkhlbGxvIFZ1ZTM8L3NwYW4+XHJcbiAgPHNwYW4+e3sgbXNnIH19PC9zcGFuPlxyXG48L2Rpdj4iLCJvcHRpb25zIjp7ImhvaXN0U3RhdGljIjp0cnVlfX0=
Hello Vue3
Hello Vue3
Hello Vue3
Hello Vue3
Hello Vue3
Hello Vue3
Hello Vue3
Hello Vue3
Hello Vue3
Hello Vue3
{{ msg }}
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, createStaticVNode as _createStaticVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("Hello Vue3Hello Vue3Hello Vue3Hello Vue3Hello Vue3Hello Vue3Hello Vue3Hello Vue3Hello Vue3Hello Vue3", 10)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_hoisted_1,
_createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
// Check the console for the AST
缓存事件
代码演示:https://vue-next-template-explorer.netlify.app/#eyJzcmMiOiI8ZGl2PlxyXG4gIDxzcGFuIEBjbGljaz1cImNsaWNrSGFuZGxlclwiPkhlbGxvIFZ1ZTM8L3NwYW4+XHJcbjwvZGl2PiIsIm9wdGlvbnMiOnsiY2FjaGVIYW5kbGVycyI6dHJ1ZX19
Hello Vue3
import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
// export function render(_ctx, _cache, $props, $setup, $data, $options) {
// return (_openBlock(), _createElementBlock("div", null, [
// _createElementVNode("span", { onClick: _ctx.clickHandler }, "Hello Vue3", 8 /* PROPS */, ["onClick"])
// ]))
// }
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("span", {
onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.clickHandler && _ctx.clickHandler(...args)))
}, "Hello Vue3")
]))
}
// Check the console for the AST
代码演示: https://vue-next-template-explorer.netlify.app/#eyJzcmMiOiI8ZGl2PlxyXG4gIDxzcGFuPkhlbGxvIFZ1ZTM8L3NwYW4+XHJcbiAgPHNwYW4+SGVsbG8gVnVlMzwvc3Bhbj5cclxuICA8c3Bhbj5IZWxsbyBWdWUzPC9zcGFuPlxyXG4gIDxzcGFuPnt7IG1zZyB9fTwvc3Bhbj5cclxuPC9kaXY+Iiwic3NyIjp0cnVlLCJvcHRpb25zIjp7fX0=
Hello Vue3
Hello Vue3
Hello Vue3
{{ msg }}
import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from "vue/server-renderer"
export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
const _cssVars = { style: { color: _ctx.color }}
_push(`${
_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
}>Hello Vue3Hello Vue3Hello Vue3${
_ssrInterpolate(_ctx.msg)
}`)
}
// Check the console for the AST
编译时,根据不同的情况,引入不同的API
代码演示:https://vue-next-template-explorer.netlify.app/#eyJzcmMiOiI8ZGl2PlxyXG4gIDxzcGFuIHYtaWY9XCJtc2dcIj5IZWxsbyBWdWUzPC9zcGFuPlxyXG4gIDxpbnB1dCB2LW1vZGVsPVwibXNnXCIgLz5cclxuPC9kaXY+Iiwic3NyIjpmYWxzZSwib3B0aW9ucyI6e319
Hello Vue3
Hello Vue3
vite是什么?
vite为何启动快?
ES6 Module
基础代码演示:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ES Module demotitle>
head>
<body>
<p>基本演示p>
<script type="module">
import add from './src/add.js'
const res = add(1, 2)
console.log('add res', res)
script>
<script type="module">
import { add, multi } from './src/math.js'
console.log('add res', add(10, 20))
console.log('multi res', multi(10, 20))
script>
body>
html>
// add.js
import print from './print.js'
export default function add(a, b) {
print('print', 'add')
return a + b
}
// print.js
export default function (a, b) {
console.log(a, b)
}
// math.js
export function add(a, b) {
return a + b
}
export function multi(a, b) {
return a * b
}
外链演示:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ES Module demotitle>
head>
<body>
<p>外链p>
<script type="module" src="./index.js">script>
body>
html>
// index.js
import { add, multi } from './math.js'
console.log('add res', add(10, 20))
console.log('multi res', multi(10, 20))
远程引用:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ES Module demotitle>
head>
<body>
<p>远程引用p>
<script type="module">
import { createStore } from 'https://unpkg.com/redux@latest/es/redux.mjs' // mjs: module js
console.log('createStore', createStore)
script>
body>
html>
动态引用:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ES Module demotitle>
head>
<body>
<p>动态引入p>
<button id="btn1">load1button>
<button id="btn2">load2button>
<script type="module">
document.getElementById('btn1').addEventListener('click', async () => {
const add = await import('./src/add.js')
const res = add.default(1, 2)
console.log('add res', res)
})
document.getElementById('btn2').addEventListener('click', async () => {
const { add, multi } = await import('./src/math.js')
console.log('add res', add(10, 20))
console.log('multi res', multi(10, 20))
})
script>
body>
html>
代码演示:
demo.vue
Demo {{flagRef}}
- {{item}}
demo1.vue
demo.jsx
import { defineComponent, ref, reactive } from 'vue'
import Child from './Child'
export default defineComponent(() => {
const flagRef = ref(true)
function changeFlag() {
flagRef.value = !flagRef.value
}
const state = reactive({
list: ['a1', 'b1', 'c1']
})
const render = () => {
return <>
<p onClick={changeFlag}>demo1 {flagRef.value.toString()}</p>
{flagRef.value && <Child a={flagRef.value}></Child>}
<ul>
{state.list.map(item => <li>{item}</li>)}
</ul>
</>
}
return render
})
// defineComponent(props, context)=>{}
// 1. setup 函数: defineComponent(()={setup函数内容})
// 2. 组件的配置: defineComponent({配置的map})
// 可以用.jsx
//可以用.tsx typescript的形式
child.jsx
import { defineComponent } from 'vue'
export default defineComponent({
props: ['a'],
setup(props) {
const render = () => {
return <p>Child {props.a}</p>
}
return render
}
})
demo.vue
tab panel content 1
tab panel content 2
tab panel content 3
TablePanel.vue
Tabs.vue
demo.vue
tab panel content 1
tab panel content 2
tab panel content 3
TabPanel.vue
Tabs.jsx
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'Tabs',
props: ['defaultActiveKey'],
emits: ['change'],
setup(props, context) {
const children = context.slots.default()
const titles = children.map(panel => {
const { key, title } = panel.props || {}
return {
key,
title
}
})
// 当前 actKey
const actKey = ref(props.defaultActiveKey)
function changeActKey(key) {
actKey.value = key
context.emit('change', key)
}
// jsx
const render = () => <>
<div>
{/* 渲染 buttons */}
{titles.map(titleInfo => {
const { key, title } = titleInfo
return <button
key={key}
style={{ color: actKey.value === key ? 'blue' : '#333' }}
onClick={() => changeActKey(key)}
>{title}</button>
})}
</div>
<div>
{children.filter(panel => {
const { key } = panel.props || {}
if (actKey.value === key) return true // 匹配上 key ,则显示
return false // 否则,隐藏
})}
</div>
</>
return render
}
})
demo.vue
msg: {{slotProps.msg}} 123123
Child.vue
child
demo.jsx
import { defineComponent } from 'vue'
import Child from './Child'
export default defineComponent(() => {
function render(msg) {
return <p>msg: {msg} 123123</p>
}
return () => {
return <>
<p>Demo - JSX</p>
<Child render={render}></Child>
</>
}
})
child.jsx
import { defineComponent, ref } from 'vue'
export default defineComponent({
props: ['render'],
setup(props) {
const msgRef = ref('作用域插槽 Child - JSX')
return () => {
return <p>{props.render(msgRef.value)}</p>
}
}
})
script
只有一个setup函数太孤单,如何简化一下?
同时使用demo.vue
{{countRef}}
{{name}}
Child1.vue
Child1
Child2.vue
Child2 - name: {{props.name}}, age: {{props.age}}
Child3.vue
Child3
总结:
写在
前面,看起来更直观