Vue2/Vue3篇章4

603 阅读15分钟

23 前端工程化

23.1 模块化相关规范

传统开发模式的主要问题
命名冲突
文件依赖
通过模块化解决上述问题
模块化就是把单独的一个功能封装到一个模块(文件)中,模块之间相互隔离,但是可以通过特定的接口公开内部成
员,也可以依赖别的模块
模块化开发的好处:方便代码的重用,从而提升开发效率,并且方便后期的维护

浏览器端模块化规范
1. AMD
Require.js (http://www.requirejs.cn/)
2. CMD
3. Sea.js (https://seajs.github.io/seajs/docs/)
服务器端模块化规范
4. CommonJS
模块分为 单文件模块 与 包
模块成员导出:module.exports 和 exports
模块成员导入:require('模块标识符')

23.2 ES6模块化

在 ES6 模块化规范诞生之前,Javascript 社区已经尝试并提出了 AMD、CMD、CommonJS 等模块化规范。
但是,这些社区提出的模块化标准,还是存在一定的差异性与局限性、并不是浏览器与服务器通用的模块化标准,例如:
AMD 和 CMD 适用于浏览器端的 Javascript 模块化
CommonJS 适用于服务器端的 Javascript 模块化
因此,ES6 语法规范中,在语言层面上定义了 ES6 模块化规范,是浏览器端与服务器端通用的模块化开发规范。
ES6模块化规范中定义:
每个 js 文件都是一个独立的模块
导入模块成员使用 import 关键字
暴露模块成员使用 export 关键字

23.2.1 Node.js 中通过 babel 体验 ES6 模块化

1 npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node
2 npm install --save @babel/polyfill
3 项目跟目录创建文件 babel.config.js
4 babel.config.js 文件内容如右侧代码
const presets = [
 ["@babel/env", {
 targets: {
 edge: "17",
 firefox: "60",
 chrome: "67",
 safari: "11.1"
 }
 }]
 ];
 module.exports = { presets };
5 通过 npx babel-node index.js 执行代码

23.2.2 ES6 模块化的基本语法

默认导出语法 export default 默认导出的成员
默认导入语法 import 接收名称 from '模块标识符'
// 导入模块成员
import m1 from './m1.js'
console.log(m1)
// 打印输出的结果为:
// { a: 10, c: 20, show: [Function: show] }
// 当前文件模块为 m1.js
// 定义私有成员 a 和 c
let a = 10
let c = 20
// 外界访问不到变量 d ,因为它没有被暴露出去
let d = 30
function show() {}
// 将本模块中的私有成员暴露出去,供其它模块使用
export default {
a,
c,
show
}
注意:每个模块中,只允许使用唯一的一次 export default,否则会报错!


按需导出 与 按需导入
按需导出语法 export let s1 = 10
按需导入语法 import { s1 } from '模块标识符'
// 导入模块成员
import { s1, s2 as ss2, say } from './m1.js'
console.log(s1) // 打印输出 aaa
console.log(ss2) // 打印输出 ccc
console.log(say) // 打印输出 [Function: say]

// 当前文件模块为 m1.js
// 向外按需导出变量 s1
export let s1 = 'aaa'
// 向外按需导出变量 s2
export let s2 = 'ccc'
// 向外按需导出方法 say
export function say = function() {}
注意:每个模块中,可以使用多次按需导出

直接导入并执行模块代码
有时候,我们只想单纯执行某个模块中的代码,并不需要得到模块中向外暴露的成员,此时,可以直接导入并执行模块代码。
// 当前文件模块为 m2.js
// 在当前模块中执行一个 for 循环操作
for(let i = 0; i < 3; i++) {
console.log(i)
}

23.2.3 属性的增强写法

<script>
  // const obj = new Object()

  // const obj = {
  //   name: 'why',
  //   age: 18,
  //   run: function () {
  //     console.log('在奔跑');
  //   },
  //   eat: function () {
  //     console.log('在次东西');
  //   }
  // }

  // 1.属性的增强写法
  const name = 'why';
  const age = 18;
  const height = 1.88

  // ES5的写法
  // const obj = {
  //   name: name,
  //   age: age,
  //   height: height
  // }
  //ES6的写法
  // const obj = {
  //   name,
  //   age,
  //   height,
  // }
  //
  // console.log(obj);


  // 2.函数的增强写法
  // ES5的写法
  // const obj = {
  //   run: function () {
  //
  //   },
  //   eat: function () {
  //
  //   }
  // }
  //ES6的写法
  const obj = {
    run() {

    },
    eat() {

    }
  }
</script>

23.3 webpack

*

webpack 是一个流行的前端项目构建工具(打包工具),可以解决当前 web 开发中所面临的困境。
webpack 提供了友好的模块化支持,以及代码压缩混淆、处理 js 兼容问题、性能优化等强大的功能,从而让程序员把
工作的重心放到具体的功能实现上,提高了开发效率和项目的可维护性。
目前绝大多数企业中的前端项目,都是基于 webpack 进行打包构建的。

23.3.1 webpack 的基本使用

1. 创建列表隔行变色项目
- 新建项目空白目录,并运行 npm init –y 命令,初始化包管理配置文件 package.json
- 新建 src 源代码目录
- 新建 src -> index.html 首页
- 初始化首页基本的结构
- 运行 npm install jquery –S 命令,安装 jQuery
- 通过模块化的形式,实现列表隔行变色效果

2. 在项目中安装和配置 webpack
- 运行 npm install webpack webpack-cli –D 命令,安装 webpack 相关的包
- 在项目根目录中,创建名为 webpack.config.js 的 webpack 配置文件
- 在 webpack 的配置文件中,初始化如下基本配置:
module.exports = {
 mode: 'development' // mode 用来指定构建模式
}
④ 在 package.json 配置文件中的 scripts 节点下,新增 dev 脚本如下:
"scripts": {
"dev": "webpack" // script 节点下的脚本,可以通过 npm run 执行
}
⑤ 在终端中运行 npm run dev 命令,启动 webpack 进行项目打包。
webpack 的 4.x 版本中默认约定:
打包的入口文件为 src -> index.js
打包的输出文件为 dist -> main.js

如果要修改打包的入口与出口,可以在 webpack.config.js 中新增如下配置信息:
const path = require('path') // 导入 node.js 中专门操作路径的模块
module.exports = {
 entry: path.join(__dirname, './src/index.js'), // 打包入口文件的路径
 output: {
 path: path.join(__dirname, './dist'), // 输出文件的存放路径
 filename: 'bundle.js' // 输出文件的名称
 }
}

4. 配置 webpack 的自动打包功能
- 运行 npm install webpack-dev-server –D 命令,安装支持项目自动打包的工具
- 修改 package.json -> scripts 中的 dev 命令如下:
"scripts": {
 "dev": "webpack-dev-server" // script 节点下的脚本,可以通过 npm run 执行
}
- 将 src -> index.html 中,script 脚本的引用路径,修改为 "/buldle.js“
- 运行 npm run dev 命令,重新进行打包
- 在浏览器中访问 http://localhost:8080 地址,查看自动打包效果
注意:
webpack-dev-server 会启动一个实时打包的 http 服务器
webpack-dev-server 打包生成的输出文件,默认放到了项目根目录中,而且是虚拟的、看不见的
5. 配置 html-webpack-plugin 生成预览页面
- 运行 npm install html-webpack-plugin –D 命令,安装生成预览页面的插件
- 修改 webpack.config.js 文件头部区域,添加如下配置信息:
// 导入生成预览页面的插件,得到一个构造函数
const HtmlWebpackPlugin = require('html-webpack-plugin')
const htmlPlugin = new HtmlWebpackPlugin({ // 创建插件的实例对象
 template: './src/index.html', // 指定要用到的模板文件
 filename: 'index.html' // 指定生成的文件的名称,该文件存在于内存中,在目录中不显示
})

③ 修改 webpack.config.js 文件中向外暴露的配置对象,新增如下配置节点:
module.exports = {
 plugins: [ htmlPlugin ] // plugins 数组是 webpack 打包期间会用到的一些插件列表
}

6. 配置自动打包相关的参数
// package.json中的配置
 // --open 打包完成后自动打开浏览器页面
 // --host 配置 IP 地址
 // --port 配置端口
 "scripts": {
 "dev": "webpack-dev-server --open --host 127.0.0.1 --port 8888"
 },
 
webpack 中的加载器
1. 通过 loader 打包非 js 模块
在实际开发过程中,webpack 默认只能打包处理以 .js 后缀名结尾的模块,其他非 .js 后缀名结
尾的模块,webpack 默认处理不了,需要调用 loader 加载器才可以正常打包,否则会报错!
loader 加载器可以协助 webpack 打包处理特定的文件模块,比如:

less-loader 可以打包处理 .less 相关的文件

sass-loader 可以打包处理 .scss 相关的文件

url-loader 可以打包处理 css 中与 url 路径相关的文件

23.3.2 loader 的调用过程

*

23.3.2.1 loaderwebpack 中加载器的基本使用
1. 打包处理 css 文件
- 运行 npm i style-loader css-loader -D 命令,安装处理 css 文件的 loader
- 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
// 所有第三方文件模块的匹配规则
 module: {
 rules: [
 { test: /\.css$/, use: ['style-loader', 'css-loader'] }
 ]
 }
其中,test 表示匹配的文件类型, use 表示对应要调用的 loader
注意:
use 数组中指定的 loader 顺序是固定的
多个 loader 的调用顺序是:从后往前调用

2. 打包处理 less 文件
- 运行 npm i less-loader less -D 命令
- 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
// 所有第三方文件模块的匹配规则
 module: {
 rules: [
 { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }
 ]
 }

3. 打包处理 scss 文件
- 运行 npm i sass-loader node-sass -D 命令
- 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
// 所有第三方文件模块的匹配规则
 module: {
 rules: [
 { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }
 ]
 }
4. 配置 postCSS 自动添加 css 的兼容前缀
- 运行 npm i postcss-loader autoprefixer -D 命令
- 在项目根目录中创建 postcss 的配置文件 postcss.config.js,并初始化如下配置:
 const autoprefixer = require('autoprefixer') // 导入自动添加前缀的插件
 module.exports = {
 plugins: [ autoprefixer ] // 挂载插件
 }
- 在 webpack.config.js 的 module -> rules 数组中,修改 css 的 loader 规则如下:
 module: {
 rules: [
 { test:/\.css$/, use: ['style-loader', 'css-loader', 'postcss-loader'] }
 ]
 }
5. 打包样式表中的图片和字体文件
- 运行 npm i url-loader file-loader -D 命令
- 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
 module: {
 rules: [
 {
 test: /\.jpg|png|gif|bmp|ttf|eot|svg|woff|woff2$/,
 use: 'url-loader?limit=16940'
 }
 ]
 }
 其中 ? 之后的是 loader 的参数项。
limit 用来指定图片的大小,单位是字节(byte),只有小于 limit 大小的图片,才会被转为 base64 图片

6. 打包处理 js 文件中的高级语法
- 安装babel转换器相关的包:npm i babel-loader @babel/core @babel/runtime -D
- 安装babel语法插件相关的包:npm i @babel/preset-env @babel/plugin-transformruntime @babel/plugin-proposal-class-properties –D
- 在项目根目录中,创建 babel 配置文件 babel.config.js 并初始化基本配置如下:
module.exports = {
 presets: [ '@babel/preset-env' ],
 plugins: [ '@babel/plugin-transform-runtime', '@babel/plugin-proposalclass-properties’ ]
 }
- 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
  // exclude 为排除项,表示 babel-loader 不需要处理 node_modules 中的 js 文件
 { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }

23.3.3 Vue 单文件组件

单文件组件的组成结构
template 组件的模板区域
script 业务逻辑区域
style 样式区域
 <template>
 <!-- 这里用于定义Vue组件的模板内容 -->
 </template>
 <script>
 // 这里用于定义Vue组件的业务逻辑
 export default {
 data: () { return {} }, // 私有数据
 methods: {} // 处理函数
 // ... 其它业务逻辑
 }
 </script>
 <style scoped>
 /* 这里用于定义组件的样式 */
 </style>

webpack 中配置 vue 组件的加载器
- 运行 npm i vue-loader vue-template-compiler -D 命令
- 在 webpack.config.js 配置文件中,添加 vue-loader 的配置项如下:
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
module: {
rules: [
// ... 其它规则
{ test: /\.vue$/, loader: 'vue-loader' }
]
},
plugins: [
// ... 其它插件
new VueLoaderPlugin() // 请确保引入这个插件!
]
}

在 webpack 项目中使用 vue
- 运行 npm i vue –S 安装 vue
- 在 src -> index.js 入口文件中,通过 import Vue from 'vue' 来导入 vue 构造函数
- 创建 vue 的实例对象,并指定要控制的 el 区域
- 通过 render 函数渲染 App 根组件
// 1. 导入 Vue 构造函数
import Vue from 'vue'
// 2. 导入 App 根组件
import App from './components/App.vue'
const vm = new Vue({
// 3. 指定 vm 实例要控制的页面区域
el: '#app',
// 4. 通过 render 函数,把指定的组件渲染到 el 区域中
render: h => h(App)
})

webpack 打包发布
上线之前需要通过webpack将应用进行整体打包,可以通过 package.json 文件配置打包命令:
// 在package.json文件中配置 webpack 打包命令
// 该命令默认加载项目根目录中的 webpack.config.js 配置文件
"scripts": {
// 用于打包的命令
"build": "webpack -p",
// 用于开发调试的命令
"dev": "webpack-dev-server --open --host 127.0.0.1 --port 3000",
},

24 Vue CLI 脚手架

24.1 什么是CLI

命令行界面(英语:command-line interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为字符用户界面(CUI)

24.2 什么是Vue CLI

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统。使用Vue 脚手架之后我们开发的页面将是一个完整系统(项目)。

24.3 Vue CLI优势

- 通过 vue-cli 搭建交互式的项目脚手架。bootstrap css js jquery js     通过执行命令方式下载相关依赖
- 通过 @vue/cli + @vue/cli-service-global 快速开始零配置原型开发    vue页面 vuejs  vuerouter        axios(一条命令)
- 一个运行时依赖 (@vue/cli-service),该依赖:
  - 可升级;  一条命令
  - 基于 webpack 构建,并带有合理的默认配置;  webpack  项目打包方式     编译好的项目源码===>部署到服务器上直接使用
  - 可以通过项目内的配置文件进行配置;               默认配置文件,通过修改默认配置文件达到自己想要的项目环境            
  - 可以通过插件进行扩展。                                       vue v-charts  elementui 
- 一个丰富的官方插件集合,集成了前端生态中最好的工具。Nodejs(tomcat)  Vue VueRouter webpack yarn
- 一套完全图形化的创建和管理 Vue.js 项目的用户界面

24.4 Vue CLI安装

24.4.1 环境准备
24.4.1.1 npm使用国内镜像
命令
//设置淘宝源
npm config set registry https://registry.npm.taobao.org
//设置公司的源
npm config set registry http://127.0.0.1:4873
//查看源,可以看到设置过的所有的源
npm config get registry
通过使用cnpm安装
npm install -g cnpm --registry=https://registry.npm.taobao.org
使用cnpm
cnpm install xxx
1.下载nodejs
		http://nodejs.cn/download/
			windows系统:   .msi  安装包(exe)指定安装位置   .zip(压缩包)直接解压缩指定目录
		  mac os 系统:   .pkg  安装包格式自动配置环境变量  .tar.gz(压缩包)解压缩安装到指定名

2.配置nodejs环境变量
	windows系统:
	1.计算上右键属性---->  高级属性 ---->环境变量 添加如下配置:
				NODE_HOME=  nodejs安装目录
        PATH    = xxxx;%NODE_HOME%
    2.macos 系统
    		推荐使用.pkg安装直接配置node环境
 
3.验证nodejs环境是否成功
		node -v 
		
4.npm介绍
		node package mangager    nodejs包管理工具       前端主流技术  npm 进行统一管理
			maven 管理java后端依赖   远程仓库(中心仓库)      阿里云镜像
			npm   管理前端系统依赖    远程仓库(中心仓库)      配置淘宝镜像
		
5.配置淘宝镜像
	  npm config set registry https://registry.npm.taobao.org
	  npm config get registry

6.配置npm下载依赖位置
	 windows:
		npm config set cache "D:\nodereps\npm-cache"
		npm config set prefix "D:\nodereps\npm_global"
	 mac os:
	 	npm config set cache "/Users/chenyannan/dev/nodereps"
		npm config set prefix "/Users/chenyannan/dev/nodereps"

7.验证nodejs环境配置
	npm config ls
	
    ; userconfig /Users/chenyannan/.npmrc
    cache = "/Users/chenyannan/dev/nodereps"
    prefix = "/Users/chenyannan/dev/nodereps"
    registry = "https://registry.npm.taobao.org/"

24.4.2 安装脚手架
安装
1.x或2.x
npm install vue-cli -g
3.x以上
npm install -g @vue/cli  
# OR yarn global add @vue/cli
卸载
前提条件
自己电脑已经安装node.js和npm
卸载vue-cli(1.x 或2.x)
npm uninstall vue-cli -g 或yarn global remove vue-cli 
卸载cli3
npm uninstall -g @vue/cli 或 yarn global remove @vue/cli

升级
如需升级全局的 Vue CLI 包,请运行:
npm update -g @vue/cli
# 或者
yarn global upgrade --latest @vue/cli

解决npm下载速度慢的问题
使用淘宝定制的cnpm命令行工具替代默认安装npm
npm install -g cnpm --registry=https://registry.npm.taobao.org


创建vue-cli2项目
vue init webpack 项目名称

创建vue-cli3项目
图形界面的方式创建项目
vue ui
通过命令行创建(主流方式)
通过命令行创建(主流方式)

脚手架2启动方式
npm run dev
脚手架3启动方式
npm run serve

脚手架2打包
npm run build # 生成的是build文件
脚手架3打包
npm run build # 生成的是dist文件

脚手架2本机测试
serve build # 因为你最后直接给的是打包文件,交工之前直接测试一下,运行打包文件,查看项目是否完整
脚手架3本机测试
serve dist


24.4.3 第一个vue脚手架项目
1.创建vue脚手架第一个项目
	vue init webpack 项目名
2.创建第一个项目
	hello     ------------->项目名
    -build  ------------->用来使用webpack打包使用build依赖
    -config ------------->用来做整个项目配置目录
    -node_modules  ------>用来管理项目中使用依赖
    -src					 ------>用来书写vue的源代码[重点]
    	+assets      ------>用来存放静态资源 [重点]
      components   ------>用来书写Vue组件 [重点]
      router			 ------>用来配置项目中路由[重点]
      App.vue      ------>项目中根组件[重点]
      main.js      ------>项目中主入口[重点]
    -static        ------>其它静态
    -.babelrc      ------> 将es6语法转为es5运行
    -.editorconfig ------> 项目编辑配置
    -.gitignore    ------> git版本控制忽略文件
    -.postcssrc.js ------> 源码相关js
    -index.html    ------> 项目主页
    -package.json  ------> 类似与pom.xml 依赖管理  jquery 不建议手动修改
    -package-lock.json ----> 对package.json加锁
    -README.md         ----> 项目说明文件

3.如何运行在项目的根目录中执行
		npm run dev

4.如何访问项目
		http://localhost:8081    

5.Vue Cli中项目开发方式
	 注意: 一切皆组件   一个组件中   js代码  html代码  css样式
	 
	 	1. VueCli开发方式是在项目中开发一个一个组件对应一个业务功能模块,日后可以将多个组件组合到一起形成一个前端系统
	 	2. 日后在使用vue Cli进行开发时不再书写html,编写的是一个个组件(组件后缀.vue结尾的文件),日后打包时vue cli会将组件编译成运行的html文件	  
24.4.4 如何开发Vue脚手架
注意:在Vue cli 中一切皆组件
24.4.5 Vue 脚手架的自定义配置
1. 通过 package.json 配置项目
 // 必须是符合规范的json语法
 "vue": {
 "devServer": {
 "port": "8888",
 "open" : true
 }
 },
注意:不推荐使用这种配置方式。因为 package.json 主要用来管理包的配置信息;为了方便维护,推荐将 vue 脚
手架相关的配置,单独定义到 vue.config.js 配置文件中。

2. 通过单独的配置文件配置项目
- 在项目的跟目录创建文件 vue.config.js
- 在该文件中进行相关配置,从而覆盖默认配置
// vue.config.js
 module.exports = {
 devServer: {
 port: 8888
 }
 }

25 在脚手架中使用axios

25.1 安装axios

# 1.安装axios
	npm install axios --save-dev

# 2.配置main.js中引入axios
	import axios from 'axios';

	Vue.prototype.$http=axios;

# 3.使用axios
	在需要发送异步请求的位置:this.$http.get("url").then((res)=>{}) this.$http.post("url").then((res)=>{})

26 Element-UI 的基本使用

Element-UI:一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。
官网地址为: http://element-cn.eleme.io/#/zh-CN
1. 基于命令行方式手动安装
- 安装依赖包 npm i element-ui –S
- 导入 Element-UI 相关资源
 // 导入组件库
 import ElementUI from 'element-ui';
 // 导入组件相关样式
 import 'element-ui/lib/theme-chalk/index.css';
 // 配置 Vue 插件
 Vue.use(ElementUI);
2. 基于图形化界面自动安装
- 运行 vue ui 命令,打开图形化界面
- 通过 Vue 项目管理器,进入具体的项目配置面板
- 点击 插件 -> 添加插件,进入插件查询面板
- 搜索 vue-cli-plugin-element 并安装
- 配置插件,实现按需导入,从而减少打包后项目的体积

27 Vuex

27.1 组件之间共享数据的方式

父向子传值:v-bind 属性绑定
子向父传值:v-on 事件绑定
兄弟组件之间共享数据: EventBus
- $on 接收数据的那个组件
- $emit 发送数据的那个组件
但是这种范围比较小,维护起来不方便,所以vuex就诞生了.
Vuex 是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享。

在这里插入图片描述

27.2 Vuex实现组件之间共享数据

使用 Vuex 统一管理状态的好处
- 能够在 vuex 中集中管理共享的数据,易于开发和后期维护
- 能够高效地实现组件之间的数据共享,提高开发效率
- 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步

什么样的数据适合存储到 Vuex 中
一般情况下,只有组件之间共享的数据,才有必要存储到 vuex 中;对于组件中的私有数据,依旧存储在组件自身的 data 中即可。

27.3 Vuex 的基本使用

1. 安装 vuex 依赖包
 npm install vuex --save
2. 导入 vuex 包
import Vuex from 'vuex'
Vue.use(Vuex)
3. 创建 store 对象
const store = new Vuex.Store({
 // state 中存放的就是全局共享的数据
 state: { count: 0 }
})
4. 将 store 对象挂载到 vue 实例中
new Vue({
 el: '#app',
 render: h => h(app),
 router,
 // 将创建的共享数据对象,挂载到 Vue 实例中
 // 所有的组件,就可以直接从 store 中获取全局的数据了
 store
})

27.3.1 计数器

App.vue

<template>
  <div>
    <my-addition></my-addition>

    <p>---------------------------------</p>

    <my-subtraction></my-subtraction>
  </div>
</template>

<script>
import Addition from './components/Addition.vue'
import Subtraction from './components/Subtraction.vue'

export default {
  data() {
    return {}
  },
  components: {
    'my-addition': Addition,
    'my-subtraction': Subtraction
  }
}
</script>

Addition.vue
<template>
  <div>
    <!-- <h3>当前最新的count值为:{{$store.state.count}}</h3> -->
    <h3>{{$store.getters.showNum}}</h3>
    <button @click="btnHandler1">+1</button>
    <button @click="btnHandler2">+N</button>
    <button @click="btnHandler3">+1 Async</button>
    <button @click="btnHandler4">+N Async</button>
  </div>
</template>

<script>
export default {
  data() {
    return {}
  },
  methods: {
    btnHandler1() {
      this.$store.commit('add')
    },
    btnHandler2() {
      // commit 的作用,就是调用 某个 mutation 函数
      this.$store.commit('addN', 3)
    },
    // 异步地让 count 自增 +1
    btnHandler3() {
      // 这里的 dispatch 函数,专门用来触发 action
      this.$store.dispatch('addAsync')
    },
    btnHandler4() {
      this.$store.dispatch('addNAsync', 5)
    }
  }
}
</script>

Subtraction.vue
<template>
  <div>
    <!-- <h3>当前最新的count值为:{{count}}</h3> -->
    <h3>{{showNum}}</h3>
    <button @click="btnHandler1">-1</button>
    <button @click="subN(3)">-N</button>
    <button @click="subAsync">-1 Async</button>
    <button @click="subNAsync(5)">-N Async</button>
  </div>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

export default {
  data() {
    return {}
  },
  computed: {
    ...mapState(['count']),
    ...mapGetters(['showNum'])
  },
  methods: {
    ...mapMutations(['sub', 'subN']),
    ...mapActions(['subAsync', 'subNAsync']),
    btnHandler1() {
      this.sub()
    }
  }
}
</script>

store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  // 只有 mutations 中定义的函数,才有权利修改 state 中的数据
  mutations: {
    add(state) {
      // 不要在 mutations 函数中,执行异步操作
      // setTimeout(() => {
      //   state.count++
      // }, 1000)
      state.count++
    },
    addN(state, step) {
      state.count += step
    },
    sub(state) {
      state.count--
    },
    subN(state, step) {
      state.count -= step
    }
  },
  actions: {
    addAsync(context) {
      setTimeout(() => {
        // 在 actions 中,不能直接修改 state 中的数据;
        // 必须通过 context.commit() 触发某个 mutation 才行
        context.commit('add')
      }, 1000)
    },
    addNAsync(context, step) {
      setTimeout(() => {
        context.commit('addN', step)
      }, 1000)
    },
    subAsync(context) {
      setTimeout(() => {
        context.commit('sub')
      }, 1000)
    },
    subNAsync(context, step) {
      setTimeout(() => {
        context.commit('subN', step)
      }, 1000)
    }
  },
  getters: {
    showNum(state) {
      return '当前最新的数量是【' + state.count + '】'
    }
  }
})

main.js

import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')


27.4 Vuex的核心概念

State
Mutation
Action
Getter
State
State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 的 State 中进行存储。

// 创建store数据源,提供唯一公共数据
 const store = new Vuex.Store({
 state: { count: 0 }
 })
组件访问 State 中数据的第一种方式:
this.$store.state.全局数据名称

组件访问 State 中数据的第二种方式:
// 1. 从 vuex 中按需导入 mapState 函数
import { mapState } from 'vuex'
通过刚才导入的 mapState 函数,将当前组件需要的全局数据,映射为当前组件的 computed 计算属性:
// 2. 将全局数据,映射为当前组件的计算属性
computed: {
 ...mapState(['count'])
}



Mutation
Mutation 用于变更 Store中 的数据。
- 只能通过 mutation 变更 Store 数据,不可以直接操作 Store 中的数据。
- 通过这种方式虽然操作起来稍微繁琐一些,但是可以集中监控所有数据的变化。

 // 定义 Mutation
 const store = new Vuex.Store({
 state: {
 count: 0
 },
 mutations: {
 add(state) {
 // 变更状态
 state.count++
 }
 }
 })
 // 触发mutation
 methods: {
 handle1() {
 // 触发 mutations 的第一种方式
 this.$store.commit('add')
 }
 } 
可以在触发 mutations 时传递参数
// 定义Mutation
 const store = new Vuex.Store({
 state: {
 count: 0
 },
 mutations: {
 addN(state, step) {
 // 变更状态
 state.count += step
 }
 }
 })

// 触发mutation
 methods: {
 handle2() {
 // 在调用 commit 函数,
 // 触发 mutations 时携带参数
 this.$store.commit('addN', 3)
 }
 } 

this.$store.commit() 是触发 mutations 的第一种方式,触发 mutations 的第二种方式:
// 1. 从 vuex 中按需导入 mapMutations 函数
import { mapMutations } from 'vuex'
通过刚才导入的 mapMutations 函数,将需要的 mutations 函数,映射为当前组件的 methods 方法:
// 2. 将指定的 mutations 函数,映射为当前组件的 methods 函数
methods: {
 ...mapMutations(['add', 'addN'])
}

Action
Action 用于处理异步任务。
如果通过异步操作变更数据,必须通过 Action,而不能使用 Mutation,但是在 Action 中还是要通过触发
Mutation 的方式间接变更数据。

// 定义 Action
 const store = new Vuex.Store({
 // ...省略其他代码
 mutations: {
 add(state) {
 state.count++
 }
 },
 actions: {
 addAsync(context) {
 setTimeout(() => {
 context.commit('add')
 }, 1000)
 }
 }
// 触发 Action
 methods: {
 handle() {
 // 触发 actions 的第一种方式
 this.$store.dispatch('addAsync')
 }
 } 
 }) 
触发 actions 异步任务时携带参数:
 // 定义 Action
 const store = new Vuex.Store({
 // ...省略其他代码
 mutations: {
 addN(state, step) {
 state.count += step
 }
 },
 actions: {
 addNAsync(context, step) {
 setTimeout(() => {
 context.commit('addN', step)
 }, 1000)
 }
 }
 })
// 触发 Action
 methods: {
 handle() {
 // 在调用 dispatch 函数,
 // 触发 actions 时携带参数
 this.$store.dispatch('addNAsync', 5)
 }
 } 
 
this.$store.dispatch() 是触发 actions 的第一种方式,触发 actions 的第二种方式:
// 1. 从 vuex 中按需导入 mapActions 函数
import { mapActions } from 'vuex'

通过刚才导入的 mapActions 函数,将需要的 actions 函数,映射为当前组件的 methods 方法:
// 2. 将指定的 actions 函数,映射为当前组件的 methods 函数
methods: {
 ...mapActions(['addASync', 'addNASync'])
}

Getter
Getter 用于对 Store 中的数据进行加工处理形成新的数据。
- Getter 可以对 Store 中已有的数据加工处理之后形成新的数据,类似 Vue 的计算属性。
- Store 中数据发生变化,Getter 的数据也会跟着变化。

// 定义 Getter
 const store = new Vuex.Store({
 state: {
 count: 0
 },
 getters: {
 showNum: state => {
 return '当前最新的数量是【'+ state.count +'】'
 }
 }
 })
使用 getters 的第一种方式:
this.$store.getters.名称
使用 getters 的第二种方式:
import { mapGetters } from 'vuex'
computed: {
 ...mapGetters(['showNum'])
}

27.5 基于 Vuex 的案例

Todos

在这里插入图片描述

1. 初始化项目
实现步骤
- 通过 vue ui 命令打开可视化面板,创建新项目 vuex-demo2
- 安装 vuex 依赖包 npm install vuex axios ant-design-vue –S
- 实现 Todos 基本布局(基于已有样式模板)

2. 完成具体功能
- 动态加载任务列表数据
- 实现文本框与store数据的双向同步
- 完成添加任务事项的操作
- 完成删除任务事项的操作
- 动态绑定复选框的选中状态
- 修改任务事项的完成状态
- 统计未完成的任务的条数
- 清除已完成的任务事项
- 实现任务列表数据的动态切换
App.vue
<template>
  <div id="app">
    <a-input placeholder="请输入任务" class="my_ipt" :value="inputValue" @change="handleInputChange" />
    <a-button type="primary" @click="addItemToList">添加事项</a-button>

    <a-list bordered :dataSource="infolist" class="dt_list">
      <a-list-item slot="renderItem" slot-scope="item">
        <!-- 复选框 -->
        <a-checkbox :checked="item.done" @change="(e) => {cbStatusChanged(e, item.id)}">{{item.info}}</a-checkbox>
        <!-- 删除链接 -->
        <a slot="actions" @click="removeItemById(item.id)">删除</a>
      </a-list-item>

      <!-- footer区域 -->
      <div slot="footer" class="footer">
        <!-- 未完成的任务个数 -->
        <span>{{unDoneLength}}条剩余</span>
        <!-- 操作按钮 -->
        <a-button-group>
          <a-button :type="viewKey === 'all' ? 'primary' : 'default'" @click="changeList('all')">全部</a-button>
          <a-button :type="viewKey === 'undone' ? 'primary' : 'default'" @click="changeList('undone')">未完成</a-button>
          <a-button :type="viewKey === 'done' ? 'primary' : 'default'" @click="changeList('done')">已完成</a-button>
        </a-button-group>
        <!-- 把已经完成的任务清空 -->
        <a @click="clean">清除已完成</a>
      </div>
    </a-list>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  name: 'app',
  data() {
    return {}
  },
  created() {
    this.$store.dispatch('getList')
  },
  computed: {
    ...mapState(['inputValue', 'viewKey']),
    ...mapGetters(['unDoneLength', 'infolist'])
  },
  methods: {
    // 监听文本框内容变化
    handleInputChange(e) {
      this.$store.commit('setInputValue', e.target.value)
    },
    // 向列表中新增 item 项
    addItemToList() {
      if (this.inputValue.trim().length <= 0) {
        return this.$message.warning('文本框内容不能为空!')
      }

      this.$store.commit('addItem')
    },
    // 很据Id删除对应的任务事项
    removeItemById(id) {
      // console.log(id)
      this.$store.commit('removeItem', id)
    },
    // 监听复选框选中状态变化的事件
    cbStatusChanged(e, id) {
      // 通过 e.target.checked 可以接受到最新的选中状态
      // console.log(e.target.checked)
      // console.log(id)
      const param = {
        id: id,
        status: e.target.checked
      }

      this.$store.commit('changeStatus', param)
    },
    // 清除已完成的任务
    clean() {
      this.$store.commit('cleanDone')
    },
    // 修改页面上展示的列表数据
    changeList(key) {
      // console.log(key)
      this.$store.commit('changeViewKey', key)
    }
  }
}
</script>

