本文最后更新于 2025年4月21日 上午
前言 最近想系统性学习一下vue,在B站看到了技术蛋老师
的教学视频https://github.com/eggtoopain/vue-router-4-tutorial
,受益匪浅,蛋老师提供了各个章节源码,但是没有归纳笔记,因此本人按照视频目录粗略归纳了一下内容,以便快速回顾。
创建vue应用和插值表达式
1 <script src ="https://unpkg.com/vue@3" > </script >
1 2 3 <script > Vue .createApp ({data ( ){}, methods :{}, template :'' }).mount ('#app' ) </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > vue3核心笔记</title > <script src ="https://unpkg.com/vue@3" > </script > </head > <body > <div id ="app" > <h1 > {{ title }}</h1 > </div > <script > Vue .createApp ({ data ( ) { return { title : '零食清单' } } }).mount ('#app' ); </script > </body > </html >
v-for,v-bind,v-model用法
v-for:循环元素
v-bind:单向绑定属性data->dom,“v-bind:”可以简写为“:”
v-model:双向绑定data<->dom
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <ul > <li v-for ="food in foods" > <img v-bind:src ="food.image" > <span > {{ food.name }}</span > <input type ="checkbox" v-model ="food.purchased" > <span > {{ food.purchased }}</span > </li > </ul > <script > Vue .createApp ({ data ( ) { return { title : '零食清单' , foods : [ { id : 1 , name : '原味鱿鱼丝' , image : './images/原味鱿鱼丝.png' , purchased : false }, { id : 2 , name : '辣味鱿鱼丝' , image : './images/辣味鱿鱼丝.png' , purchased : false }, { id : 3 , name : '炭烧味鱿鱼丝' , image : './images/炭烧味鱿鱼丝.png' , purchased : false } ] } } }).mount ('#app' ); </script >
key,v-show,computed用法
key:给每一个元素添加唯一标识,避免vue对元素进行重新渲染
v-show:根据条件显示元素,原理是根据CSS的display属性控制元素的显示与隐藏
computed:计算属性,依赖于data中的数据,可以缓存计算结果,提高渲染效率,适用于高频计算的属性
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <div id ="app" > <section v-show ="beforeBuy.length" > <h2 > 未购零食</h2 > <ul > <li v-for ="food in beforeBuy" :key ="food.id" > <img :src ="food.image" > <span > {{ food.name }}</span > <input type ="checkbox" v-model ="food.purchased" > </li > </ul > </section > <section v-show ="afterBuy.length" > <h2 > 已购零食</h2 > <ul > <li v-for ="food in afterBuy" :key ="food.id" > <img :src ="food.image" > <span > {{ food.name }}</span > <input type ="checkbox" v-model ="food.purchased" > </li > </ul > </section > </div > <script > Vue .createApp ({ data ( ) { return { foods : [ { id : 1 , name : '原味鱿鱼丝' , image : './images/原味鱿鱼丝.png' , purchased : false }, { id : 2 , name : '辣味鱿鱼丝' , image : './images/辣味鱿鱼丝.png' , purchased : false }, { id : 3 , name : '炭烧味鱿鱼丝' , image : './images/炭烧味鱿鱼丝.png' , purchased : false } ] } }, computed : { beforeBuy ( ) { return this .foods .filter (item => !item.purchased ); }, afterBuy ( ) { return this .foods .filter (item => item.purchased ); } } }).mount ('#app' ); </script >
拆组件和引用组件
拆组件:将一个大的组件拆分成多个小组件,提高代码的可维护性
引用组件:在其他组件中引用其他组件,提高代码的复用性
步骤
创建组件:在单独的文件中定义组件,使用template标签定义组件的结构,如新建components/AppSection.js文件
1 2 3 4 5 export default { template : `` , data ( ) {}, computed : {} }
引用组件:在其他组件中引用组件,使用template标签引用组件,由于html不区分大小写,对于驼峰式命名的组件,需要使用kebab-case命名法,如AppSections.vue文件,在html引用时写成
1 2 3 4 5 6 7 8 import AppSections from "./AppSections.js" export default { components : { AppSections }, template : ` <app-sections></app-sections> ` }
AppSection.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 export default { template : ` <section v-show="beforeBuy.length"> <h2>未购零食</h2> <ul> <li v-for="food in beforeBuy" :key="food.id"> <img :src="food.image"> <span>{{ food.name }}</span> <input type="checkbox" v-model="food.purchased"> </li> </ul> </section> <section v-show="afterBuy.length"> <h2>已购零食</h2> <ul> <li v-for="food in afterBuy" :key="food.id"> <img :src="food.image"> <span>{{ food.name }}</span> <input type="checkbox" v-model="food.purchased"> </li> </ul> </section> ` , data ( ) { return { foods : [ { id : 1 , name : '原味鱿鱼丝' , image : './images/原味鱿鱼丝.png' , purchased : false }, { id : 2 , name : '辣味鱿鱼丝' , image : './images/辣味鱿鱼丝.png' , purchased : false }, { id : 3 , name : '炭烧味鱿鱼丝' , image : './images/炭烧味鱿鱼丝.png' , purchased : false } ] } }, computed : { beforeBuy ( ) { return this .foods .filter (item => !item.purchased ); }, afterBuy ( ) { return this .foods .filter (item => item.purchased ); } } }
App.js
1 2 3 4 5 6 7 8 import AppSections from "./AppSections.js" export default { components : { AppSections }, template : ` <app-sections></app-sections> ` }
index.html
1 2 3 4 5 6 7 <div id ="app" > </div > <script type ="module" > import App from './components/App.js' ; Vue .createApp (App ).mount ('#app' ); </script >
props和组件复用
继续拆组件,把已购零食和未购零食拆分成两个组件,两个组件不同的是标题和数组,可以使用props来传递这两个数据
用props传参的方式来写子组件,子组件中规定参数类型
子组件:AppSectionsList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export default { template : ` <section v-show="buyChild.length"> <h2>{{headline}}</h2> <ul> <li v-for="food in buyChild" :key="food.id"> <img :src="food.image"> <span>{{ food.name }}</span> <input type="checkbox" v-model="food.purchased"> </li> </ul> </section> ` , props : { headline : String , buyChild : Object } }
父组件首先import子组件,然后在template中引用子组件,并传递props
父组件的computed中定义filters对象,filters对象中包含两个数组,分别为未购零食和已购零食的数组 父组件:AppSections.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import AppSectionsList from "./AppSectionsList.js" ;export default { components : { AppSectionsList }, template : ` <app-sections-list headline="未购零食" :buyChild="filters.beforeBuy"> </app-sections-list> <app-sections-list headline="已购零食" :buyChild="filters.afterBuy"> </app-sections-list> ` , data ( ) { return { foods : [ { id : 1 , name : '原味鱿鱼丝' , image : './images/原味鱿鱼丝.png' , purchased : false }, { id : 2 , name : '辣味鱿鱼丝' , image : './images/辣味鱿鱼丝.png' , purchased : false }, { id : 3 , name : '炭烧味鱿鱼丝' , image : './images/炭烧味鱿鱼丝.png' , purchased : false } ] } }, computed : { filters ( ) { return { beforeBuy : this .foods .filter (item => !item.purchased ), afterBuy : this .foods .filter (item => item.purchased ) } } } }
爷爷组件:App.js
1 2 3 4 5 6 7 8 import AppSections from "./AppSections.js" export default { components : { AppSections }, template : ` <app-sections></app-sections> ` }
v-on,methods,v-if,style和class绑定事件
v-on:绑定事件,可以简写为@ @submit.prevent可以阻止表单的默认事件,可以在事件处理函数中执行自定义逻辑
v-if:真正的条件渲染,根据条件决定是否渲染元素,可以有v-else-if、v-else
v-show的原理是根据CSS的display属性控制元素的显示与隐藏,不会移除dom元素
:class={className: true}
1 2 3 4 5 6 <form @submit.prevent ="add" > <input type ="text" placeholder ="输入爱吃的鱿鱼丝..." v-model ="newFood" /> <button type ="submit" v-if ="foods.length <=3" > 添加</button > <button type ="submit" :class ="{buttonColor: true}" v-else-if ="foods.length > 3 && foods.length < 5" > 再加</button > <button type ="submit" :class ="{buttonColor: true}" v-else > 继续加</button > </form >
AppSections.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import AppSectionsList from "./AppSectionsList.js" ;export default { components : { AppSectionsList }, template : ` <app-sections-list headline="未购零食" :buyChild="filters.beforeBuy"></app-sections-list> <app-sections-list headline="已购零食" :buyChild="filters.afterBuy"></app-sections-list> <form @submit.prevent="add"> <input type="text" placeholder="输入爱吃的鱿鱼丝..." v-model="newFood" /> <button type="submit" v-if="foods.length <=3">添加</button> <button type="submit" :class="{buttonColor: true}" v-else-if="foods.length > 3 && foods.length < 5">再加</button> <button type="submit" :class="{buttonColor: true}" v-else>继续加</button> </form> ` , data ( ) { return { foods : [ { id : 1 , name : '原味鱿鱼丝' , image : './images/原味鱿鱼丝.png' , purchased : false }, { id : 2 , name : '辣味鱿鱼丝' , image : './images/辣味鱿鱼丝.png' , purchased : false }, { id : 3 , name : '炭烧味鱿鱼丝' , image : './images/炭烧味鱿鱼丝.png' , purchased : false } ], newFood : '' , } }, methods : { add ( ) { this .foods .push ({ id : this .foods .length + 1 , name : this .newFood , image : '../images/鱿鱼丝.png' , purchased : false }); this .newFood = '' ; } }, computed : { filters ( ) { return { beforeBuy : this .foods .filter (item => !item.purchased ), afterBuy : this .foods .filter (item => item.purchased ) } } } }
index.html
1 <link rel ="stylesheet" href ="./style.css" >
style.css
1 2 3 .buttonColor { background-color : aquamarine; }
$emit
$emit 是一个实例方法,用于在子组件中触发事件,使得父组件可以监听并响应这些事件。$emit 方法的第一个参数是事件名称,随后的参数是传递给事件处理函数的数据。
子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export default { template : ` <form @submit.prevent="add"> <input type="text" placeholder="输入爱吃的鱿鱼丝..." v-model="newFood" /> <button type="submit">添加</button> </form> ` , data ( ) { return { newFood : '' } }, methods : { add ( ) { this .$emit('addFather' , this .newFood ); this .newFood = '' ; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import AppSectionsList from "./AppSectionsList.js" ;import AppSectionsForm from "./AppSectionsForm.js" ;export default { components : { AppSectionsList , AppSectionsForm }, template : ` <app-sections-list headline="未购零食" :buyChild="filters.beforeBuy"></app-sections-list> <app-sections-list headline="已购零食" :buyChild="filters.afterBuy"></app-sections-list> <app-sections-form @addFather="fatherAdd" ></app-sections-form> ` , data ( ) { return { foods : [ { id : 1 , name : '原味鱿鱼丝' , image : './images/原味鱿鱼丝.png' , purchased : false }, { id : 2 , name : '辣味鱿鱼丝' , image : './images/辣味鱿鱼丝.png' , purchased : false }, { id : 3 , name : '炭烧味鱿鱼丝' , image : './images/炭烧味鱿鱼丝.png' , purchased : false } ] } }, methods : { fatherAdd (youyusi ) { this .foods .push ({ id : this .foods .length + 1 , name : youyusi, image : '../images/鱿鱼丝.png' , purchased : false }); } }, computed : { filters ( ) { return { beforeBuy : this .foods .filter (item => !item.purchased ), afterBuy : this .foods .filter (item => item.purchased ) } } } }
vue项目组成逻辑、文件关系和作用 vue把页面理解为一个组件树,每个组件对应一个.vue文件,组件树的根节点是App.vue文件,App.vue文件中包含了整个页面的结构和逻辑。
SFC Vue 单文件组件(Single File Component,简称 SFC)是 Vue.js 的一个核心特性,它允许开发者将组件的模板、逻辑和样式封装在一个单独的 .vue 文件中。这种文件结构使得组件的组织和复用变得更加简单和直观。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <div class="hello"> <h1>{{ greeting }} World</h1> </div> </template> <script> export default { data() { return { greeting: 'Hello' }; } }; </script> <style scoped> .hello { color: #42b983; } </style>
使用 SFC 的好处:
1.组件化:每个 SFC 都是一个独立的组件,可以包含自己的模板、逻辑和样式。
2.模块化:SFC 使得组件可以被轻松地导入和导出,便于模块化开发。
3.维护性:将相关的代码组织在一起,便于维护和理解。
4.开发效率:可以在一个文件中快速地编辑模板、逻辑和样式,提高开发效率。
npm npm(node package manager)是一个开源的包管理工具,它可以帮助我们管理和发布代码。
生成 package.json 文件:
安装vue依赖包,会生成package-lock.json文件,该文件记录了当前项目依赖的具体版本号,以便于后续项目依赖的复现,依赖全都安装在node_modules目录下。
package.json文件:简易包管理文件
package-lock.json文件:详细包管理文件
webpack webpack是一个模块打包工具,它可以将多个模块按照一定规则转换成浏览器可以识别的静态资源。
1 2 3 npm i - D webpack webpack-cli webpack-dev-server //-D表示开发环境依赖,并不放到生产环境中 npm i - D babel-loader @babel/core @babel/preset-env //防止浏览器不认识ES6语法,需要用babel把代码从高版本的ES6转为低版本的ES5 npm i - D vue-loader vue-template-compiler css-loader vue-style-loader html-webpack-plugin
创建webpack.config.js文件,配置webpack的入口和出口文件,以及loader:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const HtmlWebpackPlugin = require ('html-webpack-plugin' );const { VueLoaderPlugin } = require ('vue-loader' );module .exports = { entry : './src/main.js' , module : { rules : [ { test : /\.vue$/ , use : 'vue-loader' }, { test : /\.css$/ , use : ['vue-style-loader' , 'css-loader' ] }, { test : /\.js$/ , use : 'babel-loader' } ] }, plugins : [ new HtmlWebpackPlugin ({ template : './src/index.html' }), new VueLoaderPlugin () ] }
创建.babelrc文件,配置babel的转译规则:
1 2 3 moudle.exports = { presets: [ '@babel/preset-env'] }
webpack命令简化,回到package.json文件中,添加scripts命令:
1 2 3 4 "scripts" : { "dev" : "webpack-dev-server --mode development" , "build" : "webpack --mode production" }
可以使用npm run dev命令启动webpack开发环境,使用npm run build命令打包生产环境代码。
git
新建.gitignore文件,配置不需要提交的文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 .DS_Store node_modules dist npm-debug.log* yarn-debug.log* yarn-error.log* /*.log .idea .vscode .env.local .env.development.local .env.test.local .env.production.local # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? # System files Thumbs.db
链接到远程仓库
1 git remote add origin https://github.com/username/project.git
提交代码
1 2 3 git add . //添加所有文件 git commit -m "提交信息" //提交代码 git push origin master //推送代码到远程仓库的master分支,本地也需要有master分支,查看分支:git branch -a,查看远程分支:git branch -r
其他
1 2 3 4 5 6 git branch -m old_name new_name //本地分支改名 git checkout -b new_branch //创建新分支 git branch -D branch_name //删除本地分支 git checkout branch_name //切换分支 git fetch //只下载远程分支的最新更改,但不会自动合并到你的本地分支,需要手动合并。 git pull //下载远程分支的最新更改,并尝试将这些更改合并到你的本地分支。相当于 git fetch 和 git merge。
jsconfig.json jsconfig.json文件是VS Code的配置文件,它可以帮助我们在VS Code中更好地开发JavaScript项目。
1 2 3 4 5 6 7 8 9 { "compilerOptions" : { "baseUrl" : "." , "paths" : { "@/*" : [ "src/*" ] } , "lib" : [ "esnext" , "dom" , "dom.iterable" , "scripthost" ] } }
vue-cli vue-cli是一个脚手架工具,它可以帮助我们快速搭建基于Vue的项目结构。
创建一个新项目:
1 npm i -g @vue/cli //全局安装vue-cli
1 vue create my-project //创建新项目
目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ├──.git/ │ └── 版本控制相关文件 ├── dist/ │ ├── css/ │ │ └── 样式文件 │ └── js/ │ ├── favicon.ico │ └── index.html ├── node_modules/ │ └── 项目依赖的 Node.js 模块 ├── public/ │ ├── favicon.ico │ └── index.html ├── src/ │ ├── assets/ │ │ └── 静态资源文件,如图片、样式等 │ │ └── logo.png │ ├── components/ │ │ └── Vue 组件目录 │ │ └── HelloWorld.vue │ ├── App.vue │ └── main.js ├──.gitignore ├── babel.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── README.md └── vue.config.js
vue-router客户端路由的核心基础知识 这里使用vite创建项目,使用vue-cli也差不多:
1 2 npm install -g create-vite create-vite my-vue-vite-project --template vue
cd进入项目目录安装依赖:
1 2 npm install vue-router npm install
新建router文件夹,创建index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { createRouter,createWebHistory } from 'vue-router' import Home from '../views/Home.vue' import About from '../views/About.vue' const routes = [ { path : '/' , component : Home }, { path : '/about' , component : About }, ] const router = createRouter ( { history : createWebHistory (), routes } ) export default router
新建views文件夹,创建对应组件
1 2 3 4 5 <template> <h1> Home </h1> </template>
1 2 3 4 5 <template> <h1> About </h1> </template>
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 <template> <nav> <a href="/">a标签:主页</a> | <a href="/about">a标签:关于</a> <br> <router-link to="/">router-link:主页</router-link> | <router-link to="/about">router-link:关于</router-link> <br>点击a标签页面会有新的http请求,router-link不会。 </nav> <router-view></router-view> <!-- 指定路由组件显示的位置 --> </template>
main.js
1 2 3 4 5 import { createApp } from 'vue' import './style.css' import App from './App.vue' import router from './router' createApp (App ).use (router).mount ('#app' )
进阶案例 路由由数据动态决定,不写死,比如: 准备一个data.json文件,内容如下: 要把每一条数据单独做一条路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 [ { "id" : 1 , "name" : "鸡蛋" , "type" : "chicken-egg" , "image" : "chicken-egg.jpeg" , "description" : "又名鸡卵、鸡子,是母鸡所产的卵" , "flavour" : "味甘,性平,无毒(煮熟后)" } , { "id" : 2 , "name" : "鸭蛋" , "type" : "duck-egg" , "image" : "duck-egg.jpeg" , "description" : "又名鸭子、鸭卵、太平、鸭春、青皮等,为鸭科动物家鸭的卵,受精卵可孵化成小鸭" , "flavour" : "性涼、味甘" } , { "id" : 3 , "name" : "鹅蛋" , "type" : "goose-egg" , "image" : "goose-egg.jpeg" , "description" : "家禽鹅生下的卵" , "flavour" : "有些油" } , { "id" : 4 , "name" : "鹌鹑蛋" , "type" : "quail-egg" , "image" : "quail-egg.jpeg" , "description" : "鵪鶉所產的卵,蛋殼表面帶有棕褐色斑點" , "flavour" : "味甘、性平" } , { "id" : 5 , "name" : "笨蛋" , "type" : "dumb-egg" , "image" : "dumb-egg.jpeg" , "description" : "我才不是笨蛋" , "flavour" : "没吃过" } ]
详细的逻辑请看注释****详细的逻辑请看注释 App.vue中使用v-for添加路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <template> <nav> <a href="/">a标签:主页</a> | <a href="/about">a标签:关于</a> <br> <router-link to="/">router-link:主页</router-link> | <router-link to="/about">router-link:关于</router-link> <br>点击a标签页面会有新的http请求,router-link不会。 <br>下面是动态的路由: <br> <router-link v-for="item in dataEggs" :key="item.id" :to="`/eggs/${item.type}`" > {{ item.name }} | </router-link> </nav> <router-view></router-view> <!-- 指定路由组件显示的位置 --> <button @click="backward">退后</button> <button @click="forward">前进</button> </template> <script> import dataEggs from './data.json' export default { data() { return { dataEggs//ES6语法 } }, methods: { backward() { this.$router.go(-1)//查看历史操作 }, forward() { this.$router.go(1) } } } </script> <style scoped> .egg-active { color: red; } </style>
router/index.js中配置路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import { createRouter,createWebHistory } from 'vue-router' import Home from '../views/Home.vue' import About from '../views/About.vue' const routes = [ { path : '/' , component : Home }, { path : '/about' , component : About }, { path : '/eggs/:eggType' , component : () => import ('../views/Eggs.vue' ) }, { path : '/eggs' , redirect : '/eggs/chicken-egg' }, { path : '/:pathMatch(.*)*' , component : () => import ('../views/NotFound.vue' ) }, ] const router = createRouter ( { history : createWebHistory (), routes, linkActiveClass : 'egg-active' } ) export default router
Egg.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <template> <div v-if="!dataEgg"> <h1>查无此蛋</h1> </div> <div v-else> <h1>{{ dataEgg.name }}</h1> <p>{{ dataEgg.description }}</p> <p>{{ dataEgg.flavour }}</p> <!-- <img :src="`../../src/assets/images/${dataEgg.image}`"> --> </div> </template> <script> import dataEggs from '../data.json' export default { computed: { eggType() { return this.$route.params.eggType //this.$route 是一个包含当前路由信息的对象,它提供了许多有用的属性 }, dataEgg() { return dataEggs.find( dataEgg => dataEgg.type === this.eggType ) } } } </script> <style scoped> img { width: 150px; height: 150px; } </style>
其他:views/NotFound.vue
1 2 3 4 5 <template> <h1> 404 </h1> </template>