• Vue中SFC的概念及作用详解
  • 发布于 1个月前
  • 236 热度
    0 评论
一、SFC的基本概念
Vue SFC 是一种特殊的文件格式,其扩展名为.vue。在这个文件中,将一个 Vue 组件所需的模板(template)、逻辑(script)和样式(style)整合在一起。例如,一个简单的 Vue SFC
可能如下所示:
<template>
  <div>
    <h1>{{ message }}</h1>
    <button @click="changeMessage">改变消息</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: '初始消息'
    };
  },
  methods: {
    changeMessage() {
      this.message = '消息已改变';
    }
  }
};
</script>

<style scoped>
h1 {
  color: blue;
}
</style>
在上述代码中,<template> 定义组件 HTML 结构,含标题与按钮,用双花括号{{ }}数据绑定,按钮绑定点击事件。<script> 以export default导出包含数据message(用于模板显示)及方法changeMessage(改变message值)的对象。<style> 设定组件内 h1 标签文字颜色为蓝色。

二、SFC的作用
1.清晰的代码结构与模块化
.与传统的将 HTML、CSS 和 JavaScript 分别写在不同文件然后组合的方式相比,SFC 将一个组件的所有相关代码放在一个文件中,使得代码结构更加清晰。
.从模块化的角度来看,每个 .vue 文件都可以看作是一个独立的模块。这有助于在大型项目中更好地组织代码,提高代码的可维护性和可复用性 。例如,在一个电商项目中,可能有商品列表组件、商品详情组件等多个组件,每个组件都可以用一个 SFC 来实现,并且可以方便地在不同页面或其他组件中引入和使用。

2.作用域隔离的样式
在 SFC 中,当你在 style 标签上 scoped 时,样式就只用于此 Vue 文件,避免造成组件样式混淆。在 Vue2中,你使用 vue-cli 构建项目的时候,webpack 会通过一个 vue-loader 的一个加载器,把 style 文件里涉及到的标签都加上一个作用域:data-v-[hash]。这样就可以起到样式隔离的效果,原理可以看下面的示例,Vue 的具体的源码讲解在后面。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>堆代码 duidaima.com</title>
    <style>
      [data-v="component1"].test {
        color: blue;
        font-size: 24px;
      }
      [data-v="component2"].test {
        color: red;
        font-size: 20px;
      }
    </style>
  </head>
  <body>
    <div data-v="component1" class="test">
      <h1>这是组件1的标题</h1>
      <p>这是组件1的一些内容描述。</p >
    </div>
    <div data-v="component2" class="test">
      <h1>这是组件2的标题</h1>
      <p>这是组件2的一些内容描述。</p >
    </div>
  </body>
</html>

加上作用域的样式,就只会作用于对应的作用域。如果是 vite 的话,使用的是 vIte 服务器( vite 启动后会在浏览器上启动一个服务器,用于模块的编译)里的 @vue/compiler-sfc。效果跟 vue2 的是一样的。具体也会在后面讲解。


3.方便的组件复用与组合
组件复用是 Vue 开发中的重要特性,SFC 使得组件的复用更加便捷。由于一个组件的所有代码都封装在一个文件中,当需要在其他地方使用这个组件时,只需要简单地导入这个.vue 文件即可。