<style scoped>
#app {
  padding: 10px;
}

.my_ipt {
  width: 500px;
  margin-right: 10px;
}

.dt_list {
  width: 500px;
  margin-top: 10px;
}

.footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

store.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    // 所有的任务列表
    list: [],
    // 文本框的内容
    inputValue: 'aaa',
    // 下一个Id
    nextId: 5,
    viewKey: 'all'
  },
  mutations: {
    initList(state, list) {
      state.list = list
    },
    // 为 store 中的 inputValue 赋值
    setInputValue(state, val) {
      state.inputValue = val
    },
    // 添加列表项
    addItem(state) {
      const obj = {
        id: state.nextId,
        info: state.inputValue.trim(),
        done: false
      }
      state.list.push(obj)
      state.nextId++
      state.inputValue = ''
    },
    // 根据Id删除对应的任务事项
    removeItem(state, id) {
      // 根据Id查找对应项的索引
      const i = state.list.findIndex(x => x.id === id)
      // 根据索引,删除对应的元素
      if (i !== -1) {
        state.list.splice(i, 1)
      }
    },
    // 修改列表项的选中状态
    changeStatus(state, param) {
      const i = state.list.findIndex(x => x.id === param.id)
      if (i !== -1) {
        state.list[i].done = param.status
      }
    },
    // 清除已完成的任务
    cleanDone(state) {
      state.list = state.list.filter(x => x.done === false)
    },
    // 修改视图的关键字
    changeViewKey(state, key) {
      state.viewKey = key
    }
  },
  actions: {
    getList(context) {
      axios.get('/list.json').then(({ data }) => {
        // console.log(data)
        context.commit('initList', data)
      })
    }
  },
  getters: {
    // 统计未完成的任务的条数
    unDoneLength(state) {
      return state.list.filter(x => x.done === false).length
    },
    infolist(state) {
      if (state.viewKey === 'all') {
        return state.list
      }
      if (state.viewKey === 'undone') {
        return state.list.filter(x => !x.done)
      }
      if (state.viewKey === 'done') {
        return state.list.filter(x => x.done)
      }
      return state.list
    }
  }
})

