[学习vue3]准备阶段[一]

调试环境准备

迁出Vue3源码: git clone https://github.com/vuejs/vue-next.git
安装依赖: yarn --ignore-scripts
生成sourcemap文件,package.json

"dev": "node scripts/dev.js --sourcemap"

编译: yarn dev

生成结果:
packages\vue\dist\vue.global.js
packages\vue\dist\vue.global.js.map

vue3源码架构

vue3初始化过程

createApp()是如何创建vue实例的;创建的vue实例执行mount()都做了些什么?

//init.html
<div id="app"> 
     <h1>vue3初始化流程</h1>
</div>
<script src="../../dist/vue.global.js"></script>
<script>
const {createApp} = Vue
createApp({}).mount('#app') 
</script>

断点调试createApp()
ensureRenderer() => renderer => createApp()
createAppAPI() => createApp
app.mount() => render() => patch() => processComponent() => mountComponent() => setupComponent() => setupRenderEffect()

执行流程

createApp() packages/runtime-dom/src/index.ts 创建vue实例、扩展mount方法
createRenderer()/baseCreateRenderer()
packages/runtime-core/src/renderer.ts
创建renderer对象,它对外暴露3个重要方法 render , hydrate , createApp ,其中 render ,和 hydrate 的实际使用者是createApp()返回的vue实例对象。
createAppAPI(render, hydrate)
packages/runtime-core/src/apiCreateApp.ts 返回生产vue实例的createApp函数

render的使用者是vue实例的mount方法 我们发现component()/directive()/use()/mixin()这些方法都变成了实例方法,它们也会返回实例本身,链式调用成为可能
filter方法被移除了

createApp({})
.component('comp', { template: '<div>this is comp</div>' }) .directive('focus', { mounted(el) { el.focus() } }) 
.mount('#app')
  • mount(rootContainer: HostElement, isHydrate?: boolean)
    packages/runtime-core/src/apiCreateApp.ts
    将 createApp(rootComponent) 中传入的根组件转换为vnode,然后渲染到宿主元素rootContainer 中。
  • render(vnode, container) 将传入vnode渲染到容器container上。
  • patch(n1, n2, container)
    将传入的虚拟节点 n1 跟 n2 进行对比,并转换为dom操作。初始化时 n1 并不存在,因此操作将是一次dom创建。
  • mount(rootContainer)
    packages/runtime-core/src/apiCreateApp.ts 执行根组件挂载,创建其vnode,并将它render()出来
  • render()
    packages/runtime-core/src/renderer.ts 执行补丁函数patch()将vnode转换为dom。
  • patch(n1, n2, container)
    packages/runtime-core/src/renderer.ts 根据n2的类型执行相对应的处理函数。对于根组件,执行的是processComponent()
  • processComponent()
    packages/runtime-core/src/renderer.ts 执行组件挂载或更新,由于首次执行时n1为空,因此执行组件挂载逻辑mountComponent()
  • mountComponent()
    packages/runtime-core/src/renderer.ts 创建组件实例,执行setupComponent()设置其数据状态,其中就包括setup()选项的执行

setup()如何生效

在vue3中如果要使用composition-api,就需要写在setup()中,它是如何生效并和options-api和谐共处的?

<div id="app"> <h1>setup()如何生效</h1> <p>{{foo}}</p>
</div>
<script src="../../dist/vue.global.js"></script> 
<script>
const { createApp, h, ref } = Vue
  createApp({
    setup() {
const foo = ref('hello, vue3!')
      return { foo }
    }
}).mount('#app') 
</script>

执行过程

根组件执行挂载mount()时,执行渲染函数render()获取组件vnode,然后执行补丁函数patch()将其转换 为真实dom,对于组件类型会调用processComponent(),这里会实例化组件并处理其setup选项。
setupComponent() packages/runtime-core/src/component.ts 初始化props、slots和data

