VUE 3——3:脚手架构建工具介绍及在创建 Vue 3 项目中的应用

这是针对 Vue 新人的一个简单指导。

在前面的文章中,我们不推荐新手直接使用 vue-cli,(尤其是在还不熟悉基于 node.js 的构建工具时),而是通过直接引用的方式:

<script src="https://unpkg.com/vue@next"></script>

随着页面中组件越写越多,单个 HTML 文件终究无法完成实际的整个 Vue 项目,同时为了对 Vue 的使用有个更整体的了解,这里将展示如何通过 vuecli 脚手架工具创建 Vue 3 项目以及一些前置说明。

一,安装 node.js

(一)什么是 node.js

node.js 听起来似乎是某个 JavaScript 库,而实际上,根据 nodejs.org 的说法,这是一个基于 Chrome V8 引擎的 Javascript 运行环境。

一切都要从 JavaScript 说起。

JavaScript 出现已经好多年了,一种事件驱动的、跨平台的脚本语言,诞生之初,就被用在 Netscape Navigator 浏览器中操作网页,良好的事件驱动特性让 JavaScript 能轻松应对网页中的各种用户操作事件。

人们越来越对 JavaScript 感兴趣,无疑推动了 JavaScript 的发展,非常多安全的、异步的代码被贡献出来,使得它不仅仅能被用于在浏览器中操作网页,甚至还能胜任实现网络服务器、文件处理程序、图形处理程序、数据库驱动程序等等工作。

同时,随着 JavaScript 在网站前端代码中越来越多地承担一些逻辑工作以及各个浏览器对处理 JavaScript 性能的巨大提升,使得 JavaScript 更加受欢迎。

正是 JavaScript 的优秀表现,让一个名叫 Ryan Dahl 的帅哥在本打算开发一个事件驱动、非阻塞I/O 的服务器时注意到它。

Ryan Dahl 就将 Chrome’s V8 JavaScript engine 与一些必要的 JavaScript 运行环境和 JavaScript 库封装在一起,构建了一个类似于不能渲染页面的浏览器核心,于是,在2009年,node.js 诞生了。可以浏览下面的链接了解 Ryan Dahl 与 node.js 的内容:

node.js 的出现一步地激发了在服务器端使用 JavaScript 能力,越来越多的 JavaScript 包被贡献出来。为了方便地做包管理,npm 就被开发出来。越来越丰富的 JavaScript 等静态资源逐渐

JavaScript能做什么,该做什么?
How Node.js works
vue为什么需要nodejs 的环境
为什么用vue.js,为什么前端开发46%的人都在用?

(二)为什么要在前端开发中使用 node.js

尽管 node.js 的出现将前后端开发语言统一为 JavaScript,但这里我们只从前端开发的角度简单说一下为什么要在前端开发中使用 node.js。

前端开发和后端开发一样,已经是一项工程化模块化的工作。

一个工程化的应用显然就也不是一两个文件就能搞定的,它不再是单页应用(SPA),而是一系列的对功能和服务做出支持作用的文件的集合。

因为编码的过程就是一个抽象的过程,为了实现抽象结果的高复用性,我们通常会将代码按照功能的的痛进行封装,成为各个函数、类、模块、包、库等,JavaScript 也不会例外。

正是代码之间的复杂的依赖关系,让开发人员急需一套能有效管理依赖并能协同工作的代码管理(尤其是第三方库)工具——包管理器。

在 node.js 中,最初的包管理器就是一个名为"pm"("pkgmakeinst"的简称)的 bash 实用程序——一个在各种平台上安装各种东西的 bash 函数。

后来这个默认的包管理器程序逐渐扩展为由一个命令行客户端(npm)和一个包含公共和付费私人软件包的在线数据库(npm registry)两部分组成的工具。这个包管理器就是 npm

npm registry提供了许多包公使用,可在npm 客户端通过一系列的命令来安装、删除、更新项目所依赖的包。两者配合,形成了强大的依赖管理能力,提高了开发人员的工作效率。

可以这么说,就算没有 node.js 没有 npm,也会有 XXX,但正是 node.js 中的 npm 明显让前端开发人员免于应付复杂繁琐的包管理工作,这就是它在前端开发中最大的作用之一。

纯前端开发眼里nodejs到底是什么?
我们为什么要使用NodeJS

(三)怎样使用 node.js

