小埋摸鱼学习法 - vue
type: channel
link: https://topzhang.cn/category/vue/
description: vue.js的学习
items: 基于vite，ant-design-vue和i18n的国际化方案
items: 基于vite实现mock数据请求
items: vite初体验
items: vue关于h函数的简单认识
items: Vue Composition API（更新中）
items: Vue3 v-model 组件中的使用
items: Vue 异步组件
items: Vue组件
items: 如何触发组件的更新
items: Vue (Vue2)基础知识梳理

基于vite，ant-design-vue和i18n的国际化方案
type: item
date: 2021-11-24T11:55:00+00:00
description: 本文主要讲解使用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'…

基于vite实现mock数据请求
type: item
date: 2021-11-22T07:13:00+00:00
description: 作为一个前端开发，没有后端接口而自己还不会写后端，那可以自己写数据来进行页面的渲染展示。我们首先可以想到的是直接在代码中写大量的数据，直接造嘛，简单粗暴。缺点就是大量的数据代码破坏整体代码结构，而且后期对接真正的接口会进行大量的修改，所以还是需要一定的工作量。我们想的是先把所有请求方式参数都订好，后期对接只需要切换下请求的接口，自动换成后台的数据。所以我们可以使用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…

vite初体验
type: item
date: 2021-11-11T12:04:00+00:00
description: 开始刚入职，了解了公司的基本技术栈，用的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:…

vue关于h函数的简单认识
type: item
date: 2021-07-22T07:31:16+00:00
description: 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（更新中）
type: item
date: 2021-07-20T12:18:00+00:00
description: 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 } }模板中展示…

Vue3 v-model 组件中的使用
type: item
date: 2021-07-20T03:31:00+00:00
description: 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 异步组件
type: item
date: 2021-07-20T03:02:10+00:00
description: 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组件
type: item
date: 2021-06-27T09:20:00+00:00
description: 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);//取消动态组件有…

如何触发组件的更新
type: item
date: 2021-06-27T09:18:53+00:00
description: 如何触发组件的更新情况一定义在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)基础知识梳理
type: item
date: 2021-06-26T09:41:00+00:00
description: 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}} 数组语法单独把一个对象当成数组每一个数组对象 {{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 r…