回首Vue的单文件组件(SFC),探索新的模式,然后一起飙车?(Vue2)

1. 回首Vue单文件组件

1.1 单文件组件是什么

Vue的单文件组件SFC(single-file-component)指的是.vue结尾的文件,内容中通常包含三个块
template(模板块) script(js脚本块) style(样式块)

回首Vue的单文件组件(SFC),探索新的模式,然后一起飙车?(Vue2)

使用.vue单文件组件开发的好处:

  • 编写代码时更好的代码高亮和语法提示
  • 其他模板语法(pug)\css预处理(less)的支持
  • 组件复用,例如某些多个页面都要用到的同样的功能我们可以抽离出成为一个公共组件…

1.2 .vue文件构建流程

插播一个重要知识点-webpack loader的配置方式
官方文档:webpack.docschina.org/concepts/lo…

  • 1.webpack.config.js配置方式 这样webpack用正则去校验文件名
    loader 从右到左(或从下到上)地取值(evaluate)/执行(execute)
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          },
        ]
      }
    ]
  }
};
内
 
  • 2.内联方式执行,可以不写css配置,然后直接引入,例如某个js文件中引入style:
//app.js
import style0 from 'style-loader!css-loader!./my.unknow'
// 这样就指定了到my.unknow这个文件时以(从右到左)css-loader=>style-loader的顺序去解析
console.log('hey')
 
  • 2.另外一种loader: pitching loader:

官方说法:loader 总是 从右到左被调用.有些情况下,loader 只关心request后面的元数据(metadata),并且忽略前一个 loader 的结果.在实际(从右到左)执行loader之前,会先从左到右调用 loader 上的 pitch 方法
白话:pitch方法从左到右,上到下,功能:共享数据,提前返回(跳过剩余loader)
例如下面的例子:

//loaderAwithPitch.js
module.exports = function (source) {
  console.log('runA')
  return `10000\n${source}`
};
// 到处pitch方法给webpack
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  console.log('pitch',remainingRequest,precedingRequest,data)
  return 'nothing';
  // return source;
}
 
//loaderB.js
module.exports = function (source) {
  console.log('runB')
  return `20000\n${source}\n`
};
 

然后直接在入口文件中内联的方式配置loader

//index.js
import str from "./loaderAwithPitch!./loaderB.js!./todo.js";
console.log(str);
 
//webpack配置:
const { resolve } = require("path");
module.exports = {
  mode: "development",
  entry: "./index.js",
  output: {
    path: resolve(__dirname),
    filename: "bundle.js",
  },
};
 

打包结果:只写入了loaderAwithPitch中的pitch方法中的内容,后续loaderB loaderAwithPitch的默认导出函数没有执行

pitcvh未命名1621242580.png
过程有点像DOM事件中的捕获和冒泡:

|- loaderAwithPitch `pitch`
  |- loaderB `pitch`
      |- requested module is picked up as a dependency
  |- loaderB normal execution
|- loaderAwithPitch normal execution
//流程是 loaderAwithPitch的pitch方法=>loaderB的pitch方法(如果有)=>loaderB执行=>loaderA执行
//期间如果pitch方法提前返回,将不执行后面的流程
 

那.vue文件最终是怎么构建的?我们来看一个简单的webpack工程:

project未命名1621240317.png
入口文件是main.js,然后引入了vue文件
我们都知道浏览器可以直接运行js代码,vue文件中的代码是不能直接放在浏览器运行的
所以webpack对vue文件进行了转化,配置中如下

const { VueLoaderPlugin } = rquire("vue-loader");
module.exports = {
  mode: "development",
  module: {
    rules: [
      {
        test: /\.vue/,
        use: "vue-loader",
      },
      {
        test: /\.css$/,
        use: ["vue-style-loader", "css-loader"],
      },
    ],
  },
  plugins: [new VueLoaderPlugin()],
}
 

VueLoaderPlugin -对webpack配置的module.rule进行转化,伪代码:

vueplu未命名1621260327.png

vue-loader -对.vue文件进行转化:

vload未命名1621263770.png

  • VueLoaderPlugin对module.rule操作,加进去pitchLoader和对vue文件中对应block块规则的loader(从原来的rule规则上复制的)
  • vue-loader转换vue文件为带有import语句的js代码=>pitchLoader转换import语句为对应block块loader的export语句=>export语句的loader转换block块内容并返回处理后的结果

当其他文件import这个vue文件的时候,var component = normalizer()执行,normalizer中组装代码,加上新的属性;返回对象如下:

aaa未命名1621265799.png

总结下简单的模型:
.vue文件=>vue-loader=>js文件
import这个文件后里面的代码执行并返回一个vue组件配置对象

2. 探索新的模式

.vue文件的好处不用多说,现在我们来思考缺点,与我而言就是
有时候组件划分过多,在维护的时候需要在IDE中反复横跳🤣

下面我们开始开车:
既然import的是个对象,那就搞个能生成对象的,但同时能做一些data/methods
/computed这些配置的,好像函数|对象|class都能实现,先用class试试吧,同时要写对应的loader去解析我们的定义的template区域的模板代码

class VueCar {
constructor() {
this.options = {
data: {},
methods: {},
components: {},
mounted:[]
};
}
// 我们只要无脑的return this就能无限打点了
components(obj) {
Object.assign(this.options.components, ...obj);
return this;
}
template() {
// do
return this;
}
style() {
// do
return this;
}
done() {
return this;
}
data(obj) {
Object.assign(this.options.data, ...obj);
return this;
}
methods(fn) {
Object.assign(this.options.methods, ...fn.call(this.options));
return this;
}
mounted(fn){
this.options.mounted.push(()=>fn(this.options))
return this;
}
final(){//最后导出调用的方法,翻译this.options成VuecomponentOptions格式
return transformToVue(this.options)
}
}

然后这样写js文件

import List from './List.vue' //兼容vue文件
let Header = new VueCar()
.template(() => <header>this is header</header>)
.done()
.style(
<style>
.message{
color:blue;
}
</style>
)
.done()
.mounted((options) => {
console.log("mounted");
});
let Content = new VueCar()
.components({List})
.template(() => <footer><List/>is footer</footer>)
.mounted(() => console.log("footer mounted"));
let App = new VueCar();
App.components({
Header,
Content,
})
.template(() => (
<div>
<div>{{ message }}</div>
<button v-on:click="changeMessage">changeMessage</button>
</div>
))
.done()
.data({
message: "hey,vue",
})
.methods((options) => ({
changeMessage() {
options.data.message = Math.random();
},
}));
export { Header, Content, App };

对这个js文件也要配个vueCarLoader, 对其中的template/style块进行解析替换(所以需要.done,为了正则匹配template/style的结尾,然后传给templateLoader解析😂)
这样写的潜力是,我们可以在一个js文件中分离几个组件,并且还能分别导出,同时也许我们的class还能支持typescript类型配置…

ps:实测中发现js中无论如何都写不了style块,IDE会报错,也许需要未来插件的支持?

sss未命名1621269157.png

根据上面的class模型,我搞了个测试版的,名字不好起就叫vue-highway了,以下是预览项目,朋友们都看看
github.com/avenger9527…

image.png
图中可以看到这样搞
优点– 一个js中写多个组件 / 兼容原来的写法
缺点– template中不支持@click(IDE报错),只能写v-on:click / 也不能写style块(IDE)…

大佬们一起加入讨论吧!为了更有趣的coding~ 😂

多多批评!

原创文章,作者:我心飞翔,如若转载,请注明出处:https://www.pipipi.net/14718.html

发表评论

登录后才能评论