main.js
import Vue from 'vue'
import App from './App.vue'

// 1. 导入 ant-design-vue 组件库
import Antd from 'ant-design-vue'
// 2. 导入组件库的样式表
import 'ant-design-vue/dist/antd.css'
import store from './store.js'

Vue.config.productionTip = false
// 3. 安装组件库
Vue.use(Antd)

new Vue({
  render: h => h(App),
  store
}).$mount('#app')

Vue3

28 Vue2.0和vue3.0响应式原理对比

Vue2.0中使用ES5中的Object.defineProperty方法实现响应式数据
缺点
无法监测到对象属性的动态添加和删除
无法监测到数组的下标和length属性的变更 
解决方案
Vue2.0提供Vue.set方法用于动态给对象添加属性
Vue2.0提供Vue.delete方法用于动态删除对象的属性
重写vue中数组的方法,用于监测数组的变更
01-Object.defineproperty
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      // vue会做一个数据劫持,,,监视数据的变化,一旦数据变化了,更新DOM
      const data = {
        name: 'zs',
        age: 18
      }

      for (let k in data) {
        let temp = data[k]
        Object.defineProperty(data, k, {
          get() {
            console.log(`我劫持了${k}的获取`)
            return temp
          },
          set(value) {
            console.log(`我劫持了${k}的设置,值${value}`)
            temp = value
          }
        })
      }

      // Object.defineProperty有缺点
      // 1. 无法监视到新增的属性的变更和删除
      // 2. 无法劫持到数组的下标和长度
    </script>
  </body>
