探索Vue小程序框架的底层原理

Mars框架解析
Mars是一款基于Vue的多端开发框架,支持一套代码在百度小程序、微信小程序及H5运行。通过将Vue模板编译为小程序模板,并在小程序环境中运行Vue运行时,实现数据驱动视图更新。

最近晚上有时间复盘之前研究小程序框架的相关内容,总结文章记录一下。

本篇文章主要介绍百度19年开源的Mars小程序开发框架,和Taro、mpvue、uniapp类似,都是编译型小程序框架,都是通过将 Vue 或 React 源码直接编译为小程序源码,实现小程序快速开发。我们来介绍一下其设计思路与原理。

Mars是什么?

Mars 是由 Vue 驱动的多端开发框架,其语法规范完全遵循 Vue,支持一套代码同时运行到百度小程序、微信小程序以及 H5 Web 端。Mars 诞生于搜索垂类产品对于小程序和 H5 Web 端相同的产品业务需求的场景下。从框架设计研发之初,就定位于解决小程序以及 H5 Web 端的复用和同构开发能力。

设计思路

Mars 框架的设计思路是将跨多端的应用拆分为逻辑层和视图层,逻辑层采用同一套核心运行时进行数据驱动以及生命周期管理,视图层使用同一套模板语法,经过编译转换为特定平台的视图语言。

考虑到学习成本、生态完善程度以及在多端上的扩展性、业务场景等原因,我们选择了 Vue 技术栈,采用 Vue 单文件组件和模板语法来书写组件代码,引入标准基础组件和 API 规范和标准生命周期规范。

在此开发规范之上,基于 Vue 的模板语法和基础组件来构建视图层,基于 Vue 数据驱动及标准生命周期规范来构建逻辑层,实现多端运行。框架总体原理图如下:

image.png

我们分别从模板、逻辑和数据来聊聊是如何编译的。

模板

我们先看一下Vue模板和小程序模板是如何书写的:

Vue模版如下:

<!--index.wxml-->
<template class="container">
    <view class="userinfo">
        <button v-if="!hasUserInfo&&canIUse" open-type="getUserInfo">获取头像昵称</button>
        <block v-else>  
            <image @tap="bindViewTap" class="userinfo-avatar" src=""/>
            <text class="userinfo-nickname">{{userInfo.nickName}}</text>
        </block>
    </view>
    <view class="usermotto">
        <text class="user-motto">{{motto}}</text>
    </view>
</template>

小程序的模版内容如下:

<!--index.wxml-->
<view class="container">
    <view class="userinfo">
        <button wx:if="{{!hasUserInfo&&canIUse}}" open-type="getUserInfo">获取头像昵称</button>
        <block wx:else>  
            <image bindtap="bindViewTap" class="userinfo-avatar" src=""/>
            <text class="userinfo-nickname">{{userInfo.nickName}}</text>
        </block>
    </view>
    <view class="usermotto">
        <text class="user-motto">{{motto}}</text>
    </view>
</view>

如果我们对比小程序和Vue模版,会发现他们与html语法是十分相似的。区别只在于标签上属性值的写法,相互之间通过编译是可以转化的。我们可以在编译阶段由Vue模版编译到小程序模版。

逻辑

我们先看一下小程序的逻辑和Vue的逻辑部分:

Vue逻辑如下:

<script>
    export default{
        data(){},
        methods:{
            bindViewTap(){},
            getUserInfo(){}
        },
        mounted(){}
    }
</script>

小程序的逻辑如下:

Page({
    data:{},
    bindViewTap: function(){},
    onLoad: function(){},
    getUserInfo: function(){}
})

逻辑部分,小程序与Vue在书写方式上有很大差异,他们的逻辑代码在各自的运行时中执行。并且逻辑部分用户书写的灵活度是很大的,没有办法通过编译将Vue的逻辑编译成小程序的逻辑去执行。

那该怎么办呢,我们不如换一种思路。Vue运行时和Vue组件的逻辑在生产中都是以JS代码执行的,在小程序提供的环境中是可以执行的。我们可以让Vue运行时也可以在小程序中执行,这样开发者编写的Vue逻辑代码也可以在小程序中执行了。

数据

数据部分是最简单的,因为数据是以JS对象的形式存在的,在小程序和Vue中是相同的。

原理

通过对视图、逻辑和数据这三个部分的分析,我们可以使用以下思路来使用Vue开发小程序。

首先将Vue template部分编译成小程序的模版,之后在小程序逻辑部分运行整个Vue的运行时,以及开发者编写的逻辑代码。最后Vue数据发生变化时同步给小程序,触发视图刷新。

image.png
我们需要在编译阶段产出 .wxml、.css、.js以及.json文件,在template部分需要将v-bind等语法转换成小程序使用的格式。样式内容则可以直接提取出来作为css文件。我们会在Vue中规定一个字段作为配置,这部分配置会提取出来作为.json文件。

