尤雨溪大神解读哔哩哔哩教程
视频内容中的代码:(请结合视频学习,每一部分代码可在浏览器中运行调试)
1、渲染函数小案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>渲染函数演示</title>
</head>
<style>
.mt-10{
margin-left: 10px
}
</style>
<script src="/vue@next"></script>
<body>
<div id="app"></div>
</body>
<script>
const { createApp,h } = Vue
const Stack = {
props:{
size:{
type:String,
default:''
}
},
setup(props,{ slots }){
const slot = ?():[]
return ()=>h(
'div',
{ class:'stack' },
(child=>{
return h(
'div',
{ class:`mt-${}` },
[ child ]
)
})
)
}
}
const App = {
components:{
Stack
},
template:`
<Stack size="10">
<div>hello</div>
<Stack size="10">
<div>hello</div>
<div>hello</div>
</Stack>
</Stack>
`
}
createApp(App).mount('#app')
</script>
</html>
2、渲染函数挂载原理
<div id="app"></div>
<style>
.blue{color: blue}
.red{color: red}
</style>
<script>
// h函数将参数整合到一个对象返回
function h(tag,props,children){
return {
tag,
props,
children
}
}
// 挂载函数对虚拟Dom进行解析
function mount(vnode,container){
const el = = ()
// 将属性添加到元素节点
if(){
().forEach(key=>{
(key,[val])
})
}
/*
*解析子节点
*当子节点为字符串时直接将子节点设置为文本节点
*当子节点为数组时,遍历子节点数组执行mount函数
*/
if(){
if(typeof === 'string'){
=
}else{
(child=>{
mount(child,el)
})
}
}
// 将节点树插入到根节点
(el)
}
//虚拟dom树
const vDom = h(
'div',
{ class:'red' },
[
h(
'div',
null,
'hello'
)
]
)
// vDmo实际上经过h函数后返回一个对象树
// const vnode = {
// tag:"div",
// props:{class:'red'},
// children:[{
// tag:"div",
// props:null,
// children:[
// "hello"
// ]}
// ]
// }
mount(vDom,('app'))
// dom更新函数(对比虚拟dom)
function patch(n1,n2){
if( === ){ //当标签相同时
// 对比props
const el = = //保存元素节点信息,用于更新快照
const oldProps = || {}
const newProps = || {}
for(key in newProps){
const oldValue = oldProps[key]
const newValue = newProps[key]
if(newValue !== oldValue){
(key,newValue)
}
}
for(key in oldProps){
if(!(key in newProps)){
(key)
}
}
// 对比children
const oldChildren =
const newChildren =
// 新的子节点是一个字符串
if(typeof newChildren === 'string'){
if(typeof oldChildren === 'string'){
if(newChildren !== oldChildren){
= newChildren
}
}else{
= newChildren
}
}else{
/*
* 新的子节点是一个数组
* 旧的子节点是一个字符串
* 将元素的innerHtml置空,遍历调用mount函数解析虚拟dom
*/
if(typeof oldChildren === 'string'){
= ''
(child=>{
mount(child,el)
})
}else{
/*
* 假设元素的key不改变(即顺序不变的情况下)
* 新的子节点是一个数组
* 旧的子节点是一个数组
* 当旧的子节点数组比新的子节点数组长度大,则移除多余部分的长度
* 当旧的子节点数组比新的子节点数组长度小,则遍历调用mount函数解析多的部分借点为虚拟dom
*/
const commonLength = (,)
for(let i = 0 ; i < commonLength ; i++){
patch(oldChildren[i],newChildren[i])
}
if( > ){
().forEach(child=>{
()
})
}
if( < ){
().forEach(child=>{
mount(child,el)
})
}
}
}
}else{
// replace
// mount(n2,)
}
}
// 更新后的的虚拟dom
const vDom2 = h(
'div',
{ class:'blue' },
[
h(
'div',
null,
'changed!'
)
]
)
// 执行dom更新函数
patch(vDom,vDom2)
</script>
3、响应式原理
<script>
// 全局变量存储effect
let activeEffect
class Dep{
constructor(value){
= new Set()
this._value = value
}
get value(){
()
return this._value
}
set value(newValue){
this._value = newValue
()
}
// 添加依赖
depend(){
if(activeEffect){
(activeEffect)
}
}
// 通知
notify(){
(effect => {
effect()
})
}
}
function watchEffect(effect){
activeEffect = effect
effect()
activeEffect = null
}
const dep = new Dep('hello')
watchEffect(()=>{
()
})
= 'changed!'
</script>
4、响应式代理模式简单实现
<script>
let activeEffect
class Dep{
subscribers = new Set()
// 添加依赖
depend(){
if(activeEffect){
(activeEffect)
}
}
// 通知
notify(){
(effect => {
effect()
})
}
}
function watchEffect(effect){
activeEffect = effect
effect()
activeEffect = null
}
const targetMap = new WeakMap()
function getDep(target,key){
let depsMap = (target)
if(!depsMap){
depsMap = new Map()
(target,depsMap)
}
let dep = (key)
if(!dep){
dep = new Dep()
(key,dep)
}
return dep
}
// const dep = new Dep()
// 设置响应式代理
const reactiveHandlers = {
get(target,key,receiver){
const dep = getDep(target,key)
()
// return target[key]
return (target,key,receiver)
},
set(target,key,value,receiver){
const dep = getDep(target,key)
const result = (target,key,value,receiver)
()
return result
}
}
// 响应式代理入口函数
function reactive(raw){
return new Proxy(raw,reactiveHandlers)
}
// 初始化对象设置代理模式
const state = reactive({
count:0
})
watchEffect(()=>{
()
})
++
</script>
5、mini-vue项目
<div id="app"></div>
<script>
// h函数将参数整合到一个对象返回
function h(tag,props,children){
return {
tag,
props,
children
}
}
// 挂载函数对虚拟Dom进行解析
function mount(vnode,container){
const el = = ()
if(){
().forEach(val=>{
if(('on')){
((2).toLocaleLowerCase(),[val])
}else{
(val,[val])
}
})
}
if(){
if(typeof === 'string'){
=
}else{
(child=>{
mount(child,el)
})
}
}
(el)
}
// dom更新函数
function patch(n1,n2){
if( === ){
// 对比props
const el = =
const oldProps = || {}
const newProps = || {}
for(key in newProps){
const oldValue = oldProps[key]
const newValue = newProps[key]
if(newValue !== oldValue){
(key,newValue)
}
}
for(key in oldProps){
if(!(key in newProps)){
(key)
}
}
// 对比children
const oldChildren =
const newChildren =
// 新的子节点是一个字符串
if(typeof newChildren === 'string'){
if(typeof oldChildren === 'string'){
if(newChildren !== oldChildren){
= newChildren
}
}else{
= newChildren
}
}else{
/*
* 新的子节点是一个数组
* 旧的子节点是一个字符串
* 将元素的innerHtml置空,遍历调用mount函数解析虚拟dom
*/
if(typeof oldChildren === 'string'){
= ''
(child=>{
mount(child,el)
})
}else{
/*
* 假设元素的key不改变(即顺序不变的情况下)
* 新的子节点是一个数组
* 旧的子节点是一个数组
* 当旧的子节点数组比新的子节点数组长度大,则移除多余部分的长度
* 当旧的子节点数组比新的子节点数组长度小,则遍历调用mount函数解析多的部分借点为虚拟dom
*/
const commonLength = (,)
for(let i = 0 ; i < commonLength ; i++){
patch(oldChildren[i],newChildren[i])
}
if( > ){
().forEach(child=>{
()
})
}
if( < ){
().forEach(child=>{
mount(child,el)
})
}
}
}
}else{
// replace
}
}
let activeEffect
class Dep{
subscribers = new Set()
// 添加依赖
depend(){
if(activeEffect){
(activeEffect)
}
}
// 通知
notify(){
(effect => {
effect()
})
}
}
function watchEffect(effect){
activeEffect = effect
effect()
activeEffect = null
}
const targetMap = new WeakMap()
function getDep(target,key){
let depsMap = (target)
if(!depsMap){
depsMap = new Map()
(target,depsMap)
}
let dep = (key)
if(!dep){
dep = new Dep()
(key,dep)
}
return dep
}
// 设置响应式代理
const reactiveHandlers = {
get(target,key,receiver){
const dep = getDep(target,key)
()
// return target[key]
return (target,key,receiver)
},
set(target,key,value,receiver){
const dep = getDep(target,key)
const result = (target,key,value,receiver)
()
return result
}
}
// 响应式代理入口函数
function reactive(raw){
return new Proxy(raw,reactiveHandlers)
}
const App = {
data:reactive({
count:0
}),
render(){
return h(
'div',
{
onClick:()=>{
++
}
},
String()
)
}
}
function mountApp(component,container){
let isMount = false
let prevVdom
watchEffect(()=>{
if(!isMount){
prevVdom = ()
mount(prevVdom,container)
isMount = true
}else{
const newVdom = ()
patch(prevVdom,newVdom)
prevVdom = newVdom
}
})
}
mountApp(App,('app'))
</script>
本文章代码来源于原视频中的讲解,仅供学习使用!!!