前言
本章我们会对一个 Vue 项目的目录结构进行讲解,解释各子目录以及文件的作用,前端的模块化,Vue 单文件组件规范等。
1、基础目录和文件介绍
在上一章,我们通过 vue-cli 创建了一个新的项目,生成的项目目录里已经包含了基础的项目通用目录和文件。在这一小节我们就这些基础目录和文件进行介绍,后续存在新增目录和文件的情况再作说明。
README.md
README 顾名思义“读我”,其存在的意义便是让别人或着说开发者能更快更直观的了解项目,以便别人拿到项目源码后,能快速的了解项目的结构、技术栈、开发规范、运行和部署等。一个好的 README.md 应至少具备以下内容:
- 项目的背景介绍和应用场景。
- 项目上手指南,比如项目依赖的环境和工具的安装步骤、版本说明、常用命令。
- 源代码的目录树介绍,核心文件的说明。
- 大型项目还应增加架构图、开发规范标准。
- 开源项目则可注明开源协议以及用法示例。
package.json
每个项目的根目录下面,一般都有一个
package.json
文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。npm install
命令根据这个配置文件,自动下载所需的模块,也就是配置项目所需的运行和开发环境。我们以 vue-cli 生成的 package.json 为基本参考并适当的添加一些常见属性进行说明。
// vue-cli 生成的默认模板
{
"name": "hello-world",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.2",
"@vue/cli-plugin-eslint": "^4.1.2",
"@vue/cli-service": "^4.1.2",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
基础属性
name: 项目名称(必须)。
version: 项目版本(必须)。
private: 设置为
true
时,npm将拒绝发布它,可防止意外发布私有存储库。
description: 项目的描述。
keywords: 设置关键词,这是一个字符串数组。这有助于别人在 npm 搜索中发现你的项目。
scripts
scripts 指定了运行脚本命令的npm命令行缩写,比如
serve
指定了运行npm run serve
时,所要执行的命令vue-cli-service serve
。除了运行基本的 scripts 命令,还可以结合 pre 和 post 完成前置和后续操作。我们在原来的 scripts 中新增test
pretest
posttest
的命令配置:
在项目根目录分别创建这三个文件并写下一句 console 代码:
// index.js
console.log("scripts: index.js")
// beforeIndex.js
console.log("scripts: before index.js")
// afterIndex.js
console.log("scripts: after index.js")
当我们执行
npm run test
命令时,输出结果如下:
可以看到,三个命令都执行了,执行顺序是 pretest → test → posttest。
dependencies 和 devDependencies
dependencies
字段指定了项目运行所依赖的模块,devDependencies
指定项目开发所需要的模块。它们都指向一个对象。该对象的各个成员,分别由模块名和对应的版本要求组成,表示依赖的模块及其版本范围。
在这里,dependencies 下的
core.js
包和vue
包会被编译到最终生成的文件中,而 devDependencies 下的这些包不会被编译进去,因为这些提供的功能(语法转换、开发服务器、语法检查、vue的编译器)仅在开发阶段用到。
其中,依赖模块的版本可以加上各种限定,主要有以下几种:
-
指定版本: 比如
1.2.2
,遵循大版本.次要版本.小版本
的格式规定,安装时只安装指定版本。 -
波浪号+指定版本: 比如
~1.2.2
,表示安装1.2.x的最新版本(不低于1.2.2),但是不安装1.3.x,也就是说安装时不改变大版本号和次要版本号。 -
插入号+指定版本: 比如
ˆ1.2.2
,表示安装1.x.x的最新版本(不低于1.2.2),但是不安装2.x.x,也就是说安装时不改变大版本号。需要注意的是,如果大版本号为0,则插入号的行为与波浪号相同,这是因为此时处于开发阶段,即使是次要版本号变动,也可能带来程序的不兼容。 -
latest: 安装最新版本。
值得一提的是,通常我们首次从远程仓库拿到项目代码后,需要先执行
npm install
或npm i
(不指定包名)来安装项目的所有依赖,这时候 npm 就是根据这里记录的信息去安装依赖。细心的你可能已经发现,我们在聊 npm 的时候,提到过依赖和开发依赖,当你执行这样的命令(npm i 包名 -D
或npm i 包名 --save-dev
)时,安装的依赖将作为开发依赖记录到 devDependencies 里。
eslintConfig
这里是进行 eslint(JavaScript 代码检测工具) 的语法规则配置。除了在 package.json 中配置,我们更推荐的做法是单独在项目根目录下创建
.eslintrc.js
或.eslintrc.json
进行语法检查规则的配置,具体的配置规则可在 https://eslint.org/ 查阅。
browserslist
指定了项目需要适配的浏览器范围。这个值会被 @babel/preset-env(使我们的 JavaScript 代码能够运行在当前和旧版本的浏览器或其他环境中的工具) 和 Autoprefixer (浏览器前缀处理工具)用来确定需要转译的 JavaScript 特性和需要添加的 CSS 浏览器前缀。
其他
上面可能出现了一些你不熟悉的依赖包或工具,但你现在并不需要完全了解它,它们将在项目的底层发挥作用,使我们获得更好的开发体验,完整的 package.json 属性可在 https://docs.npmjs.com/cli/v8/configuring-npm/package-json 查阅。
package-lock.json
这个文件是在执行在
npm install
的时候自动生成,用以记录当前状态下实际安装的各个依赖包的具体来源和版本号以及这个模块又依赖了哪些依赖。你可以简单的理解为对 package.json 里 dependencies 和 devDependencies 的精确描述。在 package.json 里的记录都类似"core-js": "^3.6.5"
的大版本指定。当某个依赖进行小版本更新时,可能导致依赖它的其他包出现问题。所以 package-lock.json 目的就是确保所有库包与你上次安装的完全一样,当存在 package-lock.json 文件时其下载速度也会更快。
babel.config.js
上面我们提到过
babel
作为转译工具使我们的 JavaScript 代码正常运行在旧的或不支持新语法和api的环境。而这个文件就是用来进行 babel 配置的,通常我们不会过于细节的对目标环境会涉及的哪些语法进行转换而进行一一配置,而是通过@babel/preset-env
这个包进行“智能”预设。实际上 vue-cli 已经帮我们做好这些工作,你可以在本地依赖目录找到它:
.gitignore
关于
git
的内容我们甚至应该单独开一个章节去说明,因为这确实是实际开发必不可少的工具。但在这个 Vue 的学习系列课程中,先暂时抑制下我们的求知欲望,待结束该系列的学习后,再来详细的学习 git 工具的用法。这里先大概对它进行一个介绍。
可以说,在多人共同开发的项目里面 git 是保证代码同步、实现版本控制的最佳实践。简单点来讲,就是你和其他人的代码有一个公共的代码仓库,你和其他人需要定时的去向公共仓库上传你们新增或更改的代码和从中拉取最新的代码,这时候
.gitignore
文件就负责标记哪些目录或文件是不需要上传上去的,比如之前提到的node_modules
和dist
目录,或者自己写的一些测试文件。
src
在这之前介绍到的文件几乎都是项目的一些配置文件,
src
目录包含的算是我们真正的“源代码”了,也是我们开发的主战场即项目涉及到的页面、样式、脚本都集中在此编写。
public
任何放置在
public
文件夹的静态资源都会被简单的复制,而不经过 webpack(vue-cli依赖的打包工具)。你需要通过绝对路径来引用它们。注意我们推荐将资源作为你的模块依赖图的一部分导入,这样它们会通过 webpack 的处理并获得如下好处:
- 脚本和样式表会被压缩且打包在一起,从而避免额外的网络请求。
- 文件丢失会直接在编译时报错,而不是到了用户端才产生 404 错误。
- 最终生成的文件名包含了内容哈希,因此你不必担心浏览器会缓存它们的老版本。
通常,我们只需要关注
public/index.html
这个文件,它会在构建过程中被注入处理后的 JavaScript 和 CSS 等的资源链接。同时,它也提供了 Vue 实例挂载的目标。
node_modules
之前提到过,当前项目所需依赖的保存目录。
.git
上面说到,我们在多人共同开发的项目里,会使用 git 来进行代码的管理。项目本地的
.git
就是用来管理 git 仓库的目录,其中记录了项目仓库的配置信息、代码的各个版本和提交记录、多分支代码的记录、本地暂存区二进制文件、一些 shell 脚本等等。
2、Vue 单文件组件
上一小节我们熟悉了 Vue 项目根目录各个文件和目录的作用,现在我们深入
src
目录进行“代码级”的学习。
src 下面的目录结构:
main.js
:默认为整个项目的入口文件。App.vue
:是项目的主组件,页面的入口文件。assets
:静态资源存放目录,和 public 目录不同的是这个目录会被打包处理,需要使用相对路径进行引用。components
:组件存放目录,项目的公共组件可以存放在此供其他地方使用。
main.js
作为我们项目的入口文件,可以看到它初始化了 Vue 实例,而这个实例就是我们项目的根实例。在其参数选项里多了一个render: h => h(App)
,其作用通过创建VNode(后续在深入Vue原理时再讲)的方式指定我们的挂载模板,当没有指定template
和render
的情况下会将我们挂载的 Dom 元素的 HTML 作为模板。如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用vm.$mount()
手动地挂载一个未挂载的实例。这里我们回头看一看public/index.html
,里面就放置了一个待挂载的<div id="app"></div>
。
在这里,你会发现出现了
import xxx from xxx
的语法。假设你没有其他语言的使用经验,那么先介绍一下前端的模块化规范。
ES Module
通常一个项目都会去依赖大量的第三方模块或提取自己的模块来避免重复“造轮子”,以提升开发效率。当我们按照传统的 HTML 引入多个
<script>
来集成我们需要的脚本能力时,会遭遇以下挑战:
- 请求过多: 我们要依赖多个 JavaScript 文件时,通过一一引入的方式,那样就会发送多个请求,导致请求过多。
- 依赖模糊: 我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。
- 难以维护: 基于以上两条,当我们项目拥有大量模块需要管理时,很可能出现牵一发而动全身的情况,导致项目出现严重的问题。
而上面这些问题可以通过模块化规范来解决,目前流行的 JavaScript 模块化规范有
CommonJS
、AMD
、CMD
以及ES Module
,现在仅就 main.js 中出现的import xxx from xxx
也就是ES Module
进行简单说明。ES Module
把一个文件当作一个模块,每个模块有自己的独立作用域,那如何把每个模块联系起来呢?核心点就是模块的导入(import)与导出(export)。
// 我们在main.js同级目录下创建moduleA.js文件,其中代码如下
let student = {
name: '李四',
age: 21
}
export function getName() {
return student.name
}
export function getAge() {
return student.age
}
// 在main.js使用import使用
import { getAge, getName } from './moduleA.js'
console.log(getAge(), getName())
现在我们查看浏览器控制台,可以发现main.js可正常使用
getAge()
,getName()
:
除了使用
export
进行导出,还可以使用export default
导出,需要注意的是:
- 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
- 通过 export 方式导出,在导入时要加{ },export default 则不需要。
// moduleA.js文件,修改代码如下
let student = {
name: '李四',
age: 21
}
function getName() {
return student.name
}
function getAge() {
return student.age
}
export default {
getName,
getAge
}
// 在main.js修改import的引入方式
import moduleA from './moduleA.js'
console.log(moduleA.getAge(), moduleA.getName())
Vue 单文件组件规范
.vue
文件是一个自定义的文件类型,用类 HTML 语法描述一个 Vue 组件。每个.vue
文件包含三种类型的顶级语言块<template>
、<script>
和<style>
。最后他们将组装成一个 ES Module,它的默认导出是一个 Vue.js 组件选项的对象。在main.js
中,这个被导入并使用的对象是来自APP.vue
,那么现在我们开始聚焦于 App.vue 文件。
模板
- 每个
.vue
文件最多包含一个<template>
块。 <template>
块只能有一个根元素。
脚本
- 每个
.vue
文件最多包含一个<script>
块。 - 这个脚本会作为一个
ES Module
来执行。 - 它的默认导出应该是一个 Vue.js 的组件选项对象。
- 组件选项对象中的
data
必须是个函数,以便每个实例可以维护一份被返回对象的独立的拷贝。
样式
- 一个
.vue
文件可以包含多个<style>
标签。 <style>
标签可以有scoped
或者module
属性 (查看 scoped CSS和 CSS Modules) 以帮助你将样式封装到当前组件。具有不同封装模式的多个<style>
标签可以在同一个组件中混合使用。
内容预告
本章我们介绍了 Vue 项目目录结构以及初步认识了 Vue 单文件组件,下一章我们将在单文件组件的方式下继续进行 Vue 知识的学习,包括一些常用指令、条件渲染、列表渲染等。