而对于js部分,由于我们的逻辑执行在Vue中,因此只需要用到小程序的生命周期,在生命周期中执行Vue运行时以及业务逻辑代码就可以了。

例如,Vue单文件组件内容如下:

<template>
    <view class="home-wrap">
        <navigator :url="item.bookApi" v-for="(item,index) in bookList">
            <book :poster="item.poster"></book>
        </navigator>
    </view>
</template>
<script>
import Book from 'components/Book/index';
export default{
    config:{
        navigationBarTitleText: "标题"
    },
    data(){},
    components:{
        book:Book
    }
}
</script>
<style>
    .home-wrap{
        width:100vw;
        height:100vh;
    }
</style>

编译成小程序的组件内容如下:

<!--wxml模板内容-->
 <view class="home-wrap">
        <navigator url="{{item.bookApi}}" v-for="(item,index) in bookList">
            <book poster="{{item.poster}}" compId="{{ (compId ? compId : '$root') + ',0' }}"></book>
        </navigator>
</view>
 /*wxss样式内容*/
 .home-wrap{
        width:100vw;
        height:100vh;
    }
/*json配置内容*/
{
        "navigationBarTitleText": "标题",
        "usingComponents":{"book":"../../components/Book/index"}
},
//js逻辑内容
import {createPage} from "../../mars-core/index"
import Book from "../../components/Book/index.vue"
Page(createPage({
    data(){},
    components:{
        book:Book
    }
}))

为了执行Vue运行时以及业务逻辑代码,我们需要在小程序中创建Vue实例,Vue在生产环境中是以JS代码来运行的。因此我们可以直接将Vue引入,然后在小程序onLoad阶段new一个Vue实例出来。

import Vue from 'vue'

Page({
    onLoad(){
        const vm = new Vue(options)
        this.$vue = vm
    }
})

但是要注意,Vue正常是要执行在浏览器中的,在执行时会进行DOM操作完成页面渲染,在小程序中我们需要将Vue进行DOM操作的部分删掉。做到这里模版已经有了,样式也有了,创建了Vue实例后逻辑也可以执行了,但到目前为止,小程序与Vue也没有真正联系上。

通过之前的分析我们了解到,小程序与Vue之间是通过数据来联系的,Vue中执行逻辑,修改数据,将数据变化同步给小程序,触发试图更新。因此,我们现在要做的就是在每次Vue中更新视图时,把数据修改同步给小程序,那么如何知道Vue中的逻辑执行造成了视图刷新了呢?

我们可以使用Vue的updated钩子函数。

const vueMixin = {
    updated(){
        setData(vm,this)
    }
}

updated钩子函数会在数据发生变化导致视图刷新后触发。我们可以在其中调用小程序的setData方法,来将变化后的数据同步给小程序,现在我们在Vue和小程序之间建立了联系。但这个联系还是单向的,Vue的数据变化可以修改小程序的视图。但小程序中用户的操作还不能传递给Vue进行处理。用户的操作体现在tap等事件中,由于我们所有的逻辑都在Vue中,因此需要让Vue接管小程序的事件处理。

我们可以在小程序的模版中去设置一个代理函数handleProxy,在这个事件代理函数中,调用Vue实例中的事件处理函数,触发开发者编写的业务处理逻辑。这样用户的操作通过事件代理传递给Vue进行处理,Vue处理过程中会修改数据,触发VirtualDom的更新,VirtualDom更新后会触发updated钩子函数,我们在updated钩子函数中将数据变化同步给小程序,使得小程序视图更新,完成了整个用户操作响应流程。

现在我们已经完成了Vue与小程序结合的整体结构,视图绘制发生在小程序中,业务逻辑运行在Vue中,小程序与Vue用事件和数据来进行通信。

组件机制原理也是一样的,视图依旧由小程序组件来绘制,业务逻辑运行在Vue组件中,小程序组件与Vue组件通过事件和数据来进行通信。

但是这么做的前提是我们需要将小程序组件与Vue组件关联起来,在我们创建Vue实例时有两种选择,一种是我们只在小程序根组件也就是Page中去创建Vue实例,Vue会继续创建组件实例。

image.png

在这种情况下小程序组件和Vue组件的创建分别是同时进行的,那么我们就需要将小程序组件与Vue组件之间进行关联匹配,否则他们之间的通信也就无从谈起了。

那么如何匹配呢?我们可以给每个组件都标记一个唯一的ID,然后通过ID来进行匹配。标记的方法就是从根组件开始,将根组件标记为root,那么它的子组件就是root,那么它的子组件就是root,那么它的子组件就是root.0,$root.1等,不同层级间使用.来分割,其中列表循环特殊对待,我们使用横线来标记循环项。

例如root.1中有一个循环列表,这个循环列表中渲染了一个组件这个组件自身ID位root.1.0,然后循环产生的第一个子组件就是root.1.0-0,第二个就是root.1.0-1,这样我们通过ID给每个组件增加了标记,将相同ID的小程序组件与Vue组件匹配在一起。