三、源码解析
当我们在项目中使用 Vue SFC 时,实际上在背后有一系列的编译过程。以使用 Webpack 配合 vue-loader 为例,当 Webpack 遇到 .vue 文件时,vue-loader 会对其进行处理。vue-loader 会将 .vue 文件解析成一个 JavaScript 模块,它会分别处理模板、逻辑和样式部分。
下面会通过一段伪代码,来帮助理解这个过程
// 读取.vue文件内容
      function readVueFile(filePath) {
        // 简单假设能读取到文件内容,实际需用Node.js文件系统API处理
        return vueFileContent;
      }
      // 堆代码 duidaima.com
      // 拆分.vue文件为template、script、style部分
      function splitVueFile(vueFileContent) {
        return [
          vueFileContent.match(/<template>([\s\S]*?)<\/template>/)[1].trim(),
          vueFileContent.match(/<script>([\s\S]*?)<\/script>/)[1].trim(),
          vueFileContent
            .match(/<style([\s\S]*?)>([\s\S]*?)<\/style>/)[2]
            .trim(),
        ];
      }

      // 编译template为渲染函数
      function compileTemplate(template) {
        // <template>
        //  <div>
        //    <h1>{{ message }}</h1>
        //    <button @click="changeMessage">改变消息</button>
        //  </div>
        // </template>
        returnfunction render() {
          // 经过一系列逻辑,处理成下面这样
          return h("div", [
            h("h1", this.message),
            h(
              "button",
              {
                on: {
                  click: this.changeMessage,
                },
              },
              "改变消息"
            ),
          ]);
        };
      }

      // 提取script内容作为模块(简化处理)
      function extractScriptModule(script) {
        return script;
      }

      // 处理style(简单处理scoped添加作用域,很简化)
      function handleStyle(style) {
        if (style.includes("scoped")) {
          return style.replace(/([^\s,]+)/g, "$1[data-v-uniqueId]");
        }
        return style;
      }

      // 主函数模拟处理整个.vue文件
      function processVueFile(filePath) {
        const content = readVueFile(filePath);
        const [template, script, style] = splitVueFile(content);
        const renderFunction = compileTemplate(template);
        const scriptModule = extractScriptModule(script);
        const processedStyle = handleStyle(style);

        return {
          template: renderFunction,
          script: scriptModule,
          style: processedStyle,
        };
      }

      // 示例调用
      const result = processVueFile("example.vue");
1. 模板部分,就是被编译成渲染函数。
这个渲染函数会在组件实例化时被调用,用于生成组件的虚拟 DOM(Virtual DOM),然后通过 Vue 的响应式系统和 DOM 更新机制将虚拟 DOM 渲染成真实 DOM 并更新到页面上。

2. 对于逻辑部分
它会直接将 <script> 中的 JavaScript 代码提取出来,作为一个模块的导出内容,就像普通的 JavaScript 模块一样被处理和引入到项目中。

3.对于样式部分
如果是普通的 CSS 样式,vue-loader 会将其提取出来,通过一些处理(如添加作用域标识等)后,交给 Webpack 的 CSS 处理模块(如 css-loader、style-loader 等)进行处理,最终将样式应用到组件对应的 DOM 元素上。如果是使用了预处理器(如 SCSS、LESS 等),则会先通过相应的预处理器编译,再进行后续处理。通过这样的编译过程,Vue SFC 能够在开发过程中以一种简洁、高效的方式编写组件,而在构建时又能转换为浏览器能够理解和高效运行的代码形式。

四、如果没有SFC我们只能使用什么方式开发
如果没有 SFC,我们只能采用传统的分离式开发方式,即将 HTML、CSS 和 JavaScript 代码分别写在不同的文件中,然后通过某种方式将它们组合起来应用到页面中。例如,我们可能会有一个index.html文件作为页面的入口:
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Vue App without SFC</title>
  <link rel="stylesheet" href="styles.css">
</head>

<body>
  <div id="app"></div>
  <script src="vue.js"></script>
  <script src="app.js"></script>
</body>

</html>
在styles.css文件中定义样式:
h1 {
  color: green;
}
在app.js文件中编写 Vue 应用的逻辑并实例化 Vue:
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello from non - SFC Vue'
  },
  methods: {
    changeMessage() {
      this.message = 'Message changed';
    }
  },
  template: `
    <div>
      <h1>{{ message }}</h1>
      <button @click="changeMessage">Change Message</button>
    </div>
  `
});
这种方式存在一些明显的弊端:
1.代码组织混乱
对于一个复杂的组件,需要在多个文件中切换查看其模板、样式和逻辑,不利于开发和维护。尤其是在大型项目中,随着组件数量的增加,代码的管理难度会急剧上升。
2.样式作用域问题 
在 CSS 文件中定义的样式是全局的,容易出现样式冲突。例如,如果多个组件都有h1标签,并且都设置了不同的样式,那么可能会相互干扰,导致页面显示异常。
3.组件复用不便
当需要复用一个组件时,需要分别复制其 HTML、CSS 和 JavaScript 代码,并确保正确地引入和组合,这过程较为繁琐且容易出错。相比之下,SFC 很好地解决了这些问题,大大提高了 Vue 应用的开发效率和可维护性。以上就是对 SFC 的初步解析,实际源码里的代码需要考虑更多更复杂的逻辑,所以实际的源码会更加复杂,通过简单的伪代码理解基础逻辑才是王道。
用户评论