</html>

02-vue2中响应式数据
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <h1>vue的例子</h1>
      <p>{{car.brand}} --- {{car.color}} ---{{car.price}}</p>
      <p>{{arr}}</p>
    </div>
    <script src="https://unpkg.com/vue@2.6.12/dist/vue.js"></script>
    <script>
      // vue中需要使用$set方法动态的增加一个响应式属性
      const vm = new Vue({
        el: '#app',
        data: {
          car: {
            brand: '奔驰',
            color: 'blue'
          },
          arr: ['张三', '李四']
        }
      })
    </script>
  </body>
</html>

29 Vue3.0响应式原理

Vue3.0中使用ES6中的proxy语法实现响应式数据
优点
可以检测到代理对象属性的动态添加和删除
可以监测到数组的下标和length属性的变更
缺点
ES6的proxy语法对于低版本浏览器不支持,IE11
Vue3.0会针对于IE11出一个特殊的版本用于支持ie11
03-es6的proxy语法.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div></div>
    <script>
      const data = {
        name: 'zs',
        age: 18
      }
      // 能够监测到对象动态新增的属性以及删除的属性
      // proxy
      const proxyData = new Proxy(data, {
        get(target, name) {
          console.log(`检测到${name}的获取`)
          return target[name]
        },
        set(target, name, value) {
          console.log(`检测到${name}的设置,值为${value}`)
          target[name] = value
        },
        deleteProperty(target, key) {
          console.log(`监测到删除${key}`)
          return delete target[key]
        }
      })
    </script>
  </body>