1,安装 node.js

有很多方法可以安装 node.js,这里只介绍最简单的:通过可执行文件安装——访问 node.js下载页面下载不同操作系统的安装包,傻瓜式“下一步”。

node.js安装及环境配置超详细教程【Windows系统安装包方式】

回到教程👨‍💻,安装完成后,查看一下能否工作:

# 查看 node.js 版本
F:\web前端\vue\my-project>node -v
v12.18.3

2,使用 node.js

先看看能否正常工作:

F:\web前端\vue\mytest\my-project>node -v
v12.18.3

F:\web前端\vue\mytest\my-project>npm -v
8.6.0

在项目目录中创建一个名为 first.js 的 JavaScript 文件:

// 引入 http 模块
var http = require("http");

// 创建http服务器
var server = http.createServer(function (req, res) {
    // 设置响应头
    res.setHeader('Content-Type', 'text/html;charset=utf-8');
    // 设置响应状态码
    res.statusCode = 200;
    // 设置响应数据
    res.end('<h1>Hello World</h1>');
});

//运行服务器,监听8001端口
server.listen(8001, "127.0.0.1");

// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8001/')
  • 在端口 8080 上访问时,打印“Hello World!”

通过 node.js 启动文件:

F:\web前端\vue\mytest\my-project>node first.js
Server running at http://127.0.0.1:8001/

访问 http://127.0.0.1:8001/,结果如下:
在这里插入图片描述

详细的使用教程,请参考官方文档。有几个重点需要关注:

  • npm 包管理器的使用。
  • package.json 文件的作用。
  • 如何使用 Node.js REPL。

可以跟着使用 Node.js 构建 JavaScript 应用程序来实际体验。

回到教程👨‍💻。
1,先将包管理器切换为 nrp。

  • 默认使用 npm,但国内使用会比较慢,可自行选择其它包管理工具。
# 查看 npm 版本
F:\web前端\vue\my-project>npm -v
8.6.0

# 安装 nrm
F:\web前端\vue\my-project>npm install nrm -g
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142

added 58 packages, and audited 316 packages in 14s

13 packages are looking for funding
  run `npm fund` for details

3 moderate severity vulnerabilities

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

2,切换镜像源。

# 查看可用的镜像源
F:\web前端\vue\my-project>nrm ls

  npm ---------- https://registry.npmjs.org/
  yarn --------- https://registry.yarnpkg.com/
  tencent ------ https://mirrors.cloud.tencent.com/npm/
  cnpm --------- https://r.cnpmjs.org/
  taobao ------- https://registry.npmmirror.com/
  npmMirror ---- https://skimdb.npmjs.com/registry/

# 使用 taobao 镜像
F:\web前端\vue\my-project>nrm use taobao

   Registry has been set to: https://registry.npmmirror.com/

最后放几篇对 Ryan Dahl 的采访:

二,使用 Vue 脚手架创建项目

(一)webpack 与 Vue 脚手架

node.js 是 JavaScript 运行环境和库的集合,并不参与项目的直接的创建等构建性的工作。

如果在项目中要用到第三方的包,实现包括安装、更新、删除等操作,我们可以使用 node.js 默认提供的包管理器 npm,它会通过依赖关系自动管理包。

在实际项目的模块化编程中,开发者通过抽象与封装,将程序分解成一个个模块,从而降低代码耦合度、提高可重用性,进而方便校验、测试。应用程序中每个模块都具有条理清楚的设计和明确的目的。

根据 webpack 官网的介绍,webpack 就是一款 JavaScript 应用程序的静态模块打包器(module bundler)。当用 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 包:
在这里插入图片描述
新手可以先浏览 webpack :概念了解一些核心概念,然后再跟着 webpack :起步走一个流程,来看看如何使用 webpack。

脚手架就是构建项目的工具,使用脚手架能组织项目代码,形成符合项目规范的目录与文件结构。

Vue 项目的官方脚手架是 Vue CLI,它内部封装了 webpack,致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。

尽管现在有更加强大的项目创建工具(比如 vite),但目前我们还是使用 vuecli 吧~

前端模块化开发中webpack、npm、node、nodejs之间的关系[小白总结]
前端开发3年了,竟然不知道什么是 Vue 脚手架?(下)
npm,node,webpack关系

(二)安装 Vue 3 脚手架