另一种是我们去掉Vue创建组件实例的逻辑,自己在每个小程序组件创建时new一个Vue实例,但如果这么做,我们需要自己维护Vue各个实例间的父子关系。

image.png

<think>嗯,用户问的是微信小程序底层是否基于Vue。这个问题其实挺常见的,需要从技术架构层面解释清楚两者的关系。 先整理下引用里的关键信息:引用1提到mpvue是将Vue引入小程序开发的框架,引用3和4则详细描述了小程序底层架构特点。特别是引用4明确指出小程序使用自制技术栈替代了HTML5+CSS3。 用户可能接触过Vue开发的小程序项目,所以产生这种误解。需要区分清楚"用Vue开发小程序"和"小程序底层Vue"的本质区别。前者是通过mpvue框架进行的语法转换,后者则是技术架构的根本属性。 从技术实现看,小程序双线程架构是核心差异点。渲染层和逻辑层分离的设计与Vue单文件组件的运行机制完全不同。虽然小程序WXML和Vue模板语法看起来相似,但前者最终编译为原生组件树,后者操作虚拟DOM。 用户可能是前端开发者,熟悉Vue但刚接触小程序。解释时需要对比两者技术栈:小程序的自定义组件系统、响应式原理、生命周期等都与Vue存在显著差异。比如小程序通过setData触发更新,而Vue依赖响应式代理。 特别要注意澄清WXS的特殊性——虽然命名像JS,但其实是受限的脚本语言,与Vue的computed/watch机制完全不同。这个细节能帮助用户理解底层差异。 最后要强调工具链的独立性。微信开发者工具完全封闭的编译流程也印证了底层技术的自主性。就算开发者写Vue语法,最终也会被彻底转换为小程序原生代码。 </think>微信小程序底层架构**并非基于Vue**,而是腾讯自研的技术体系。以下是关键分析: --- ### 一、核心架构差异 1. **双线程模型** 小程序采用**逻辑层(JS线程)与渲染层(WebView线程)分离**的架构,通过Native层桥接通信。Vue基于单线程虚拟DOM渲染,无此隔离设计。 2. **专属技术栈** - **WXML/WXSS**:取代HTML/CSS,通过自定义组件树渲染(类似虚拟DOM但非Vue实现)[^3][^4] - **WXS脚本**:受限的JS子集,运行在渲染层(类比Vue模板语法但底层原理不同) - **响应式更新**:需显式调用`setData()`更新视图,而Vue自动依赖追踪[^3] 3. **原生组件机制** 小程序提供`<map>`/`<camera>`等原生组件,直接由客户端渲染,与Vue的纯Web组件有本质区别[^4]。 --- ### 二、Vue相关框架的角色 1. **mpvue框架的定位** 如引用[1]所述,mpvue是**将Vue语法编译为小程序代码的转换层**,本质是语法糖工具链: ```javascript // Vue语法 → 编译为小程序代码 new Vue({...}) => Page({...}) // 被转译成小程序生命周期 ``` **底层仍依赖小程序原生运行时**。 2. **性能边界** 转换框架受限于小程序架构。例如: - `v-model`需转为`setData()`调用,数据量大时性能下降 - 无法绕过小程序线程通信机制[^4] --- ### 三、技术栈对比 | **维度** | 微信小程序原生技术 | Vue框架 | |----------------|--------------------------|-----------------------| | 视图层 | WXML + WXSS | HTML + CSS/SCSS | | 逻辑层 | JS + WXS | JavaScript/TypeScript | | 数据绑定 | `setData()`显式更新 | 响应式自动更新 | | 组件通信 | 事件总线/全局状态 | Props/Emit/Vuex | | 底层渲染 | WebView + Native组件 | 虚拟DOM | --- ### 四、结论 - ✅ **小程序底层是独立技术栈**:包含自研的渲染引擎、组件系统、线程模型[^3][^4] - ✅ **Vue是开发工具而非基础**:mpvue框架仅提供语法兼容层,最终仍编译为小程序原生代码 - ✅ **设计目标不同**:小程序强调轻量化与跨平台一致性,Vue专注Web应用灵活性 > 开发者可通过mpvueVue风格写小程序,但这**不改变小程序底层Vue的事实**,如同React Native应用底层仍是原生而非React[^1][^4]。 --- #### 相关问题 1. 微信小程序的双线程模型如何提升安全性? 2. mpvue框架在编译Vue代码时会遇到哪些兼容性问题? 3. 小程序原生组件与Web组件在渲染机制上有何本质区别? 4. 如何优化小程序中`setData()`调用的性能? [引用来源说明] [^1]: mpvue框架定位解析 [^3]: 小程序全局配置与原生组件机制 [^4]: 小程序底层架构与Web技术差异
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值