</html>

04-vue3中响应式原理
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <h1>vue的例子</h1>
      <p>{{car.brand}} --- {{car.color}} ---{{car.price}}</p>
      <p>{{arr}}</p>
    </div>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
      const App = {
        data() {
          return {
            car: {
              brand: '宝马',
              color: 'green'
            },
            arr: ['张三', '李四']
          }
        },
        methods: {
          fn() {
            this.brand = '奔驰'
            this.brand = '奔驰'
            // 会在DOM更新后才会执行回调函数
            nextTick(function () {
              // vue为了性能考虑,不会改变完数据就立即更新DOM
              console.log(document.querySelector('p').innerHTML)
            })
          }
        }
      }
      const vm = Vue.createApp(App).mount('#app')
    </script>
  </body>
</html>

30 创建vue3.0项目


安装vue-cli到最新版本 (必须高于4.5.0)
npm init vite-app   <project-name>
cd  <project-name>
npm install 
npm run dev

31 Composition API的使用

Options API 选项api
Options API的优点是容易学习和使用,代码有明确的书写位置
Options API的缺点就是相似逻辑不容易复用,在大项目中尤为明显。
Options API可以通过mixins提取相同的逻辑,但是容易发生命名冲突且来源不清晰
Composition API 组合API
Composition API是根据逻辑功能来组织代码的,一个功能所有的api放到一起
即便项目很大,功能很多,都能够快速的定位到该功能所有的API
Composition API提高了代码可读性和可维护性
Vue3.0中推荐使用composition API,也保留了options API。

