vue 学习
1. 组件基础
1.1 什么是组件
组件是可以服用的 vue 实例,说白了是一组可以重复使用的模板。组件里面可以套用组件。
//定义组件
//使用Vue.component来定义组件
Vue.component("my-component-li",{
template: "这是一个名为my-component-li的组件 "
})
//组件定义好了,引用组件
//1
//但是上面的方法只是把li 显示出来,一般li 是需要做列表渲染的,也就是要把数据传进去的,所以需要做一些改动
//1.要把data数据里的数组渲染到 li 里,先给 li 组件绑定 v-for 循环。
//2.然后把 item替换到li里
2. 前端,服务器和数据库
[图片上传失败...(image-3a922-1656668459121)]
解释:前端可以是 vue 等框架,服务器可以是由 node 搭建的也可以是 node 的框架 express,数据库使用 MySQL
3 vue 从零开始一个项目
3.1 安装 vue
安装 vue 的方法有四种
-
在页面上以 CDN 引入
-
下载 javascript 文件并自行托管
即下载 vue 的 .js 文件然后放到自己的工程文件中,然后在项目中去引用。
-
使用 npm 安装它
npm 为你和你的团队打开了连接整个 JavaScript 天才世界的一扇大门。它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个 包(package) (即代码模块)。来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。 下面是关于 npm 的快速介绍: npm 由三个独立的部分组成: 网站 注册表(registry) 命令行工具 (CLI) 网站 是开发者查找包(package)、设置参数以及管理 npm 使用体验的主要途径。 注册表 是一个巨大的数据库,保存了每个包(package)的信息。 CLI 通过命令行或终端运行。开发者通过 CLI 与 npm 打交道。
-
使用 vue 脚手架 CLI 来构建它。推荐使用 vue 脚手架。
Vue 提供了一个官方的 CLI,为单页面应用 (SPA) 快速搭建繁杂的脚手架。它为现代前端工作流提供了功能齐备的构建设置。只需要几分钟的时间就可以运行起来并带有热重载、保存时 lint 校验,以及生产环境可用的构建版本。更多详情可查阅 Vue CLI 的文档
3.1.1 使用 vue-cli 创建项目
安装 node.js 使用 vue-cli 必须先安装 node
-
安装 vue cli
npm install -g @vue/cli
-
创建项目名
vue create 项目名
根据提示进行选择
ps. 因为 npm 的包的下载速度很慢,可以使用淘宝的镜像资源 cnpm 来加快速度:
npm install -g cnpm --registry=https://registry.npm.taobao.org
使用 npm 去安装 cnpm
当项目使用 vue create 项目名并且选择配置完成以后会自动生成一些文件
cd 项目名 // 进入项目中
npm run serve 启动项目
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.31.146:8080/
即项目启动成功
[图片上传失败...(image-974b78-1656668459121)]
[图片上传失败...(image-27abf5-1656668459121)]
方法和计算属性
方法定义在
methods(){
方法体
}
computed:{
计算属性
}
两个使用中非常相似,不同点是计算属性是静态的。相当于把计算结果缓存起来成为静态的属性,避免系统开销
4. mysql
mysql 环境变量配置了以后可以直接用 cmd 去启动 mysql
mysql -uroot -p
//连接的命令
如果不配置环境变量,则每次需要进入到 mysql 的安装文件的bin目录的去启动,本机的安装目录是 C:\Program Files\MySQL\MySQL Server 8.0\bin
即,如果没有配置环境变量,则需要在 cmd 中输入 cd C:\Program Files\MySQL\MySQL Server 8.0\bin 进入到这个目录中再链接数据库。
本数据库的账户密码都是 root
5. node.js
5.1 创建一个经典的服务器
创建空文件夹 nodejsdemo
初始化 npm init
在文件目录中看到主入口文件是 index.js, 所以创建 index.js
[图片上传失败...(image-1d4eff-1656668459121)]
在 index.js 中写入 console.log(1234), 终端输入node index.js 运行输出 1234说明 node 运行了 index.js
-
这样一个简单的服务器就做好了
// setServers const http = require("http"); // require 引入模块的方法,http 是一个默认的模块,所以可以直接写不用再从第三方引入 const hostname = "127.0.0.1"; //定义本地地址(服务器地址) const port = 3000; //定义端口号 //http有createServer 方法(接受参数,req 为请求参数,res 为响应参数) var server = http.createServer((req,res)=>{ res.statusCode = 200; res.setHeader("Content-type","text/plain") res.write("hello node") res.end() }); //server 启动以后使用listion 方法去看是否启动成功 server.listen(port, hostname, ()=>{ console.log("服务器已启动"); })
- nodemon 自动重新启动项目
- 先全局安装 npm i nodemon -g
- 然后在 package.json 文件里添加 “serve”:nodemon index.js
5.2 node中的 http 模块
-
http 服务端
在服务端使用
请求对象(IncomingMessage类的实例)和相应对象(ServerResponse类的实例)
-
http客户端
- 客户端使用(request, get)
- 请求对象(ClientRequest类的实例)和相应对象(IncomingMessage类的实例)
5.3 node 的其他模块(看文档)
- Buffer 模块
- url 模块
- fs 模块
- crypto模块
- path模块
- 模板引擎
- nodemon 自动重新启动项目
6. express.js
基于 node.js 的极简,快速,开放的 web 开发框架。exprss 和 node 的关系就如同 jQuery 和 js 的关系。
6.1 express 安装
npm init 初始化文件
-
安装 express 并将其保存在依赖列表中
npm install express //因为express 是第三方的库,所以要安装才能使用
-
安装完成进入 hello wold 页面
const express = require("express"); //引入express模块 var app=express(); //express 函数运行以后赋值给 app。 app 即是应用对象 //使用get方法 app.get("/",function(req,res){ res.send("hello express") }) //使用 liston方法监听 app.listen(3000,function(){ console.log("运行环境") })
代码赋值到 index.js(如果在 package.jason文件中改了入口文件,那么就需要运行改动后的文件。index.js 是默认的入口文件), node 运行 index.js
浏览器中运行 localhost:3000
6.2 请求本地的 json 数据
module.expors={
"name":"zhangsan",
"age":20
}
//module.exports 意思是当我们创建了一个 data的js 文件以后,我们要把这个文件暴露出去,这样别的程序可以访问的到
6.3 静态资源
为了提供如图片,js,css文件之类的静态资源,使用 express 中的中间件 express.static.
例如,图片放在 static 文件加下面,我们如果要使用这些静态资源则使用
app.use(express.static("static")),那么 static 文件夹里的文件就对外开放了。
app.use(express.static("",))
6.4 路由
路由的目的,上面的所有get/put/path等接口都写在了主文件 mian.js里,这样太乱了,为了把这些接口函数放到一个专门的地方,所以引入了路由的概念。
- 创建一个文件夹 route
- 在文件夹里创建文件 index.js 文件
7. 链接 mysql
-
现在终端安装mysql
npm install mysql
在路由文件去引入 mysql
var mysql = require(mysql)
8. dos 命令
- cd xx 进入到哪个文件
- cd ../ or cd.. 进入上一个文件夹
- cls 清除
实战项目
安装 node 搭建后台 (略)
-
搭建后台项目
1. cmd 输入 npm init 初始化项目。 (npm init -y 快速初始化项目) 2. 初始化成功后将在项目里出现 package.json 文件 3. 创建与入口文件同名的文件js 4. 新增项目启动命令 5. nodemon: 自动重启项目当code内容改变以后 npm i nodemon -g 然后在 package.json 文件里添加 “serve”:nodemon index.js
-
package.json 字段解释
"dependencies": 项目依赖 "devDependencies":开发依赖(只在开发的时候会用,项目打包上线的时候,这个部分不会被打包) "name": "expressdemo", 项目名称 "version": "1.0.0", 项目版本 "description": "", 项目描述 "main": "main.js" 项目的入口文件
如果 npm 比较满,安装一下淘宝镜像 cnpm
-
安装并使用 koa + koa-router
koa 基于 node.js 的下一代 web 开发框架。
koa 是新的框架由 express 原班人马打造,致力于成为 web 应用和 api 开发的更小的,更富有表现力的基石。
koa 建立在 es6+ 之上,提升现代的 js 语法。
使用 generators 和 async / await
优雅,简洁,灵活,体积小
相关名利 npm - koa -s, npm - koa-router -s
//入口文件的基本设置
//1. 引入 koa
const Koa = require("koa");
//2. 实例化 koa
const App = new Koa();
//3. 搭建服务
App.use(async ctx=>{
ctx.body = "荆承鹏学习前端";
})
//4. 监听端口
App.listen(5000, ()=>{
console.log("服务器启动成功:5000")
})
// nodemon index.js 启动服务 http://localhost:5000 可以访问
// 这个时候有个问题,上面的无法指定特定的 api 端口,比如 http://localhost:5000/index, http://localhost:5000/index1 是不同的路径,这个时候就需要 koa-router
// 安装 koa-router : npm i koa-router -s
-
引入 koa-router 以后需要做的
//1. 引入 koa const Koa = require("koa"); const Router = require("koa-router"); //2. 实例化 koa const App = new Koa(); const router = new Router(); //3. 搭建服务 // App.use(async ctx=>{ // ctx.body = "荆承鹏学习前端"; // }) // 使用 koa-router 来启动服务,并且可以指定不同的路径 router.get("/index", async ctx=>{ ctx.status = 200; ctx.body = "荆承鹏学习前端koa-router"; }) router.get("/index1", async ctx=>{ ctx.status = 200; ctx.body = "月薪过万不是梦" }) //配置路由 // 调用router.routes()来组装匹配好的路由,返回一个合并好的中间件 // 调用router.allowedMethods()获得一个中间件,当发送了不符合的请求时,会返回 `405 Method Not Allowed` 或 `501 Not Implemented` App.use(router.routes()); App.use(router.allowedMethods()); //4. 监听端口 App.listen(5000, ()=>{ console.log("服务器启动成功:5000") })
搭建数据库环境 (安装 mysql 略)
安装 Navicat premium (mysql 的图形管理系统)
-
编写后台接口
使用 node 连接 sql 数据库 安装依赖 数据库 npm i mysql -s 跨域 npm i koa2-cors -s 获取body 请求参数 npm i koa-bodyparser -s 封装 bd.js: 对链接数据库的函数进行模块封装 编写接口:接口 koa-router+mysql编写接口 测试接口:使用 vscode 中postman 插件进行接口测试
bd.js 内容 // 引入 bd.js 作为数据库的链接文件 // 引入 mysql const mysql = require("mysql"); //声明变量用来存储数据库绑定连接以后的对象属性 const poolSql = mysql.createPool({ host: "localhost", user:"root", password:"root", port: "3306", database:"test" }) function query(sql, value){ return new Promise((resolve,reject)=>{ poolSql.query(sql,value, (err,result)=>{ if(err){ reject(err) }else{ resolve(result) } }) }) } module.exports = query;
入口页面 index.js 内容
//1. 引入 koa const Koa = require("koa"); const Router = require("koa-router"); const Pool = require("mysql/lib/Pool"); //引入bd.js 里的模块 const poopSql = require("./bd.js"); const cors = require("koa2-cors"); //跨域 const bodyparser = require("koa-bodyparser");//body 参数 //2. 实例化 koa const App = new Koa(); const router = new Router(); //3. 搭建服务 // App.use(async ctx=>{ // ctx.body = "荆承鹏学习前端"; // }) // 使用 koa-router 来启动服务,并且可以指定不同的路径 router.get("/index", async ctx=>{ ctx.status = 200; ctx.body = "荆承鹏学习前端koa-router"; }) router.get("/query", async ctx=>{ ctx.status = 200; try { let _sql = "SELECT * FROM t_user"; let _data = await poopSql(_sql); ctx.body = { errorMessage : "", result:true, data: _data } } catch (error) { ctx.body = { errorMessage : "查询失败", result:false, data: null } } }) //配置路由 // 调用router.routes()来组装匹配好的路由,返回一个合并好的中间件 // 调用router.allowedMethods()获得一个中间件,当发送了不符合的请求时,会返回 `405 Method Not Allowed` 或 `501 Not Implemented` App.use(router.routes()); App.use(router.allowedMethods()); App.use(cors()); App.use(bodyparser()); //4. 监听端口 App.listen(5000, ()=>{ console.log("服务器启动成功:5000") })
-
查询,添加,修改,删除数据库字段的写法
// bd.js //1. 引入 koa const Koa = require("koa"); const bodyparser = require("koa-bodyparser");//body 参数 const Router = require("koa-router"); const Pool = require("mysql/lib/Pool"); //引入bd.js 里的模块 const poolSql = require("./bd.js"); const cors = require("koa2-cors"); //跨域 //2. 实例化 koa const App = new Koa(); const router = new Router(); //3. 搭建服务 // App.use(async ctx=>{ // ctx.body = "荆承鹏学习前端"; // }) // 使用 koa-router 来启动服务,并且可以指定不同的路径 router.get("/index", async ctx => { ctx.status = 200; ctx.body = "荆承鹏学习前端koa-router"; }) //查询数据库 router.get("/query", async ctx => { ctx.status = 200; try { let _sql = "SELECT * FROM t_user"; let _data = await poolSql(_sql); ctx.body = { errorMessage: "", result: true, data: _data } } catch (error) { ctx.body = { errorMessage: "查询失败", result: false, data: null } } }) //向数据库表中添加信息 router.post("/add", async ctx => { ctx.status = 200; let _info = ctx.request.body; //上下文request 里的 body 的内容赋给 _info //表中username 是必填项,所以先判断一下 if(!_info.name){ ctx.body = { errorMessage:"name 是必填项目", result:false, data:null } return } try { let _sql = "INSERT INTO demo (id,name,number,price) VALUES (?,?,?,?)"; let _value = [_info.id,_info.name, _info.number, _info.price]; await poolSql(_sql,_value); ctx.body = { errorMessage: "", result: true, data: null } } catch (error) { ctx.body = { errorMessage: "添加失败", result: false, data: null } } }) //向数据库表中修改信息 router.put("/put", async ctx=>{ ctx.status = 200; let _info = ctx.request.body; if(!_info.id){ ctx.body = { errorMessage:"id 是必填项目", result:false, data:null } return }else if(!_info.name){ ctx.body = { errorMessage:"name 是必填项目", result:false, data:null } return } try{ let _sql = "UPDATE demo SET name=?,number=?,price=? WHERE id=?"; let _value = [_info.name, _info.number, _info.price,_info.id]; await poolSql(_sql, _value); ctx.body = { errorMessage: "", result: true, data: null } }catch(err){ ctx.body = { errorMessage: "修改失败", result: false, data: null } } }) //删除数据库中的数据,通过路径传参的方式 router.delete("/delete/:id", async ctx=>{ ctx.status = 200; console.log(ctx); let _info = ctx.params; try{ let _sql = "DELETE FROM demo WHERE id=?"; let _value = [_info.id]; await poolSql(_sql,_value); ctx.body ={ errorMessage: "", result: true, data: null } }catch(err){ ctx.body = { errorMessage:"删除错误", result:false, data:null } } }) //配置路由 // 调用router.routes()来组装匹配好的路由,返回一个合并好的中间件 // 调用router.allowedMethods()获得一个中间件,当发送了不符合的请求时,会返回 `405 Method Not Allowed` 或 `501 Not Implemented` App.use(cors()).use(bodyparser()).use(router.routes()).use(router.allowedMethods()); //4. 监听端口 App.listen(5000, () => { console.log("服务器启动成功:5000") })
-
搭建前端框架
使用 vue3 + vite 搭建项目
此步骤以前的都是服务器端的设置,项目里新建文件夹把服务器端的项目文件命名为 server 然后在这个项目里开始搭建前端。
$ npm create vite@latest // 快速搭建一个项目
Vue3 基础 api
- defineComponent 这是对 setup 函数进行封装,返回 options 的对象,主要就是为了服务 ts 而存在的。 - ref 接受一个内部值返回一个响应式可变的 ref 对象。 ref 对象具有指向内部值的单个 property.value - reactive 与 ref 类似,不同的是它用来定义更为复杂的数据类型 - toRefs 将响应式对象转为普通对象,其中结果对象的每个 property 都指向原始对象对应的 property 的 ref - toRaw 只是对原生数据进行修改,不改变更新 ui 视图 - setup 函数 这个函数主要是为了使用组合式 api,使用setup函数时候,有两个参数 props: 可以从中获取组件的 props 参数,他是响应式的,也就是传入新的 prop 时候,它被更新; context: context是一个普通的 JavaScript对象,暴露了其他可能在 setup 中有用的值。这些值就是之前 vue2中 this 上暴露的 property 属性,比如 attrs, slots, emit 注意:在setup() 中, this 不是该活跃实例的引用。
引用 ant design vue
npm install ant-design-vue --save
当前端界面完成,开始前端和联调
使用 axios 封装 api
axios 是一个基于 promise 的 http 库,可以在浏览器和node.js 中使用。
它符合现在的 mvvm 浪潮。
vue笔记
1. vue 零碎的知识点
-Vue中有两种数据绑定,v-band 和 v-module
1. 单向数据绑定 v-band:xxx 简写:xxx; 数据只能从 data 流向页面。
2. 双向数据绑定 v-model:xxx 简写为 v-model = ""
- el 和 data 的两种写法
- el的两种写法
(1) new Vue 时候配置el 的属性
(2)先创建实例子,然后通过$monunt("挂载") - data的两种写法:对象式,函数式
(1)对象式 data:{
//写入data 的值,可以直接用在模板中
}
(2)函数式写法 data(){
return {
//写入data 的值,可以直接用在模板中
}
}
在实际的项目中,主要谢函数式
上面是这个的变形写法 data:function(){
return {
//写入data 的值,可以直接用在模板中
}
} - 一个重要的原则:由 Vue 管理的函数一定不要写箭头函数,一旦写了箭头函数 this 的指向就不再是 Vue 了。
- el的两种写法
2. mvvm 模型
MVVM 模型,细说其实是 M-V-VM 模型
M (model:指的是 data 中的数据)
V (view) 视图:模板代码,即显示在页面中的内容
VM (ViewModel) 视图模型,即 Vue 实例所做的事情
发现:1: data 中所有数据都出现在了 vm 身上;vm身上所有属性,包括从原型链上继承来的都可以直接使用在模板中直接使用。
3. vue2 数据双向绑定的原理
Object.defineProperty() 实现数据代理
- vue中的数据代理
通过vm对象来代理data对象中的属性的操作(读/写) - vue中的数据代理的好处
更加方便的操作data中的数据 - 基本原理
- 通过Object.defineProperty()把data中的对象所有属性添加到vm上。
- 为每个添加到vm的属性,都指定一个getter/setter.
- 在getter/setter内部去操作(读/写)data中对应的的属性
4. 事件处理
- 使用v-on:xxx或者@xxx绑定事件,其中xxx是事件名称;
- 事件回调需要配置在methods对象中,最终会在vm上;
- methods中配置的函数,不要用箭头函数,否则this就不是vm了;
- methods中配置的函数,都是被vue所管理的函数,this的指向是vm或者组件实例对象;
- @lick="demo"和@lick="demo($event)"效果一致,但是后者可以传参。
5. 事件修饰符
描述:就是修饰事件的
- prevent:阻止默认事件(常用)
- stop:阻止事件冒泡(常用)
- once:事件只触发一次(常用)
- capture:使用事件的捕获模式
- self:只有event.target是当前操作的元素时才触发事件
- passive:事件的默认行为立即执行,不用等事件回调执行完毕。
案例:@click.once="showMe",这个事件只执行一次;@click.prevent="showMe"阻止事件的默认行为,比如在 a 标签上添加此修饰符,在点击a标签以后,执行showMe函数,a标签的跳转行为不执行。 - 修饰符可以连续写
6. 键盘事件
-
vue中常用的按键别名:
- 回车 => enter
- 删除 => delete (捕获删除和退格键)
- 退出 => esc
- 空格 => space
- 换行 => tab
- 上 => up
- 下 => down
- 左 => left
- 右 => right
vue未提供别名的按键,可以使用按键原始的key值去绑定,但是要注意转换为kebab-case(短横线命名)
-
系统修饰键(用法特殊):ctrl, alt, shift, meta
- 配合keyup使用:按下修饰键的同时,再按其他键释放后触发;
- 配合keydown使用:正常触发事件。
也可以使用keycode去指定具体的案件(不推荐)
Vue.config.keycodes.自定义按键名 = 键码,可以去定制按键别名。
案例:@keyup.enter="showMe" (键盘的按键输入enter键,在按键升起的时候触发)。或者@keydown.enter="showMe" (键盘的按键输入enter键,在按键按下的时候触发)。
7. 内置指令
v-bind:单向绑定解析表达式,可以简写为:xxx
v-model:双向数据绑定
v-for:遍历数据、对象、字符串
v-on:绑定事件监听,简写为@xxx
v-if:条件渲染(动态控制节点是否存在)
v-show:条件渲染(动态控制节点是否展示)
v-else:条件渲染(动态控制节点是否存在)
v-text:向所在节点中添加渲染文本内容;与插值语法区别:v-text会替换掉节点中的内容,{{xxx}}则不会。
-
v-html
- 作用:向指定节点中渲染包含html结构的内容;
- 与插值语法的区别:v-html会替换掉节点中所有的内容,{{xxx}}不会
- 严重注意:v-html有安全性问题。在网站上动态渲染任意html是非常危险的,容易导致xss攻击
- 一定要在可信的内容上使用v-html,永远不要用在用户输入上。
-
v-once
- v-once所在节点在初次动态渲染后,就视为静态内容了。
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
-
v-pre
- 跳过其所在节点的编译过程
- 可用它跳过没有使用指令语法,没有使用插值语法的节点,会加快编译
v-cloak: v-cloak(没有值)
- 本质是一个特殊属性,vue实例创建完毕并接管容器以后会删除v-cloak的属性
- 使用css配合v-cloak可以解决网速慢时页面展示出插值语法的问题。
8. 自定义指令
- 定义语法
- 局部指令:
new Vue({
directives:{指令名称:配置对象}
})
或者
new Vue({
directives:{指令名:回调函数}
})
- 全局指令
Vue.directive(指令名,配置对象)或者Vue.directive(指令名,回调函数)
-
配置对象中的常用的三个回调
- .bind:指令与元素成功绑定是调用
- .inserted:指令所在元素被插入页面时调用
- .updated:指令所在模板结构被重新解析时调用
-
备注:
- 指令定义时不加v-,但是使用时需要加v-
- 指令名如果是多个单词,要使用连字符链接,不要用驼峰命名
9. 计算属性
- 定义:要用的属性不存在,需要通过已有属性计算而来。
- 原理:底层借助了Object.defineProperty()方法提供的getter和setter
- get函数什么时候执行?
- 初次读取的时候会运行一次;
- 当依赖的数据发生变化时会被再次调用。
- 优势:与methods相比,内部有缓存机制(复用),效率更高,测试方便
- 备注:计算属性会最终出现在 vm 上,直接读取使用即可;如果计算属性要被修改,那必须写set函数去相应修改,且set中要引起计算时依赖的数据发生改变。
10. 监视属性 watch
- 当被监视的属性发生变化时,回调函数自动调用,进行相关操作。
- 监视属性必须存在,才能进行监视
- 监视的两种写法:
- new Vue 时传入 watch 配置
- 通过vm.$watch("",{})监视
11.条件渲染
- v-in
- 写法:v-if、v-else-if,v-else
- 适用于切换频率较低的场景
- 特点:不展现 dom节点。dom直接被移除
- 注意:v-if可以和v-else-if,v-else 一起使用,但是要求结构不能被打断。
- v-show
- 写法:v-show
- 适用于切换场景较高的场景
- 不展示dom,但是dom被隐藏
- 备注:使用v-if时,元素可能无法获取到,而是用v-show的时候时可以获取到的
12 v-for 指令
- 用于展示列表信息
- 语法:v-for="(item, index) in xxx" :key="yyy"
- 可以遍历:数组,对象,字符串(用的少),指定次数(用的少)
12. 面试题 react,vue中key的作用(key的内部原理)
- 虚拟 dom 中 key 的作用:key是虚拟dom对象的标识,当数据发生变化时,vue会根据新数据生成新的虚拟dom,随后vue进行新虚拟dom与旧虚拟dom的差异比较,比较规则如下:
- 旧虚拟dom用到了与新dom相同的key:
(1)若虚拟dom中内容没有变化,直接复用之前的真是dom;
(2)虚拟dom内容变化了,则生成新的真实dom,随后替换掉页面中旧的真实dom - 旧虚拟dom没有找到与新虚拟dom相同的key:创建新的真实dom,然后渲染到页面上;
- 旧虚拟dom用到了与新dom相同的key:
- 用 index 作为key可能会引发的问题
- 若对数据进行逆序添加,逆序删除等破坏顺序的操作,会产生没有必要的真实dom更新 ---> 页面没有问题,但是效率低。
- 如果结构中还包含输入类的dom,会产生错误的dom更新 --> 界面会有问题
- 开发中如何选择key
- 最好使用每条数据唯一的标识作为key,比如id,手机号,身份证号等。
- 如果不存在对数据的逆序添加,逆序删除等破坏顺序的操作,仅用于渲染列表的展示,使用index作为key是没有问题的。