回到教程👨‍💻,由于我们一直以来使用的都是 Vue 3 的语法,自然创建的就是 Vue 3 的项目。

先卸载 Vue 2 版本的脚手架。

# vue 2 脚手架对应于 vue-cli
F:\web前端\vue\mytest>npm uninstall vue-cli -g

up to date, audited 1 package in 178ms

found 0 vulnerabilities

然后安装 Vue 3 版本的脚手架。

# vue 3 脚手架对应于 @vue/cli
F:\web前端\vue\mytest>npm install -g  @vue/cli

Vue CLI v5.0.4
? Please pick a preset:
> Default ([Vue 3] babel, eslint)
  Default ([Vue 2] babel, eslint)
  Manually select features

npm WARN deprecated source-map-url@0.4.1: See https://github.com/lydell/source-map-url#deprecated
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm WARN deprecated apollo-tracing@0.15.0: The `apollo-tracing` package is no longer part of Apollo Server 3. See https://www.apollographql.com/docs/apollo-server/migration/#tracing for details
npm WARN deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated
npm WARN deprecated graphql-extensions@0.15.0: The `graphql-extensions` API has been removed from Apollo Server 3. Use the plugin API instead: https://www.apollographql.com/docs/apollo-server/integrations/plugins/
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated apollo-cache-control@0.14.0: The functionality provided by the `apollo-cache-control` package is built in to `apollo-server-core` starting with Apollo Server 3. See https://www.apollographql.com/docs/apollo-server/migration/#cachecontrol for details.
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated subscriptions-transport-ws@0.9.19: The `subscriptions-transport-ws` package is no longer maintained. We recommend you use `graphql-ws` instead. For help migrating Apollo software to `graphql-ws`, see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws    For general help using `graphql-ws`, see https://github.com/enisdenjo/graphql-ws/blob/master/README.md
npm WARN deprecated graphql-tools@4.0.8: This package has been deprecated and now it only exports makeExecutableSchema.\nAnd it will no longer receive updates.\nWe recommend you to migrate to scoped packages such as @graphql-tools/schema, @graphql-tools/utils and etc.\nCheck out https://www.graphql-tools.com to learn what package you should use instead

changed 895 packages in 51s

# 查看脚手架版本
F:\web前端\vue\mytest>vue -V
@vue/cli 5.0.4

(三)创建 Vue 3 项目

创建 vue 项目。

F:\web前端\vue\mytest>vue create demo


Vue CLI v5.0.4
? Please pick a preset: Default ([Vue 3] babel, eslint)


Vue CLI v5.0.4
✨  Creating project in F:\web前端\vue\mytest\demo.
�  Initializing git repository...
⚙️  Installing CLI plugins. This might take a while...


added 842 packages in 46s
�  Invoking generators...
�  Installing additional dependencies...


added 100 packages in 6s
⚓  Running completion hooks...

�  Generating README.md...

�  Successfully created project demo.
�  Get started with the following commands:

 $ cd demo
 $ npm run serve

启动项目。

F:\web前端\vue\mytest>cd demo

F:\web前端\vue\mytest\demo>npm run serve

> demo@0.1.0 serve
> vue-cli-service serve

 INFO  Starting development server...


 DONE  Compiled successfully in 4725ms                                                                 9:29:54 ├F10: PM┤


  App running at:
  - Local:   http://localhost:8081/
  - Network: http://192.168.1.11:8081/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

访问 http://localhost:8081/,效果如下:
在这里插入图片描述
连按两次 ctrl+c 可退出项目:

  App running at:
  - Local:   http://localhost:8081/
  - Network: http://192.168.1.11:8081/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

终止批处理操作吗(Y/N)? Y

F:\web前端\vue\mytest\demo>

(四)项目目录解析

查看一下刚刚创建好的项目的结构:
在这里插入图片描述
这里对 Vue 3 默认的项目结构做一个简单的说明:

|-node_modules          -- 存放项目所有的依赖包
|-public                -- 存放存放第三方插件相关的静态资源
---|favicon.ico         -- 网站的显示图标
---|index.html          -- 是一个用于生成项目入口的模板文件,即浏览器访问项目时默认打开该文件
|-src                   -- 源文件目录,编写的代码基本都在这个目录下
---|assets              -- 存放本地的静态资源,比如logo.png
---|components          -- 存放自定义的公共组件
    ---|HelloWorld.vue  -- 存放自定义的HelloWorld组件