31.1 setup

Setup函数是一个新的组件选项,作为组件中composition API的起点
从生命周期钩子的角度来看,setup会在beforeCreate钩子函数之前执行
Setup中不能使用this,this指向undefined
<template>
  <div class="app"></div>
</template>

<script>
export default {
  setup() {
    // composition api的入口
    // 不能访问this
    console.log('setup执行了')
    console.log(this)
  },
  beforeCreate() {
    console.log('beforeCreate')
  }
}
</script>

<style></style>

31.2 reactive

Reactive函数接受一个普通对象,返回该对象的响应式代理。

在这里插入图片描述

<template>
  <div class="app">
    <div>{{ car.brand }}----{{ car.price }}</div>
    <button @click="car.brand = '奔驰'">修改</button>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  setup() {
    // 1. setup需要返回值, setup中返回的值才能在模板中使用
    // 2. reactive中传入一个普通对象,返回一个代理对象
    // 3. 普通对象没有响应式,需要reactive
    const car = reactive({
      brand: '宝马',
      price: 100
    })

    return {
      car
    }
  }
}
</script>

<style></style>

31.3 ref

ref函数接受一个简单类型的值,返回一个可改变的ref对象。返回的对象有唯一的属性 value
在setup函数中,通过ref对象的value属性可以访问到值
在模板中,ref属性会自动解套,不需要额外的.value
如果ref接受的是一个对象,会自动调用reactive
<template>
  <div class="app">
    <div>我的金钱:{{ money }}</div>
    <button @click="money++">修改</button>
  </div>
