本文主要讲解使用vite2和vue3和ant-design搭建的项目如何使用国际化。开始引入插件这个插件主要是配合i18n进行使用，具体作用可以查看文档。# npm npm install @intlify/vite-plugin-vue-i18n -D # yarn yarn add @intlify/vite-plugin-vue-i18n -D下载vue-i18n:注意：这里使用9.x的版本。# npm npm install vue-i18n@next # yarn yarn add vue-i18n@nextvite.config.ts中配置：import { defineConfig, loadEnv } from 'vite'; import vueI18n from '@intlify/vite-plugin-vue-i18n'; import path from 'path'; //... export default ({ mode }) => { return defineConfig({ plugins: [ //... vueI18n({ include: path.resolve(__dirname, './src/locales/**') }), ], //... }); };路径写我们要配置国际化语言包的路径，这里我准备在src/locales下配置。配置语言包在设置的目录下创建自定义语言包，这里我选择json文件方式配置：文件名随意取。zhCN.json{ "HelloWorld": "你好世界" }en.json{ "HelloWorld": "hello,world!" }以上我就配置了两种语言，中文和英文，二者的key都是HelloWorld。之后我们会根据这个key来显示和切换语言。导入配置我这里设置了一个文件(xxx.ts)来引入设置：// 国际化 import type { App } from 'vue'; import { createI18n } from 'vue-i18n'; //这个是帮助我们导入自定义语言包的，详情可以看vite-plugin-vue-i18n插件文档 import messages from '@intlify/vite-plugin-vue-i18n/messages'; const i18n = createI18n({ locale: 'zhCN',//设置默认语言，这个字符串通过上面messages方式导入默认为自定义语言包的文件名（比如这里就'zhCN'和'en'之后我们就可以根据这两个标识来动态的切换语言） messages }); export const setI18n = (app: App) => { app.use(i18n); }; export default i18n;在main.ts中use注册使用：import { createApp } from 'vue'; import App from './App.vue'; import router from './router'; // 国际化 import { setI18n } from './locales'; //... const app = createApp(App); setI18n(app); app.mount('#app');组件中使用//导入依赖 import { useI18n } from 'vue-i18n'; import i18n from '@/locales';//setup标签中使用 const { t } = useI18n(); const localeRef: any = ref(i18n.global.locale); 模板中使用 {{t('HelloWorld')}} 修改语言:localRef.value = 'en'//设置为英文ant-design-vue使用国际化之前我们只设置了自定义字段的国际化，我们引入的组件库要国际化可以使用其自身的国际化方式。首先导入antd自带的语言包，这里导入两种，中英文。import zhCN from 'ant-design-vue/es/locale/zh_CN'; import en from 'ant-design-vue/es/locale/en_GB'; //注意，antd中时间相关的组件国际化需要设置moment，因为内部字段是使用的moment的国际化方式 //这里vite只支持es module方式导入，所以请注意模块化引入的方式 import 'moment/dist/locale/zh-cn'; import 'moment/dist/locale/en-gb';const locale = ref(zhCN); //设置moment的国际化 moment.locale('zh-cn');最后导入语言变量，通过ant-design-vue自带的config组件注册给子组件使用:这里可以在App.vue中包裹，反正要保证antd的组件包裹在其中 <a-config-provider :locale="locale"> <router-view></router-view> </a-config-provider> 这个locale就是我们要切换的语言包。要修改语言就需要修改locale,注意如果使用了时间相关组件需要再设置moment的国际化。总结以上就是国际化的简单应用，这里我推荐一种国际化切换方式就是将语言变量放置在url中比如设置为一个query，当我们分享链接也能保证语言一致了,当然注意每次跳转也买你都要带上这个query哦。
作为一个前端开发，没有后端接口而自己还不会写后端，那可以自己写数据来进行页面的渲染展示。我们首先可以想到的是直接在代码中写大量的数据，直接造嘛，简单粗暴。缺点就是大量的数据代码破坏整体代码结构，而且后期对接真正的接口会进行大量的修改，所以还是需要一定的工作量。我们想的是先把所有请求方式参数都订好，后期对接只需要切换下请求的接口，自动换成后台的数据。所以我们可以使用mockjs来进行数据的填写。之前我有写过一篇基于vue-cli的mockjsmockjs基本使用的基本使用。但本次项目使用vite开发，同时还有一个基于vitejs开发的数据模拟插件vite-plugin-mock。能帮助我们更快更方便实现数据模拟，妈妈再也不用担心没有后端数据了。安装利用vite创建一个脚手架，这里我创建的是vue3+ts项目。安装依赖：# yarn yarn add mockjs # npm npm i mockjs -S# yarn yarn add vite-plugin-mock -D # npm npm i vite-plugin-mock -D这里主要说明在开发环境中使用，详细的可以查看插件文档。配置配置vite.config.tsimport { defineConfig, loadEnv } from 'vite'; import { viteMockServe } from 'vite-plugin-mock'; import vue from '@vitejs/plugin-vue'; import path from 'path'; export default ({ mode }) => { return defineConfig({ plugins: [ vue(), viteMockServe({ mockPath: 'src/mock', localEnabled: !!loadEnv(mode, process.cwd()).VITE_MOCK }), ], }); }; 可以看到我们引入配置对象配置了两个选项:mockPath主要是说明模拟数据文件的位置，这里写的路径会自动拼接项目的工作目录。localEnabled，设置是否引入本地的模拟数据文件，传入一个布尔值。我们在开发环境就设置为true，生产环境就设置为false就行了。可以根据环境变量来判断这个值。 我们可以再项目根目录配置.env.xxx文件来设置环境变量。注意：在vite中我们在项目文件中获取环境变量和在vite.config.ts是不一样的。例如：配置的开发环境变量如下。在项目中引入：import.meta.env.VITE_BASE_API而在vite.config.ts中引入需要这样：// 在vite中引入loadEnv依赖 import { defineConfig, loadEnv } from 'vite'; //以函数的方式配置,解构使用mode参数 export default ({ mode }) => { return defineConfig({ plugins: [ //... viteMockServe({ mockPath: 'src/mock', localEnabled: !!loadEnv(mode, process.cwd()).VITE_MOCK }), ], }); };模拟数据配置在设置的mock目录下创建xxx.ts文件：这里我设置了一个登录模拟接口。import type { MockMethod } from 'vite-plugin-mock'; // 登录模拟 const apiArr: MockMethod[] = [ { url: '/api/login', method: 'post', rawResponse: async (req, res) => { let reqbody = ''; await new Promise((resolve) => { req.on('data', (chunk) => { reqbody += chunk; }); req.on('end', () => { const body = JSON.parse(reqbody); if (body.name === 'saber' && body.password === 'Qw123456') { res.statusCode = 200; reqbody = JSON.stringify({ code: 200, data: { id: 0, token: 'djlk23123fwqead89-08s8d09-88' } }); } else { res.statusCode = 400; reqbody = '账号或者密码错误'; } resolve(undefined); }); }); res.setHeader('Content-Type', 'application/json;charset=UTF-8'); res.end(reqbody); } } ]; export { apiArr }; 详细的配置可以文档，我们通过这个数组来配置多个请求，我们通过axios的请求就会被拦截走这里的逻辑，这里的响应方式还可以选择原生的方式，来使请求更加的真实。注意，我们配置的这个数据最后要默认导出，你可以设置一个文件来统一导出这些数组，总之要有一个文件汇总这些请求配置到一个数组中，然后默认导出这个数组，之后插件就会自动读取了。
开始刚入职，了解了公司的基本技术栈，用的vue3+ts+vite+antd-vue+pinia，技术基本都是比较新的，学了那么久前端的我意识到前端真是学无止尽，你的学习速度跟不上新技术迭代速度了哇~~~。废话不多说开始！！！vitevite作为vue作者强力推荐的一款前端构建工具,代码打包速度相较于一些其他的打包构建工具提升很明显。下载vite 选择项目启动//npm npm init vite@latest //yarn yarn create viteeslint引入eslint，vscode记得下载ESLint插件这里我通过cli方式创建eslint//npm npm install eslint npx eslint --init //yarn yarn add eslint yarn eslint --init之后根据提示进行选择，选择后会检查依赖是否齐全，否则的话就提示下载依赖，但是默认好像是使用npm下载，可以取消后根据提示信息使用yarn手动安装这些依赖之后再重新eslint init一次。初始化之后可以定义一些未使用的变量来测试eslint是否生效。检查：这里检查src下的所有文件。eslint './src/*'自动修复一些错误：eslint --fix './src/*'引入prettier安装prettier，vscode也可以安装prettier插件。//npm npm install prettier -D //yarn yarn add prettier -D配置：详见前端开发工作流。这里主要记录下和eslint的结合使用，因为这两者在使用上都是对代码进行格式化，所以会有一些规则上的冲突，这里安装以下插件来解决：//npm npm install eslint-config-prettier eslint-plugin-prettier -D //yarn yarn add eslint-plugin-prettier eslint-config-prettier -D在.eslintrc.js中配置：//... extends: [ //... + "prettier", + "prettier/@typescript-eslint", + "plugin:prettier/recommended" ], //...husky/pre-commit/msg-commit/lint-staged配置详见前端开发工作流。ant-design-vue使用下载安装：//npm npm i --save ant-design-vue@next //yarn yarn add ant-design-vue@next -S按需引入这里主要讲解下如何按需引入。首先下载插件：//npm npm install unplugin-vue-components -D //yarn yarn add unplugin-vue-components -Dvite的配置在vite.config.js中设置：import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' //引入下面这两个依赖 import Components from 'unplugin-vue-components/vite' import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [ vue(), //插件导入 Components({ resolvers: [AntDesignVueResolver()] }) ] }) 通过上述的的配置就可直接使用antd的组件了，不用再去import导入了。这个插件会自动解析使用过的组件来生成按需导入的import语句。只管使用antd就行了~~~动态主题切换使用vite的主题切换方式如下：第一步，引入antd的less样式。import { createApp } from 'vue' import App from './App.vue' // 引入less用于定制化样式 import './theme/default-vars.less' createApp(App).mount('#app')这里为了之后样式主题动态切换，我把引入的antd的less样式放到另一个文件default-var.less了：/*引入的antd样式*/ @import 'ant-design-vue/dist/antd.less'; //less变量 @primary-color: #1890ff; // 全局主色 @link-color: #1890ff; // 链接色 @success-color: #52c41a; // 成功色第二步，下载less依赖。vite虽然内置了一些css预处理器支持模块，但是预处理器本身的核心依赖是没有的，需要自己下载。//npm npm install less -D //yarn yarn add less -D第三步修改less变量。import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import Components from 'unplugin-vue-components/vite' import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [ vue(), Components({ resolvers: [AntDesignVueResolver()] }) ], css: { preprocessorOptions: { less: { //开启less的js支持 javascriptEnabled: true, modifyVars: { //这里就修改了主题色，还有很多的变量可以在官网查看 'primary-color': '#bfa' } } } }, }) 动态修改主题颜色之前的是静态的修改主题色，我们要实现动态的样式切换。这里推荐使用一个大佬开发的动态样式切换编译插件：@zougt/vite-plugin-theme-preprocessor。这个插件是基于vite v2.0+的插件。具体操作如下：第一步，安装和下载。# use npm npm install @zougt/vite-plugin-theme-preprocessor -D # use yarn yarn add @zougt/vite-plugin-theme-preprocessor -D第二步，vite.config.js导入插件。import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import Components from 'unplugin-vue-components/vite'; import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'; import themePreprocessorPlugin from '@zougt/vite-plugin-theme-preprocessor'; import path from 'path'; export default defineConfig({ plugins: [ vue(), Components({ resolvers: [AntDesignVueResolver()] }), themePreprocessorPlugin({ less: { multipleScopeVars: [ { scopeName: 'theme-default', path: path.resolve('src/theme/default-vars.less') }, { scopeName: 'theme-green', path: path.resolve('src/theme/green-vars.less') } ] } }) ], //... });第三步，创建不同主题样式的less文件。vite中的配置引入了不同的主题样式，之后这些样式文件都会编译成css，只是类民不同，我们通过改变类名就可以实现动态切换了。default-vars.less文件：@import 'ant-design-vue/dist/antd.less'; @primary-color: #1890ff; // 全局主色 @link-color: #1890ff; // 链接色 @success-color: #52c41a; // 成功色green-vars.less文件：@import 'ant-design-vue/dist/antd.less'; @primary-color: #bfa; // 全局主色 @link-color: #bfa; // 链接色 @success-color: #bfa; // 成功色第四步，切换样式。import { toggleTheme } from '@zougt/vite-plugin-theme-preprocessor/dist/browser-utils.js'; export const changeTheme = (scopeName: string): void => { toggleTheme({ scopeName }); };另一个文件导入使用：//作为按钮回调函数 const changeColor = () => { //这里传入的参数就是vite.config.js中scopName changeTheme('theme-green'); console.log('切换颜色'); };点击bar切换后：
vue关于h函数的简单认识vue在绝大多数情况下都推荐使用模板来编写html结构,但是对于一些复杂场景下需要完全的JS编程能力，这个时候我们就可以使用渲染函数 ，它比模板更接近编译器vue在生成真实的DOM之前，会将我们的节点转换成VNode，而VNode组合在一起形成一颗树结构，就是虚拟DOM（VDOM）我们之前编写的 template 中的HTML 最终也是使用渲染函数生成对应的VNodeh()函数参数参数一：一个html标签名，一个组件或者一个异步组件，或者函数式组件（必要）参数二：与attr,prop 和事件相对应的对象（可选），不写的话最好用null占位参数三：子VNode,使用h()函数构建，或者使用字符串获取文本VNode或者有插槽的对象（可选）基本使用h()函数可以在两个地方使用：render 函数中setup函数中render函数中使用render() { return h( "div", { class: "app", }, [ // 这里this是可以取到setup中的返回值的 h("h2", null, `当前计数: ${this.counter}`), h("button", {onclick:() => this.counter++}, "+1"), h("button", {onclick:() => this.counter--}, "-1"), ] ); },setup函数中使用 setup() { const counter = ref(0); return () => h( "div", { class: "app", }, [ // 这里this是可以取到setup中的返回值的 h("h2", null, `当前计数: ${counter.value}`), h("button", { onclick: () => counter.value++ }, "+1"), h("button", { onclick: () => counter.value-- }, "-1"), ] ); },函数组件和插槽使用引入使用HelloWorld组件，组件有一个默认插槽和一个具名插槽 setup() { const read = shallowRef({name:"zhangsan"}); setTimeout(() => { read.value.name = "lls"; triggerRef(read); console.log(read.value) }, 5000) return () => h( HelloWorld, null, { default:props => h("span", null, `${read.value.name} app传入到组件中的内容${props.name}`), usejsx: props => h(usejsx, null) } ); },HelloWorld组件： render() { return h("div", null, [ h("h2", null, "hello world"),[ this.$slots.default ? this.$slots.default({name:"zhangsan"}):h("h2", null, "我是默认插槽"), this.$slots.usejsx() ] ]); }其实从上述我们可以看出插槽其实就是一个函数的调用，也能解释解构插槽原理了
Vue Composition API（更新中）setup函数简介函数主要参数：propscontextprops就是父组件传递过来的属性，当然我们需要事先在组件中配置接收props props:{ name:{ type:String, default:"" }, age:{ type:Number, default:10 } }, setup(props, context) { console.log(props); }context是个对象，也可以称之为SetupContext ，包含三个对象：attrs 所有非props中配置的attr，比如再父组件中对组件设置的id class 等属性slots 父组件传递过来的插槽，一般我们使用render函数渲染组件的时候会用到emit 当我们租价内部想向父组件发出事件时会用到注意：setup函数中不能使用this ,因为 this并没有指向当前组件实例,并且在setup被调用之前，data、computed、methods等都没有被解析.所以无法在setup中获取this函数返回值setup函数返回值首先应该是一个对象（不是的话会报警告）可以用作template 模板中使用，用来代替data选项，我们也可以在返回对象中添加函数,也就代替methods 选项了 setup(props, context) { console.log(props, context); const increment = () => { //... } return { message:"hello setup", title:"zhang", increment }注意我们在setup返回的数据是没有响应式特性了，所以要想使用响应式的数据我们可以使用以下API:Reactive API使用方式import { reactive } from "vue" setup() { const counterState = reactive({counter:100}); const increment = () => { counterState.counter++; } return { increment, counterState } } {{counterState.counter}} 这样就可以实现响应式了，但是ractive 这个API只能传入一个对象或者数组类型，传入基本类型会报警告，而且使用的时候还需要以对象取属性方式手动解包，所以我们还可以使用另一个API refRef APIref 会返回一个可变的响应式对象。该对象作为一个响应式的引用维护着它内部的值，这就是ref名称的来源import { ref } from "vue" setup() { const counter = ref(100) const increment = () => { //这里使用是不会为我们自动解包 counter.value++ } return { increment, counter } } {{counter}} 另外将ref处理后的返回值放到ractive的对象中也是会为我们解包的const name = ref("zhangsan"); const infoState = reactive({ age:18, name//实际为name:name.value });readonlyreadonly 返回一个原生对象的代理，这个代理是只读的，有时候我们传递给其他组件数据，但是只想让其展示，不做修改就可以这样使用//一般readonly方法会传入三个类型的参数 /* 1. 普通对象 2. reactive返回的对象 3. ref返回的对象 */setup(props, context) { //普通对象 const info = {name:"lilisi"}; const readOnlyInfo = readonly(info); //reactive const info2 = reactive({name:"wang"}); const readOnlyInfo2 = readonly(info2); //ref const info3 = ref("saber"); const readOnlyInfo3 = readonly(info3); const increment = () => { readOnlyInfo.name = "saber"; readOnlyInfo2.name = "haha"; readOnlyInfo3.value = "hello"; } return { readOnlyInfo, readOnlyInfo2, readOnlyInfo3 } }模板中展示： {{readOnlyInfo.name}}---{{readOnlyInfo2.name}}---{{readOnlyInfo3}} readonly返回的对象都是不允许修改的, 是经过readonly处理的原来的对象是允许被修改的,所以我们要修改的话就要修改原来的对象，当然数据都是响应式的，毕竟本质上就是readonly返回的对象的setter方法被劫持了而已Reactive 判断的APIisProxy判断对象是否是reactive或者readonly创建的proxy const state = reactive({name:'zhangsan'}); const read = readonly({age:18}); const counter = ref(100); console.log(isProxy(state));//true console.log(isProxy(read));//true console.log(isProxy(counter));//falseisReactive检查对象是否由reactive 创建的响应式代理，如果该代理是readonly创建的但包裹了由reactive 创建的另一个代理，也会返回true const state = reactive({name:'zhangsan'}); const read = readonly(state); const counter = ref(100); console.log(isReactive(state));//true console.log(isReactive(read));//true console.log(isReactive(counter));//falseisReadonly检查对象是否由readonly创建的只读代理 const state = reactive({name:'zhangsan'}); const read = readonly(state); const counter = ref(100); console.log(isReadonly(state));//false console.log(isReadonly(read));//true console.log(isReadonly(counter));//falsetoRaw返回reactive或者readonly代理的原始对象（但是不建议过久的保留对原始对象的引用），该API需要谨慎使用 const state = reactive({name:'zhangsan'}); const read = readonly(state); console.log(toRaw(state)); console.log(state); console.log(toRaw(read)); console.log(read);返回结果：shallowReactive创建一个响应式代理，它跟踪对象自身的property的响应式，但是不执行嵌套对象的深层次响应式转换（浅响应式转换）shallowReadonly创建一个只读代理但是只是浅层次的property为只读，不执行嵌套对象的深层次只读只读转换Ref相关APItoRefs有时候我们想使用解构语法来获取值，对于reactive 返回的代理对象我们使用解构后其数据就不再是响应式的const state = reactive({name:"zhangsan", age:18}); const {name, age} = state;//此时name和age不再具有响应式特性通过toRefs这个函数可以将reactive返回的对象中的属性转成ref,所以我们解构出来的name和age 都是等同于通过ref创建的对象了，就具有响应式特性const state = reactive({name:"zhangsan", age:18}) ;const {name, age} = toRefs(state);//此时name和age具有响应式特性toRef我们有时候不希望将reactive 返回的对象中的所有属性都转换成ref，就只需要转换其中的一个属性为ref,我们就可以使用这个APIconst state = reactive({name:"zhangsan", age:18}); const {name} = toRef(state, "name");unref有时候我们需要获取一个ref中的value,可以通过该方法该方法其实就是一个语法糖，实际的代码如下：val = isRef(val) ? val.value : valisRef判断值是否为ref对象 const state = reactive({name:'zhangsan'}); const read = ref(100); console.log(isRef(state));//false console.log(isRef(read));//false console.log(isRef(read.value));//falseshallowRef创建一个浅层的ref对象const info = shallowRef({name: "zhangsan"});//修改name值 info.value.name = "lls";//该操作不是响应式的triggerRef可以手动触发和shallowRef相关联的副作用，比如shallowRef的副作用就是进行视图的更新，但是深层次属性修改并不会触发该副作用，所以我们可以手动触发该副作用来更新视图 const info = shallowRef({name:"zhangsan"}); setTimeout(() => { info.value.name = "lls";//修改是不会更新视图的 triggerRef(info);//手动来触发更新视图 }, 5000)customRef创建一个自定义的ref ,并对其依赖项跟踪和更新触发进行显示控制，该API需要一个工厂函数，函数需要接收track和trigger函数作为参数，同时该工厂函数需要返回一个对象，该对象带有get和set方法这个其实还比较难理解，这里展示一个例子来简单解释下如何使用：对双向绑定的属性进行节流操作import { createApp, customRef } from 'vue'; // 自定义ref export default function (value, delay = 300) { let timer = null; return customRef((track, trigger) => { return { get() { track(); // 收集依赖 return value; }, set(newValue) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { value = newValue; trigger(); }, delay); } }; }); } {{message}}import debounceRef from './hook/useDebounceRef'; setup() { const message = debounceRef("Hello World"); return { message } }computed函数在Composition API中，我们可以在setup函数中使用computed方法来编写一个计算属性写法一：接收一个getter函数，并以getter函数返回的值返回一个不变的ref对象(只读)const firstName = ref("Kobe"); const lastName = ref("Bryant"); const fullName = computed(() => firstName.value + " " + lastName.value);写法二：接收一个具有get和set的对象，返回一个可变的（可读写）ref对象const firstName = ref("Kobe"); const lastName = ref("Bryant"); const fullName = computed({ get: () => firstName.value + " " + lastName.value, set(newValue) { const names = newValue.split(" "); firstName.value = names[0]; lastName.value = names[1]; } }); setup中使用ref这里的ref指的是我们用来获取组件或者元素的ref属性我们需要定义一个ref对象，然后绑定到元素和组件的ref属性上即可（注意这里不能动态绑定该属性）const title = ref(null); 哈哈哈 我们使用title.value就可以获取该元素dom对象了数据的侦听在Composition API中，我们可以使用watchEffect和watch来完成响应式数据的侦听watchEffect基本使用watchEffect传入的函数会被立即执行一次，并且在执行的过程中会收集依赖,只有收集的依赖发生变化时，watchEffect传入的函数才会再次执行setup() { const name = ref("why"); const age = ref(18); const changeName = () => name.value = "kobe" const changeAge = () => age.value++ watchEffect(() => { console.log("name:", name.value, "age:", age.value); });结果是控制台先打印一次，然后修改操作执行时再执行watchEffect是自动收集响应式数据的依赖,函数内部会自动检测收集响应式的对象，不用我们自己去指定watchEffect的停止监听watchEffect函数会返回一个函数，我们执行该函数就可以停止监听 const name = ref("why"); const age = ref(18); const stop = watchEffect(() => { console.log("name:", name.value, "age:", age.value); }); //执行该函数就会停止监听 stop()watchEffect清除副作用有时候我们监听某个数据发生变化再发送网络请求或者其他耗时的操作，但是我们在请求结果还未达到等情况时又触发了侦听，或者停止了侦听器，我们应该把上次的网络请求任务取消掉，这就是副作用我们可以在给传入watchEffect的回调函数中添加一个参数，我们一般命名为onInvalidate 该参数是一个函数，接收一个回调函数，当副作用即将重新执行或者侦听器被停止时会执行该函数传入的回调函数，我们就可以在该回调函数中做一些清除工作，如取消监听，清除定时器等 // watchEffect: 自动收集响应式的依赖 const name = ref("zhangsan"); const age = ref(18); const stop = watchEffect((onInvalidate) => { const timer = setTimeout(() => { console.log("网络请求成功~"); }, 2000) // 根据name和age两个变量发送网络请求 onInvalidate(() => { // 在这个函数中清除额外的副作用 clearTimeout(timer); console.log("onInvalidate"); }) console.log("name:", name.value, "age:", age.value); });watchEffect的执行机制实例代码如下： 哈哈哈 setup() { const title = ref(null); watchEffect(() => { console.log(title.value); }) return { title } } }默认情况下组件的更新会在副作用函数执行之前，比如我们在watchEffect中通过ref属性获取元素dom，因为watchEffect传入的回调函数会默认立即执行一次，但这时候dom并没有挂载，所以打印对象为null ,当组件挂载完毕后为给我们title.value 赋值，就会触发侦听更新然后再次打印， 结果如下我们希望第一次打印的时候就获取到dom元素，所以我们就需要修改副作用函数的执行时机watchEffect函数的第二个参数就是一个配置对象，里面有个属性flush有三个值可设置pre 默认值，会在元素挂载或者更新前就会执行post 会在组件的首次渲染完成后执行sync 强制效果始终同步触发。然而，这是低效的，应该很少使用 setup() { const title = ref(null); watchEffect(() => { console.log(title.value); }, { flush: "post" }) return { title } } }这样第一次就可以获取dom元素了watchwatch的API完全等同于组件watch选项的Propertywatch 需要侦听特定的数据源，并在回调函数中执行副作用和watchEffect相比，watch是惰性的，默认只在侦听源发生改变才会执行（第一次不会执行），而且能够更详细的说明当那些状态发生改变时，触发侦侦听器执行，当然可以访问侦听源的变化前后的值侦听单个数据源watch侦听函数的数据源有两种类型：这里以一个按钮修改name来演示侦听结果 const changeData = () => { info.name = "kobe"; //name.value = "kobe"; }一个是getter函数，该函数必须返回可响应式的对象（reactive/ref）const info = reactive({name: "why", age: 18}); watch(() => info.name, (newValue, oldValue) => { console.log("newValue:", newValue, "oldValue:", oldValue); }) 输出结果：另一个是直接传入一个可响应式的对象（reactive/ref）一般为ref watch(info, (newValue, oldValue) => { console.log("newValue:", newValue, "oldValue:", oldValue); },)我们可以发现上面的输出结果还是代理对象，如果我们希望newValue和oldValue是一个普通的对象,可以使用getter函数返回响应式对象的副本 watch(() => { return {...info} }, (newValue, oldValue) => { console.log("newValue:", newValue, "oldValue:", oldValue); }) 监听一个ref对象 ,ref对象获取newValue和oldValue是value值的本身 const name = ref("why"); watch(name, (newValue, oldValue) => { console.log("newValue:", newValue, "oldValue:", oldValue); })侦听多个数据源使用数组对多个数据源进行侦听 const info = reactive({name: "why", age: 18}); const name = ref("why"); watch([() => ({...info}), name], ([newInfo, newName], [oldInfo, oldName]) => { console.log(newInfo, newName, oldInfo, oldName); })watch的选项我们可以设置深层次的监听和立即执行监听watch(name, (newValue, oldValue) => { console.log("newValue:", newValue, "oldValue:", oldValue); }, {deep:true, immediate:true});生命周期函数setup函数中也有特定的函数来代替以往的options API 函数，这些函数都是以onX命名我们用的时候需要自行导入 import { onMounted, onUpdated, onUnmounted, ref } from 'vue'; onMounted(() => { console.log("App Mounted1"); }) onMounted(() => { console.log("App Mounted2"); }) onUpdated(() => { console.log("App onUpdated"); }) onUnmounted(() => { console.log("App onUnmounted"); })注意setup函数 是围绕 beforeCreate 和 created 生命周期钩子运行的，所以不需要显式地定义它们。换句话说，在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。Provide和injectsetup中也可以编写provide和inject使用provide我们需要显式的导入该函数provide 函数允许你通过两个参数定义 property：name(String类型)valueimport { provide } from 'vue' setup() { provide('location', 'North Pole') provide('geolocation', { longitude: 90, latitude: 135 }) }使用inject也需要显式的导入函数inject函数也有两个参数：要 inject 的 property 的 name默认值 (可选)import { inject } from 'vue' const userLocation = inject('location', 'The Universe') const userGeolocation = inject('geolocation')为了增加 provide 值和 inject 值之间的响应性，我们可以在 provide 值时使用ref或 reactive const location = ref('North Pole') const geolocation = reactive({ longitude: 90, latitude: 135 }) provide('location', location) provide('geolocation', geolocation)另外我们修改响应式的数据最好在数据的提供位置来修改，比如将修改的方法共享，在后代组件中调用
Vue3 v-model 组件中的使用当我们需要自己封装一个组件的时候 我们再组件上使用v-model<home v-model="name"></home>相当于<home :modelValue="name" @update:modelValue="name=$event"></home>当然组件中需要将modelValue绑定到props中， 同时定义自定义事件抛出一般写法如下： props:["modelValue"], emits:["update:modelValue"], methods:{ inputChange(event) { this.$emit("update:modelValue", event.target.value); } } 我们可以发现input上的写法其实和v-model类似，但是我们不能直接将modelValue绑定 上述的写法会有问题，并不能和父组件双向绑定，而且我们也尽量不直接修改props中的值computed 写法如下：需要一个新的值来绑定 这里就命名为value computed:{ value:{ //在设置值的发出一个自定义事件 set(value) { this.$emit("update:modelValue", value) }, get() { return this.modelValue } } },另外vue3 组件也可以设置多个 v-model 了(vue2是.sync)， 默认的 v-model会命名为modelValu 我们可以通过v-model:xxx 来自己命名， 但是要注意事件取名格式为 @update:xxx
Vue 异步组件webpack代码分包首先说下webpack的代码分包，vue在打包的过程中会将自己写的代码统一打包再app.js 中 但是随着项目过大，这个文件会越来越大，可能会导致页面加载速度慢，所以我们可以对代码进行拆分，使其按需加载。我们可以借助webpack和es2015语法来按需引入代码js代码的分包，这里可以使用魔法注释来定义打包名字import(/* webpackChunkName: 'math' */"./utils/math").then(({}) => { console.log(sum(2, 3)); })打包结果：vue实现异步组件组件也可以异步的进行加载Vue3提供了一个函数 defineAsyncComponent 使用方式一：函数可以接受一个工厂函数，需要返回一个Promise对象const someCon = defineAsyncComponent(() => import(/* webpackChunkName: 'someCon' */"./components/someCom.vue"))使用方式二：函数接受一个对象类型，可以对异步函数进行配置const AsyncCom = defineAsyncComponent({ loader:() => import(/* webpackChunkName: 'asyncCom' */"@/components/asyncCom.vue"), loadingComponent:"xxx"//占位组件，在异步组件未加载出来显示 errorComponent:"xxx"//加载失败显示组件 delay:2000//延迟显示组件 onEror:function(err, retry, fail, attempts){}//错误回调函数，可以尝试重新加载和设置记录重新加载次数 })当然我们还可以结合 suspense 这个内置组件来使用因为对象方式配置加载组件和错误组件遇到组件传值时不太好解决，所以我们配合 suspense 可以更好地解决这个问题default：如果default可以显示，那么显示default的内容fallback：如果default无法显示，那么会显示fallback插槽的内容 <suspense> 正常组件 加载组件 </suspense>
Vue组件基本使用通过 Vue.extend 来创建组件构造器对象const cpnC = Vue.extend({ template:` hhhhh xxxxxx ` });全局注册组件Vue.component('my-cpn',cpnC);局部注册组件,在对应的vue实例中去注册即可 const app = new Vue({ el: '#app', data: { message: 'helloWorld！' }, components:{ cpn:cpnC } });全局注册语法糖Vue.component('cpn',{ template:` 我是哈哈ha 我是内容 ` });局部注册语法糖 const app = new Vue({ el: '#app', data: { message: 'helloWorld！' }, components:{ 'cpn2':{ template:` 我是哈哈ha 我是内容hhhh ` } } });组件通信1. 父传子通过props方式2. 子传父通过$emit方式组件访问1. 父访问子$children父组件调用$children会获取一个包含所有子组件的数组,可以通过该方式获取子组件的一些方法与属性$refs对象类型，默认是一个空对象。当有组件标签上加上了ref="xxx",这时这个对象才会返回这个组件的一些属性,可以用来操作一些DOM行为2. 子访问父通过$parent 方式provide/inject如果我们需要跨层级传递,上述介绍的方式使用起来就会很繁琐而且维护性不高如上图,我们需要通过A组件为最底层各个组件传递属性或者方法就可以采用 provide/inject方式A组件:provide() { return { theme: { //这里设置的color不是响应式的, color: this.color } }; },//下面就能实现响应式(提供了响应式的数据) provide() { return { theme: this }; },如果实在不想把整个实例查传过去,可以使用Vue.observable 它能将对象变成响应式 provide() { //另类实现响应式 this.theme = Vue.observable({ color: "blue" }); return { theme: this.theme }; },vue3推出的新api computed也可以实现响应式//需要从vue中引入一个函数 import { computed } from "vue" provide() { return { theme:computed(() => this.color) } }后代组件通过inject获取 theme对象inject: { theme: { default: () => ({}) } },函数式组件(没有上下文对象)获取inject方式 I 结点 通过computed处理过的数据在子组件中的访问方式与其他的不同，因为computed返回的是一个ref对象，需要通过 .value方式去取数据注意,inject的方式是类似与冒泡的方式去查找父级的provide 以最近的为准事件总线vue3移除掉了$on $off $once 等方法，这里使用一个第三方库 mitt来实现下载npm install mitt -S使用方式：创建一个文件夹 eventBus.js//导入 import mitt from 'mitt' //创建一个emitter对象 const emitter = mitt(); export default emitter;例如 A B组件互为兄弟组件，A想向B组件发送一些数据A组件import emitter from "@/eventBus.js" //发出一个自定义事件 emitter.emit('zhang', {name:'san', age:'18'}); B组件import emitter from "@/eventBus.js" //监听事件 emitter.on('zhang', (info) => { console.log(info) }) //监听所有事件，这里参数有两个，一个是事件类型，一个是数据 emitter.on('*', (type, info) => { console.log(info) })事件的取消//取消所有事件监听 emitter.all.clear(); //取消特定的事件 //我们这里监听事件就要传入一个函数的引用了 function fn() {} emitter.on('zhang', fn);//监听 emitter.off('zhang', fn);//取消动态组件有时候我们需要在一个页面切换不同的组件，我们可以使用 v-if来条件渲染，也可以使用路由来进行切换，当然还可以使用 动态路由来实现动态路由使用vue内置组件component 通过一个属性 is 来实现<component :is="currentTab" @pageClick="pageClick" name="zhangsan" :age="20" ></component>is 填写组件的内容可以为component函数注册的组件，也可以是在一个组件对象的components对象中注册的组件。对于组件间传值和自定义事件也是和普通组件一样没有变化另外，如果我们在切换组件的时候想要将组件的状态缓存，可以使用内置组件 keep-alive<keep-alive> <component :is="currentTab" @pageClick="pageClick" name="zhangsan" :age="20" ></component> </keep-alive>包裹在外层即可keep-alive 属性include - string | RegExp | Array 只有匹配的组件才会被缓存exclude - string | RegExp | Array 任何名称匹配的组件都不会被缓存max - number | string 最多可以缓存多少组件实例，一旦达到这个数字，那么缓存组件中最近没有被访问的实例会被销毁include 和 exclude 都可以使用逗号分隔字符串，匹配首先检查组件自身的name属性
如何触发组件的更新情况一定义在data 返回值外部,此时修改name是不会触发视图的更新 data() { //不会触发更新 this.name = name; return { }; },情况二没有事先在data中定义就修改使用,也是不会触发this.info.name="xxxx" data() { return { info: {}, }; },可以使用如下方式解决this.$set(this.info, 'name', 1)情况三事先在data中 定义了的但是没有在组件中使用也是不会触发更新的情况四使用一些常用的数组方法来修改数组,比如push 数组的引用虽然没有改变但是依然会出发组件的更新,简单说因为 Vue框架对数组的push、pop、shift、unshift、sort、splice、reverse方法进行了改造，在调用数组的这些方法时，Vue会Notify Wacher并更新视图如果你通过索引值的方式也想要触发组件更新,可以使用Vue.set方法Vue.set(this.list, 0, 'value') //set(要修改的对象, 索引值, 修改后的值)
Vue (Vue2)基础知识梳理插值操作1. mustache 语法首先定义一些变量:const app = new Vue({ el:"app", data:{ meseage:"hello world", firstName:"zhang", lastName:"heng" count:100 } })然后我们可以在html标签中使用{{}}形式来将变量展现出来 {{message}} {{message}},one {{firstName}} {{lastName}} 也可以在语法中写一些简单的表达式 {{firstName + " "+lastName}} {{content*2}} 2.常见标签指令的使用v-once该指令后面没有表达式,用于执行一次性的插值.当数据再次改变时,插值处的数据是不会更新的 {{message}} v-html双大括号会将数据解释为普通的文本节点, 如果你要是渲染html代码,就使用该指令 {{url}} ``` ```js const app = new Vue({ el: '#app', data: { --------------------------------------------------- message: 'helloWorld！', url:'百度一下' } });渲染出来的效果如下:DOM节点如下:注意:这个指令使用需要注意安全问题,不要随意渲染任何html代码,有可能会导致XSS攻击,所以要对可信的html代码进行渲染v-text该标签和插值语法一样,但是不够灵活v-pre用于跳过这个元素和它子元素的编译过程 {{message}} 结果:v-bindMustache 语法不能作用在 HTML attribute 上,v-bind可以动态改变属性值1. 基本使用动态修改img标签的src属性值 v-bind的简写形式 此时imgURL就是一个变量2. 动态绑定class属性对象形式 {{message}} 可以通过这样的方式来绑定class属性，若对应类名的布尔值为true，则就添加这个类名，反之则去除，同时你用了这种动态绑定的方式绑定的类名,也可以用最原始的方式来再添加类名，然后这些类名会合并赋予给标签数组形式 {{message}} 数组项也可以是变量 {{message}} const app = new Vue({ el: '#app', data: { message: 'helloWorld！', active:'aaa', line:'bbb' } });3. 动态绑定style属性对象语法 {{message}} {{message}} {{message}} {{message}} 数组语法单独把一个对象当成数组每一个数组对象 {{message}} const app = new Vue({ el: '#app', data: { message: 'helloWorld！', baseStyle: { backgroundColor: 'red' }, baseStyle1: { fontSize: '100px' } } });计算属性模板中虽然可以写表达式,但是在模板中写大量的表达式会显得很混乱,对于一些复杂的逻辑都该使用计算属性1. 基本使用 {{fullName}} const app = new Vue({ el: '#app', data: { message: 'helloWorld！', firstName:'阿尔托莉雅', lastName:'潘德拉贡' }, //计算属性 computed: { fullName:function(){ return this.firstName+" "+this.lastName; } } });2. 复杂操作 总价格：{{totalPrice}} const app = new Vue({ el: '#app', data: { message: 'helloWorld！', books:[ {id:110,name:'xxx从入门到放弃',price:19}, {id:111,name:'yyy从入门到放弃',price:29}, {id:112,name:'zzz从入门到放弃',price:39}, {id:113,name:'java从入门到放弃',price:49} ] }, computed: { totalPrice:function(){ let result=0; for(let i=0;i<this.books.length;i++) { result+=this.books[i].price; } return result; } }, });这里我们使用方法也可以实现,但是计算属性有缓存机制,是基于响应式的缓存机制,如果相关响应式依赖没有发生改变,计算属性是会利用缓存,多次访问只会返回之前的结果,而方法则是访问一次就会执行一次,比较消耗性能3. 计算属性的getter和setter之前我们的写法是一种简写方式 computed: { fullName:function(){ return this.firstName+" "+this.lastName; } } 完整写法如下: fullName:{ //set方法就是监视fullName的值，当fullName发生改变时会自动回调set方法，当然你也可以在set方法里设置修改 fullName的值 //可以给set方法设置一个参数来引入修改值，从而修改fullName //一般set是不设置的所以就不写 set:function(newValue){ //return '123'; const names=newValue.split(' '); this.firstName=names[0]; this.lastName=names[1]; }, //使用fullName这个属性本质上就是调用get这个方法，所以就可以使用上便的简写方式，而且也可以解释为啥fullName不用加(),而使用方法是需要加()的 get:function(){ //return 'abc'; return this.firstName+" "+this.lastName; } } }侦听器计算属性可以做到的watch都能做到反之不行,当然能用计算属性就用计算属性,watch中可以执行任何逻辑,如函数节流,Ajax异步获取数据,操作DOM等基本使用watch: { // 如果 `question` 发生改变，这个函数就会运行,函数有两个参数,变量改变后的值,变量改变前的值 question: function (newQuestion, oldQuestion) { this.answer = 'Waiting for you to stop typing...' this.debouncedGetAnswer() }, //如果你要单独监听对象中的某个属性也可这样写 "info.name":function(new, old) {//...} 深度侦听此时可以将要侦听的对象设置成对象形式，就可以设置一些其他的选项了watch:{ info:{ handler(new， old) { //... }, deep:true,//是否开启深度监听 immediate:true//是否立即调用一次 } }$watch //通过以下形式使用 const unwatch = this.$watch("info", function(new, old) {}, {//option deep:true, } ) //$watch函数返回一个函数，调用该函数可以取消监听 unwatch();如果我们要侦听对象数组中某一个对象的属性变化，可以使用一个组件，将数组的每一项传入渲染出来的组件中，在组件中去侦听对象的属性改变，而不是在父组件中通过deep侦听。动态渲染1. 条件渲染v-if用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。 Vue is awesome! 当然也有 v-else , 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面，否则它将不会被识别。 0.5"> Now you see me Now you don't v-show与v-if 用法一样,区别就是 v-show 仅仅只是给dom节点修改display属性,而 v-if 是删除和重新渲染dom节点,v-if的开销较大,所以一般频繁显示的dom推荐使用 v-show2. 列表渲染v-for用于遍历数组,生成列表 {{ item.message }} const example1 = new Vue({ el: '#example-1', data: { items: [ { message: 'Foo' }, { message: 'Bar' } ] } })在 v-for 作用域中我们可以访问父级所有的属性值, v-for 可以指定第二个参数就是当前项的索引v-for 中的key作用 key作用事件绑定1. 绑定到原生DOM元素上通过v-on 标签监听 DOM 事件,并且绑定一个方法,在事件触发时执行 原生DOM中 原生DOM中 export default { name:'', data () { return {} }, methods: { handleButton1(){ console.log("原生dom事件"); } }, }vue会把默认事件对象作为参数传入方法,如果需要手动手动获取浏览器的event对象,可以使用 $event2. 绑定到组件上我们引入一个组件,并为其绑定一个事件<btn @click="handleButton2" />btn.vue组件 组件中的dom 直接在组件上绑定点击是不会触发的,有两种方式可以使用修饰符.native 在一个组件的根元素上直接监听一个原生事件。<btn @click.native="handleButton2" />在组件内部绑定一个事件,然后通过$emit方式 定义一个自定义事件,让父组件来执行相应逻辑 组件中的dom 父组件:<btn @click="handleButton2" />一般组件的点击事件可以通过第二种实现,保证父组件来处理逻辑过滤器用于一些常用的文本格式化 在组建的选项中定义局部过滤器filters: { capitalize: function (value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) } }定义全局的过滤器import Vue from 'vue' Vue.filter('capitalize', function (value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) })数据双向绑定1. 基本使用使用v-model指令进行数据绑定 {{message}} const app = new Vue({ el: '#app', data: { message: 'helloWorld！' } });输入框修改, 会同步修改message的值 同理修改message也会修改输入框的值2. 和其他表单控件使用textarea radioradio 原来是需要name属性来保证选项互斥,使用v-model后就不需要了 One Two Picked: {{ picked }}checkbox单个复选框绑定布尔值 同意协议 你选择的是：{{ isAgree }} 多个复选框需要绑定数组 篮球 足球 排球 你的爱好：{{ hobbies }} data() { return { isAgree:true, hobbies:[] }; },select默认单选 苹果 香蕉 榴莲 梨子 你选了个{{fruit}} select 标签中添加 multiple 属性即可单选,需要ctrl键配合,fruit将展示为数组3. v-model基本原理以input文本框为例, v-model可以看作以下代码的简写 总之v-model就是语法糖4. v-model 在组件中使用vue2的v-model在vue3中有改动父组件:<childCpn v-model="propsData" /> 默认相当于:<childCpn :value="propsData" @input="propsData=$event" />子组件: props:['value']注意事项:组件props要改成 value，$emit事件要声明成 input如果我们组件有多个数据需要双向绑定的话可以使用.sync修饰符, 因为v-model只能在一个组件标签中出现一个 <childCpn :otherData.sync="otherData" />{{otherData}}该修饰符也是一种语法糖,实际写法如下: @update:属性名="userInfo.name = $event" :属性名="userInfo.name"子组件: 得到{{otherData}} 这里注意子组件$emit 发出的事件要根据该形式来编写,另外也不要直接修改props中的otherData,把改动传到父组件来修改是最好的,否则会出现警告插槽1. 基本使用父组件引入子组件:<childCpn> 我是插槽中的内容 </childCpn>子组件: 你好 <slot></slot> 父组件引用的子组件中的文字内容就会替换子组件中的slot标签,插槽内可以包含任何模板代码，包括 HTML,注意:父组件里面向子组件传递插槽内容的作用域也是父组件,不能访问子组件中的内容,总之父级模板里的所有内容都是在父级作用域中编译的；子模板里的所有内容都是在子作用域中编译的,还可以给子组件中的slot指定默认内容,如果父组件没有传递插槽内容给子组件们,会默认显示2. 具名插槽有时我们需要多个插槽 slot有个属性name, 不带name属性的插槽也会有个隐含的name值为default 你好 <slot></slot> <slot name="first"></slot> <slot name="second"></slot> 父组件使用方式: <childCpn> 我是插槽中的内容 我是第一 饿哦是第二 </childCpn>现在 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot 的 中的内容都会被视为默认插槽的内容。3. 作用域插槽我们需要父组件能够访问到子组件中的内容子组件:<slot name="first" :value="value"></slot>类似props的方式将value 所谓slot 的一个属性父组件: 我是第一{{slotProps.value}} 父组件将传递过来的props对象保存在我们自己命名的slotProps中,我们可取任意,当然也可以使用es6的解构语法省去取名了 我是第一{{value}}