---|App.vue             -- 根组件,会在大规模项目中用于存放路由
---|main.js             -- 程序入口文件,主要作用是初始化vue实例
|-.gitignore            -- 配置gitignore
|-bable.config.js       -- babel配置文件,用于转换es6语法
|-.browserslistrc       -- 在不同前端工具之间公用目标浏览器和node版本的配置文件,作用是设置兼容性
|-jsconfig.json         -- 配置js代码规范,用于规范js代码
|-package.json          -- 配置项目的依赖包,用于规范项目依赖包
|-package-lock.json     -- 配置项目依赖包的版本,用于规范项目依赖包的版本
|-README.md             -- 项目的描述文件
|-vue.config.js         -- 配置vue的相关配置

(五)工程化的 Vue 项目工作流程分析

在前面,我们将所有东西都一股脑儿地放在了一个 html 文件中,在哪里定义根组件、在哪里注册子组件、在哪里创建应用并挂载等等操作都比较明显。

现在我们创建了一个简单的 Vue 项目,简单来说就是将原本的单 html 文件进行了切割,效果就是启动并访问指定的地址后,显示一个介绍页。但它内部是怎样的工作流程?

这就来简单说说项目结构中的各部分的组成内容以及它们是怎样配合工作的。

1,程序入口文件

当运行项目启动命令后,会进入程序入口文件 main.js,那就先来看看 main.js 做了什么事情:

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

其实是好理解的,就是使用 import 语句从根组件定义所在的 App.vue 文件中导入一个名为 App 的对象,并实例化。

这里就要说明一下,在工程化的 Vue 项目中,我们会尽量将提供某项功能的组件封装到一个单独的 .vue 文件中,这种文件就是组件文件。

2,根组件文件

现在就来看看根组件文件 App.vue 的内容:

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

一个组件文件通常包含三个部分:

  • 由 包含的 html 模板代码。
  • 由 包含的 js 逻辑代码。
  • 由 包含的 css 样式代码。

先看模板代码:

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
  1. 使用相对路径展示一个logo。
  2. 使用了一个 HelloWorld 组件,并通过 msg 传入了一个字符串。

这个 HelloWorld 组件是在创建项目时自动产生的自定义组件,在这里它就是根组件的一个子组件。

这个子组件是怎样以哪种方式注册的?msg 有什么意义?

结渣看 js 逻辑代码:

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>
  • 同样地,首先从自定义的组件中导入 HelloWorld。
  • 然后通过 export 语句默认导出一个对象:指定了 name,并通过 components 注册上面导入的组件(只有这样才能使用导入的组件)。

export 语句用于对外提供本模块的接口,这里是一个注册过并指定了 name 的对象。

这两段代码共同作用的效果相当于:

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  template: `
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>`
}
</script>

只不过在单文件组件中我们将它们分离了。

3,自定义组件

在进入 src/components/HelloWorld.vue 组件看看这个自定义的组件 HelloWorld :

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>
      For a guide and recipes on how to configure / customize this project,<br>
      check out the
      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
    </p>
    <h3>Installed CLI Plugins</h3>
    <ul>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
    </ul>
    <h3>Essential Links</h3>
    <ul>
      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
    </ul>
    <h3>Ecosystem</h3>
    <ul>
      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

仍然包含三个部分。

模板文件中的 {{ msg }} 表明 msg 是一个插值,显然它来自于后面 props 中的 msg,进一步表明,它是可以从父组件接收数据的。

还有,此处的 <style> 元素有一个 scoped 属性,表明要让此样式私有化。

好了,现在来捋一捋关系。

  • 程序运行后进入 main.js 文件,根据导入的根组件创建应用。
  • 根组件由 App.vue 文件定义并可导出使用。其中注册了导入进来的子组件并在模板中使用。
  • 子组件定义在 src/components/ 目录下的 .vue 文件(又称单文件组件SFC)里并可导出使用。

就这样吧,其他的东西我们也还没接触,就不说了。

(六)代码重构

现在就来将我们前面的一个例子以工程化的形式重构一下。
重构前:

<h2> Welcome to XXX's BLOG</h2>
<div id="app">
    <div id="dynamic-component">
        <button
                v-for="tab in tabs"
                :key="tab"
                :class="['tab-button', { active: currentTab === tab }]"
                @click="currentTab = tab"
        >
            {{ tab }}
        </button>

        <component v-bind:is="currentTabComponent" class="tab"></component>
    </div>

    <person-info :info="info">USER:</person-info>
    <div>
        <custom-input v-model="searchText"></custom-input>
        <br>
        <span v-if="searchText">you want to search {{ searchText }}</span>
    </div>
    <div :style="{ fontSize: postFontSize + 'em' }"
    >
        <posts-info v-for="post in posts"
                    :title="post.title"
                    :hot="post.hot"
                    :likes="post.likes"
                    :tags="post.tags"
                    :style="styleObject"
                    @like="likePost(post)"
        ></posts-info>
    </div>
</div>

<script>
    // 1,创建应用
    const Root = {
        data() {
            return {
                info: {
                    name: 'xiaolu2333',
                },
                posts: [
                    {id: 1, title: 'My journey with Vue😘', likes: 23, hot: false, tags: ['vue', 'vue.js', 'vue3']},
                    {id: 2, title: 'Blogging with Vue🚄', likes: 14, hot: false, tags: ['vue.js', 'vue3']},
                    {id: 3, title: 'Why Vue is so interesting🥰', likes: 100, hot: true, tags: ['vue', 'vue3']}
                ],
                styleObject: {
                    title: 'color: green',
                    info: 'color: blue',
                    lineBreak: 'white-space:nowrap'
                },
                postFontSize: 1,
                searchText: '',
                currentTab: 'Home',
                tabs: ['Home', 'Posts', 'Archive']
            }
        },
        methods: {
            likePost(post) {
                post.likes += 1
            }
        },
        computed: {
            currentTabComponent() {
                return 'tab-' + this.currentTab.toLowerCase()
            }
        }
    }

    // 2,创建应用
    const app = Vue.createApp(Root)

    // 3,定义一个名为 person-info 的新全局组件
    app.component('person-info', {
        props: ['info'],
        template: `
          <h3>
          <slot></slot>
          {{ info.name }}
          </h3>`
    });

    // 3,定义一个名为 posts-info 的新全局组件
    app.component('posts-info', {
        props: ['title', 'likes', 'hot', 'tags', 'style'],
        template: `
          <div>
          <h3 :style="style.title">{{ title }}</h3>
          <div :style="style.info">
            <span v-if="hot">🔥HOT </span>
            <button @click="$emit('like')">{{ likes }} 💖</button>
            🚩: <span v-for="tag in tags"><a>{{ tag + " " }} </a></span>
          </div>
          </div>`
    })
    // 在组件上使用 v-model 自定义输入框
    app.component('custom-input', {
        props: ['modelValue'],
        emits: ['update:modelValue'],
        template: `
          <input
              :value="modelValue"
              @input="$emit('update:modelValue', $event.target.value)"
          >
          <button>search</button>
        `
    })

    app.component('tab-home', {
        template: `<div class="demo-tab">Home component</div>`
    })
    app.component('tab-posts', {
        template: `<div class="demo-tab">Posts component</div>`
    })
    app.component('tab-archive', {
        template: `<div class="demo-tab">Archive component</div>`
    })

    // 4,挂载应用实例到 DOM,创建根组件实例
    const vm = app.mount('#app')
</script>

我们先将原本由 app.component 注册的各个组件抽取到各个 SFC 文件中:

src/components/PostsList.vue:
<template>
  <div CLASS="postslist">
    <h3>
      <slot></slot>
    </h3>
    <ul>
      <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "PostsList",
  props: ['posts'],
}
</script>

<style scoped>

</style>
  • PostsList 组件专门用于渲染文章列表
src/components/SearchPost.vue:
<template>
  <div class="searchpost">
    <input type="text"
           :value="modelValue"
           @input="$emit('update:modelValue', $event.target.value)"
           placeholder="搜索文章">
  </div>
</template>

<script>
export default {
  name: "SearchPost",
  props: ['modelValue'],
  emits: ['update:modelValue'],
}
</script>

<style scoped>

</style>
  • SearchPost 组件专门用于搜索文章
src/components/ThemeSwitcher.vue:
<template>
  <div class="switchtheme">
    <button @click="$emit('switch-theme', ++clickCounter)">切换主题</button>
  </div>
</template>