</template>

<script>
import { reactive, ref } from 'vue'
export default {
  setup() {
    // 1. ref函数接受一个简单类型, 返回一个响应式的对象
    // 2. 这个响应式对象只有一个属性 value
    // 3. 在模板中使用ref,会自动解套,,,,会自动调用value
    let money = ref(100)

    money.value++
    return {
      money
    }
  }
}
</script>

<style></style>

31.4 toRefs

把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
Reactive的响应式功能是赋予给对象的,但是如果给对象解构或者展开的时候,会让数据丢失响应式的能力。
使用toRefs可以保证该对象展开的每一个 属性都是响应式的
<template>
  <div class="app">
    <div>我的金钱:{{ money }}</div>
    <div>{{ car.brand }} --- {{ car.price }}</div>
    <div>{{ name }}</div>
    <button @click="money++">修改</button>
    <button @click="name = 'ls'">修改</button>
  </div>
</template>

<script>
import { reactive, ref, toRefs } from 'vue'
export default {
  setup() {
    // 1. toRefs
    // const money = ref(100)
    // const car = reactive({
    //   brand: '宝马',
    //   price: 1000000
    // })
    // const name = ref('zs')

    const state = reactive({
      money: 100,
      car: {
        brand: '宝马',
        price: 1000000
      },
      name: 'zs'
    })

    return {
      // money,
      // car,
      // name
      ...toRefs(state)
    }
  }
}
</script>

<style></style>

32 readOnly

传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。
一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。
可以防止对象被修改
<template>
  <div class="app">
    <div>我的钱: {{ money }}</div>
    <div>{{ car.brand }} ---{{ car.price }}</div>
    <button @click="money++">按钮</button>
    <button @click="car.price = 200">按钮</button>
  </div>
</template>

<script>
import { reactive, ref, toRefs, readonly } from 'vue'
export default {
  setup() {
    const money = ref(100)

    const car = readonly({
      brand: 'zs',
      price: 18
    })

    return {
      money: readonly(money),
      car
    }
  }
}
</script>

<style></style>

33 computed

Computed函数用于创建一个计算属性
如果传入的是一个getter函数,会返回一个不允许修改的计算属性
如果传入的是一个带有getter和setter函数的对象,会返回一个允许修改的计算属性
<template>
  <div class="app">
    <div>我今年的年龄:<input type="text" v-model="age" /></div>
    <div>我明年的年龄:<input type="text" v-model="nextAge" /></div>
    <div>我后年的年龄: <input type="text" v-model="nextAge2" /></div>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
export default {
  setup() {
    // computed计算属性的使用
    const age = ref(18)

    // computed是一个函数
    // 1. 传入一个函数 getter 返回一个不允许修改的计算属性。
    const nextAge = computed(() => {
      return parseInt(age.value) + 1
    })

    // 2. 传入一个对象,包括get和set,,可以创建一个可以修改的计算属性
    const nextAge2 = computed({
      get() {
        return parseInt(age.value) + 2
      },
      set(value) {
        age.value = value - 2
      }
    })

    return {
      age,
      nextAge,
      nextAge2
    }
  }
}
</script>

<style></style>

34 watch

Watch函数接受3个参数。
参数1:数据源,可以是ref或者getter函数
参数2:回调函数
参数3:额外选项,immediate和deep
Watch可以监听一个ref或者一个带有返回值的getter函数
Watch可以监听单个数据源,也可以监听多个数据源
watch函数会有返回值,用于停止监听。
<template>
  <div class="app">
    <div>{{ money }}</div>
    <div>{{ car.brand }}</div>
    <button @click="money++">按钮</button>
    <button @click="car.brand = '奔驰'">按钮2</button>
  </div>