setupStatefulComponent(instance, isSSR) packages/runtime-core/src/component.ts 代理组件实例上下文,调用setup()

setup()会接收两个参数,分别是props和setupContext,可用于获取属性、插槽内容和派发事件

createApp({
props: ['bar'], // 属性依然需要声明 setup(props) {
// 作为setupResult返回
return { bar: props.bar } }
// 传入rootProps
}, {bar: 'bar'}).mount('#app')

handleSetupResult(instance, setupResult, isSSR) packages/runtime-core/src/component.ts 处理setup返回结果,如果是函数则作为组件的渲染函数,如果是对象则对其做响应化处理。

自定义渲染器

可以自定义渲染器,将获取到的vnode转换为特定平台的特定操作

范例:利用canvas画图

第一步:我们创建一个渲染器,需要给它提供节点和属性的操作

const { createRenderer } = Vue
// 创建一个渲染器,给它提供节点和属性操作
const nodeOps = {}
const renderer = createRenderer(nodeOps);

第二步:创建画布,我们通过扩展默认createApp做到这一点

// 保存画布和其上下文 let ctx;
let canvas;
// 扩展mount,首先创建一个画布元素
function createCanvasApp(App) {
    const app = renderer.createApp(App);
    const mount = app.mount;
    app.mount = function (selector) {
        canvas = document.createElement('canvas');
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        document.querySelector(selector).appendChild(canvas);
        ctx = canvas.getContext('2d');
        mount(canvas);
    }
    return app;
}
// 创建app实例 createCanvasApp({}).mount('#app')

此时已经可以看到canvas,但是会报一个错误,是因为我们上面组件是空的,vue想要创建一个 comment元素导致

第三步:添加模板

<script type="text/x-template" id="chart">
      <bar-chart :data="chartData"></bar-chart>
</script>
<div id="app"></div>
createCanvasApp({
    template: '#chart', data() {
        return {
            chartData: [
                { title: "⻘铜", count: 200, color: "brown" },
                { title: "砖石", count: 300, color: "skyblue" },
                { title: "星耀", count: 100, color: "purple" },
                { title: "王者", count: 50, color: "gold" }
            ]
        }
    }
}
)

第四步:节点操作实现


// 保存canvas实例和上下文 let ctx, canvas
const nodeOps = {
    createElement: (tag, isSVG, is) => {
        // 创建元素时由于没有需要创建的dom元素,只需返回当前元素数据对象 return {tag}
    },
    insert: (child, parent, anchor) => {
        // 我们重写了insert逻辑,因为在我们canvasApp中不存在实际dom插入操作 // 这里面只需要将元素之间的父子关系保存一下即可
        child.parent = parent
        if (!parent.childs) {
            parent.childs = [child]
        } else {
            parent.childs.push(child)
        }
        // 只有canvas有nodeType,这里就是开始绘制内容到canvas 
        if (parent.nodeType === 1) {
            draw(child)
        }
    },
    patchProp(el, key, prevValue, nextValue) {
        el[key] = nextValue;
    }
}

第四步:绘图逻辑


const draw = (el, noClear) => {
    if (!noClear) {
        ctx.clearRect(0, 0, canvas.width, canvas.height)
    }
    if (el.tag == 'bar-chart') {
        const { data } = el;
        const barWidth = canvas.width / 10,
            gap = 20,
            paddingLeft = (data.length * barWidth + (data.length - 1) * gap) / 2, paddingBottom = 10;
        // x轴
        // 柱状图
        data.forEach(({ title, count, color }, index) => {
            const x = paddingLeft + index * (barWidth + gap)
            const y = canvas.height - paddingBottom - count
            ctx.fillStyle = color
            ctx.fillRect(x, y, barWidth, count)
            // text
        });
    }
    // 递归绘制子节点
    el.childs && el.childs.forEach(child => draw(child, true));
}

配置自定义组件白名单:
app.config.isCustomElement = tag => tag === 'bar-chart'

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容