<script>
export default {
  name: "ThemeSwitcher",
  emits: ['switch-theme'],
  data() {
    return {
      clickCounter: 0
    }
  },
}
</script>

<style scoped>

</style>
  • ThemeSwitcher 组件专门用于切换主题
src/components/UserInfo.vue:
<template>
  <div class="userinfo">
    <p>用户名:{{ userinfo.username }} 等级:{{ userinfo.grade }}</p>
  </div>
</template>

<script>
export default {
  name: "UserInfo",
  props: ['userinfo'],
}
</script>

<style scoped>

</style>
  • UserInfo 组件专门用于渲染用户信息
src/components/HomeComponent.vue:
<template>
  <div class="Home">Home page</div>
  <UserInfo :userinfo="userInfo"/>
  <ThemeSwitcher @switch-theme="switchTheme" :style="themeSettings"/>
  <SearchPost v-model="searchText"/>
  <PostsList :posts="postsList" :style="themeSettings">热门文章</PostsList>
</template>

<script>
import SearchPost from './SearchPost.vue'
import ThemeSwitcher from "@/components/ThemeSwitcher.vue";
import PostsList from './PostsList.vue'
import UserInfo from "@/components/UserInfo";


export default {
  name: "HomeComponent",
  components: {
    SearchPost, ThemeSwitcher, PostsList, UserInfo
  },

  data() {
    return {
      userInfo: {
        username: 'xiaolu2333',
        grade: 12,
      },
      postsList: [
        {id: 1, title: 'My journey with Vue😘'},
        {id: 2, title: 'Blogging with Vue🚄'},
        {id: 3, title: 'Why Vue is so fun🥰'}
      ],
      searchText: "",
      themeSettings: {
        color: 'green',
        background: 'white'
      },
    }
  },
  watch: {
    // eslint-disable-next-line no-unused-vars
    searchText(newVal, oldVal) {
      let searchResult = [];
      if (newVal !== "") {
        for (let i = 0; i < this.postsList.length; i++) {
          if (this.postsList[i].title.toLowerCase().includes(newVal.toLowerCase())) {
            searchResult.push(this.postsList[i]);
          }
        }
        console.log(searchResult);
      }
    }
  },
  methods: {
    switchTheme(clickCounter) {
      console.log(clickCounter);
      this.themeSettings.color = '#' + Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, '0');
      this.themeSettings.background = '#' + Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, '0');
    },
  },
}
</script>

<style scoped>
</style>
  • HomeComponent 组件专门用于渲染首页内容,包括用户信息、切换主题、搜索文章、文章列表
src/components/PostsComponent.vue:
<template>
  <div class="Posts">Posts page</div>
</template>

<script>
export default {
  name: "PostsComponent",
}
</script>

<style scoped>

</style>
src/components/ArchiveComponent.vue:
<template>
  <div class="Archive">Archive page</div>
</template>

<script>
export default {
  name: "ArchiveComponent"
}
</script>

<style scoped>

</style>

最后修改 App.vue 文件:

<template>
  <img alt="Vue logo" src="./assets/logo.png"><br>
  <div class="demo">
    <div
        v-for="tab in tabs"
        :key="tab.name"
        :class="['tab-button', { active: currentTab.name === tab.name }]"
        @click="currentTab = tab"
    >
      {{ tab.name }}
    </div>
    <component :is="currentTab.component" class="tab"></component>
  </div>
</template>

<script>
import HomeComponent from "@/components/HomeComponent";
import PostsComponent from "@/components/PostsComponent";import ArchiveComponent from "@/components/ArchiveComponent";

export default {
  name: 'App',
  components: {
    HomeComponent,
    PostsComponent,
    ArchiveComponent,
  },
  data() {
    return {
      currentTab: {name: 'Home', component: HomeComponent},
      tabs: [
        {name: 'Home', component: HomeComponent},
        {name: 'Posts', component: PostsComponent},
        {name: 'Archive', component: ArchiveComponent},
      ]
    }
  }
}
</script>

<style>
</style>

组件结构如下:
在这里插入图片描述
效果如下:
在这里插入图片描述

好了,暂时我们只需要知道如何用 vuecli 创建 vue3 项目、知道项目中各个文件或文件夹的大概作用、知道如何创建并使用 SFC 就行了,更多的内容耨面再说。

(七)项目效果

这里继续适当重构并添加一些样式,稍微美化一下,看下效果:

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值