</template>

<script>
import { ref, toRefs, watch, reactive } from 'vue'
export default {
  setup() {
    const state = reactive({
      money: 100,
      car: {
        brand: '宝马'
      }
    })

    // 接受3个参数
    // 参数1:监视的数据源 可以是一个ref 或者是一个函数
    // 参数2:回调函数 (value, oldValue) =>{}
    // 参数3:额外的配置 是一个对象 {  deep: true, immediate: true }
    // watch(
    //   () => state.money,
    //   (value, oldValue) => {
    //     console.log('money变化了', value, oldValue)
    //   }
    // )

    // watch(money, (value, oldValue) => {
    //   console.log('money变化了', value, oldValue)
    // })

    // watch(
    //   () => state.car,
    //   (value) => {
    //     console.log('车变了', value)
    //   },
    //   {
    //     deep: true,
    //     immediate: false
    //   }
    // )
    // watch([() => state.money, () => state.car], ([money, car]) => {
    //   console.log('数据变化了', money, car)
    // })

    watch(
      state,
      (value) => {
        console.log('数据变化了', value)
      },
      {
        deep: true
      }
    )

    // watch用于实现监听
    return {
      ...toRefs(state)
    }
  }
}
</script>

<style></style>

35 生命周期钩子函数

Vue3提供的生命周期钩子注册函数只能在 setup() 期间同步使用
Vue3生命周期钩子函数与vue2对比

在这里插入图片描述

36 依赖注入

Vue3中提供了provide和inject提供依赖注入,用于实现组件之间的通讯。类似于vue2中的provide和inject
Vue3提供的provide和inject可以用于跨多级组件进行通讯
<template>
  <div class="app">
    <h1>钩子函数----{{ money }}</h1>
    <button @click="money++">按钮</button>
    <hr />
    <Demo :money="money"></Demo>
  </div>
</template>

<script>
import Demo from './Demo.vue'
import { ref, provide } from 'vue'
export default {
  components: {
    Demo
  },
  setup() {
    // provider和inject
    const money = ref(100)

    // js中作用域问题
    const changeMoney = (n) => {
      console.log(money)
      money.value = n
    }

    // 组件提供了money属性
    provide('money', money)
    provide('changeMoney', changeMoney)
    return {
      money
    }
  }
}
</script>

<style></style>

37 模板refs

为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup() 中声明一个 ref 并返回它
<template>
  <div class="app">
    <h1 ref="hRef">钩子函数----123</h1>

    <Demo ref="dRef"></Demo>
  </div>
</template>

<script>
import Demo from './Demo.vue'
import { ref, provide, onMounted } from 'vue'
export default {
  components: {
    Demo
  },
  setup() {
    // 创建了一个空的ref
    const hRef = ref(null)
    const dRef = ref(null)

    onMounted(() => {
      console.log(hRef.value.innerHTML)
      console.log(dRef.value)
    })

    return {
      hRef,
      dRef
    }
  }
}
</script>

<style></style>

38 综合案例todolist

<template>
  <section class="todoapp">
    <header class="header">
      <h1>todos</h1>
      <input
        class="new-todo"
        placeholder="What needs to be done?"
        autofocus
        v-model="todoName"
        @keyup.enter="addTodo"
      />
    </header>
    <!-- This section should be hidden by default and shown when there are todos -->
    <section class="main">
      <input
        id="toggle-all"
        class="toggle-all"
        type="checkbox"
        v-model="checkAll"
      />
      <label for="toggle-all">Mark all as complete</label>
      <ul class="todo-list">
        <li
          :class="{
            completed: item.done,
            editing: item.id === currentId
          }"
          v-for="item in list"
          :key="item.id"
        >
          <div class="view">
            <input class="toggle" type="checkbox" v-model="item.done" />
            <label @dblclick="showTodo(item.id, item.name)">{{
              item.name
            }}</label>
            <button class="destroy" @click="delTodo(item.id)"></button>
          </div>
          <input
            class="edit"
            v-model="currentName"
            @keyup.esc="currentId = ''"
            @keyup.enter="editTodo(item)"
          />
        </li>
      </ul>
    </section>
    <!-- This footer should hidden by default and shown when there are todos -->
    <footer class="footer" v-if="list.length > 0">
      <!-- This should be `0 items left` by default -->
      <span class="todo-count"
        ><strong>{{ leftCount }}</strong> item left</span
      >
      <!-- Remove this if you don't implement routing -->
      <ul class="filters">
        <li>
          <a class="selected" href="#/">All</a>
        </li>
        <li>
          <a href="#/active">Active</a>
        </li>
        <li>
          <a href="#/completed">Completed</a>
        </li>
      </ul>
      <!-- Hidden if no completed items are left ↓ -->
      <button class="clear-completed" v-if="isShow" @click="clearTodo">
        Clear completed
      </button>
    </footer>
  </section>
</template>

<script>
import { reactive, toRefs, computed, watch } from 'vue'
export default {
  setup() {
    const state = reactive({
      todoName: '',
      // 用于记录需要修改的任务的id
      currentId: '',
      // 用于记录需要修改的任务的名字
      currentName: '',
      // 需要完成的任务列表
      list: JSON.parse(localStorage.getItem('todos')) || []
    })

    const delTodo = (id) => {
      // console.log('删除', id)
      state.list = state.list.filter((item) => item.id !== id)
    }

    const addTodo = () => {
      const todo = {
        id: Date.now(),
        name: state.todoName,
        done: false
      }
      state.list.unshift(todo)
      state.todoName = ''
    }

    const showTodo = (id, name) => {
      state.currentId = id
      state.currentName = name
      console.log(state.currentId)
      console.log(state.currentName)
    }

    const editTodo = (todo) => {
      todo.name = state.currentName
      state.currentId = ''
    }

    const clearTodo = () => {
      state.list = state.list.filter((item) => !item.done)
    }

    const computedData = reactive({
      leftCount: computed(() => {
        return state.list.filter((item) => !item.done).length
      }),
      isShow: computed(() => {
        return state.list.some((item) => item.done)
      }),
      checkAll: computed({
        get: () => {
          return state.list.every((item) => item.done)
        },
        set: (value) => {
          state.list.forEach((item) => (item.done = value))
        }
      })
    })

    watch(
      () => state.list,
      (value) => {
        localStorage.setItem('todos', JSON.stringify(state.list))
      },
      {
        // 深度监听
        deep: true
      }
    )
    return {
      ...toRefs(state),
      ...toRefs(computedData),
      delTodo,
      addTodo,
      showTodo,
      editTodo,
      clearTodo
    }
  }
}
</script>